build: assert DEBUG is off in every emitted bundle (closes #170)
All checks were successful
check / check (push) Successful in 40s
All checks were successful
check / check (push) Successful in 40s
PR #169 made DEBUG a build-time flag defaulting off, but nothing guarded the wiring. The tests load src/shared/constants.js outside a bundle and take the jest fallback branch, so deleting the __BUILD_DEBUG__ define from build.js left all tests passing and make check green while silently restoring the drainable-wallet vulnerability in every shipped artifact. The property only exists in the emitted output, so it is now asserted against the emitted output. script/verify-build reads two independent facts per bundle. Which bundles must be inspected comes from esbuild's metafile: build.js writes dist/constants-bundles.txt naming every emitted JS output whose input set includes constants.js, so the set is derived from the real dependency graph rather than a hardcoded count or filenames. What each bundle's DEBUG state is comes from BUILD_DEBUG_MARKER, a new constant derived from DEBUG itself that the bundler folds to exactly one of two string literals. Deriving the bundle set from the marker would be the silent-pass hole: a bundle with no marker would be indistinguishable from content/index.js, which legitimately contains none. The marker is a plain string rather than a match on minified `DEBUG:!1`, because minifier output is not a contract across esbuild versions. When DEBUG is not known at build time the fold cannot happen and both literals survive, which is exactly the shape of the regression this guards against. Every way of failing to determine a bundle's state is a hard failure: missing manifest, empty manifest, a listed file that does not exist, both markers, neither marker, the wrong marker, or a bundle carrying a marker while absent from the manifest. There is no path on which the script exits 0 without positively identifying the expected marker in at least one bundle. It runs on the build path only. make build and make build-debug both invoke it, the latter asserting the inverse, and Dockerfile:17 runs a bare make build, so CI fails on a release build with a live debug branch. It is deliberately not in script/check: that would make check depend on dist/ existing and pull a full build into its time budget, and the obvious workaround -- skip when dist/ is absent -- is precisely the silently-green behaviour this exists to prevent.
This commit is contained in:
136
script/verify-build
Executable file
136
script/verify-build
Executable file
@@ -0,0 +1,136 @@
|
||||
#!/bin/sh
|
||||
# script/verify-build: assert the compiled DEBUG state of the emitted
|
||||
# bundles. Our own extension to scripts-to-rule-them-all, run at the end of
|
||||
# make build / make build-debug.
|
||||
#
|
||||
# Why this exists: DEBUG makes the publicly committed test recovery phrase the
|
||||
# output of wallet creation, so a release artifact built with it live hands
|
||||
# every new wallet to anyone who reads the repo. The test suite cannot see
|
||||
# this, because it loads src/shared/constants.js outside a bundle and takes
|
||||
# the fallback branch; the property only exists in the emitted output, so it
|
||||
# has to be asserted against the emitted output.
|
||||
#
|
||||
# What it reads: dist/constants-bundles.txt, written by build.js from
|
||||
# esbuild's metafile, naming every emitted bundle that contains
|
||||
# src/shared/constants.js. Each of those must carry exactly one of the two
|
||||
# BUILD_DEBUG_MARKER literals that constants.js folds down to.
|
||||
#
|
||||
# It fails rather than passes whenever it cannot determine a bundle's state.
|
||||
# Minified output is not a stable contract, so "matched neither form" is not
|
||||
# evidence of anything and must never read as green.
|
||||
set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
|
||||
MANIFEST="dist/constants-bundles.txt"
|
||||
MARKER_ON="autistmask-build-debug=on"
|
||||
MARKER_OFF="autistmask-build-debug=off"
|
||||
|
||||
# Set by read_marker.
|
||||
MARKER=""
|
||||
|
||||
fail() {
|
||||
echo "verify-build: FAIL: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
has_marker() {
|
||||
grep -q -F "$1" "$2" 2>/dev/null
|
||||
}
|
||||
|
||||
# Read one bundle's DEBUG state into MARKER. Exactly one marker must be
|
||||
# present. Both means the ternary in constants.js was never folded, which is
|
||||
# what happens when the __BUILD_DEBUG__ define goes missing from build.js:
|
||||
# DEBUG stops being known at build time and the debug branch is live again.
|
||||
# Neither means we are reading output we do not understand. Both are hard
|
||||
# failures; neither is ever treated as absence of a problem.
|
||||
read_marker() {
|
||||
_file="$1"
|
||||
_on=no
|
||||
_off=no
|
||||
if has_marker "$MARKER_ON" "$_file"; then _on=yes; fi
|
||||
if has_marker "$MARKER_OFF" "$_file"; then _off=yes; fi
|
||||
|
||||
if [ "$_on" = yes ] && [ "$_off" = yes ]; then
|
||||
fail "$_file carries both debug markers, so the build-time DEBUG value
|
||||
was never resolved and the debug branch is still live. Check that build.js
|
||||
still defines __BUILD_DEBUG__."
|
||||
fi
|
||||
if [ "$_on" = no ] && [ "$_off" = no ]; then
|
||||
fail "$_file carries no debug marker, so its DEBUG state cannot be
|
||||
determined. Either BUILD_DEBUG_MARKER is gone from src/shared/constants.js
|
||||
or the emitted output changed shape. Refusing to report success."
|
||||
fi
|
||||
|
||||
if [ "$_on" = yes ]; then
|
||||
MARKER="$MARKER_ON"
|
||||
else
|
||||
MARKER="$MARKER_OFF"
|
||||
fi
|
||||
}
|
||||
|
||||
# The manifest says which bundles must carry a marker. This says no other
|
||||
# emitted bundle may carry one, which catches a manifest that has gone stale
|
||||
# or short rather than trusting whatever it happens to list.
|
||||
check_unlisted_bundles() {
|
||||
_listing="$(find dist -type f -name '*.js' | sort)"
|
||||
while read -r _file; do
|
||||
[ -n "$_file" ] || continue
|
||||
if grep -q -x -F "$_file" "$MANIFEST"; then
|
||||
continue
|
||||
fi
|
||||
if has_marker "$MARKER_ON" "$_file" ||
|
||||
has_marker "$MARKER_OFF" "$_file"; then
|
||||
fail "$_file carries a debug marker but is absent from $MANIFEST,
|
||||
so the manifest no longer describes the emitted bundles."
|
||||
fi
|
||||
done <<EOF
|
||||
$_listing
|
||||
EOF
|
||||
}
|
||||
|
||||
# The requested mode, read from our own environment using build.js's exact
|
||||
# rule: only the literal 1 opts in. Deliberately not taken from anything
|
||||
# build.js records about itself, so build.js cannot vouch for build.js.
|
||||
expected_marker() {
|
||||
if [ "${AUTISTMASK_DEBUG-}" = "1" ]; then
|
||||
echo "$MARKER_ON"
|
||||
else
|
||||
echo "$MARKER_OFF"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
|
||||
expected="$(expected_marker)"
|
||||
echo "Verifying emitted bundles (expecting $expected)..."
|
||||
|
||||
[ -f "$MANIFEST" ] ||
|
||||
fail "$MANIFEST is missing. build.js writes it at the end of a
|
||||
successful build; run make build first."
|
||||
[ -s "$MANIFEST" ] ||
|
||||
fail "$MANIFEST is empty, so no emitted bundle was found to contain
|
||||
src/shared/constants.js. That is never correct, so it is a failure and not
|
||||
a pass."
|
||||
|
||||
count=0
|
||||
while read -r file; do
|
||||
[ -n "$file" ] || continue
|
||||
[ -f "$file" ] ||
|
||||
fail "$MANIFEST lists $file, which does not exist."
|
||||
read_marker "$file"
|
||||
[ "$MARKER" = "$expected" ] ||
|
||||
fail "$file is $MARKER but this build expects $expected."
|
||||
echo " ok: $file ($MARKER)"
|
||||
count=$((count + 1))
|
||||
done <"$MANIFEST"
|
||||
|
||||
[ "$count" -gt 0 ] || fail "no bundles were inspected."
|
||||
|
||||
check_unlisted_bundles
|
||||
|
||||
echo "verify-build: $count bundle(s) verified $expected"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user