doxa-maps/plugin-designs/regions-dashboard/scripts/check-embedder-keys.mjs
Doxa Maps dc226c2e01 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.
2026-09-26 17:10:06 +00:00

62 lines
4.3 KiB
JavaScript

#!/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)