check:no-free-identifiers reads internal/ and .mjs, which is where two gates had crashed on load

A commit on 09-25 gave some 30 browser gates `const ROOT = …(_fileURLToPath(…))`.
Two never imported it, and crashed with a ReferenceError the moment they
loaded: check-legend-info-localized (found by hand after four days, fixed
this morning) and library/scripts/posters/verify-posters.mjs, found now by
this change and fixed here. Both run only by hand, so nothing noticed.

The gate read only .vue and .js under plugin-designs/ and library/. It now
reads .mjs as well, and internal/. Scanning them surfaced standard globals
the list lacked (eval, EvalError, Bun, and the window members that
page.evaluate() callbacks use), which are added. A file tagged @one-shot
is skipped: it is kept as a reproduction recipe, not run.
This commit is contained in:
Doxa Maps 2026-09-29 08:58:30 +00:00
parent 89471b2880
commit 5e81b05281
2 changed files with 13 additions and 4 deletions

View file

@ -32,7 +32,7 @@ import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path'
const __dirname = dirname(fileURLToPath(import.meta.url));
const DIR = process.env.POSTER_DIR || join(__dirname, 'out', 'posters');
const PREFIX = process.argv[2] || '';
const ROOT = _resolvePath(_dirnamePath(_fileURLToPath(import.meta.url)), '../../..')
const ROOT = _resolvePath(_dirnamePath(fileURLToPath(import.meta.url)), '../../..')
// ★ MEASURE THIS BUILD, OR MEASURE NOTHING. Several worktrees run on one machine and a default port
// belongs to whichever server started first — measured 2026-09-24 on :4173, where browser gates

View file

@ -38,7 +38,10 @@ import * as acorn from 'acorn'
const TAG = 'NO-FREE-IDENTIFIERS'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..')
const ROOTS = ['plugin-designs', 'library']
// internal/ too, and .mjs files (below): 30 browser gates gained `const ROOT = …(_fileURLToPath(…))` in one
// commit, and the two that never imported it crashed on load for four days, because they run only by hand.
// A free identifier at module scope is the same defect in a Node script as in an SFC.
const ROOTS = ['plugin-designs', 'library', 'internal']
// Vue's <script setup> compiler macros: real bindings the compiler provides, not free names.
const VUE_MACROS = [
@ -79,6 +82,10 @@ const GLOBALS = [
// i18nAreasPlugin prepends `const __DX_LOCALE_SRI__ = {…}` to library/i18n/index.js; without it the
// name does not exist, which is why its one read sits behind `typeof … !== 'undefined'`.
'__DX_LOCALE_SRI__',
// Standard globals the first lists missed, found by extending the scan to internal/*.mjs: eval and
// EvalError; Bun in scripts `bun` runs; and window members used inside page.evaluate() callbacks,
// which run in the browser, not in Node.
'eval', 'EvalError', 'Bun', 'scrollX', 'scrollY', 'scrollTo', 'Storage',
]
const PRELUDE = new Set([...VUE_MACROS, ...GLOBALS])
@ -265,7 +272,7 @@ function vueFiles(dir, out = []) {
// and the bug that emptied Big Picture's Religion and Region charts was a plain .js module:
// useDashboardAggregations.js read NEUTRAL five times and never imported it. A bundle compiles
// .js and .vue alike; the gate was seeing half of what ships.
else if (e.endsWith('.vue') || e.endsWith('.js')) out.push(p)
else if (e.endsWith('.vue') || e.endsWith('.js') || e.endsWith('.mjs')) out.push(p)
}
return out
}
@ -277,8 +284,10 @@ let scanned = 0
for (const file of files) {
const src = readFileSync(file, 'utf8')
// A dated one-shot diagnostic (tagged @one-shot, see check-no-orphan-gates) is kept as a recipe, not run.
if (/^\s*(?:\*|\/\/)\s*@one-shot\b/m.test(src.slice(0, 2600))) continue
let descriptor
if (file.endsWith('.js')) {
if (file.endsWith('.js') || file.endsWith('.mjs')) {
// A .js module is its own script block. Running it through the SFC parser is what broke the first
// attempt at this: parseSFC found no <script>, the content came back shifted, and acorn reported
// "Unexpected token (2:1)" — the second line of the file's own doc comment. offset 0 keeps the