diff options
Diffstat (limited to 'tools/feature-registry-check.js')
| -rw-r--r-- | tools/feature-registry-check.js | 51 |
1 files changed, 20 insertions, 31 deletions
diff --git a/tools/feature-registry-check.js b/tools/feature-registry-check.js index 9b7ac5e..0931156 100644 --- a/tools/feature-registry-check.js +++ b/tools/feature-registry-check.js @@ -1,40 +1,29 @@ -// Guard: src/features/index.js and build.js's ALL_FEATURES must agree. +// Guard: src/features/index.js and build.js's featureIndexSource must agree. // They drifted once (nwc was added to index.js only, so its i18n strings // shipped but its code never did — the settings card silently never rendered, // because build.js SWAPS OUT features/index.js for a generated module). +// +// The strongest form of agreement is equality: the checked-in module must be +// code-identical (comments and whitespace aside) to what featureIndexSource +// generates for the full feature set — imports, construction order, deferred +// placeholder slots, and the deferred loader all included. // bun tools/feature-registry-check.js import { readFileSync } from 'node:fs'; +import { featureIndexSource, enabledFeatures } from '../build.js'; -const idx = readFileSync(new URL('../src/features/index.js', import.meta.url), 'utf8'); -const build = readFileSync(new URL('../build.js', import.meta.url), 'utf8'); +const strip = (s) => s + .replace(/\/\/[^\n]*/g, '') // line comments + .replace(/\s+/g, ' ') // whitespace runs + .replace(/, /g, ',').replace(/ ,/g, ',') + .trim(); -const inIndex = [...idx.matchAll(/import \{ (\w+Feature) \} from '\.\/([\w-]+)\.js'/g)] - .map((m) => ({ fn: m[1], name: m[2] })); -const registry = build.match(/const ALL_FEATURES = \{([^}]*)\}/s)?.[1] || ''; -const inBuild = [...registry.matchAll(/(\w+):\s*'(\w+Feature)'/g)].map((m) => ({ name: m[1], fn: m[2] })); +const idx = readFileSync(new URL('../src/features/index.js', import.meta.url), 'utf8'); +const gen = featureIndexSource(enabledFeatures(undefined)); -let ok = true; -for (const f of inIndex) { - if (!inBuild.find((b) => b.fn === f.fn)) { - console.log(`x ${f.fn} (${f.name}.js) is in features/index.js but NOT in build.js ALL_FEATURES — it will not ship`); - ok = false; - } -} -for (const b of inBuild) { - if (!inIndex.find((f) => f.fn === b.fn)) { - console.log(`x ${b.fn} is in build.js ALL_FEATURES but not imported in features/index.js`); - ok = false; - } -} -// Compare the ORDER FEATURES ARE CONSTRUCTED IN (hook precedence depends on -// it), not the order they happen to be imported in. -const built = [...(idx.match(/return \[([^\]]*)\]/s)?.[1] || '') - .matchAll(/(\w+Feature)\(ctx\)/g)].map((m) => m[1]); -const orderIdx = built.join(','); -const orderBuild = inBuild.map((b) => b.fn).join(','); -if (ok && orderIdx !== orderBuild) { - console.log(`x feature ORDER differs (hook precedence depends on it)\n index.js: ${orderIdx}\n build.js: ${orderBuild}`); - ok = false; +if (strip(idx) !== strip(gen)) { + console.log('x src/features/index.js does not match build.js featureIndexSource (full set)'); + console.log('\n--- generated (normalized) ---\n' + strip(gen)); + console.log('\n--- checked in (normalized) ---\n' + strip(idx)); + process.exit(1); } -if (ok) console.log(`ok ${inIndex.length} features registered consistently`); -process.exit(ok ? 0 : 1); +console.log(`ok features/index.js matches the generator (${enabledFeatures(undefined).length} features, deferred slots included)`); |
