From 44ef31b2c66d724ad5e830d817c1d64d421d8ec0 Mon Sep 17 00:00:00 2001 From: doxa-maps-88655c70 <88627970+doxa-maps-88655c70@users.noreply.github.com> Date: Fri, 25 Sep 2026 15:42:10 +0000 Subject: [PATCH] Tooling reads the tree's name from the build config, in one place The output tree's name has always been configurable (vite.doxa-maps-build-config.json "name"), but 61 tooling files ignored it and hardcoded 'doxa-maps' (181 literals), so a rename would have moved the build while the gates kept reading the old folder. - internal/_tree-name.mjs reads it once (validated as one URL-safe path segment; falls back to the historical name) and exports TREE_NAME / TREE_NAME_RE; `node internal/_tree-name.mjs` prints it for shell scripts. - A codemod rewrote 171 literals in internal/ to TREE_NAME; the regex literals, messages with backticks, atomic-public-swap.sh (fails loudly if the name cannot be read), build-lock.sh and vite.config.js (its duplicate readMfeName is gone) were done by hand. - The name is unchanged: old and new tooling produce all 1408 files under public/ byte for byte. Code lines still naming the tree in internal/ + vite.config.js: 2, both intended (the helper's fallback, the private-repo-name pattern in public-hygiene-check). This renames nothing visible; the eventual rename becomes a one-line config change. --- internal/_serves-this-tree.mjs | 5 +- internal/_tree-name.mjs | 45 +++++++++++++++ internal/acceptance/interop-harness.mjs | 5 +- internal/atomic-public-swap.sh | 23 +++++--- internal/build-lock.sh | 2 +- internal/check-a11y-button-names.mjs | 7 ++- internal/check-aria-labels-localized.mjs | 3 +- internal/check-basemap-labels-localized.mjs | 5 +- internal/check-capability-citations.mjs | 3 +- internal/check-capability-exemplars.mjs | 3 +- internal/check-catalog-currency.mjs | 13 +++-- internal/check-catalog-identity.mjs | 5 +- internal/check-composite-embed-knobs.mjs | 7 ++- internal/check-composite-freshness.mjs | 7 ++- internal/check-composite-srcs.mjs | 3 +- internal/check-element-census-vs-live.mjs | 5 +- internal/check-embed-base-resolution.mjs | 9 +-- internal/check-embed-basemap-paints.mjs | 3 +- internal/check-embedded-rtl.mjs | 7 ++- internal/check-experimental-shelf.mjs | 5 +- internal/check-filter-width-stable.mjs | 3 +- internal/check-fits-the-screen.mjs | 11 ++-- internal/check-fixed-anchoring.mjs | 3 +- internal/check-gospel-composed-filters.mjs | 3 +- internal/check-gospel-english-leaks.mjs | 3 +- internal/check-gospel-leaves-mount.mjs | 5 +- internal/check-host-locale-contract.mjs | 9 +-- internal/check-legend-export-reachable.mjs | 3 +- internal/check-legend-info-localized.mjs | 3 +- internal/check-locale-reachable.mjs | 3 +- internal/check-manifest-covers-discovery.mjs | 5 +- internal/check-map-embed-cells.mjs | 7 ++- internal/check-maplibre-layer-drops.mjs | 5 +- internal/check-nav-reachable-mobile.mjs | 5 +- internal/check-nested-token-delivery.mjs | 3 +- internal/check-no-internal-codename.mjs | 3 +- internal/check-one-palette.mjs | 5 +- internal/check-poster-card-fits.mjs | 5 +- internal/check-poster-wall-localised.mjs | 5 +- internal/check-prayer-walk-planner.mjs | 9 +-- internal/check-profile-descriptions.mjs | 5 +- internal/check-provider-switch.mjs | 3 +- internal/check-published-matches-build.mjs | 7 ++- internal/check-research-gallery-embed.mjs | 11 ++-- internal/check-share-policy.mjs | 3 +- ...check-shared-component-reached-bundles.mjs | 3 +- internal/check-shot-freshness.mjs | 9 +-- internal/check-spa-tab-order.mjs | 3 +- internal/check-staging-forwards-lang.mjs | 5 +- internal/check-upg-map-data-painted.mjs | 9 +-- internal/clean.mjs | 5 +- internal/drop-portability-check.mjs | 3 +- internal/embed-smoke.mjs | 7 ++- internal/fix-sri.mjs | 3 +- internal/generate-capability-exemplars.mjs | 5 +- internal/generate-gallery.mjs | 11 ++-- internal/generate-tree.mjs | 7 ++- internal/inline-parse-check.mjs | 3 +- internal/measure-map-load.mjs | 9 +-- internal/plugin-gallery/dev-plugin.mjs | 7 ++- internal/public-hygiene-check.mjs | 3 +- internal/readme-shots-fix.mjs | 3 +- internal/readme-shots.mjs | 55 ++++++++++--------- internal/serve-strict.mjs | 9 +-- internal/set-app-start-page.mjs | 5 +- internal/settled-tree-gate.mjs | 3 +- internal/smoke.mjs | 5 +- internal/sri-consistency-check.mjs | 5 +- internal/sync-dropin.mjs | 3 +- internal/verify-5-capabilities.mjs | 3 +- internal/verify-browser-all.mjs | 9 +-- internal/verify-gospel-map.mjs | 9 +-- internal/verify-map-paint.mjs | 5 +- internal/verify-shell-scroll.mjs | 5 +- vite.config.js | 21 +++---- 75 files changed, 320 insertions(+), 204 deletions(-) create mode 100644 internal/_tree-name.mjs diff --git a/internal/_serves-this-tree.mjs b/internal/_serves-this-tree.mjs index 34133c4eb..8d4bc86e1 100644 --- a/internal/_serves-this-tree.mjs +++ b/internal/_serves-this-tree.mjs @@ -22,8 +22,9 @@ import { readFileSync, existsSync, realpathSync } from 'node:fs' import { createHash } from 'node:crypto' import { resolve, dirname, join } from 'node:path' +import { TREE_NAME, TREE_NAME_RE } from './_tree-name.mjs' -const MANIFEST = 'doxa-maps/manifest.json' +const MANIFEST = `${TREE_NAME}/manifest.json` /** * @param {string} base e.g. http://127.0.0.1:4173 (a trailing /doxa-maps/ is tolerated) @@ -37,7 +38,7 @@ export async function servesThisTree(base, repo) { } catch { return { ok: false, why: `no local public/${MANIFEST} to compare against — build the tree first` } } - const root = String(base).replace(/\/+$/, '').replace(/\/doxa-maps$/, '') + const root = String(base).replace(/\/+$/, '').replace(new RegExp(`/${TREE_NAME_RE}$`), '') const r = await fetch(`${root}/${MANIFEST}`).catch(() => null) if (!r || !r.ok) return { ok: false, why: `nothing serving ${MANIFEST} at ${root}` } const served = createHash('sha256').update(Buffer.from(await r.arrayBuffer())).digest('hex') diff --git a/internal/_tree-name.mjs b/internal/_tree-name.mjs new file mode 100644 index 000000000..eeefde608 --- /dev/null +++ b/internal/_tree-name.mjs @@ -0,0 +1,45 @@ +/** + * _tree-name.mjs — the ONE place the output tree's name is read. + * + * The built tree lives at public// and every embeddable URL carries it + * (…////index.html). Its name has always been configurable — + * vite.doxa-maps-build-config.json `name` — but a census on 2026-09-25 found 61 tooling files + * that ignored the knob and hardcoded 'doxa-maps' (181 literals), so changing the config renamed + * the build while those gates kept reading the old folder. Every tool now imports TREE_NAME from + * here, which makes renaming the tree a one-line config change (see + * _qa-cycle/03-deployment-cycle/qa-loops/RENAME-IMPACT-dx.md, mechanism A). + * + * import { TREE_NAME, BUILD_CONFIG_FILE } from './_tree-name.mjs' + * TREE=$(node internal/_tree-name.mjs) # from a shell script + * + * The fallback, used only when the config is missing or has no name, is the historical name, + * so a checkout without the config behaves exactly as before. + */ +import { readFileSync } from 'node:fs' +import { resolve, dirname } from 'node:path' +import { fileURLToPath } from 'node:url' + +const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') +export const BUILD_CONFIG_FILE = resolve(ROOT, 'vite.doxa-maps-build-config.json') +const FALLBACK = 'doxa-maps' + +function read() { + let name = '' + try { + const cfg = JSON.parse(readFileSync(BUILD_CONFIG_FILE, 'utf8')) + if (cfg && typeof cfg.name === 'string') name = cfg.name.trim() + } catch { /* no config: the fallback */ } + if (!name) return FALLBACK + // A tree name is ONE path segment that also appears in public URLs: refuse anything that could + // escape public/ or need escaping in a URL, rather than build into a surprising place. + if (!/^[a-z0-9][a-z0-9._-]*$/i.test(name) || name.includes('..')) { + throw new Error(`[_tree-name] build config "name" must be a single URL-safe path segment, got ${JSON.stringify(name)}`) + } + return name +} + +export const TREE_NAME = read() +// The name escaped for use inside a RegExp source (a '.' in a future name must not match any char). +export const TREE_NAME_RE = TREE_NAME.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + +if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) process.stdout.write(TREE_NAME + '\n') diff --git a/internal/acceptance/interop-harness.mjs b/internal/acceptance/interop-harness.mjs index 91ce100e4..71fec1c68 100644 --- a/internal/acceptance/interop-harness.mjs +++ b/internal/acceptance/interop-harness.mjs @@ -68,6 +68,7 @@ import { setTimeout as sleep } from 'node:timers/promises' import { fileURLToPath } from 'node:url' import { dirname, resolve } from 'node:path' import { execFileSync } from 'node:child_process' +import { TREE_NAME } from '../_tree-name.mjs' const __dirname = fileURLToPath(new URL('.', import.meta.url)) const ROOT = resolve(__dirname, '..', '..') @@ -178,7 +179,7 @@ async function readGrid(page) { async function runDesktop(browser) { const PAIR = 'desktop dashboard-plus-grid' const page = await browser.newPage({ viewport: { width: 1600, height: 1000 } }) - const url = `${BASE}/doxa-maps/dashboard-plus-grid/dashboard-plus-grid/index.html` + const url = `${BASE}/${TREE_NAME}/dashboard-plus-grid/dashboard-plus-grid/index.html` console.log(`\n=== ${PAIR} — ${url} ===`) try { await page.goto(url, { waitUntil: 'load', timeout: 60000 }) @@ -344,7 +345,7 @@ async function runDesktop(browser) { async function runMobile(browser) { const PAIR = 'mobile research-map-plus-grid' const page = await browser.newPage({ viewport: { width: 420, height: 900 } }) - const url = `${BASE}/doxa-maps/research-map-plus-grid/research-map-plus-grid/index.html` + const url = `${BASE}/${TREE_NAME}/research-map-plus-grid/research-map-plus-grid/index.html` console.log(`\n=== ${PAIR} — ${url} ===`) try { await page.goto(url, { waitUntil: 'load', timeout: 60000 }) diff --git a/internal/atomic-public-swap.sh b/internal/atomic-public-swap.sh index 65c976448..b99331145 100755 --- a/internal/atomic-public-swap.sh +++ b/internal/atomic-public-swap.sh @@ -34,11 +34,16 @@ DRY_RUN="${DRY_RUN:-0}" die() { echo "❌ atomic-public-swap: $*" >&2; exit 1; } -[ -n "$STAGE_DIR" ] || die "usage: atomic-public-swap.sh (dir holding a built doxa-maps/ + index.html)" +# The tree's name comes from the build config (internal/_tree-name.mjs), never a literal: this script +# renames live folders, so it must fail rather than swap the wrong one if the name cannot be read. +TREE="$(node "$(dirname "$0")/_tree-name.mjs")" || die "could not read the tree name (internal/_tree-name.mjs)" +[ -n "$TREE" ] || die "empty tree name from internal/_tree-name.mjs" + +[ -n "$STAGE_DIR" ] || die "usage: atomic-public-swap.sh (dir holding a built $TREE/ + index.html)" [ -d "$STAGE_DIR" ] || die "STAGE_DIR '$STAGE_DIR' is not a directory" [ -d "$PUBLIC_DIR" ] || die "PUBLIC_DIR '$PUBLIC_DIR' is not a directory (run from the studio root)" -STAGE_TREE="$STAGE_DIR/doxa-maps" +STAGE_TREE="$STAGE_DIR/$TREE" STAGE_INDEX="$STAGE_DIR/index.html" # --- GATE: the stage must be a COMPLETE, verified tree before we touch the live web-root --------------------- @@ -81,12 +86,12 @@ stage_dev=$(stat -c '%d' "$STAGE_DIR") pub_dev=$(stat -c '%d' "$PUBLIC_DIR") [ "$stage_dev" = "$pub_dev" ] || die "STAGE_DIR and PUBLIC_DIR are on different filesystems ($stage_dev != $pub_dev) — rename() would not be atomic. Stage under the same volume as public/." -OLD_TREE="$PUBLIC_DIR/doxa-maps.old.$$" +OLD_TREE="$PUBLIC_DIR/$TREE.old.$$" if [ "$DRY_RUN" = "1" ]; then echo "DRY_RUN — plan (no changes):" - echo " 1. mv $PUBLIC_DIR/doxa-maps -> $OLD_TREE" - echo " 2. mv $STAGE_TREE -> $PUBLIC_DIR/doxa-maps" + echo " 1. mv $PUBLIC_DIR/$TREE -> $OLD_TREE" + echo " 2. mv $STAGE_TREE -> $PUBLIC_DIR/$TREE" echo " 3. cp $STAGE_INDEX -> $PUBLIC_DIR/index.html" echo " 4. rm -rf $OLD_TREE" echo " (NEVER touched: $PUBLIC_DIR/assets/ $PUBLIC_DIR/.gitignore $PUBLIC_DIR/.build.lock)" @@ -94,11 +99,11 @@ if [ "$DRY_RUN" = "1" ]; then fi # --- THE SWAP (scoped; rename-based; public/ itself and the lock never move) --------------------------------- -if [ -d "$PUBLIC_DIR/doxa-maps" ]; then - mv "$PUBLIC_DIR/doxa-maps" "$OLD_TREE" # 1. instant; old tree still fully valid under new name +if [ -d "$PUBLIC_DIR/$TREE" ]; then + mv "$PUBLIC_DIR/$TREE" "$OLD_TREE" # 1. instant; old tree still fully valid under new name fi -mv "$STAGE_TREE" "$PUBLIC_DIR/doxa-maps" # 2. instant; new tree (built+verified) becomes live +mv "$STAGE_TREE" "$PUBLIC_DIR/$TREE" # 2. instant; new tree (built+verified) becomes live cp "$STAGE_INDEX" "$PUBLIC_DIR/index.html" # 3. fresh root gallery page rm -rf "$OLD_TREE" # 4. drop the superseded tree -echo "🔀 atomic swap complete — $PUBLIC_DIR/doxa-maps is the freshly-built tree; assets/ + .build.lock untouched." +echo "🔀 atomic swap complete — $PUBLIC_DIR/$TREE is the freshly-built tree; assets/ + .build.lock untouched." diff --git a/internal/build-lock.sh b/internal/build-lock.sh index be1ed19fd..70c89bab1 100755 --- a/internal/build-lock.sh +++ b/internal/build-lock.sh @@ -51,7 +51,7 @@ mkdir -p "$(dirname "$LOCK_FILE")" # that distinguishes coordinator from subagent; anything claiming to is an untested guess. if [ "${BUILD_ALLOW:-}" != "1" ]; then echo "🚫 FULL BUILD REFUSED — a full build is almost never what you want here, and its FIRST" >&2 - echo " step deletes public/doxa-maps/ entirely, which tears" >&2 + echo " step deletes public/$(node "$(dirname "$0")/_tree-name.mjs" 2>/dev/null || echo "")/ entirely, which tears" >&2 echo " the tree out from under every other agent reading it (measured 3+ times, incl. a" >&2 echo " 461-file phantom deletion and several false 'element did not mount' diagnoses)." >&2 echo "" >&2 diff --git a/internal/check-a11y-button-names.mjs b/internal/check-a11y-button-names.mjs index c765453cd..0c0dc46e8 100644 --- a/internal/check-a11y-button-names.mjs +++ b/internal/check-a11y-button-names.mjs @@ -35,10 +35,11 @@ import { resolve, dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' import { chromium } from 'playwright' import { servesThisTree } from './_serves-this-tree.mjs' +import { TREE_NAME } from './_tree-name.mjs' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') -const MANIFEST = join(ROOT, 'public', 'doxa-maps', 'manifest.json') -const BASE = process.env.BASE || 'http://127.0.0.1:4173/doxa-maps/' +const MANIFEST = join(ROOT, 'public', TREE_NAME, 'manifest.json') +const BASE = process.env.BASE || `http://127.0.0.1:4173/${TREE_NAME}/` const ALL = process.env.ALL === '1' if (!existsSync(MANIFEST)) { @@ -55,7 +56,7 @@ if (!existsSync(MANIFEST)) { if (!tree.ok) { console.error('A11Y-BUTTON-NAMES: NOT MEASURED — the server is not serving this checkout.') console.error(` ${tree.why}`) - console.error(' Point BASE at your own server (BASE=http://127.0.0.1:PORT/doxa-maps/).') + console.error(` Point BASE at your own server (BASE=http://127.0.0.1:PORT/${TREE_NAME}/).`) process.exit(2) } } diff --git a/internal/check-aria-labels-localized.mjs b/internal/check-aria-labels-localized.mjs index c67bd36bf..0ae75d9a2 100644 --- a/internal/check-aria-labels-localized.mjs +++ b/internal/check-aria-labels-localized.mjs @@ -30,6 +30,7 @@ import { chromium } from 'playwright' import { requireThisTree } from './_serves-this-tree.mjs' import { resolve as _resolvePath, dirname as _dirnamePath } from 'node:path' import { fileURLToPath as _fileURLToPath } from 'node:url' +import { TREE_NAME } from './_tree-name.mjs' const URL_ = process.argv[2] || process.env.ARIA_URL const ROOT = _resolvePath(_dirnamePath(_fileURLToPath(import.meta.url)), '..') @@ -43,7 +44,7 @@ await requireThisTree(URL_, ROOT, 'ARIA-LABELS-LOCALIZED') if (!URL_) { console.log('ARIA-LABELS-LOCALIZED: SKIP — no leaf url given.') console.log(' Pass one, or set ARIA_URL. Serve the TREE ROOT (public/) and point at a leaf,') - console.log(' e.g. doxa-maps/my-upg-100-list/upg-100-list/index.html') + console.log(` e.g. ${TREE_NAME}/my-upg-100-list/upg-100-list/index.html`) process.exit(0) } const UNDER_TEST = (process.argv[3] || 'es,ar,zh').split(',').map((s) => s.trim()).filter(Boolean) diff --git a/internal/check-basemap-labels-localized.mjs b/internal/check-basemap-labels-localized.mjs index fc7d887c3..79ac7eda5 100644 --- a/internal/check-basemap-labels-localized.mjs +++ b/internal/check-basemap-labels-localized.mjs @@ -30,6 +30,7 @@ import { spawn } from 'node:child_process' import { createServer } from 'node:http' import { resolve, dirname } from 'node:path' import { fileURLToPath } from 'node:url' +import { TREE_NAME } from './_tree-name.mjs' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') const argv = process.argv.slice(2) @@ -41,13 +42,13 @@ const PAIRS = { Germany: 'Германия', Ukraine: 'Украина', Turkey: const port = await new Promise((ok) => { const s = createServer().listen(0, '127.0.0.1', () => { const p = s.address().port; s.close(() => ok(p)) }) }) const srv = spawn(process.execPath, [resolve(ROOT, 'internal/serve-strict.mjs'), '--port', String(port), '--directory', DIR], { stdio: 'ignore' }) await new Promise((r) => setTimeout(r, 900)) -const up = await fetch(`http://127.0.0.1:${port}/doxa-maps/${LEAF}/index.html`).then((r) => r.ok).catch(() => false) +const up = await fetch(`http://127.0.0.1:${port}/${TREE_NAME}/${LEAF}/index.html`).then((r) => r.ok).catch(() => false) if (!up) { srv.kill(); console.log(`BASEMAP-LABELS: NOT MEASURED — could not serve ${DIR}`); process.exit(2) } const browser = await chromium.launch({ args: ['--use-gl=swiftshader', '--enable-webgl', '--ignore-gpu-blocklist'] }) async function labels(provider, lang) { const page = await browser.newPage({ viewport: { width: 1280, height: 900 } }) - await page.goto(`http://127.0.0.1:${port}/doxa-maps/${LEAF}/index.html?lang=${lang}${provider === 'maplibre' ? '&provider=maplibre' : ''}`, { waitUntil: 'networkidle' }) + await page.goto(`http://127.0.0.1:${port}/${TREE_NAME}/${LEAF}/index.html?lang=${lang}${provider === 'maplibre' ? '&provider=maplibre' : ''}`, { waitUntil: 'networkidle' }) const FIND = `(() => { const f = (r) => { for (const e of r.querySelectorAll('*')) { if (e.__doxaMap) return e.__doxaMap; if (e.shadowRoot) { const m = f(e.shadowRoot); if (m) return m } } return null }; return f(document) })()` let ok = false for (let i = 0; i < 60 && !ok; i++) { await page.waitForTimeout(500); ok = await page.evaluate(`(() => { const m = ${FIND}; return !!(m && m.isStyleLoaded && m.isStyleLoaded()) })()`).catch(() => false) } diff --git a/internal/check-capability-citations.mjs b/internal/check-capability-citations.mjs index bbff70299..e6bd30e17 100644 --- a/internal/check-capability-citations.mjs +++ b/internal/check-capability-citations.mjs @@ -34,10 +34,11 @@ import { readFileSync, existsSync, readdirSync } from 'node:fs' import { resolve, dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' +import { TREE_NAME } from './_tree-name.mjs' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') const README = join(ROOT, 'README.md') -const MANIFEST = join(ROOT, 'public', 'doxa-maps', 'manifest.json') +const MANIFEST = join(ROOT, 'public', TREE_NAME, 'manifest.json') // ★ WIDENED 2026-09-22 TO documentation/ AS WELL AS README.md. // diff --git a/internal/check-capability-exemplars.mjs b/internal/check-capability-exemplars.mjs index 9d237101b..55e72dbf9 100644 --- a/internal/check-capability-exemplars.mjs +++ b/internal/check-capability-exemplars.mjs @@ -22,11 +22,12 @@ import { readFileSync, existsSync } from 'node:fs' import { execFileSync } from 'node:child_process' import { resolve, dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' +import { TREE_NAME } from './_tree-name.mjs' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') const OUT = join(ROOT, 'documentation', 'reference-capability-exemplars.md') const GEN = join(ROOT, 'internal', 'generate-capability-exemplars.mjs') -const MANIFEST = join(ROOT, 'public', 'doxa-maps', 'manifest.json') +const MANIFEST = join(ROOT, 'public', TREE_NAME, 'manifest.json') if (!existsSync(MANIFEST)) { console.log('CAPABILITY-EXEMPLARS: SKIP — no manifest.json (nothing built yet).') diff --git a/internal/check-catalog-currency.mjs b/internal/check-catalog-currency.mjs index 588d55e85..d5f8c98f4 100644 --- a/internal/check-catalog-currency.mjs +++ b/internal/check-catalog-currency.mjs @@ -33,18 +33,19 @@ import { readFileSync, existsSync } from 'node:fs' import { createHash } from 'node:crypto' import { resolve, dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' +import { TREE_NAME } from './_tree-name.mjs' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') const args = process.argv.slice(2) const argOf = (k, d) => { const i = args.indexOf(k); return i >= 0 ? args[i + 1] : d } -const BASE = (argOf('--base', 'https://nuxtbase.app/plugin-assets/doxa-maps')).replace(/\/+$/, '') +const BASE = (argOf('--base', `https://nuxtbase.app/plugin-assets/${TREE_NAME}`)).replace(/\/+$/, '') const TIMEOUT = Number(argOf('--timeout', 25000)) const sha384 = (buf) => 'sha384-' + createHash('sha384').update(buf).digest('base64') -const localManifestPath = join(ROOT, 'public', 'doxa-maps', 'manifest.json') +const localManifestPath = join(ROOT, 'public', TREE_NAME, 'manifest.json') if (!existsSync(localManifestPath)) { - console.log('CATALOG-CURRENCY: SKIP — no local build (public/doxa-maps/manifest.json missing). Run `bun run build`.') + console.log(`CATALOG-CURRENCY: SKIP — no local build (public/${TREE_NAME}/manifest.json missing). Run \`bun run build\`.`) process.exit(0) } const local = JSON.parse(readFileSync(localManifestPath, 'utf8')) @@ -125,7 +126,7 @@ for (const b of local.bundles || []) { const profile = b.profiles?.[0]?.profile if (!profile) continue const rel = `${b.bundle}/${profile}/index.html` - const localPath = join(ROOT, 'public', 'doxa-maps', rel) + const localPath = join(ROOT, 'public', TREE_NAME, rel) if (!existsSync(localPath)) continue const { buf, err } = await fetchBuf(`${BASE}/${rel}`) // Same MISSING-vs-UNREACHABLE split as the bundle loop above: a 4xx is the server telling us the @@ -157,7 +158,7 @@ if (missing.length) { '\n Something was LOST between the build and the catalog — the merge dropped it, or the publish\n' + ' never included it. This is the guarantee Card 1 Step 1 asks for ("merge all bundles so nothing\n' + ' is lost"). Re-run the publish and assert the resulting bundle SET, not just the changed files:\n' + - ' rsync -a --delete public/doxa-maps/ /app/doxa-maps/ # --delete mirrors removals\n' + + ` rsync -a --delete public/${TREE_NAME}/ /app/${TREE_NAME}/ # --delete mirrors removals\n` + ' then confirm every local bundle name appears live before pushing.' ) process.exit(1) @@ -167,7 +168,7 @@ if (stale.length) { for (const s of stale) console.error(` ✗ ${s.bundle}\n local ${s.want}…\n live ${s.got}…`) console.error('\n The live catalog is BEHIND this build. Publish per') console.error(' _qa-cycle/03-deployment-cycle/delegation/RUNBOOK-publish-bundles-to-catalog.md') - console.error(' (rsync public/doxa-maps/ into the catalog, then push with -c http.postBuffer=524288000).') + console.error(` (rsync public/${TREE_NAME}/ into the catalog, then push with -c http.postBuffer=524288000).`) process.exit(1) } console.log('CATALOG-CURRENCY: PASS — every local bundle is present live and matches this build.') diff --git a/internal/check-catalog-identity.mjs b/internal/check-catalog-identity.mjs index 59e08205e..f6110859f 100644 --- a/internal/check-catalog-identity.mjs +++ b/internal/check-catalog-identity.mjs @@ -34,8 +34,9 @@ import { readFileSync, existsSync } from 'node:fs' import { createHash } from 'node:crypto' import { join } from 'node:path' +import { TREE_NAME } from './_tree-name.mjs' -const BASE = (process.env.BASE || 'https://nuxtbase.app/plugin-assets/doxa-maps/').replace(/\/?$/, '/') +const BASE = (process.env.BASE || `https://nuxtbase.app/plugin-assets/${TREE_NAME}/`).replace(/\/?$/, '/') const CATALOG = process.env.CATALOG || '' const sha384 = (buf) => 'sha384-' + createHash('sha384').update(buf).digest('base64') @@ -64,7 +65,7 @@ try { console.error('CATALOG-IDENTITY: FAIL — the manifest URL did not return JSON.') console.error(` ${BASE}manifest.json served ${liveManifestBuf.length} bytes that do not parse.`) console.error(' This is what the LOGIN WALL looks like: HTTP 200 with an HTML sign-in page.') - console.error(' The public door is /plugin-assets/doxa-maps/ — NOT /app/doxa-maps/.') + console.error(` The public door is /plugin-assets/${TREE_NAME}/ — NOT /app/${TREE_NAME}/.`) process.exit(1) } diff --git a/internal/check-composite-embed-knobs.mjs b/internal/check-composite-embed-knobs.mjs index 69da686ce..09d68150b 100644 --- a/internal/check-composite-embed-knobs.mjs +++ b/internal/check-composite-embed-knobs.mjs @@ -49,9 +49,10 @@ import { spawn } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { resolve, dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' +import { TREE_NAME } from './_tree-name.mjs' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') -const MANIFEST = join(ROOT, 'public', 'doxa-maps', 'manifest.json') +const MANIFEST = join(ROOT, 'public', TREE_NAME, 'manifest.json') if (!existsSync(MANIFEST)) { console.log('COMPOSITE-EMBED-KNOBS: SKIP — no built manifest (run `bun run tree` first).') @@ -95,7 +96,7 @@ const bakedToken = (() => { for (const b of manifest.bundles || []) { for (const p of b.profiles || []) { if (!p.isMap || !p.iframeUrl) continue - const leaf = join(ROOT, 'public', 'doxa-maps', String(p.iframeUrl).replace(/^\.\//, '')) + const leaf = join(ROOT, 'public', TREE_NAME, String(p.iframeUrl).replace(/^\.\//, '')) if (!existsSync(leaf)) continue const m = readFileSync(leaf, 'utf8').match(/BAKED_TOKEN\s*=\s*"(pk\.[^"]+)"/) if (m) return m[1] @@ -109,7 +110,7 @@ const freePort = () => new Promise((ok) => { }) const TREE_PORT = await freePort() const HOST_PORT = await freePort() -const TREE = `http://127.0.0.1:${TREE_PORT}/doxa-maps` +const TREE = `http://127.0.0.1:${TREE_PORT}/${TREE_NAME}` let pageHtml = 'nothing yet' const tree = spawn(process.execPath, ['internal/serve-strict.mjs', '--port', String(TREE_PORT)], { cwd: ROOT, stdio: 'ignore' }) diff --git a/internal/check-composite-freshness.mjs b/internal/check-composite-freshness.mjs index 27e68e402..f618ec3f0 100644 --- a/internal/check-composite-freshness.mjs +++ b/internal/check-composite-freshness.mjs @@ -41,16 +41,17 @@ import { spawnSync } from 'node:child_process' import { tmpdir } from 'node:os' import { resolve, dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' +import { TREE_NAME } from './_tree-name.mjs' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') const DESIGNS = join(ROOT, 'plugin-designs') -const TREE = join(ROOT, 'public', 'doxa-maps') +const TREE = join(ROOT, 'public', TREE_NAME) const argv = process.argv.slice(2) const oi = argv.indexOf('--only') const ONLY = oi >= 0 && argv[oi + 1] ? new Set(argv[oi + 1].split(',')) : null if (!existsSync(join(TREE, 'manifest.json'))) { - console.log('COMPOSITE-FRESHNESS: SKIP — no built tree (public/doxa-maps/manifest.json).') + console.log(`COMPOSITE-FRESHNESS: SKIP — no built tree (public/${TREE_NAME}/manifest.json).`) process.exit(0) } const manifest = JSON.parse(readFileSync(join(TREE, 'manifest.json'), 'utf8')) @@ -81,7 +82,7 @@ try { for (const e of composes) if (!resolveEntry(e, bundle)) unresolved.push(`${bundle} composes "${e}" — resolves to no bundle`) composites++ const committed = join(TREE, bundle, `${bundle}.js`) - if (!existsSync(committed)) { failures.push(`${bundle}: no committed bundle at public/doxa-maps/${bundle}/${bundle}.js`); continue } + if (!existsSync(committed)) { failures.push(`${bundle}: no committed bundle at public/${TREE_NAME}/${bundle}/${bundle}.js`); continue } const out = join(tmp, bundle) const r = spawnSync('bun', ['x', 'vite', 'build'], { cwd: ROOT, env: { ...process.env, BUNDLE: bundle, DOXA_OUT_ROOT: out }, encoding: 'utf8' }) const built = join(out, bundle, `${bundle}.js`) diff --git a/internal/check-composite-srcs.mjs b/internal/check-composite-srcs.mjs index 64afa73d2..08ffec380 100644 --- a/internal/check-composite-srcs.mjs +++ b/internal/check-composite-srcs.mjs @@ -33,6 +33,7 @@ import { readFileSync, existsSync, readdirSync } from 'node:fs' import { resolve, dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' +import { TREE_NAME } from './_tree-name.mjs' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') @@ -51,7 +52,7 @@ const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') // printed: "checked against public/doxa-maps/" looked identical whether the name came from the // config or from the fallback. Printing WHICH means a future regression shows up in normal output // instead of waiting for a rename to expose it. -let treeName = 'doxa-maps' +let treeName = TREE_NAME let treeNameSource = 'built-in default (build config unreadable)' try { const cfg = JSON.parse(readFileSync(join(ROOT, 'vite.doxa-maps-build-config.json'), 'utf8')) diff --git a/internal/check-element-census-vs-live.mjs b/internal/check-element-census-vs-live.mjs index 86f1ac34b..f327de6fa 100644 --- a/internal/check-element-census-vs-live.mjs +++ b/internal/check-element-census-vs-live.mjs @@ -43,6 +43,7 @@ import { createServer } from 'node:http' import { readFileSync, readdirSync, existsSync } from 'node:fs' import { resolve, dirname, join, basename } from 'node:path' import { fileURLToPath } from 'node:url' +import { TREE_NAME } from './_tree-name.mjs' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') const PRESET_DIR = join(ROOT, 'internal', 'element-census') @@ -94,7 +95,7 @@ const port = await new Promise((ok) => { const s = createServer().listen(0, '127 const srv = spawn(process.execPath, [resolve(ROOT, 'internal/serve-strict.mjs'), '--port', String(port), '--directory', DIR], { stdio: 'ignore' }) const stopSrv = () => { try { srv.kill() } catch { /* already gone */ } } let up = false -for (let i = 0; i < 40 && !up; i++) { await new Promise((r) => setTimeout(r, 150)); up = await fetch(`http://127.0.0.1:${port}/doxa-maps/manifest.json`).then((r) => r.ok).catch(() => false) } +for (let i = 0; i < 40 && !up; i++) { await new Promise((r) => setTimeout(r, 150)); up = await fetch(`http://127.0.0.1:${port}/${TREE_NAME}/manifest.json`).then((r) => r.ok).catch(() => false) } if (!up) { stopSrv(); console.log(`ELEMENT-CENSUS: NOT MEASURED — could not serve ${DIR}`); process.exit(NOT_MEASURED) } const THIRD_PARTY = /mapbox|openstreetmap|improve this map|maxar|geoboundaries|©/i @@ -141,7 +142,7 @@ for (const [name, p] of presets) { const oursByLeaf = new Map() let negSeenInOursOnly = null for (const pg of p.pages) { - const ours = await read(`http://127.0.0.1:${port}/doxa-maps/${pg.leaf}`) + const ours = await read(`http://127.0.0.1:${port}/${TREE_NAME}/${pg.leaf}`) oursByLeaf.set(pg.leaf, ours) const theirs = await read(pg.live) const needH1 = p.requireH1 !== false diff --git a/internal/check-embed-base-resolution.mjs b/internal/check-embed-base-resolution.mjs index d27a321be..8be0ca31d 100644 --- a/internal/check-embed-base-resolution.mjs +++ b/internal/check-embed-base-resolution.mjs @@ -36,6 +36,7 @@ */ import { resolve, dirname } from 'node:path' import { fileURLToPath, pathToFileURL } from 'node:url' +import { TREE_NAME } from './_tree-name.mjs' const HERE = dirname(fileURLToPath(import.meta.url)) const REPO = process.env.DOXA_REPO || resolve(HERE, '..') @@ -53,10 +54,10 @@ try { // The leaf's own arithmetic is the oracle — computed here rather than hardcoded, so the // expectation cannot drift from what the generated HTML actually means. const TREES = [ - ['live catalog', 'https://nuxtbase.app/plugin-assets/doxa-maps'], - ['dev tree', 'http://127.0.0.1:4173/doxa-maps'], - ['doxa.life drop-in', 'https://doxa.life/js/doxa-maps-build/doxa-maps'], - ['deep subpath', 'https://cdn.example.org/a/b/c/doxa-maps'], + ['live catalog', `https://nuxtbase.app/plugin-assets/${TREE_NAME}`], + ['dev tree', `http://127.0.0.1:4173/${TREE_NAME}`], + ['doxa.life drop-in', `https://doxa.life/js/doxa-maps-build/${TREE_NAME}`], + ['deep subpath', `https://cdn.example.org/a/b/c/${TREE_NAME}`], ] const fails = [] diff --git a/internal/check-embed-basemap-paints.mjs b/internal/check-embed-basemap-paints.mjs index 92137d9cf..3c15e7b66 100644 --- a/internal/check-embed-basemap-paints.mjs +++ b/internal/check-embed-basemap-paints.mjs @@ -33,12 +33,13 @@ import { writeFileSync, mkdtempSync, readFileSync } from 'node:fs' import { resolve, dirname } from 'node:path' import { tmpdir } from 'node:os' import { fileURLToPath } from 'node:url' +import { TREE_NAME } from './_tree-name.mjs' const HERE = dirname(fileURLToPath(import.meta.url)) const REPO = resolve(HERE, '..') const PUBLIC = resolve(REPO, 'public') -const LEAF = 'doxa-maps/gospel-resources/resources-map/index.html' +const LEAF = `${TREE_NAME}/gospel-resources/resources-map/index.html` // EMBED_BASEMAP_PORT pins the port — used to prove the identity guard below (occupy it with another // tree's server first; the gate must refuse rather than measure that tree). const leafPort = Number(process.env.EMBED_BASEMAP_PORT) || (5810 + Math.floor(Math.random() * 60)) diff --git a/internal/check-embedded-rtl.mjs b/internal/check-embedded-rtl.mjs index 1fdfbec97..4a3ca56a0 100644 --- a/internal/check-embedded-rtl.mjs +++ b/internal/check-embedded-rtl.mjs @@ -25,13 +25,14 @@ import { createServer } from 'node:http' import { resolve, dirname } from 'node:path' import { fileURLToPath } from 'node:url' import { servesThisTree } from './_serves-this-tree.mjs' +import { TREE_NAME } from './_tree-name.mjs' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') const BASE = process.argv[2] || process.env.BASE_URL || 'http://127.0.0.1:4173' const HOST_PORT = Number(process.env.RTL_HOST_PORT || 47393) let reachable = false -try { const r = await fetch(`${BASE}/doxa-maps/manifest.json`, { signal: AbortSignal.timeout(4000) }); reachable = r.ok } catch { /* precondition */ } +try { const r = await fetch(`${BASE}/${TREE_NAME}/manifest.json`, { signal: AbortSignal.timeout(4000) }); reachable = r.ok } catch { /* precondition */ } if (!reachable) { console.log(`EMBEDDED-RTL: SKIP — nothing served at ${BASE}.`); process.exit(0) } const treeCheck = await servesThisTree(BASE, ROOT) if (!treeCheck.ok) { console.error(`EMBEDDED-RTL: NOT MEASURED — ${treeCheck.why}`); process.exit(2) } @@ -52,11 +53,11 @@ const hostHtml = (c) => ` renders zero cards and the locale has nothing to translate. The first run of this gate omitted it and reported "never mounted" — a harness gap, not the RTL defect. --> `) }).listen(HP, 'localhost') await new Promise(r => setTimeout(r, 800)) @@ -44,10 +45,10 @@ await new Promise(r => setTimeout(r, 800)) // pass or a fail — the same code the shared browser locks use for "busy, re-run later". { const { readFileSync } = await import('node:fs') - const disk = readFileSync(resolve(ROOT, 'public/doxa-maps/manifest.json'), 'utf8') + const disk = readFileSync(resolve(ROOT, `public/${TREE_NAME}/manifest.json`), 'utf8') let served = '' for (let i = 0; i < 20 && !served; i++) { - try { const r = await fetch(`http://127.0.0.1:${TP}/doxa-maps/manifest.json`); if (r.ok) served = await r.text() } catch { /* not up yet */ } + try { const r = await fetch(`http://127.0.0.1:${TP}/${TREE_NAME}/manifest.json`); if (r.ok) served = await r.text() } catch { /* not up yet */ } if (!served) await new Promise(r => setTimeout(r, 300)) } if (served !== disk) { diff --git a/internal/check-profile-descriptions.mjs b/internal/check-profile-descriptions.mjs index 872cc978c..d715ee3e0 100644 --- a/internal/check-profile-descriptions.mjs +++ b/internal/check-profile-descriptions.mjs @@ -34,9 +34,10 @@ import { readFileSync, existsSync } from 'node:fs' import { resolve, dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' +import { TREE_NAME } from './_tree-name.mjs' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') -const MANIFEST = join(ROOT, 'public', 'doxa-maps', 'manifest.json') +const MANIFEST = join(ROOT, 'public', TREE_NAME, 'manifest.json') // A description shorter than this is a placeholder, not an explanation. Calibrated against the // REAL corpus rather than a guess (measured 2026-09-22): shortest genuine description 62 chars, @@ -52,7 +53,7 @@ const MIN_CHARS = 25 const MIN_EXPECTED_PROFILES = 20 if (!existsSync(MANIFEST)) { - console.error('PROFILE-DESCRIPTIONS: FAIL — no manifest.json at public/doxa-maps/.') + console.error(`PROFILE-DESCRIPTIONS: FAIL — no manifest.json at public/${TREE_NAME}/.`) console.error(' This gate reads the BUILT manifest (the artifact consumers actually read).') console.error(' Run `bun run build` first; an absent manifest is a failure, not a pass.') process.exit(1) diff --git a/internal/check-provider-switch.mjs b/internal/check-provider-switch.mjs index ff023e0de..686252160 100644 --- a/internal/check-provider-switch.mjs +++ b/internal/check-provider-switch.mjs @@ -16,6 +16,7 @@ import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { chromium } from 'playwright' import { servesThisTree, serveThisTree } from './_serves-this-tree.mjs' +import { TREE_NAME } from './_tree-name.mjs' const REPO = resolve(dirname(fileURLToPath(import.meta.url)), '..') @@ -30,7 +31,7 @@ const REPO = resolve(dirname(fileURLToPath(import.meta.url)), '..') // executed shape check:packets-all had. Default to the upg-100 leaf (the profile this // gate was written for) and SKIP with a named verdict when nothing is served there. const DEFAULT_LEAF = (process.env.BASE_URL || 'http://127.0.0.1:4173') - + '/doxa-maps/my-upg-100-list/upg-100-list/index.html' + + `/${TREE_NAME}/my-upg-100-list/upg-100-list/index.html` let LEAF = process.argv[2] || DEFAULT_LEAF let OWN_SERVER = null { diff --git a/internal/check-published-matches-build.mjs b/internal/check-published-matches-build.mjs index 9b2fa335a..7bc873b7b 100644 --- a/internal/check-published-matches-build.mjs +++ b/internal/check-published-matches-build.mjs @@ -37,12 +37,13 @@ import { readFileSync, existsSync, statSync } from 'node:fs' import { createHash } from 'node:crypto' import { resolve, dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' +import { TREE_NAME } from './_tree-name.mjs' const HERE = dirname(fileURLToPath(import.meta.url)) const REPO = process.env.DOXA_REPO || resolve(HERE, '..') const PUBLISHED_BASE = (process.env.PUBLISHED_BASE - || 'https://nuxtbase.app/plugin-assets/doxa-maps').replace(/\/$/, '') -const TREE = resolve(REPO, 'public/doxa-maps') + || `https://nuxtbase.app/plugin-assets/${TREE_NAME}`).replace(/\/$/, '') +const TREE = resolve(REPO, `public/${TREE_NAME}`) const MANIFEST = join(TREE, 'manifest.json') if (!existsSync(MANIFEST)) { @@ -133,7 +134,7 @@ if (stale.length) { console.error(' "red dot" pin hint) were all fixed in main and all still visible live, and each') console.error(' one read as a regression against a card marked done.') console.error('\n FIX: publish. That is a COORDINATOR action — deploy:dev runs a full build whose') - console.error(' first step deletes public/doxa-maps/ and tears the tree out from under every') + console.error(` first step deletes public/${TREE_NAME}/ and tears the tree out from under every`) console.error(' other agent, which is why the build guard refuses it without BUILD_ALLOW=1.') process.exit(1) } diff --git a/internal/check-research-gallery-embed.mjs b/internal/check-research-gallery-embed.mjs index ca728a89d..e609563dc 100644 --- a/internal/check-research-gallery-embed.mjs +++ b/internal/check-research-gallery-embed.mjs @@ -40,6 +40,7 @@ import { spawn } from 'node:child_process' import { existsSync } from 'node:fs' import { resolve, dirname } from 'node:path' import { fileURLToPath } from 'node:url' +import { TREE_NAME } from './_tree-name.mjs' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') const argv = process.argv.slice(2) @@ -101,10 +102,10 @@ const MAPBOX_KNOWN = { const freePort = () => new Promise(ok => { const s = createServer().listen(0, '127.0.0.1', () => { const p = s.address().port; s.close(() => ok(p)) }) }) const TREE_PORT = await freePort() const HOST_PORT = await freePort() -const TREE = `http://127.0.0.1:${TREE_PORT}/doxa-maps` +const TREE = `http://127.0.0.1:${TREE_PORT}/${TREE_NAME}` -if (!existsSync(resolve(ROOT, 'public/doxa-maps/doxa-maps-spa/doxa-maps-spa.js'))) { - console.log('SKIP check-research-gallery-embed: no built gallery at public/doxa-maps/doxa-maps-spa/') +if (!existsSync(resolve(ROOT, `public/${TREE_NAME}/doxa-maps-spa/doxa-maps-spa.js`))) { + console.log(`SKIP check-research-gallery-embed: no built gallery at public/${TREE_NAME}/doxa-maps-spa/`) process.exit(2) } let chromium @@ -139,10 +140,10 @@ await new Promise(r => setTimeout(r, 800)) // pass or a fail — the same code the shared browser locks use for "busy, re-run later". { const { readFileSync } = await import('node:fs') - const disk = readFileSync(resolve(ROOT, 'public/doxa-maps/manifest.json'), 'utf8') + const disk = readFileSync(resolve(ROOT, `public/${TREE_NAME}/manifest.json`), 'utf8') let served = '' for (let i = 0; i < 20 && !served; i++) { - try { const r = await fetch(`http://127.0.0.1:${TREE_PORT}/doxa-maps/manifest.json`); if (r.ok) served = await r.text() } catch { /* not up yet */ } + try { const r = await fetch(`http://127.0.0.1:${TREE_PORT}/${TREE_NAME}/manifest.json`); if (r.ok) served = await r.text() } catch { /* not up yet */ } if (!served) await new Promise(r => setTimeout(r, 300)) } if (served !== disk) { diff --git a/internal/check-share-policy.mjs b/internal/check-share-policy.mjs index 8b6c8a5a5..951d998e9 100644 --- a/internal/check-share-policy.mjs +++ b/internal/check-share-policy.mjs @@ -43,6 +43,7 @@ import { readFileSync, existsSync, readdirSync } from 'node:fs' import { resolve, join, dirname } from 'node:path' import { fileURLToPath } from 'node:url' +import { TREE_NAME } from './_tree-name.mjs' // Same fix as check-zone-census.mjs, same reason. DOXA_REPO still wins; only the FALLBACK moved // off an absolute path into one author's worktree. check:no-abs-paths rejects those outright — "a @@ -80,7 +81,7 @@ const POLICY = { } const isMap = (src) => /mapboxgl|maplibregl|useMapInstance/.test(src) -const manifest = JSON.parse(readFileSync(join(REPO, 'public', 'doxa-maps', 'manifest.json'), 'utf8')) +const manifest = JSON.parse(readFileSync(join(REPO, 'public', TREE_NAME, 'manifest.json'), 'utf8')) const published = new Set(manifest.bundles.map((b) => b.bundle)) const fails = [] diff --git a/internal/check-shared-component-reached-bundles.mjs b/internal/check-shared-component-reached-bundles.mjs index ae956d6fb..e1a2a158f 100644 --- a/internal/check-shared-component-reached-bundles.mjs +++ b/internal/check-shared-component-reached-bundles.mjs @@ -35,10 +35,11 @@ import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs' import { resolve, dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' +import { TREE_NAME } from './_tree-name.mjs' const HERE = dirname(fileURLToPath(import.meta.url)) const REPO = process.env.DOXA_REPO || resolve(HERE, '..') -const TREE = resolve(REPO, 'public/doxa-maps') +const TREE = resolve(REPO, `public/${TREE_NAME}`) // Each entry: a change that landed in a SHARED component, and a string that proves a built bundle // contains it. `source` is where the marker must also still exist — if it does not, the marker is diff --git a/internal/check-shot-freshness.mjs b/internal/check-shot-freshness.mjs index 3fb100eab..c727cffb0 100644 --- a/internal/check-shot-freshness.mjs +++ b/internal/check-shot-freshness.mjs @@ -46,11 +46,12 @@ import { execFileSync } from 'node:child_process' import { createHash } from 'node:crypto' import { resolve, dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' +import { TREE_NAME, TREE_NAME_RE } from './_tree-name.mjs' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') const SHOTS_SRC = join(ROOT, 'internal', 'readme-shots.mjs') const OUT = join(ROOT, 'documentation', 'readme-images') -const BUILT = join(ROOT, 'public', 'doxa-maps') +const BUILT = join(ROOT, 'public', TREE_NAME) if (!existsSync(SHOTS_SRC) || !existsSync(OUT)) { console.log('SHOT-FRESHNESS: SKIP — readme-shots.mjs or documentation/readme-images/ not present.') @@ -64,7 +65,7 @@ const recorded = existsSync(SHOT_BUNDLES) ? JSON.parse(readFileSync(SHOT_BUNDLES const git = (...a) => execFileSync('git', ['-C', ROOT, ...a], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }) let dirty = null try { - dirty = new Set(git('status', '--porcelain', '--', 'public/doxa-maps', 'documentation/readme-images') + dirty = new Set(git('status', '--porcelain', '--', `public/${TREE_NAME}`, 'documentation/readme-images') .split('\n').filter(Boolean).map((l) => resolve(ROOT, l.slice(3).replace(/^"|"$/g, '')))) } catch { /* not a git checkout */ } function stamp(file) { @@ -88,8 +89,8 @@ for (const { name, path } of shots) { const png = join(OUT, name + '.png') if (!existsSync(png)) { missing.push(name); continue } - const m = path.match(/^\/doxa-maps\/([^/]+)\//) - if (!m) { skipped.push(`${name} (path ${path} is not a /doxa-maps/ leaf)`); continue } + const m = path.match(new RegExp(`^/${TREE_NAME_RE}/([^/]+)/`)) + if (!m) { skipped.push(`${name} (path ${path} is not a /${TREE_NAME}/ leaf)`); continue } const bundle = join(BUILT, m[1], `${m[1]}.js`) if (!existsSync(bundle)) { skipped.push(`${name} (bundle ${m[1]} not built)`); continue } diff --git a/internal/check-spa-tab-order.mjs b/internal/check-spa-tab-order.mjs index aef0c7a91..88cf72228 100644 --- a/internal/check-spa-tab-order.mjs +++ b/internal/check-spa-tab-order.mjs @@ -35,9 +35,10 @@ import { chromium } from 'playwright' 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' +import { TREE_NAME } from './_tree-name.mjs' const __ROOT = __rs(__dn(__f2p(import.meta.url)), '..') -const BASE = process.env.BASE_URL || 'http://127.0.0.1:4173/doxa-maps' +const BASE = process.env.BASE_URL || `http://127.0.0.1:4173/${TREE_NAME}` // Measure THIS checkout, or measure nothing (exit 75 = NOT MEASURED; see _serves-this-tree.mjs). await __treeGuard(BASE, __ROOT, 'SPA-TAB-ORDER') diff --git a/internal/check-staging-forwards-lang.mjs b/internal/check-staging-forwards-lang.mjs index 6995becac..b3d71ffce 100644 --- a/internal/check-staging-forwards-lang.mjs +++ b/internal/check-staging-forwards-lang.mjs @@ -24,6 +24,7 @@ import { createServer } from 'node:http' import { readFileSync } from 'node:fs' import { resolve, dirname } from 'node:path' import { fileURLToPath } from 'node:url' +import { TREE_NAME } from './_tree-name.mjs' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') const argv = process.argv.slice(2) @@ -36,13 +37,13 @@ const port = await new Promise((ok) => { const s = createServer().listen(0, '127 const srv = spawn(process.execPath, [resolve(ROOT, 'internal/serve-strict.mjs'), '--port', String(port), '--directory', DIR], { stdio: 'ignore' }) const stop = () => { try { srv.kill() } catch { /* already gone */ } } let up = false -for (let i = 0; i < 40 && !up; i++) { await new Promise((r) => setTimeout(r, 150)); up = await fetch(`http://127.0.0.1:${port}/doxa-maps/index.html`).then((r) => r.ok).catch(() => false) } +for (let i = 0; i < 40 && !up; i++) { await new Promise((r) => setTimeout(r, 150)); up = await fetch(`http://127.0.0.1:${port}/${TREE_NAME}/index.html`).then((r) => r.ok).catch(() => false) } if (!up) { stop(); console.log(`STAGING-LANG: NOT MEASURED — could not serve ${DIR}`); process.exit(75) } const browser = await chromium.launch({ args: ['--use-gl=swiftshader', '--enable-webgl', '--ignore-gpu-blocklist'] }) const done = async (code, msg) => { console.log(msg); await browser.close(); stop(); process.exit(code) } const page = await browser.newPage({ viewport: { width: 1400, height: 900 } }) -await page.goto(`http://127.0.0.1:${port}/doxa-maps/index.html?lang=ar`, { waitUntil: 'load' }) +await page.goto(`http://127.0.0.1:${port}/${TREE_NAME}/index.html?lang=ar`, { waitUntil: 'load' }) const node = page.locator(`.node[data-key="${KEY}"]`) if (!(await node.count())) await done(75, `STAGING-LANG: NOT MEASURED — no ${KEY} node on the staging page`) await node.first().click() diff --git a/internal/check-upg-map-data-painted.mjs b/internal/check-upg-map-data-painted.mjs index 325b4273d..feeb82523 100644 --- a/internal/check-upg-map-data-painted.mjs +++ b/internal/check-upg-map-data-painted.mjs @@ -35,10 +35,11 @@ import { chromium } from 'playwright' import { resolve, dirname } from 'node:path' import { fileURLToPath } from 'node:url' import { servesThisTree } from './_serves-this-tree.mjs' +import { TREE_NAME } from './_tree-name.mjs' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') -const BASE = process.env.BASE || 'http://127.0.0.1:4173/doxa-maps' +const BASE = process.env.BASE || `http://127.0.0.1:4173/${TREE_NAME}` const DEFAULT = [ 'my-upg-100-list/upg-100-list/index.html', 'my-upg-100-list/upg-100-mfe/index.html', @@ -62,7 +63,7 @@ if (!reachable) { // "SKIP — nothing served at /tmp/doxa-maps-browser-gate.lock/…" (the slot had overwritten BASE) // and EXITED 0, so a wave counted a gate that measured nothing as green. console.log(`UPG-MAP-DATA: NOT MEASURED — nothing served at ${urls[0]}.`) - console.log(' Serve THIS checkout (internal/serve-strict.mjs on your own port) and pass BASE=http://127.0.0.1:/doxa-maps') + console.log(` Serve THIS checkout (internal/serve-strict.mjs on your own port) and pass BASE=http://127.0.0.1:/${TREE_NAME}`) process.exit(75) } @@ -76,7 +77,7 @@ if (!reachable) { // ⚠ NOT MEASURED, exit 75 — the repo's EX_TEMPFAIL for "not measured" (a9550858), which the wave // runner reports as ⊘. It was exit 2 here, a code nothing else in the repo reads that way. // This check is also what makes the :4173 default safe: another agent's tree there is refused. -const TREE_BASE = String(urls[0]).split('/doxa-maps/')[0] +const TREE_BASE = String(urls[0]).split(`/${TREE_NAME}/`)[0] const treeCheck = await servesThisTree(TREE_BASE, ROOT) if (!treeCheck.ok) { console.error('UPG-MAP-DATA: NOT MEASURED — the server is not serving this checkout.') @@ -125,7 +126,7 @@ try { console.log('UPG-MAP-DATA') const fails = [] for (const r of results) { - const name = r.url.split('/doxa-maps/')[1] || r.url + const name = r.url.split(`/${TREE_NAME}/`)[1] || r.url if (r.error) { fails.push(`${name}: navigation failed — ${r.error}`); console.log(` ${name} — ERROR`); continue } if (!r.text) { // A MISSING STATUS LINE IS A FAILURE, NOT A SKIP. If the element never rendered its own diff --git a/internal/clean.mjs b/internal/clean.mjs index 01f0ee67f..2ae4edd02 100644 --- a/internal/clean.mjs +++ b/internal/clean.mjs @@ -10,16 +10,17 @@ import { readFileSync, rmSync, mkdirSync } from 'node:fs' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' +import { TREE_NAME } from './_tree-name.mjs' const __dirname = dirname(fileURLToPath(import.meta.url)) function config() { try { const cfg = JSON.parse(readFileSync(resolve(__dirname, '..', 'vite.doxa-maps-build-config.json'), 'utf8')) - const name = (typeof cfg?.name === 'string' && cfg.name.trim()) ? cfg.name.trim() : 'doxa-maps' + const name = (typeof cfg?.name === 'string' && cfg.name.trim()) ? cfg.name.trim() : TREE_NAME const paths = Array.isArray(cfg?.paths) ? cfg.paths.filter(p => typeof p === 'string' && p.trim()) : [] return { name, paths } - } catch { return { name: 'doxa-maps', paths: [] } } + } catch { return { name: TREE_NAME, paths: [] } } } const { name, paths } = config() diff --git a/internal/drop-portability-check.mjs b/internal/drop-portability-check.mjs index 7fa428d6d..fdf75b055 100644 --- a/internal/drop-portability-check.mjs +++ b/internal/drop-portability-check.mjs @@ -18,13 +18,14 @@ import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs'; import { join, relative, dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { TREE_NAME } from './_tree-name.mjs' const here = dirname(fileURLToPath(import.meta.url)); const bundlerRoot = resolve(here, '..'); let treeDir = process.argv[2]; if (!treeDir) { - let name = 'doxa-maps'; + let name = TREE_NAME; const cfgPath = join(bundlerRoot, 'vite.doxa-maps-build-config.json'); if (existsSync(cfgPath)) { try { name = JSON.parse(readFileSync(cfgPath, 'utf8')).name || name; } catch {} diff --git a/internal/embed-smoke.mjs b/internal/embed-smoke.mjs index e55fcdc57..c71d5b697 100644 --- a/internal/embed-smoke.mjs +++ b/internal/embed-smoke.mjs @@ -22,6 +22,7 @@ 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 { TREE_NAME } from './_tree-name.mjs' const __dirname = fileURLToPath(new URL('.', import.meta.url)) const PUB = resolve(__dirname, '..', 'public') @@ -46,16 +47,16 @@ const server = createServer((req, res) => { }) // ── the page list: every profile page in the manifest ─────────────────────── -const manifest = JSON.parse(readFileSync(join(PUB, 'doxa-maps', 'manifest.json'), 'utf8')) +const manifest = JSON.parse(readFileSync(join(PUB, TREE_NAME, 'manifest.json'), 'utf8')) const targets = [] for (const b of manifest.bundles) for (const p of b.profiles) { // iframeUrl is ./-relative to the tree root - const url = p.iframeUrl.replace(/^\.\//, '/doxa-maps/') + const url = p.iframeUrl.replace(/^\.\//, `/${TREE_NAME}/`) targets.push({ bundle: b.bundle, profile: p.profile, element: p.element, url }) // Preconfigured instances are embeddable pages too — verify them the same way. for (const inst of (p.instances || [])) { targets.push({ bundle: b.bundle, profile: `${p.profile}/${inst.id}`, element: inst.element || p.element, - url: inst.iframeUrl.replace(/^\.\//, '/doxa-maps/') }) + url: inst.iframeUrl.replace(/^\.\//, `/${TREE_NAME}/`) }) } } const list = GREP ? targets.filter(t => (t.bundle + '/' + t.profile).includes(GREP)) : targets diff --git a/internal/fix-sri.mjs b/internal/fix-sri.mjs index 7ed08e5d8..5a10a9301 100644 --- a/internal/fix-sri.mjs +++ b/internal/fix-sri.mjs @@ -76,6 +76,7 @@ import { createHash } from 'node:crypto' import { tmpdir } from 'node:os' import { spawnSync } from 'node:child_process' import { fileURLToPath } from 'node:url' +import { TREE_NAME } from './_tree-name.mjs' const HERE = dirname(fileURLToPath(import.meta.url)) const CHECKER = join(HERE, 'sri-consistency-check.mjs') @@ -269,7 +270,7 @@ if (SELF_TEST) { selfTest() } else { let code = 0 - for (const root of (TREES.length ? TREES : ['public/doxa-maps'])) { + for (const root of (TREES.length ? TREES : [`public/${TREE_NAME}`])) { code = report(root, fixTree(root)) || code } process.exit(code) diff --git a/internal/generate-capability-exemplars.mjs b/internal/generate-capability-exemplars.mjs index 99f89365c..0c5c9011b 100644 --- a/internal/generate-capability-exemplars.mjs +++ b/internal/generate-capability-exemplars.mjs @@ -28,9 +28,10 @@ import { readFileSync, writeFileSync, existsSync } from 'node:fs' import { resolve, dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' +import { TREE_NAME } from './_tree-name.mjs' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') -const MANIFEST = join(ROOT, 'public', 'doxa-maps', 'manifest.json') +const MANIFEST = join(ROOT, 'public', TREE_NAME, 'manifest.json') const OUT = join(ROOT, 'documentation', 'reference-capability-exemplars.md') if (!existsSync(MANIFEST)) { @@ -68,7 +69,7 @@ lines.push('> **Do not hand-edit.** Regenerate with `bun run capability-exemplar lines.push('> check:capability-exemplars` fails if this file falls behind the manifest.') lines.push('>') lines.push('> This answers one question authoritatively: **which bundles are the capability-N') -lines.push('> examples right now?** It is derived from `public/doxa-maps/manifest.json`, which is what') +lines.push(`> examples right now?** It is derived from \`public/${TREE_NAME}/manifest.json\`, which is what`) lines.push('> the build computed from each folder\'s real shape — so it cannot drift from what ships.') lines.push('>') lines.push('> **Cite this file rather than remembering.** The same citation has been wrong in three') diff --git a/internal/generate-gallery.mjs b/internal/generate-gallery.mjs index e428564a3..3e7601e69 100644 --- a/internal/generate-gallery.mjs +++ b/internal/generate-gallery.mjs @@ -50,6 +50,7 @@ import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync } from import { join, resolve, dirname } from 'node:path' import { fileURLToPath, pathToFileURL } from 'node:url' import { PROFILE_TIER, TYPE_DISPLAY, detectProfileType } from './plugin-gallery/discover.mjs' +import { TREE_NAME } from './_tree-name.mjs' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') @@ -99,8 +100,8 @@ export function readBuildConfig() { } catch { /* fall through */ } } return { - name: str('name') || 'doxa-maps', - title: str('displayTitle') || 'doxa-maps', + name: str('name') || TREE_NAME, + title: str('displayTitle') || TREE_NAME, tokenUrl: tokenUrl || 'window.MAP_TOKEN_URL', token: str('token'), apiBaseUrl: str('apiBaseUrl').replace(/\/+$/, ''), @@ -185,7 +186,7 @@ const LADDER_JS = String.raw`