diff --git a/scripts/test-owed-withdrawal.sh b/scripts/test-owed-withdrawal.sh index 9c9cb5b..d2c7c27 100755 --- a/scripts/test-owed-withdrawal.sh +++ b/scripts/test-owed-withdrawal.sh @@ -23,7 +23,9 @@ # withdrawal had no effect, so tor-ms22 was still being told it owed a reply 41h # later for a release it never installed. # -# Usage: scripts/test-owed-withdrawal.sh [source.ts] (exit 0 = all rules behave) +# Usage: scripts/test-owed-withdrawal.sh [source.ts] +# exit 0 = all rules behave 1 = a rule broke +# exit 2 = source unreadable or does not parse 3 = the gate itself cannot run # # The optional argument exists so the suite can be pointed at a deliberately # MUTATED copy of the source to prove it is sensitive — a suite that has never @@ -39,8 +41,47 @@ trap 'rm -rf "$WORK"' EXIT [ -r "$SRC" ] || { echo "FAIL: cannot read $SRC" >&2; exit 2; } # A gate that cannot run must not pass — the standing rule in this repo. -node --experimental-strip-types --check "$SRC" \ - || { echo "FAIL: $SRC does not parse" >&2; exit 2; } +# +# NOT `node --check`: it does not type-strip, so it rejects ANY TypeScript — +# `const x: number = 1` included, not just the inline type-import at the top of +# mempalace.ts. It passed on node 22.x and stopped passing on node 24.x +# (measured on pi-devbox v1.9.1, node v24.21.0: this gate exited 2 on an +# unmodified mempalace.ts, so all 17 assertions below refused to run). Strip +# first, then syntax-check the emitted JS. `mode: "strip"` blanks type syntax +# without moving anything, so byte offsets and line numbers survive and a +# reported error line still points at the right line of the ORIGINAL .ts. +# +# The two failure modes are reported separately and on purpose. "Cannot strip" +# is a fact about the toolchain; "does not parse" is a fact about the source. +# Collapsing them is what made this very defect present itself as +# "mempalace.ts does not parse" when mempalace.ts was fine. +# +# SC2016 is disabled deliberately: the single quotes are the point. What follows +# is JavaScript, and `${process.version}` must reach node, not be expanded by the +# shell first. +# shellcheck disable=SC2016 +node --no-warnings -e ' + const { readFileSync, writeFileSync } = require("node:fs"); + const { stripTypeScriptTypes } = require("node:module"); + if (typeof stripTypeScriptTypes !== "function") { + console.error(`FAIL: node ${process.version} cannot strip TypeScript ` + + `(module.stripTypeScriptTypes needs >= 22.13) — the gate is unavailable, ` + + `which is NOT a statement about the source`); + process.exit(3); + } + let js; + try { + js = stripTypeScriptTypes(readFileSync(process.argv[1], "utf8"), { mode: "strip" }); + } catch (err) { + // The stripper is itself a parser, so a syntax error lands HERE, not in the + // --check below. This is a statement about the source: exit 2, not 3. + console.error(`FAIL: ${process.argv[1]} does not parse: ${err.message}`); + process.exit(2); + } + writeFileSync(process.argv[2], js); +' "$SRC" "$WORK/stripped.mjs" || exit $? +node --check "$WORK/stripped.mjs" \ + || { echo "FAIL: $SRC does not parse (stripped output rejected)" >&2; exit 2; } # ---------------------------------------------------------------- extractor --- cat >"$WORK/extract.mjs" <<'EXTRACT'