Only the browser seat runs browsers, and only inside internal/browser-slot.sh. That rule was remembered, not enforced: an agent ran a gate it took for a headless source check and it launched Chromium outside the slot. internal/lib/playwright-guarded.mjs is now the one way a repo file reaches playwright: its launch(), launchPersistentContext(), launchServer(), connect() and connectOverCDP() exit 75 (NOT MEASURED) with a message naming browser-slot.sh unless DOXA_BROWSER_SLOT is set, and browser-slot.sh exports it for the command it runs, so a bash -c grandchild inherits it. All 72 files that imported playwright switch to it (internal/lib/browser.mjs and 64 internal gates; plugin-designs scripts: doxa-research-map 3, my-upg-100-list 2, research-map-plus-grid 2, research-dashboard 1): a mechanical import swap. check:browser-gates-guarded (gates:pre) fails a direct import; bun run browser-gates lists the 97 scripts that launch a browser. isBrowserGate moves to internal/lib/browser-gates.mjs so the runner and the check share it.
32 lines
2.1 KiB
JavaScript
32 lines
2.1 KiB
JavaScript
/** internal/lib/browser-gates.mjs — which package.json scripts drive a real browser (read from the gate FILE).
|
|
* Shared by run-parallel (to route them through browser-slot.sh) and check-browser-gates-guarded (--list). */
|
|
import { readFileSync, existsSync } from 'node:fs'
|
|
import { resolve } from 'node:path'
|
|
|
|
/**
|
|
* Does this step drive a real browser? Asked of the GATE FILE, not of a hand-kept list:
|
|
* a list goes stale the first time someone adds a gate, and a browser gate missing from it
|
|
* is exactly the one that rejoins the dogpile. package.json names the file; the file says
|
|
* whether it imports playwright.
|
|
*/
|
|
export function isBrowserGate(ROOT, name) {
|
|
try {
|
|
const scripts = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf8')).scripts || {}
|
|
const m = String(scripts[name] || '').match(/([\w./-]+\.mjs)/)
|
|
if (!m) return false
|
|
const file = resolve(ROOT, m[1])
|
|
if (!existsSync(file)) return false
|
|
const src = readFileSync(file, 'utf8')
|
|
// Two ways a step can drive chrome: it launches one itself, or it is a RUNNER that
|
|
// launches gates which do. `verify` is the second kind — verify-all.mjs imports no
|
|
// playwright and would have been graded static, while three of the gates it spawns
|
|
// (posters, app-tables, map-paint) render pages. It already marks those `browser: true`
|
|
// for its own reasons, so that declaration is honoured here rather than duplicated.
|
|
// …but a runner only drives chrome when ASKED to: verify-all runs its browser guards only with --full,
|
|
// so plain `verify` is a static step (it printed "ALL GUARDS PASS (browser guards skipped)" and was
|
|
// still routed through the browser slot, where a headless seat could only fail or skip it).
|
|
// playwright-guarded.mjs is how every repo gate reaches playwright now (a firefox-only gate names no chromium).
|
|
if (/from 'playwright'|require\('playwright'\)|from\s+['"][^'"]*playwright-guarded\.mjs['"]|import\(\s*['"][^'"]*playwright-guarded\.mjs|chromium/.test(src)) return true
|
|
return /browser:\s*true/.test(src) && /--full\b/.test(String(scripts[name]))
|
|
} catch { return false }
|
|
}
|