doxa-maps/internal/check-browser-gates-guarded.mjs
Doxa Maps 5311a16e0c
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Update from source 33362f559
2026-09-28 09:40:13 +00:00

45 lines
3 KiB
JavaScript

#!/usr/bin/env node
/**
* check-browser-gates-guarded — no repo gate reaches Playwright except through the slot guard. Headless.
*
* Browsers run only on the browser seat and only inside internal/browser-slot.sh. The rule is enforced
* by internal/lib/playwright-guarded.mjs (a launch refuses, exit 75, without DOXA_BROWSER_SLOT), so a
* file that imports 'playwright' DIRECTLY would bypass it. This fails any such file under internal/ or
* plugin-designs/. `--list` prints every gate script that launches a browser instead.
* EXIT 0 pass · 1 fail
*/
import { readFileSync } from 'node:fs'
import { execFileSync } from 'node:child_process'
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { isBrowserGate } from './lib/browser-gates.mjs'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
if (process.argv.includes('--list')) {
const scripts = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf8')).scripts || {}
const names = Object.keys(scripts).filter((n) => !n.startsWith('gates:') && isBrowserGate(ROOT, n)).sort()
console.log(names.join('\n'))
console.error(`${names.length} script(s) launch a browser: run each as internal/browser-slot.sh bun run <name>`)
process.exit(0)
}
// A REAL import, not text that names one: a static import statement at the start of a line, or a literal
// dynamic import or require of the playwright package. The detectors (browser-gates.mjs, this file) spell the
// pattern inside regex literals, escaped, which none of these match, so they need no exclusion and a real
// import added to one still fails. Only the guard itself imports playwright, by design.
// Also a PATH to the package (node_modules/playwright…, or playwright-core) anywhere in code: a gate imported
// resolve(ROOT, 'node_modules/playwright/index.mjs') dynamically, named no specifier, and launched outside the
// slot while this check passed it (upg100 found and measured it).
const DIRECT = /^\s*import\b[^;\n]*\bfrom\s+['"]playwright(?:-core)?['"]|\bimport\(\s*['"]playwright(?:-core)?['"]\s*\)|\brequire\(\s*['"]playwright(?:-core)?['"]\s*\)|node_modules[\/\\]playwright(?:-core)?\b/m
const files = execFileSync('git', ['ls-files', '--', 'internal', 'plugin-designs'], { cwd: ROOT, encoding: 'utf8' })
.split('\n').filter((f) => /\.(mjs|js|cjs)$/.test(f) && f !== 'internal/lib/playwright-guarded.mjs')
const bad = []
for (const f of files) {
const code = readFileSync(resolve(ROOT, f), 'utf8').replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '')
if (DIRECT.test(code)) bad.push(f)
}
if (bad.length) {
for (const f of bad) console.log(` FAIL ${f} imports playwright directly; import it from internal/lib/playwright-guarded.mjs`)
console.log(`BROWSER-GATES-GUARDED: FAIL — ${bad.length} file(s) can launch a browser outside the slot.`)
process.exit(1)
}
console.log(`BROWSER-GATES-GUARDED: PASS — no repo file imports playwright except through the slot guard (${files.length} files read).`)