From fbec68ba0962c3865e61b5ee25a726a22c9e01a6 Mon Sep 17 00:00:00 2001 From: T-Crypt Date: Tue, 13 Oct 2026 10:38:35 -0600 Subject: [PATCH 1/2] build(02): the gate fails on a broken config, trims the clean list, and reads the whole report --- scripts/check-web.mjs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/scripts/check-web.mjs b/scripts/check-web.mjs index 82df4b07..e79c307c 100644 --- a/scripts/check-web.mjs +++ b/scripts/check-web.mjs @@ -6,22 +6,30 @@ import { readFileSync } from "node:fs"; const clean = readFileSync(new URL("web-clean.txt", import.meta.url), "utf8") .split("\n") + .map((line) => line.trim()) .filter(Boolean); const tsc = "node_modules/typescript/bin/tsc"; -const run = spawnSync("node", [tsc, "--noEmit", "-p", "tsconfig.web.json"], { encoding: "utf8" }); +const run = spawnSync("node", [tsc, "--noEmit", "-p", "tsconfig.web.json"], { + encoding: "utf8", + // Fits the whole list, 328 KB today: spawnSync's own 1 MiB truncates the output and reports no status. + maxBuffer: 64 * 1024 * 1024, +}); const findings = (run.stdout + run.stderr).trim(); +const reported = findings.split("\n"); if (findings) { console.log(findings); } -const broken = findings.split("\n").filter((line) => clean.some((path) => line.startsWith(path))); +const broken = reported.filter((line) => clean.some((path) => line.startsWith(path))); +// A configuration error exits 2 as well and names the config, not a file under web/, so error lines that are not +// all on web/ mean the run read no config it could check. `process.exitCode`, not `exit`: an exit drops the pipe. +const config = reported.filter((line) => line.includes("error TS") && !line.startsWith("web/")); if (broken.length) { console.log(`\nthe gate holds these clean:\n${broken.join("\n")}`); - process.exit(1); } -if (run.status !== 0 && run.status !== 2) { - process.exit(1); +if (broken.length || config.length || (run.status !== 0 && run.status !== 2)) { + process.exitCode = 1; } From 995e6ce1c5a29d7beca2347c861b67c2f4cfa80c Mon Sep 17 00:00:00 2001 From: T-Crypt Date: Tue, 13 Oct 2026 10:40:00 -0600 Subject: [PATCH 2/2] test(03): the variation selectors, the blank Hangul letters, the Khmer vowels and the Mongolian selectors too --- test/fence.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/fence.test.ts b/test/fence.test.ts index 7c20ad38..75413f4f 100644 --- a/test/fence.test.ts +++ b/test/fence.test.ts @@ -22,8 +22,8 @@ test("a closing tag loses its <, whatever stands between it and the /", () => { test("a closing tag loses its < behind every category of invisible character the class names", () => { // One representative per category, 81 of them, so the 80-character branch cannot defang it and only the class // can: a control character, a format character, a lone surrogate, a private-use character, an unassigned code - // point, a variation selector supplement, the braille blank, the Hangul fillers, and the combining grapheme - // joiner. Narrowing the class fails here. + // point, a variation selector and its supplement, the braille blank, three Hangul fillers, the combining + // grapheme joiner, a Khmer inherent vowel, and a Mongolian free variation selector. Narrowing the class fails. const hides = (character: string, what: string) => { const invisible = character.repeat(81); @@ -40,6 +40,10 @@ test("a closing tag loses its < behind every category of invisible character the hides("\u3164", "the Hangul filler"); hides("\uFFA0", "the halfwidth Hangul filler"); hides("\u034F", "the combining grapheme joiner"); + hides("\uFE00", "a variation selector"); + hides("\u115F", "the Hangul choseong filler"); + hides("\u17B4", "a Khmer inherent vowel"); + hides("\u180B", "a Mongolian free variation selector"); }); test("a closing tag with more than 80 characters between < and / is defanged only by the invisible run", () => {