Update from source a59db6c4a
This commit is contained in:
parent
b888d58586
commit
28a0083310
87 changed files with 271 additions and 121 deletions
|
|
@ -62,7 +62,7 @@
|
|||
* node internal/acceptance/interop-harness.mjs --self-test [--self-test-mode=grid-prop-drop|camera-freeze]
|
||||
* PORT=4180 node internal/acceptance/interop-harness.mjs
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from '../lib/playwright-guarded.mjs'
|
||||
import { spawn } from 'node:child_process'
|
||||
import { setTimeout as sleep } from 'node:timers/promises'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ export function devBackends(env = process.env) {
|
|||
name: 'rudder-engine',
|
||||
bin: 'backend/bin/rudder-engine',
|
||||
build: 'cd backend/rudder-engine && go build -o ../bin/rudder-engine .',
|
||||
// What a fresh clone is told at startup when this backend is not built: it is OPTIONAL (desktop voice).
|
||||
optional: 'optional: Rudder (desktop voice-to-text) is not built, which is fine for the maps. It needs Go 1.22+; see documentation/HOWTO-build-the-desktop-app.md.',
|
||||
route: '/api/rudder',
|
||||
port: Number(process.env.RUDDER_PORT || 5193),
|
||||
env: { RUDDER_PORT: String(process.env.RUDDER_PORT || 5193), RUDDER_NEMO_PORT: String(process.env.RUDDER_NEMO_PORT || 5194) },
|
||||
|
|
@ -238,7 +240,11 @@ export function backendDevPlugin(root, env = process.env) {
|
|||
b.fetch(root, logger).then((ok) => { REG.fetching.delete(b.name); if (ok) launch() })
|
||||
continue
|
||||
}
|
||||
logger.warn(`[backend] ${b.name} not built — its plugin will show "engine offline". Build: ${b.build}`)
|
||||
// An OPTIONAL backend is a note, not a warning: a stranger's first `bun run dev` line said
|
||||
// "rudder-engine not built … go build …" with no word that it is optional or needs Go (upg100's
|
||||
// fresh-clone run). A backend without that note keeps the build instruction.
|
||||
if (b.optional) logger.info(`[backend] ${b.optional}`)
|
||||
else logger.warn(`[backend] ${b.name} not built — its plugin will show "engine offline". Build: ${b.build}`)
|
||||
continue
|
||||
}
|
||||
launch()
|
||||
|
|
|
|||
|
|
@ -72,6 +72,9 @@ fi
|
|||
# the command named so its owner can find it. A long gate that legitimately needs more says so:
|
||||
# BROWSER_MAX_HOLD=1800 internal/browser-slot.sh node internal/<long-gate>.mjs
|
||||
run_held() {
|
||||
# EXPORTED, so the gate's own children (a `bash -c "…"` wrapper, a node grandchild) inherit it:
|
||||
# internal/lib/playwright-guarded.mjs refuses to launch a browser without it.
|
||||
export DOXA_BROWSER_SLOT="${DOXA_BROWSER_SLOT:-held}"
|
||||
# The gate's output still flows to the caller untouched (stdout and stderr kept apart); a COPY goes to
|
||||
# RAM (/dev/shm, never the disk that may be full) so a failure can be classified afterwards.
|
||||
local log="" rc
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
import { readFileSync, existsSync } from 'node:fs'
|
||||
import { resolve, dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { servesThisTree } from './_serves-this-tree.mjs'
|
||||
import { TREE_NAME } from './_tree-name.mjs'
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
// where no English word list could.
|
||||
//
|
||||
// Usage: node internal/check-aria-labels-localized.mjs <leafUrl> [locales]
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { requireThisTree } from './_serves-this-tree.mjs'
|
||||
import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path'
|
||||
import { fileURLToPath as _fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
* Run through internal/browser-slot.sh.
|
||||
* EXIT 0 all three hold · 1 one does not · 75 not measured.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { spawn } from 'node:child_process'
|
||||
import { createServer } from 'node:http'
|
||||
import { existsSync } from 'node:fs'
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
* NEGATIVE-TESTED 2026-09-25: the same leaf built from 319064a1's parent (before localize-labels
|
||||
* existed), served with --dir, FAILS on both engines.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { spawn } from 'node:child_process'
|
||||
import { createServer } from 'node:http'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
|
|
|
|||
42
internal/check-browser-gates-guarded.mjs
Normal file
42
internal/check-browser-gates-guarded.mjs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#!/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.
|
||||
const DIRECT = /^\s*import\b[^;\n]*\bfrom\s+['"]playwright['"]|\bimport\(\s*['"]playwright['"]\s*\)|\brequire\(\s*['"]playwright['"]\s*\)/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).`)
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
//
|
||||
// Usage: node internal/check-card-deck.mjs [lang] [stock]
|
||||
// PACKET_ORIGIN (default http://127.0.0.1:4898) · PACKET_API · PACKET_ISO3
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { requireThisTree } from './_serves-this-tree.mjs'
|
||||
import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path'
|
||||
import { fileURLToPath as _fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ if (!existsSync(MANIFEST)) {
|
|||
process.exit(0)
|
||||
}
|
||||
let chromium
|
||||
try { ({ chromium } = await import('playwright')) } catch {
|
||||
try { ({ chromium } = await import('./lib/playwright-guarded.mjs')) } catch {
|
||||
console.log('COMPOSITE-EMBED-KNOBS: SKIP — playwright is not installed.')
|
||||
process.exit(0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
// already gone and reports 0 cards for a correct deck.
|
||||
//
|
||||
// Usage: node internal/check-deck-click.mjs <wallUrl> [stockId] [expectedPage]
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { readFileSync, existsSync } from 'node:fs'
|
||||
import { resolve, dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
* EXIT 0 no unaccounted addition and no omission · 1 either · 2 bad preset/arguments ·
|
||||
* 75 NOT MEASURED (live page unreachable, a side never rendered, or the census is blind).
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { spawn } from 'node:child_process'
|
||||
import { createServer } from 'node:http'
|
||||
import { readFileSync, readdirSync, existsSync } from 'node:fs'
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
* 2. PIXELS — colour variety in a slice of the canvas. A page can 200 on everything and
|
||||
* still paint nothing, and a screenshot is the only witness to that.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { serveReaped } from './_serve-reaped.mjs'
|
||||
import { servesThisTree } from './_serves-this-tree.mjs'
|
||||
import { writeFileSync, mkdtempSync, readFileSync } from 'node:fs'
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
*
|
||||
* Usage: node internal/check-embedded-rtl.mjs [baseUrl]
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { createServer } from 'node:http'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@
|
|||
* node internal/check-facet-changes-map.mjs --self-test # NEGATIVE test: the gate must FAIL
|
||||
* BASE_URL=http://127.0.0.1:4173 PROBE_PATH=/<TREE_NAME>/... node internal/check-facet-changes-map.mjs
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
|
||||
// Defaults to the DEV server, because this gate drives a live interaction and the dev surface is
|
||||
// the one that always reflects current source. Point BASE_URL/PROBE_PATH at the built tree
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
*/
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { servesThisTree } from './_serves-this-tree.mjs'
|
||||
import { TREE_NAME } from './_tree-name.mjs'
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@
|
|||
* Grades by EXIT CODE. Teaching prose after the verdict — the per-clause table comes first so a
|
||||
* human sees which clause failed without reading the essay.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { spawn, execFileSync } from 'node:child_process'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
* plugin-studio host fail with dLeft=256, while standalone continues to pass — i.e. the gate fails
|
||||
* exactly where the old probe passed, which is the property that makes it worth having.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { requireThisTree } from './_serves-this-tree.mjs'
|
||||
import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path'
|
||||
import { fileURLToPath as _fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
* what makes the pair deadlock-free:
|
||||
* flock -w 1200 <product-lock> flock -w 1200 <agent-lock> bun run check:composed-filters
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { serveReaped } from './_serve-reaped.mjs'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
* Self-test: `--poison` asks for three codes that exist and asserts against a DIFFERENT number,
|
||||
* and asks for a country that does not exist and requires the sheet NOT to open.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { serveReaped } from './_serve-reaped.mjs'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
* window.mapboxgl before the bundle runs while maplibre is blocked) and requires the maplibre arm
|
||||
* to FAIL — if it passes, this check cannot see the defect it exists for.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { serveReaped } from './_serve-reaped.mjs'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
* one, and a check that conflated them would have sent someone to translate strings that are
|
||||
* already translated.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
* Run with --poison to prove it can fail: it asks the same questions of a URL that serves no
|
||||
* element at all, and requires every leaf-shaped assertion to go red.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { serveReaped } from './_serve-reaped.mjs'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
*
|
||||
* Run with --poison to prove it can fail: it hides the canvas and requires a red verdict.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { serveReaped } from './_serve-reaped.mjs'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
* Run with --poison to prove it can fail: the poison neuters setRTLTextPlugin in the page, which
|
||||
* reproduces the real pre-fix state — the plugin never registered — rather than a synthetic one.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { serveReaped } from './_serve-reaped.mjs'
|
||||
import { TREE_NAME } from './_tree-name.mjs'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
* @browser-slot-max-hold 2700
|
||||
* EXIT 0 every judged leaf enters the host's locale · 1 one does not · 75 not measured.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { spawn } from 'node:child_process'
|
||||
import { createServer } from 'node:http'
|
||||
import { readFileSync, existsSync } from 'node:fs'
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
* @browser-slot-max-hold 2700
|
||||
* EXIT 0 pass · 1 a leaf calls Mapbox under maplibre, or a map stopped drawing · 75 not measured.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { spawn } from 'node:child_process'
|
||||
import { createServer } from 'node:http'
|
||||
import { readFileSync, existsSync } from 'node:fs'
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
* node internal/check-leaf-english-leaks.mjs <bundle>/<leaf>:<tag> [more...]
|
||||
* LOCALES=ar,zh,ru,hi BASE_URL=http://127.0.0.1:4173
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { readFileSync, readdirSync, existsSync } from 'node:fs'
|
||||
import { resolve, dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
//
|
||||
// Usage: node internal/check-legend-export-reachable.mjs [leafUrl]
|
||||
// SKIPs when nothing is served — this needs the built tree.
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { requireThisTree } from './_serves-this-tree.mjs'
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
// version of this probe reported "(no popover)" in every locale against a working build.
|
||||
//
|
||||
// Usage: node internal/check-legend-info-localized.mjs <leafUrl> [locales]
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { readFileSync, existsSync } from 'node:fs'
|
||||
import { join, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@
|
|||
* node internal/check-map-data-painted.mjs --poison # must go RED; proves it can fail
|
||||
* Defaults to the LOCAL tree's decoupled leaves. Pass URLs explicitly to audit what is published.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { servesThisTree } from './_serves-this-tree.mjs'
|
||||
import { TREE_NAME } from './_tree-name.mjs'
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
* is then forwarded with the real token so the map can still paint. A map drawn with zero Mapbox
|
||||
* requests is fine for maps that need none; that is reported, not failed.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { spawn } from 'node:child_process'
|
||||
import { createServer } from 'node:http'
|
||||
import { readFileSync, existsSync } from 'node:fs'
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
* It refuses to pass vacuously: if the script was never requested (preloaded) or the pane was not
|
||||
* actually removed before release, the race was not staged and it exits 2, not 0.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { requireThisTree } from './_serves-this-tree.mjs'
|
||||
import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path'
|
||||
import { fileURLToPath as _fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
* node internal/check-maplibre-layer-drops.mjs
|
||||
* BASE_URL=http://127.0.0.1:4399 node internal/check-maplibre-layer-drops.mjs
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { readFileSync, existsSync } from 'node:fs'
|
||||
import { resolve, join, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
// tab — a strip that paints but does not switch is the other silent half.
|
||||
//
|
||||
// Usage: node internal/check-mfe-two-tab.mjs <url> [expectedTabCount]
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { requireThisTree } from './_serves-this-tree.mjs'
|
||||
import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path'
|
||||
import { fileURLToPath as _fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
import { readFileSync, existsSync } from 'node:fs'
|
||||
import { resolve, dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { requireThisTree } from './_serves-this-tree.mjs'
|
||||
import { TREE_NAME } from './_tree-name.mjs'
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
// PACKET_ISO3 country to build a test packet for (default LAO)
|
||||
// Needs a static server over the repo root, e.g.: python3 -m http.server 4898
|
||||
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
// Usage: node internal/check-packet-render.mjs [path/to/packet.html]
|
||||
// Default path is overridable with PACKET_HTML so this is not machine-specific.
|
||||
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { existsSync, readFileSync } from 'node:fs'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { pathToFileURL, fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
// SKIP (exit 0) only when the directory is genuinely absent or empty: packets are
|
||||
// generated on demand and a clean checkout has none. A skip says so out loud, with the
|
||||
// count, so "0 packets checked" can never read as "all packets passed".
|
||||
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
||||
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
|
||||
import { resolve, dirname, join } from 'node:path'
|
||||
// pathToFileURL, not a bare path: a dynamic import() of an absolute POSIX path works by luck on
|
||||
// linux and throws ERR_UNSUPPORTED_ESM_URL_SCHEME the moment anyone runs this on windows.
|
||||
|
|
@ -38,9 +38,33 @@ if (!existsSync(DIR)) {
|
|||
process.exit(0)
|
||||
}
|
||||
|
||||
const files = readdirSync(DIR).filter((f) => f.endsWith('.html')).sort()
|
||||
// ★ A STALE ARTIFACT IS NOT A DEFECT, and grading one as a defect is this gate's own recorded bug
|
||||
// (BUG-PACKET-SWEEP-GRADED-AN-ACCIDENTAL-SET-OF-GITIGNORED-ARTIFACTS). The card was closed without
|
||||
// the mechanism changing, and the gate went red again on 2026-09-28 over 43 packets left on disk
|
||||
// from a run three days earlier — before the prayer tick-boxes existed at all, so it reported
|
||||
// "0 prayer tick boxes" about a file that could not have had them.
|
||||
//
|
||||
// This whole directory is GITIGNORED (.gitignore:94 `library/scripts/*/out/**`), so nothing here
|
||||
// ever reaches main or another agent: it is one developer's scratch. The gate already knows that an
|
||||
// EMPTY directory is not a failure ("a clean checkout has none"); a directory of LEFTOVERS is the
|
||||
// same fact wearing different clothes.
|
||||
//
|
||||
// So a packet older than the renderer that produces it is SKIPPED, loudly, rather than failed. The
|
||||
// renderer's own mtime is the reference because that is what decides the shape a packet should have
|
||||
// — comparing against "today" would re-fail every morning, and comparing against nothing is what
|
||||
// produced this bug twice.
|
||||
const RENDERER = resolve(HERE, '..', 'library/packet/packet-render.js')
|
||||
const rendererMtime = existsSync(RENDERER) ? statSync(RENDERER).mtimeMs : 0
|
||||
const all = readdirSync(DIR).filter((f) => f.endsWith('.html')).sort()
|
||||
const stale = all.filter((f) => statSync(join(DIR, f)).mtimeMs < rendererMtime)
|
||||
const files = all.filter((f) => !stale.includes(f))
|
||||
if (stale.length) {
|
||||
console.log(`PACKETS-ALL: skipping ${stale.length} packet(s) older than the renderer — regenerate`)
|
||||
console.log(` them (ONLY=<id> node library/scripts/posters/packet.mjs) or delete them; this`)
|
||||
console.log(` directory is gitignored scratch, so a leftover is not a defect.`)
|
||||
}
|
||||
if (!files.length) {
|
||||
console.log(`PACKETS-ALL: SKIP — ${DIR} contains no .html packets`)
|
||||
console.log(`PACKETS-ALL: SKIP — ${DIR} contains no .html packets newer than the renderer`)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
|
|
@ -67,9 +91,30 @@ if (!EXPECT_FUNNEL) {
|
|||
console.log('PACKETS-ALL: ⚠ PACKETS_ALL_NO_FUNNEL=1 — page 2 is NOT required this run.')
|
||||
}
|
||||
|
||||
// ⚠ THE UNCONDITIONAL ABOVE STOPPED BEING TRUE, and this is the correction. The comment says every
|
||||
// packet comes from packet.mjs "which always passes FUNNEL" — that held until SERIES landed
|
||||
// (2026-09-28). A `country` packet DROPS the funnel page deliberately: the funnel narrows every
|
||||
// people group on earth down to THIS UPG-100 list, which is not a description of a country, and a
|
||||
// page of true numbers about the wrong thing is worse than no page.
|
||||
// So the expectation follows the SERIES that produced the packet, read from series.json rather than
|
||||
// hardcoded here — adding a series with funnel:false must not require editing this gate.
|
||||
// A packet whose series cannot be determined keeps the old unconditional expectation, because the
|
||||
// safe default for "I do not know" is the stricter check.
|
||||
const SERIES_PATH = resolve(HERE, '..', 'library/scripts/posters/series.json')
|
||||
const SERIES_DEFS = existsSync(SERIES_PATH) ? JSON.parse(readFileSync(SERIES_PATH, 'utf8')) : {}
|
||||
const NO_FUNNEL_MARKERS = Object.entries(SERIES_DEFS)
|
||||
.filter(([id, def]) => !id.startsWith('_') && def && def.funnel === false)
|
||||
.flatMap(([, def]) => (def.networks || []).map(([head]) => head).filter(Boolean))
|
||||
/** True when this packet's own text identifies it as a series that has no funnel page. */
|
||||
function expectsFunnel(html) {
|
||||
if (!NO_FUNNEL_MARKERS.length) return EXPECT_FUNNEL
|
||||
return !NO_FUNNEL_MARKERS.some((m) => html.includes(m)) && EXPECT_FUNNEL
|
||||
}
|
||||
|
||||
const failed = []
|
||||
for (const f of files) {
|
||||
const args = EXPECT_FUNNEL ? [gate, join(DIR, f), '--expect-funnel'] : [gate, join(DIR, f)]
|
||||
const wantFunnel = expectsFunnel(readFileSync(join(DIR, f), 'utf8'))
|
||||
const args = wantFunnel ? [gate, join(DIR, f), '--expect-funnel'] : [gate, join(DIR, f)]
|
||||
const res = spawnSync(process.execPath, args, { encoding: 'utf8' })
|
||||
const ok = res.status === 0
|
||||
console.log(` ${ok ? '✓' : '✗'} ${f}`)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
*
|
||||
* Usage: node internal/check-poster-card-fits.mjs [leafUrl] — SKIPs when nothing is served.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { servesThisTree } from './_serves-this-tree.mjs'
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
// `--directory public/doxa-maps`. Serve the TREE.
|
||||
//
|
||||
// Usage: node internal/check-poster-degrades-honestly.mjs <wallUrl> [zoneId]
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { requireThisTree } from './_serves-this-tree.mjs'
|
||||
import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path'
|
||||
import { fileURLToPath as _fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
// the default-drop condition, and the .html is left reachable.
|
||||
//
|
||||
// Usage: node internal/check-poster-print-fallback.mjs <wallUrl> [zoneId]
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { requireThisTree } from './_serves-this-tree.mjs'
|
||||
import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path'
|
||||
import { fileURLToPath as _fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
// Runs the composable in a browser page (it calls document/DOM), driving it with a
|
||||
// fake records provider so the assertion is about the DOCUMENTS, not about any
|
||||
// bundle's API being reachable.
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
//
|
||||
// Usage: node internal/check-poster-raster.mjs [zoneId]
|
||||
// PACKET_ORIGIN (default http://127.0.0.1:4898)
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { requireThisTree } from './_serves-this-tree.mjs'
|
||||
import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path'
|
||||
import { fileURLToPath as _fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
* Usage: node internal/check-poster-wall-localised.mjs [baseUrl]
|
||||
* SKIPs when nothing is served; exits 2 (NOT MEASURED) when the server is not this tree.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve, dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import { TREE_NAME } from './_tree-name.mjs'
|
|||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
if (!existsSync(resolve(ROOT, `public/${TREE_NAME}/prayer-walk-planner/prayer-walk-planner.js`))) { console.log('SKIP check-prayer-walk-planner: no built planner'); process.exit(2) }
|
||||
let chromium
|
||||
try { ({ chromium } = await import('playwright')) } catch { console.log('SKIP check-prayer-walk-planner: playwright not installed'); process.exit(2) }
|
||||
try { ({ chromium } = await import('./lib/playwright-guarded.mjs')) } catch { console.log('SKIP check-prayer-walk-planner: playwright not installed'); process.exit(2) }
|
||||
const OUT = process.env.OUT || '/tmp'
|
||||
const free = () => new Promise(ok => { const s = createServer().listen(0, '127.0.0.1', () => { const p = s.address().port; s.close(() => ok(p)) }) })
|
||||
const TP = await free(), HP = await free(), TREE = `http://127.0.0.1:${TP}/${TREE_NAME}`
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
//
|
||||
// Usage: node internal/check-print-geometry.mjs
|
||||
// PACKET_ORIGIN (default http://127.0.0.1:4898) — a static server over the repo root
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { requireThisTree } from './_serves-this-tree.mjs'
|
||||
import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path'
|
||||
import { fileURLToPath as _fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
// Usage: node internal/check-provider-switch.mjs <leafUrl>
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { servesThisTree, serveThisTree } from './_serves-this-tree.mjs'
|
||||
import { TREE_NAME } from './_tree-name.mjs'
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
* --poison neuters setRTLTextPlugin in the page (the real pre-fix state) and must be caught on
|
||||
* every measured row. Exit: 0 pass · 1 fail · 2 not measured.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { serveReaped } from './_serve-reaped.mjs'
|
||||
import { TREE_NAME } from './_tree-name.mjs'
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ if (!existsSync(resolve(ROOT, `public/${TREE_NAME}/doxa-maps-spa/doxa-maps-spa.j
|
|||
process.exit(2)
|
||||
}
|
||||
let chromium
|
||||
try { ({ chromium } = await import('playwright')) } catch {
|
||||
try { ({ chromium } = await import('./lib/playwright-guarded.mjs')) } catch {
|
||||
console.log('SKIP check-research-gallery-embed: playwright not installed'); process.exit(2)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
*
|
||||
* Exit: 0 all pass · 1 any fail · 2 dev server unreachable (a precondition, not a defect).
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { requireThisTree } from './_serves-this-tree.mjs'
|
||||
import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path'
|
||||
import { fileURLToPath as _fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
* NEGATIVE-TESTED: swapping the first two entries of NAV_MODEL fails by name, printing expected vs
|
||||
* actual side by side.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { requireDevServesThisCheckout as __devGuard, requireThisTree as __treeGuard } from './_serves-this-tree.mjs'
|
||||
import { fileURLToPath as __f2p } from 'node:url'
|
||||
import { dirname as __dn, resolve as __rs } from 'node:path'
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
* --url is REQUIRED. Several gates here hardcode :5173, and a drifted dev server made them measure
|
||||
* another worktree's tree — so this one refuses to guess.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { requireThisTree } from './_serves-this-tree.mjs'
|
||||
import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path'
|
||||
import { fileURLToPath as _fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
import { readFileSync, existsSync } from 'node:fs'
|
||||
import { resolve, dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
|
||||
const REPO = process.env.DOXA_REPO
|
||||
|| resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
* Defaults to the LOCAL tree. Pass live URLs explicitly to audit what is published — the two
|
||||
* diverge, and conflating them cost a whole tick of wrong diagnoses.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { servesThisTree } from './_serves-this-tree.mjs'
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ if (i > -1) {
|
|||
// Measure THIS build, or measure nothing (exit 75 = NOT MEASURED; see a9550858 / _serves-this-tree.mjs).
|
||||
const { requireThisTree } = await import('./_serves-this-tree.mjs')
|
||||
await requireThisTree(process.argv[i + 1], ROOT, 'VISION-CARD-NUMBERS')
|
||||
const { chromium } = await import('playwright')
|
||||
const { chromium } = await import('./lib/playwright-guarded.mjs')
|
||||
const b = await chromium.launch()
|
||||
const p = await b.newPage({ viewport: { width: 900, height: 1000 } })
|
||||
await p.goto(process.argv[i + 1], { waitUntil: 'networkidle' })
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { createServer } from 'node:http'
|
|||
import { readFileSync, existsSync, statSync, createReadStream } from 'node:fs'
|
||||
import { resolve, join, extname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { TREE_NAME } from './_tree-name.mjs'
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||
|
|
|
|||
32
internal/lib/browser-gates.mjs
Normal file
32
internal/lib/browser-gates.mjs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/** 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 }
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
import { readdirSync, existsSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { homedir } from 'node:os'
|
||||
import * as pw from 'playwright'
|
||||
import * as pw from './playwright-guarded.mjs'
|
||||
|
||||
export const TELEMETRY = /events\.mapbox\.com/
|
||||
|
||||
|
|
|
|||
41
internal/lib/playwright-guarded.mjs
Normal file
41
internal/lib/playwright-guarded.mjs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* internal/lib/playwright-guarded.mjs — the ONE way a repo gate reaches Playwright.
|
||||
*
|
||||
* Only the browser seat runs browsers, and only inside the shared slot (internal/browser-slot.sh, a
|
||||
* memory-floored semaphore). That rule used to be remembered, not enforced: an agent ran a gate it took
|
||||
* for a headless source check and it launched Chromium outside the slot. Every browser launch now goes
|
||||
* through this module, and launch()/launchPersistentContext()/launchServer()/connect()/connectOverCDP()
|
||||
* refuse unless browser-slot.sh has EXPORTED DOXA_BROWSER_SLOT (exported, so a `bash -c "…"` grandchild
|
||||
* inherits it). Refusal is exit 75, NOT MEASURED, never a failure and never a pass.
|
||||
* check:browser-gates-guarded fails any repo file that imports playwright directly instead.
|
||||
*/
|
||||
import * as pw from 'playwright'
|
||||
|
||||
export const NOT_MEASURED = 75
|
||||
|
||||
export function requireBrowserSlot(who = process.argv[1] || 'this gate') {
|
||||
if (process.env.DOXA_BROWSER_SLOT) return
|
||||
console.error(`${who}: this gate launches a browser. Browsers run only on the browser seat, inside the slot:\n` +
|
||||
' internal/browser-slot.sh bun run <script> (it exports DOXA_BROWSER_SLOT)\n' +
|
||||
'NOT MEASURED (exit 75).')
|
||||
process.exit(NOT_MEASURED)
|
||||
}
|
||||
|
||||
const LAUNCHERS = new Set(['launch', 'launchPersistentContext', 'launchServer', 'connect', 'connectOverCDP'])
|
||||
function guarded(browserType) {
|
||||
return new Proxy(browserType, {
|
||||
get(target, prop) {
|
||||
const v = Reflect.get(target, prop, target)
|
||||
if (typeof v !== 'function') return v
|
||||
if (LAUNCHERS.has(prop)) return (...args) => { requireBrowserSlot(); return v.apply(target, args) }
|
||||
return v.bind(target)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const chromium = guarded(pw.chromium)
|
||||
export const firefox = guarded(pw.firefox)
|
||||
export const webkit = guarded(pw.webkit)
|
||||
export const devices = pw.devices
|
||||
export const errors = pw.errors
|
||||
export default { chromium, firefox, webkit, devices, errors }
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
* to find what DOES wait on what. Every run is a FRESH browser context (cold HTTP cache) under
|
||||
* Chrome DevTools network throttling, so a local server is not mistaken for a real connection.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { spawn } from 'node:child_process'
|
||||
import { createServer } from 'node:http'
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// Targeted re-capture: cap1 (vision-card WITH token so it renders, not the token-error state)
|
||||
// and cap5 (SPA deep-linked to a plugin so a map shows, not "choose a map").
|
||||
// Retries navigation while concurrent builds churn the tree.
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { createServer } from 'node:http'
|
||||
import { readFile, stat } from 'node:fs/promises'
|
||||
import { join, extname, resolve, dirname } from 'node:path'
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
*
|
||||
* Output: documentation/readme-images/*.png
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { createServer } from 'node:http'
|
||||
import { readFile, stat, mkdir, writeFile } from 'node:fs/promises'
|
||||
import { createHash } from 'node:crypto'
|
||||
|
|
|
|||
|
|
@ -46,12 +46,14 @@
|
|||
import { spawn, spawnSync } from 'node:child_process'
|
||||
import { readdirSync, existsSync, readFileSync } from 'node:fs'
|
||||
import { isWebBuilt } from './plugin-targets.mjs'
|
||||
import { isBrowserGate as isBrowserGateAt } from './lib/browser-gates.mjs'
|
||||
import { cpus } from 'node:os'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const argv = process.argv.slice(2)
|
||||
const isBrowserGate = (name) => isBrowserGateAt(ROOT, name)
|
||||
const VERBOSE = argv.includes('--verbose')
|
||||
const BUNDLES = argv.includes('--bundles')
|
||||
const labelArg = argv.find((a) => a.startsWith('--label='))
|
||||
|
|
@ -107,32 +109,6 @@ const LOCK_WAIT_S = Number(process.env.BROWSER_LOCK_WAIT || 900)
|
|||
// EX_TEMPFAIL. Anything but 0 or 1, so it cannot be confused with a gate's own verdict.
|
||||
const LOCK_BUSY_CODE = 75
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
function isBrowserGate(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).
|
||||
if (/from 'playwright'|require\('playwright'\)|chromium/.test(src)) return true
|
||||
return /browser:\s*true/.test(src) && /--full\b/.test(String(scripts[name]))
|
||||
} catch { return false }
|
||||
}
|
||||
|
||||
/**
|
||||
* Hold a browser gate back while the box is already overloaded. Same threshold and cadence as
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@
|
|||
* NEGATIVE-TESTED: re-introducing the base-concatenation bug makes this FAIL naming the 404 URL;
|
||||
* removing tree-base makes it FAIL on the empty tab list. A gate that cannot fail is worse than none.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { requireDevServesThisCheckout as __devGuard, requireThisTree as __treeGuard } from './_serves-this-tree.mjs'
|
||||
import { fileURLToPath as __f2p } from 'node:url'
|
||||
import { dirname as __dn, resolve as __rs } from 'node:path'
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
*
|
||||
* Usage: bun run smoke (serves public/ on a free port, then drives it)
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { createServer } from 'node:http'
|
||||
import { readFile, stat } from 'node:fs/promises'
|
||||
import { join, extname, resolve, dirname } from 'node:path'
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
* BASE=http://127.0.0.1:8080/<TREE_NAME>/ node internal/verify-5-capabilities.mjs # a local serve
|
||||
* Exits non-zero if any level fails, so it can gate a release.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { requireThisTree } from './_serves-this-tree.mjs'
|
||||
import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path'
|
||||
import { fileURLToPath as _fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
* USAGE: node internal/verify-gospel-map.mjs [baseUrl] (no setup needed)
|
||||
* Skips loudly (exit 0) only if the tree is not built, the way CI sees it.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { servesThisTree } from './_serves-this-tree.mjs'
|
||||
import { spawn } from 'node:child_process'
|
||||
import { existsSync, readFileSync } from 'node:fs'
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
*
|
||||
* Exits non-zero if any leaf fails, so it can become a gate.
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { requireThisTree } from './_serves-this-tree.mjs'
|
||||
import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path'
|
||||
import { fileURLToPath as _fileURLToPath } from 'node:url'
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
* Usage: node internal/verify-shell-scroll.mjs [baseUrl]
|
||||
* (defaults to http://localhost:8899; serve ./public)
|
||||
*/
|
||||
import { chromium } from 'playwright'
|
||||
import { chromium } from './lib/playwright-guarded.mjs'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { requireThisTree } from './_serves-this-tree.mjs'
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
"apk:debug": "bun run build && bun run cap:sync && bun run app:start-page && bun run cap:verify && cd deployment/capacitor/android && ./gradlew assembleDebug",
|
||||
"apk:install": "adb install -r deployment/capacitor/android/app/build/outputs/apk/debug/app-debug.apk",
|
||||
"app:start-page": "node internal/set-app-start-page.mjs",
|
||||
"browser-gates": "node internal/check-browser-gates-guarded.mjs --list",
|
||||
"build": "bash internal/build-lock.sh",
|
||||
"build:bundle": "bun x vite build",
|
||||
"build:bundles": "node internal/run-parallel.mjs --bundles",
|
||||
|
|
@ -24,6 +25,7 @@
|
|||
"check:backlog-refs": "node internal/check-backlog-refs.mjs",
|
||||
"check:backlog-status": "node internal/check-backlog-status-enum.mjs",
|
||||
"check:basemap-labels": "node internal/check-basemap-labels-localized.mjs",
|
||||
"check:browser-gates-guarded": "node internal/check-browser-gates-guarded.mjs",
|
||||
"check:capability-citations": "node internal/check-capability-citations.mjs",
|
||||
"check:capability-derived": "node internal/check-capability-derived.mjs",
|
||||
"check:capability-exemplars": "node internal/check-capability-exemplars.mjs",
|
||||
|
|
@ -254,7 +256,7 @@
|
|||
"fix:sri": "node internal/fix-sri.mjs",
|
||||
"gallery": "node internal/generate-gallery.mjs",
|
||||
"gates:post": "node internal/run-parallel.mjs --label=post -- check:capability-derived check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:zoning-declared check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags check:backlog-status check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:backlog-refs check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed check:dashboard-geocoder-provider check:leaf-engine-per-provider check:map-props-unit check:composites-mount check:composite-interop check:no-free-identifiers check:map-fly-reduced-motion check:mst-clustering check:list-prop-unit check:profiles-mount check:cdn-pins check:no-eval check:popup-escaping check:i18n-treeshake check:pinned-umd check:one-palette:self-test check:no-cross-plugin-imports check:no-cross-plugin-imports:self-test check:map-attribution check:studio-rail check:approx-pin-note check:embedder-keys check:provider-for check:legend-logical-insets check:legend-logical-insets:self-test check:optional-t-equivalence check:text-highlight check:religion-label-not-value check:i18n-stopped-scope check:no-silent-engine check:regions-uses-library check:shadow-css-parses check:square-map-controls check:preset-config-merge",
|
||||
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test",
|
||||
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded",
|
||||
"gen:iso3-to-alpha2": "node library/scripts/generate-iso3-to-alpha2.mjs",
|
||||
"internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs",
|
||||
"library-index": "node internal/generate-library-index.mjs",
|
||||
|
|
@ -303,6 +305,7 @@
|
|||
},
|
||||
"//scripts": {
|
||||
"_": "What each script does. These notes used to sit inside the command strings as trailing \"# …\" comments, which made the shell discard any arguments passed after \"--\" (so a gate given --url silently measured its hardcoded default port — possibly another agent's server). Keep notes here, never in a command.",
|
||||
"browser-gates": "lists every package.json script that launches a browser (the same detection run-parallel uses to route them through internal/browser-slot.sh). Run each as internal/browser-slot.sh bun run <name>.",
|
||||
"cap:sync": "NOT `bunx` — bunx is not on PATH in this environment (command not found, exit 127), which broke cap:sync -> apk:debug -> phone at step one. The CLI IS installed locally (@capacitor/cli 7.6.9); invoke the local bin directly. The rest of package.json already uses the spaced `bun x` form.",
|
||||
"capability-exemplars": "regenerate documentation/reference-capability-exemplars.md — the DERIVED answer to \"which bundle is the capability-N example?\", a question answered WRONGLY in three separate artifacts (a gate, the README, the /plugin-designer skill).",
|
||||
"check:a11y-button-names": "every VISIBLE button must have an accessible name (aria-label / aria-labelledby / visible text — a `title` is a TOOLTIP and does NOT count). Measures the RENDERED DOM through shadow roots: a source grep cannot answer this (132 `<button>` hits, most with visible text). Needs a browser, so NOT in build:unlocked and NOT in CI. ALL=1 sweeps every profile.",
|
||||
|
|
@ -310,6 +313,7 @@
|
|||
"check:auto-engine": "BROWSER. The default engine is auto: on one map leaf, no token draws with MapLibre and makes zero api.mapbox.com requests, a token draws with Mapbox, and an explicit provider wins. Under the fleet browser policy it is a rega run, not part of gates:post.",
|
||||
"check:backlog-refs": "a card may not depend on a card that does not exist. A dangling depends_on can never be done, so the card can never become ready and nothing says so — it reads as ordinary blocked work forever. FAILS only for depends_on on an OPEN card; a dangling parent or a stale link on a done card is REPORTED, because failing on filing debt teaches people to delete the reference rather than restore the card. Reads all three phase backlogs — references legitimately cross phases. SKIPs when internal-tracker is absent.",
|
||||
"check:basemap-labels": "the BASEMAP's place names follow the reader's language on BOTH engines (library/maps/localize-labels.js): reads the RENDERED country labels at ?lang=en and ?lang=ru — Mapbox via the Standard basemap's place-labels featureset, MapLibre via each symbol's evaluated text — and requires them to flip. Serves this checkout on an OS-assigned port; --dir <tree> for a negative control. Negative-tested: a bundle built before 319064a1 FAILS on both engines.",
|
||||
"check:browser-gates-guarded": "no repo file imports playwright except through internal/lib/playwright-guarded.mjs, whose launch refuses (exit 75, NOT MEASURED) unless internal/browser-slot.sh has exported DOXA_BROWSER_SLOT. Browsers run only on the browser seat, inside the slot; this makes that rule enforced, not remembered.",
|
||||
"check:capability-citations": "a doc that names a bundle as \"capability N\" must match manifest.json, which is what the BUILD computed. README captioned doxa-vision-card as cap-1; it ships 2 instances so it is parameterized (cap-2). The SAME error was fixed once in verify-5-capabilities.mjs and never reached the README — two copies of one fact, nothing forcing agreement.",
|
||||
"check:capability-derived": "every bundle lands on a real capability 1-5, never 0, and a DECLARED tier must agree with the composes edges it declares. Tiers 3-5 used to be authored only - discover.mjs infers single and parameterized and nothing else - so a composite whose author forgot a meta.json shipped as capability 1; research-dashboard did exactly that while nesting ten views, and a missing file is indistinguishable from a deliberate choice, so nothing failed. The declaration stays authoritative: it is CHECKED, not overridden, because a meta that contradicts the structure means one of the two is stale and picking silently is how a tier rots. Two traps found by running the rule BEFORE writing it: composes is per-PROFILE at source but hoisted to the BUNDLE in the manifest, so deriving from the bundle row gave a composite's edges to its plain sibling leaves (11 false failures on correct data); and refs come in two forms in one field, 'bundle/profile' and a bare 'bundle', so resolving only the first demoted plugin-studio from 5 to 4. With both handled: 10 composing profiles, 10 agree, 0 disagree. An unresolved ref is REPORTED, never counted as 'not a composite' - a typo and a genuine plain child look identical from here. Negative-tested on the research-dashboard shape (a composite declared single).",
|
||||
"check:capability-exemplars": "REGENERATES and content-compares (not mtimes: a full build rewrites manifest.json every time, so an mtime rule would cry wolf after every build and get switched off).",
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ const skip = (why) => { console.log(`${TAG}: SKIP — ${why}`); process.exit(0)
|
|||
|
||||
if (!existsSync(MANIFEST)) skip('no built manifest (run `bun run tree` first).')
|
||||
let chromium
|
||||
try { ({ chromium } = await import('playwright')) } catch { skip('playwright is not installed.') }
|
||||
try { ({ chromium } = await import('../../../internal/lib/playwright-guarded.mjs')) } catch { skip('playwright is not installed.') }
|
||||
|
||||
const manifest = JSON.parse(readFileSync(MANIFEST, 'utf8'))
|
||||
const bundle = (manifest.bundles || []).find((b) => b.bundle === 'doxa-research-map')
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ if (!existsSync(resolve(ROOT, `public/${TREE_NAME}/research-map-plus-grid/resear
|
|||
console.log('SKIP check-export-poster-prints: no built research-map-plus-grid'); process.exit(2)
|
||||
}
|
||||
let chromium
|
||||
try { ({ chromium } = await import('playwright')) } catch { console.log('SKIP check-export-poster-prints: playwright not installed'); process.exit(2) }
|
||||
try { ({ chromium } = await import('../../../internal/lib/playwright-guarded.mjs')) } catch { console.log('SKIP check-export-poster-prints: playwright not installed'); process.exit(2) }
|
||||
|
||||
const freePort = () => new Promise(ok => { const s = createServer().listen(0, '127.0.0.1', () => { const p = s.address().port; s.close(() => ok(p)) }) })
|
||||
const PROVIDER = process.argv.includes('--provider') ? process.argv[process.argv.indexOf('--provider') + 1] : '' // maplibre = the keyless engine
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ const skip = (why) => { console.log(`${TAG}: SKIP — ${why}`); process.exit(0)
|
|||
|
||||
if (!existsSync(MANIFEST)) skip('no built manifest (run `bun run tree` first).')
|
||||
let chromium
|
||||
try { ({ chromium } = await import('playwright')) } catch { skip('playwright is not installed.') }
|
||||
try { ({ chromium } = await import('../../../internal/lib/playwright-guarded.mjs')) } catch { skip('playwright is not installed.') }
|
||||
|
||||
const manifest = JSON.parse(readFileSync(MANIFEST, 'utf8'))
|
||||
const bundle = (manifest.bundles || []).find((b) => b.bundle === 'doxa-research-map')
|
||||
|
|
|
|||
|
|
@ -89,8 +89,9 @@ useShadowStyles(`
|
|||
(matches legend's 10px left inset). Locked to legend width. The Mapbox
|
||||
default 10px child margin is NEUTRALIZED here by our positioning —
|
||||
without this override the geocoder would nest-indent another 10px. */
|
||||
.mapboxgl-ctrl-top-left { top:10px!important;left:10px!important;width:var(--map-legend-width,340px)!important;max-width:var(--map-legend-width,340px)!important;z-index:1200!important; }
|
||||
.mapboxgl-ctrl-top-left .mapboxgl-ctrl { margin:0!important;float:none!important; }
|
||||
/* Both engines' class (keyless is MapLibre), and the reading-start side, so Arabic puts the column on the right. */
|
||||
.mapboxgl-ctrl-top-left, .maplibregl-ctrl-top-left { top:10px!important;inset-inline-start:10px!important;width:var(--map-legend-width,340px)!important;max-width:var(--map-legend-width,340px)!important;z-index:1200!important; }
|
||||
.mapboxgl-ctrl-top-left .mapboxgl-ctrl, .maplibregl-ctrl-top-left .maplibregl-ctrl { margin:0!important;float:none!important; }
|
||||
/* Geocoder pill: inherit Mapbox's DEFAULT height + icon positions. Only
|
||||
override width + rounded border + shadow. This keeps the search-icon
|
||||
and X-button perfectly aligned by Mapbox's own calibrated CSS. */
|
||||
|
|
@ -126,7 +127,7 @@ useShadowStyles(`
|
|||
.dg-main { font-weight:500;font-size:13px;color:#222;line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;gap:6px; }
|
||||
.dg-main strong { font-weight:700;overflow:hidden;text-overflow:ellipsis; }
|
||||
.dg-meta { margin-top:2px;display:flex;flex-wrap:wrap;gap:4px 10px;font-size:11px;color:#555;line-height:1.35; }
|
||||
.dg-field strong { font-weight:600;color:#333;margin-right:2px; }
|
||||
.dg-field strong { font-weight:600;color:#333;margin-inline-end:2px; }
|
||||
/* Round 16 — labeled category tag replacing the old emoji prefixes. */
|
||||
.dg-tag { flex:none;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:0.4px;color:#3b463d;background:rgba(59,70,61,0.10);border-radius:4px;padding:1px 5px;line-height:1.5;white-space:nowrap; }
|
||||
.dg-agg-label { overflow:hidden;text-overflow:ellipsis; }
|
||||
|
|
@ -205,7 +206,7 @@ useShadowStyles(`
|
|||
.dsm-help-modal h2 { margin:0 0 12px;font-size:18px;font-weight:600; }
|
||||
.dsm-help-modal p { margin:0 0 10px;font-size:14px;line-height:1.6;color:#ccc; }
|
||||
.dsm-help-modal p:last-of-type { margin-bottom:0; }
|
||||
.dsm-help-close { position:absolute;top:12px;right:14px;background:none;border:none;color:#fff;font-size:20px;cursor:pointer;line-height:1;padding:4px 6px;border-radius:4px; }
|
||||
.dsm-help-close { position:absolute;top:12px;inset-inline-end:14px;background:none;border:none;color:#fff;font-size:20px;cursor:pointer;line-height:1;padding:4px 6px;border-radius:4px; }
|
||||
.dsm-help-close:hover { background:rgba(255,255,255,0.12); }
|
||||
`, 'doxa-simple-map')
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
// compatible serialization). It does not import or mutate src/config/color-strategies,
|
||||
// clusters.json, or any sibling bundle.
|
||||
|
||||
import { firefox } from 'playwright';
|
||||
import { firefox } from '../../../internal/lib/playwright-guarded.mjs';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
//
|
||||
// Exit 0 = all checks PASS; non-zero = at least one FAIL.
|
||||
|
||||
import { firefox } from 'playwright';
|
||||
import { firefox } from '../../../internal/lib/playwright-guarded.mjs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { mkdirSync, readFileSync } from 'node:fs';
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ const { TREE_NAME } = await import(join(ROOT, 'internal', '_tree-name.mjs'))
|
|||
const MANIFEST = join(ROOT, 'public', TREE_NAME, 'manifest.json')
|
||||
if (!existsSync(MANIFEST)) skip('no built manifest (run `bun run tree` first).')
|
||||
let chromium
|
||||
try { ({ chromium } = await import('playwright')) } catch { skip('playwright is not installed.') }
|
||||
try { ({ chromium } = await import('../../../internal/lib/playwright-guarded.mjs')) } catch { skip('playwright is not installed.') }
|
||||
|
||||
const manifest = JSON.parse(readFileSync(MANIFEST, 'utf8'))
|
||||
// The token ladder every other browser gate here uses: env first, then the build config. Presence
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ const skip = (why) => { console.log(`${TAG}: SKIP — ${why}`); process.exit(0)
|
|||
if (!existsSync(MANIFEST)) skip('no built manifest (run `bun run tree` first).')
|
||||
if (!existsSync(LOCALES_DIR)) skip('no locale catalogues to compare against.')
|
||||
let chromium
|
||||
try { ({ chromium } = await import('playwright')) } catch { skip('playwright is not installed.') }
|
||||
try { ({ chromium } = await import('../../../internal/lib/playwright-guarded.mjs')) } catch { skip('playwright is not installed.') }
|
||||
|
||||
// The composites in this lane. Both, always: the two share the grid and half the chrome, and a fix
|
||||
// proved on one has repeatedly been stale on the other.
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ const skip = (why) => { console.log(`${TAG}: SKIP — ${why}`); process.exit(0)
|
|||
|
||||
if (!existsSync(MANIFEST)) skip('no built manifest (run `bun run tree` first).')
|
||||
let chromium
|
||||
try { ({ chromium } = await import('playwright')) } catch { skip('playwright is not installed.') }
|
||||
try { ({ chromium } = await import('../../../internal/lib/playwright-guarded.mjs')) } catch { skip('playwright is not installed.') }
|
||||
|
||||
const manifest = JSON.parse(readFileSync(MANIFEST, 'utf8'))
|
||||
const bundle = (manifest.bundles || []).find((b) => b.bundle === 'research-map-plus-grid')
|
||||
|
|
|
|||
|
|
@ -425,7 +425,7 @@
|
|||
"bundle": "doxa-simple-map",
|
||||
"scriptUrl": "./doxa-simple-map/doxa-simple-map.js",
|
||||
"output": "dx-maps/doxa-simple-map/doxa-simple-map.js",
|
||||
"integrity": "sha384-xEmkBVjwtaQF27UhcRUlECpBkMnINUAlUr18Cm5pxUoY0brWXf6dGloZkHAkFzV6",
|
||||
"integrity": "sha384-9YwVbBx3d3dQyVOXKWNSTDHZMOmSKj48q24cXUs03IfAggAW4nS/pcsy2RT9UZsN",
|
||||
"elements": [
|
||||
"doxa-map",
|
||||
"doxa-simple-map"
|
||||
|
|
@ -445,7 +445,7 @@
|
|||
"scriptUrl": "./doxa-simple-map/doxa-simple-map.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./doxa-simple-map/doxa-simple-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-simple-map/doxa-simple-map.js\" integrity=\"sha384-xEmkBVjwtaQF27UhcRUlECpBkMnINUAlUr18Cm5pxUoY0brWXf6dGloZkHAkFzV6\" crossorigin=\"anonymous\"></script>\n<doxa-map profile-config='{\"profile\":\"doxa-simple-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-map>"
|
||||
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-simple-map/doxa-simple-map.js\" integrity=\"sha384-9YwVbBx3d3dQyVOXKWNSTDHZMOmSKj48q24cXUs03IfAggAW4nS/pcsy2RT9UZsN\" crossorigin=\"anonymous\"></script>\n<doxa-map profile-config='{\"profile\":\"doxa-simple-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-map>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <doxa-map> custom element. -->
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-xEmkBVjwtaQF27UhcRUlECpBkMnINUAlUr18Cm5pxUoY0brWXf6dGloZkHAkFzV6" crossorigin="anonymous"></script>
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-9YwVbBx3d3dQyVOXKWNSTDHZMOmSKj48q24cXUs03IfAggAW4nS/pcsy2RT9UZsN" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <doxa-map> custom element. -->
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-xEmkBVjwtaQF27UhcRUlECpBkMnINUAlUr18Cm5pxUoY0brWXf6dGloZkHAkFzV6" crossorigin="anonymous"></script>
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-9YwVbBx3d3dQyVOXKWNSTDHZMOmSKj48q24cXUs03IfAggAW4nS/pcsy2RT9UZsN" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <doxa-map> custom element. -->
|
||||
<script src="../doxa-simple-map.js" integrity="sha384-xEmkBVjwtaQF27UhcRUlECpBkMnINUAlUr18Cm5pxUoY0brWXf6dGloZkHAkFzV6" crossorigin="anonymous"></script>
|
||||
<script src="../doxa-simple-map.js" integrity="sha384-9YwVbBx3d3dQyVOXKWNSTDHZMOmSKj48q24cXUs03IfAggAW4nS/pcsy2RT9UZsN" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <doxa-map> custom element. -->
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-xEmkBVjwtaQF27UhcRUlECpBkMnINUAlUr18Cm5pxUoY0brWXf6dGloZkHAkFzV6" crossorigin="anonymous"></script>
|
||||
<script src="../../doxa-simple-map.js" integrity="sha384-9YwVbBx3d3dQyVOXKWNSTDHZMOmSKj48q24cXUs03IfAggAW4nS/pcsy2RT9UZsN" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -425,7 +425,7 @@
|
|||
"bundle": "doxa-simple-map",
|
||||
"scriptUrl": "./doxa-simple-map/doxa-simple-map.js",
|
||||
"output": "dx-maps/doxa-simple-map/doxa-simple-map.js",
|
||||
"integrity": "sha384-xEmkBVjwtaQF27UhcRUlECpBkMnINUAlUr18Cm5pxUoY0brWXf6dGloZkHAkFzV6",
|
||||
"integrity": "sha384-9YwVbBx3d3dQyVOXKWNSTDHZMOmSKj48q24cXUs03IfAggAW4nS/pcsy2RT9UZsN",
|
||||
"elements": [
|
||||
"doxa-map",
|
||||
"doxa-simple-map"
|
||||
|
|
@ -445,7 +445,7 @@
|
|||
"scriptUrl": "./doxa-simple-map/doxa-simple-map.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./doxa-simple-map/doxa-simple-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-simple-map/doxa-simple-map.js\" integrity=\"sha384-xEmkBVjwtaQF27UhcRUlECpBkMnINUAlUr18Cm5pxUoY0brWXf6dGloZkHAkFzV6\" crossorigin=\"anonymous\"></script>\n<doxa-map profile-config='{\"profile\":\"doxa-simple-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-map>"
|
||||
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-simple-map/doxa-simple-map.js\" integrity=\"sha384-9YwVbBx3d3dQyVOXKWNSTDHZMOmSKj48q24cXUs03IfAggAW4nS/pcsy2RT9UZsN\" crossorigin=\"anonymous\"></script>\n<doxa-map profile-config='{\"profile\":\"doxa-simple-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-map>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue