doxa-maps/internal/_tree-name.mjs
doxa-maps-88655c70 44ef31b2c6 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.
2026-09-25 16:25:22 +00:00

45 lines
2.2 KiB
JavaScript

/**
* _tree-name.mjs — the ONE place the output tree's name is read.
*
* The built tree lives at public/<TREE_NAME>/ and every embeddable URL carries it
* (…/<TREE_NAME>/<bundle>/<profile>/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')