aboutsummaryrefslogtreecommitdiffstats
path: root/tools/feature-registry-check.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/feature-registry-check.js')
-rw-r--r--tools/feature-registry-check.js51
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)`);