Register the embedder-key gate so it can actually run
check:embedder-keys asserts regions-mfe forwards the documented embedder keys to its panes, and that profile and instanceId are NOT forwarded (the host's would overwrite each pane's own and collapse both tabs into one view). Negative-tested both ways: removing dataSource fails naming it; adding profile fails naming the collapsed-tabs risk. The gate lives in the plugin's scripts/ folder, as nine check: entries already do. Its comment stripper is committed beside it (strip-code.mjs), because a committed gate cannot import from the gitignored QA folder. Registered in scripts (alphabetical, with its //scripts note) and in gates:post; source-only, ~0.2s.
This commit is contained in:
parent
c193621bc2
commit
dc226c2e01
3 changed files with 153 additions and 1 deletions
|
|
@ -61,6 +61,7 @@
|
|||
"check:embed-targets": "node internal/check-embed-targets-exist.mjs",
|
||||
"check:embed-targets:self-test": "node internal/check-embed-targets-exist.mjs --self-test",
|
||||
"check:embedded-rtl": "node internal/check-embedded-rtl.mjs",
|
||||
"check:embedder-keys": "node plugin-designs/regions-dashboard/scripts/check-embedder-keys.mjs",
|
||||
"check:english-leaks": "node internal/check-gospel-english-leaks.mjs",
|
||||
"check:experimental-shelf": "node internal/check-experimental-shelf.mjs",
|
||||
"check:export-one-click": "node plugin-designs/doxa-research-map/scripts/check-export-one-click.mjs",
|
||||
|
|
@ -212,7 +213,7 @@
|
|||
"dev": "bun x vite",
|
||||
"fix:sri": "node internal/fix-sri.mjs",
|
||||
"gallery": "node internal/generate-gallery.mjs",
|
||||
"gates:post": "node internal/run-parallel.mjs --label=post -- 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: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",
|
||||
"gates:post": "node internal/run-parallel.mjs --label=post -- 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: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",
|
||||
"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",
|
||||
"internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs",
|
||||
"library-index": "node internal/generate-library-index.mjs",
|
||||
|
|
@ -292,6 +293,7 @@
|
|||
"check:embed-base": "the shared URL resolver, pinned by ARITHMETIC — no browser, no network, no served tree, so it cannot SKIP itself into irrelevance. It computes what a leaf's own \"../../assets\" means and asserts the resolver agrees. Caught nothing for fifteen ticks because it did not exist: resolveAssetsBase computed one segment too high, every local gate passed (the dev web-root has assets BOTH inside and beside the tree), and the byte-level publish gates went green on the broken publish.",
|
||||
"check:embed-basemap": "the gospel map's BASEMAP must paint inside a genuinely cross-origin iframe on the token the page BAKES, not one a probe forces on it. Every other browser gate here forces a restricted token (u=doxalife) that 403s on the basemap, then counts PINS — which come from our own GeoJSON and paint with no token at all — so 'the map renders' had never been measured. Runs a negative control every time by rewriting the token on the wire; if the control passes it reports PROVED NOTHING rather than a pass. ~60s, needs a browser.",
|
||||
"check:embed-targets": "every embed.iframe src in manifest.json must resolve to a file that EXISTS. Those snippets ARE deployment-method 1 (iframe) — a partner pastes them verbatim — and NOTHING else asserts their targets are still emitted, because every other gate walks only files that exist and is blind to ABSENCE. `the `check:embed-targets:self-test` script` poisons a copy of the manifest and asserts the gate NOTICES: a gate that cannot fail is worse than no gate.",
|
||||
"check:embedder-keys": "regions-mfe forwards page-scoped config to its nested panes by an explicit key list (INHERITED_PAGE_KEYS). Asserts the documented embedder keys travel (dataSource, tokenUrl among them) AND that profile and instanceId do NOT: the host's would overwrite each pane's own and collapse both tabs into one view.",
|
||||
"check:english-leaks": "A translated key must not render in English. rega's method: a string is a LEAK when the catalogue has a DIFFERENT value for the locale and the page still shows the English one - a render/wiring fault, not a missing translation. Third-party attributions, data names, placeholder-only strings and the prose this bundle deliberately keeps in English are all excluded, the last by simply not being in the catalogue. Scoped to the LEAF on purpose: six keys were reported leaking in the gallery and the leaf is clean in ar/zh/ru/hi - the gallery builds its iframe src with no lang parameter, so the embed never learns the locale. Embedder defect, not a bundle one.",
|
||||
"check:experimental-shelf": "Every Experimental-folder bundle, leaf + Gallery tab, mapbox + maplibre: mounts with content, 0 errors / failed requests, maps load and DRAW their own data. Self-tests on a deliberately broken bundle first. Run through internal/browser-slot.sh.",
|
||||
"check:export-one-click": "the Simple Research 'Export poster' button gives a FILE on ONE click (no menu, no dialog, no second step): leaf AND Research Gallery, en AND ar; the file is a real print PNG (US Letter @200 DPI); 0 page errors. Run inside the shared browser slot. Exit 2 = SKIP, 75 = NOT MEASURED.",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
#!/usr/bin/env node
|
||||
// A cap-4 composite must forward the page keys an EMBEDDER is documented to set -- or the key is a
|
||||
// promise the shell silently breaks.
|
||||
//
|
||||
// PRODUCTION-MATRIX.md's own table for anyone running cells B and C names four keys and what each
|
||||
// costs when omitted: profile ("no view to render"), dataSource ("wrong/partial data -- Regions uses
|
||||
// pray-tools"), tk ("basemap never authenticates"), assetsBase ("the one key to CHANGE, not copy").
|
||||
// A nested pane has NO leaf page of its own, so anything the generator bakes once per page has to be
|
||||
// forwarded by hand. regions-mfe forwarded assetsBase and not dataSource.
|
||||
//
|
||||
// WHY THE LIST IS NOT SIMPLY "ALL FOUR":
|
||||
// profile MUST NOT be inherited. The host's profile would overwrite each pane's own, which is
|
||||
// the one thing that makes a pane a pane. Asserted as an EXCLUSION below, so a future
|
||||
// "fix" that adds it fails this gate instead of silently collapsing both tabs into one.
|
||||
// tk needs no forwarding: ProfileLoader publishes a token it is handed PAGE-WIDE
|
||||
// (ProfileLoader.vue:128) and every pane reads it via useSharedMapToken.
|
||||
// tokenUrl forwarded for symmetry rather than for a live failure -- see the note in the source.
|
||||
// The outer loader fetches and publishes, so panes have a token; without the URL they
|
||||
// simply cannot re-ask the service themselves.
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { codeOnly } from './strip-code.mjs'
|
||||
|
||||
// Resolve the target from THIS FILE's location, not from cwd. A gate that only passes when run from
|
||||
// the repo root is a gate that silently NOT-MEASURES from anywhere else, and `bun run` is not the
|
||||
// only way this gets invoked.
|
||||
const HERE = dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
let fails = 0
|
||||
const ok = (c, m) => { console.log(` ${c ? 'ok ' : 'FAIL'} - ${m}`); if (!c) fails++ }
|
||||
|
||||
const FILE = resolve(HERE, '..', 'regions-mfe.vue')
|
||||
const src = codeOnly(readFileSync(FILE, 'utf8'))
|
||||
const listMatch = /const INHERITED_PAGE_KEYS\s*=\s*\[([^\]]*)\]/.exec(src)
|
||||
ok(!!listMatch, `regions-mfe declares INHERITED_PAGE_KEYS (${FILE.replace(/.*plugin-designs/, 'plugin-designs')})`)
|
||||
const keys = listMatch ? listMatch[1].split(',').map((k) => k.trim().replace(/^['"]|['"]$/g, '')).filter(Boolean) : []
|
||||
console.log(`\n forwarded keys (${keys.length}): ${keys.join(', ')}\n`)
|
||||
|
||||
for (const k of ['assetsBase', 'dataSource', 'tokenUrl', 'locale', 'provider']) {
|
||||
ok(keys.includes(k), `forwards '${k}'`)
|
||||
}
|
||||
// ★ EXCLUSIONS, asserted so a well-meaning addition cannot pass unnoticed.
|
||||
for (const k of ['profile', 'instanceId']) {
|
||||
ok(!keys.includes(k), `does NOT forward '${k}' -- the host's would overwrite each pane's own, collapsing both tabs into one view`)
|
||||
}
|
||||
// The merge must keep a pane's own config last, or the exclusions above stop protecting anything.
|
||||
ok(/\{\s*\.\.\.inherited,\s*\.\.\.spec\.value\.config,\s*\.\.\.pane\.config\s*\}/.test(src),
|
||||
'the merge order is { ...inherited, ...spec.config, ...pane.config } -- a pane\'s own keys win last')
|
||||
// And the filter must keep `false`, which is the value pinHint:false depends on.
|
||||
ok(/!==\s*undefined\s*&&[^)]*!==\s*null\s*&&[^)]*!==\s*''/.test(src),
|
||||
'the filter rejects only undefined/null/empty-string, so a legitimate `false` (pinHint) still travels')
|
||||
|
||||
// ── CONTROLS ────────────────────────────────────────────────────────────────────────────────────
|
||||
const parse = (t) => { const m = /const INHERITED_PAGE_KEYS\s*=\s*\[([^\]]*)\]/.exec(codeOnly(t)); return m ? m[1].split(',').map((k) => k.trim().replace(/^['"]|['"]$/g, '')).filter(Boolean) : [] }
|
||||
ok(parse("const INHERITED_PAGE_KEYS = ['a', 'b']").join() === 'a,b', 'POS CONTROL: the key list parses')
|
||||
ok(!parse("// const INHERITED_PAGE_KEYS = ['profile']").includes('profile'),
|
||||
'NEG CONTROL: a COMMENTED-OUT list is not read as the real one')
|
||||
ok(parse("const INHERITED_PAGE_KEYS = []").length === 0, 'CONTROL: an empty list reads as empty, not as absent')
|
||||
|
||||
console.log(fails ? `\nEMBEDDER-KEYS: ${fails} FAIL(s)` : '\nEMBEDDER-KEYS: every documented embedder key is forwarded, and profile/instanceId are correctly not')
|
||||
process.exit(fails ? 1 : 0)
|
||||
88
plugin-designs/regions-dashboard/scripts/strip-code.mjs
Normal file
88
plugin-designs/regions-dashboard/scripts/strip-code.mjs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
// codeOnly(src) — blank out comments WITHOUT being fooled by comment-like text inside strings.
|
||||
//
|
||||
// PLACEMENT (RULE 2): this lives in the PLUGIN folder, not in library/, because it has exactly one
|
||||
// committed consumer -- check-embedder-keys.mjs beside it. It started life in the gitignored
|
||||
// _qa-cycle scratch area, which a committed gate cannot import: the gate would work here and fail on
|
||||
// a fresh clone. The scratch copy now RE-EXPORTS this file, so there is one implementation rather
|
||||
// than two that drift. If a second committed consumer appears, that is the moment it earns a move to
|
||||
// library/utils/.
|
||||
//
|
||||
// WHY THIS EXISTS. Every _chk script in this folder used the same one-line helper:
|
||||
// s.replace(/\/\*[\s\S]*?\*\//g, ' ') ...
|
||||
// It produced a FALSE GREEN on C5. `import.meta.glob('./*.vue')` contains `/*` INSIDE A STRING, so
|
||||
// the regex opened a comment there and swallowed everything up to the next `*/` -- which happened to
|
||||
// include the whole setRTLTextPlugin block the gate was looking for. The gate then reported "no
|
||||
// plugin entry hand-rolls setRTLTextPlugin any more" while all six still did.
|
||||
//
|
||||
// That is worse than a red gate: a red gate gets investigated. This one would have certified the
|
||||
// deletion as already-done. The fix has to be state-aware, because the ONLY way to know whether `/*`
|
||||
// opens a comment is to know whether you are inside a string when you reach it -- which a regex
|
||||
// cannot know. So: a character scanner, with its own negative tests below.
|
||||
//
|
||||
// Comments are replaced by SPACES, preserving length and line structure, so line numbers reported
|
||||
// against the result still point at the real file.
|
||||
export function codeOnly(src) {
|
||||
let out = ''
|
||||
let i = 0
|
||||
const n = src.length
|
||||
while (i < n) {
|
||||
const c = src[i], d = src[i + 1]
|
||||
// ── strings and template literals: copied through verbatim, never interpreted ──
|
||||
if (c === '"' || c === "'" || c === '`') {
|
||||
const q = c
|
||||
out += c; i++
|
||||
while (i < n) {
|
||||
if (src[i] === '\\') { out += src[i] + (src[i + 1] || ''); i += 2; continue }
|
||||
out += src[i]
|
||||
if (src[i] === q) { i++; break }
|
||||
i++
|
||||
}
|
||||
continue
|
||||
}
|
||||
// ── line comment ──
|
||||
if (c === '/' && d === '/') {
|
||||
while (i < n && src[i] !== '\n') { out += ' '; i++ }
|
||||
continue
|
||||
}
|
||||
// ── block comment ──
|
||||
if (c === '/' && d === '*') {
|
||||
while (i < n && !(src[i] === '*' && src[i + 1] === '/')) { out += src[i] === '\n' ? '\n' : ' '; i++ }
|
||||
out += ' '; i += 2
|
||||
continue
|
||||
}
|
||||
out += c; i++
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ── SELF-TEST. A stripper that silently mis-strips is exactly how the false green happened, so this
|
||||
// runs whenever the file is executed directly. Each case is one way the naive regex was wrong.
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
let fails = 0
|
||||
const ok = (c, m) => { console.log(` ${c ? 'ok ' : 'FAIL'} - ${m}`); if (!c) fails++ }
|
||||
const has = (s, re) => re.test(codeOnly(s))
|
||||
|
||||
ok(has("const m = import.meta.glob('./*.vue')\nwindow.mapboxgl.setRTLTextPlugin(u)", /setRTLTextPlugin\(/),
|
||||
"THE REGRESSION: a `/*` inside import.meta.glob('./*.vue') no longer swallows the code after it")
|
||||
ok(!has("/* window.mapboxgl.setRTLTextPlugin(u) */", /setRTLTextPlugin\(/),
|
||||
'a real block comment is still stripped')
|
||||
ok(!has("// window.mapboxgl.setRTLTextPlugin(u)", /setRTLTextPlugin\(/),
|
||||
'a real line comment is still stripped')
|
||||
ok(has("const url = 'http://x/*/y'\ncall()", /call\(\)/),
|
||||
'a URL containing /*/ inside a string does not open a comment')
|
||||
ok(has('const s = "// not a comment"\ncall()', /call\(\)/),
|
||||
'a // inside a double-quoted string does not start a comment')
|
||||
ok(has('const s = `a /* b`\ncall()', /call\(\)/),
|
||||
'a /* inside a template literal does not start a comment')
|
||||
ok(has("const s = 'it\\'s /* fine'\ncall()", /call\(\)/),
|
||||
'an escaped quote does not end the string early')
|
||||
ok(codeOnly('a\n/* x\ny */\nb').split('\n').length === 4,
|
||||
'line count is preserved, so reported line numbers stay true')
|
||||
// POS control: the naive regex really did fail this, proving the case is not hypothetical
|
||||
const naive = (s) => s.replace(/\/\*[\s\S]*?\*\//g, ' ')
|
||||
ok(!/setRTLTextPlugin\(/.test(naive("const m = import.meta.glob('./*.vue')\nwindow.mapboxgl.setRTLTextPlugin(u)\n/* end */")),
|
||||
'POS CONTROL: the OLD naive regex does lose the call on this input -- the bug was real, not imagined')
|
||||
|
||||
console.log(fails ? `\nSTRIP-CODE: ${fails} FAIL(s)` : '\nSTRIP-CODE: comment stripping is string-aware')
|
||||
process.exit(fails ? 1 : 0)
|
||||
}
|
||||
Loading…
Reference in a new issue