test(qa): W1 baseline and the main-chunk check

scripts/check-main-chunk.mjs (npm run check:main-chunk) finds the main
chunk of a build (the Vite manifest's entry, or the module script
dist/index.html loads), follows its static imports and fails when any of
them carries three or Navara markers; it prints the sizes. Base build:
main chunk 2,850,229 bytes (gzip 881,783), no static imports, clean.

scripts/qa-w1-baseline.mjs records Cesium mode for the W1 layers (flights,
military, local ADS-B, vessels, satellites, launches): counts, five known
objects with positions and screen points, JPEGs (path, size, sha256) at
the global / New York (JFK) / orbit poses, a tracked object's camera pose
after 2 s and what its share link restores. Live sources until the
fixture sources of Task 8.
This commit is contained in:
German Khokhlov committed 2026-10-16 14:49:24 +04:00
1 parent d50eaa28af
commit 27e28373d0
5 files changed
+265 -1

No files matched your search

+1
View File
@@ -50,6 +50,7 @@
"qa:street-level": "node scripts/qa-street-level.mjs",
"qa:street-level:fixtures": "node scripts/qa-street-level.mjs --fixtures",
"check:boundaries": "node scripts/check-import-directions.mjs && node scripts/check-package-boundaries.mjs",
"check:main-chunk": "node scripts/check-main-chunk.mjs",
"qa:transit": "node scripts/qa-transit.mjs",
"mcp": "node server/mcp/stdio.js"
},
+94
View File
@@ -0,0 +1,94 @@
#!/usr/bin/env node
/**
* The build gate for the Cesium-mode bundle: the main chunk (the module
* script dist/index.html loads, or the Vite manifest's entry when the build
* writes one) and every chunk it imports statically must carry no three or
* Navara code; those load only through navaraMode.js's import(). Prints the
* sizes (raw and gzip) and fails when a marker is found.
*
* npm run build && node scripts/check-main-chunk.mjs [--dist=dist] [--json]
*/
import fs from 'node:fs';
import path from 'node:path';
import { gzipSync } from 'node:zlib';
import { fileURLToPath } from 'node:url';
import {
entryFromHtml,
entryFromManifest,
forbiddenMarkersIn,
initialChunks,
} from '../src/tooling/mainChunk.js';
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const option = (name) =>
process.argv.find((arg) => arg.startsWith(`--${name}=`))?.split('=')[1];
const dist = path.resolve(root, option('dist') ?? 'dist');
const asJson = process.argv.includes('--json');
function findEntry() {
const manifestPath = path.join(dist, '.vite', 'manifest.json');
if (fs.existsSync(manifestPath)) {
const entry = entryFromManifest(
JSON.parse(fs.readFileSync(manifestPath, 'utf8')),
);
if (entry) return { entry, via: 'manifest' };
}
const htmlPath = path.join(dist, 'index.html');
if (!fs.existsSync(htmlPath))
throw new Error(`No build at ${dist}: run npm run build first`);
const entry = entryFromHtml(fs.readFileSync(htmlPath, 'utf8'));
if (!entry) throw new Error(`${htmlPath} loads no module script`);
return { entry, via: 'index.html' };
}
const read = (file) => {
const absolute = path.join(dist, file);
return fs.existsSync(absolute) ? fs.readFileSync(absolute, 'utf8') : null;
};
const { entry, via } = findEntry();
const chunks = initialChunks(entry, read).map((file) => {
const code = read(file);
if (code == null)
return { file, missing: true, bytes: 0, gzipBytes: 0, markers: [] };
return {
file,
bytes: Buffer.byteLength(code),
gzipBytes: gzipSync(code).length,
markers: forbiddenMarkersIn(code),
};
});
const main = chunks[0];
const failures = chunks.filter(
(chunk) => chunk.missing || chunk.markers.length,
);
const result = {
entry,
via,
mainBytes: main.bytes,
mainGzipBytes: main.gzipBytes,
initialBytes: chunks.reduce((sum, chunk) => sum + chunk.bytes, 0),
chunks,
ok: failures.length === 0,
};
if (asJson) console.log(JSON.stringify(result, null, 2));
else {
const kb = (bytes) => `${(bytes / 1000).toFixed(2)} kB`;
console.log(`[main-chunk] entry ${entry} (via ${via})`);
for (const chunk of chunks)
console.log(
`[main-chunk] ${chunk.file}: ${kb(chunk.bytes)} (gzip ${kb(chunk.gzipBytes)})` +
(chunk.missing ? ' MISSING' : '') +
(chunk.markers.length ? ` FORBIDDEN: ${chunk.markers.join(', ')}` : ''),
);
console.log(
`[main-chunk] main ${main.bytes} bytes (gzip ${main.gzipBytes}); initial chunks ${result.initialBytes} bytes`,
);
console.log(
result.ok
? '[main-chunk] ok: no three or Navara in the main chunk'
: '[main-chunk] FAIL',
);
}
process.exitCode = result.ok ? 0 : 1;
+4 -1
View File
@@ -1063,5 +1063,8 @@
"src/services/groundSnap.test.mjs",
"src/sharelinkPose.test.mjs",
"src/tooling/navaraAssets.test.mjs",
"src/tooling/tileCache.test.mjs"
"src/tooling/tileCache.test.mjs",
"scripts/check-main-chunk.mjs",
"src/tooling/mainChunk.test.mjs",
"scripts/qa-w1-baseline.mjs"
]
+80
View File
@@ -0,0 +1,80 @@
// The main chunk of a Vite build and what it must not carry: the Cesium-mode
// page loads the entry chunk and every chunk it imports statically; Navara and
// three belong in the lazy chunk navaraMode.js loads with import(). Pure
// helpers for scripts/check-main-chunk.mjs.
/**
* Markers that only three or Navara code leaves in a bundle (minification
* keeps string literals): three's own warnings name their classes, Navara's
* package names, wasm modules and attribution card classes. Plain engine
* names ('navara' as an engine kind) are not markers.
*/
export const FORBIDDEN_MARKERS = Object.freeze([
Object.freeze({
name: 'three',
pattern: /THREE\.(?:WebGLRenderer|GLTFLoader|EXRLoader|Vector\d)/,
}),
Object.freeze({
name: 'navara',
pattern: /@navaramap\/|navara_wasm|navara-attr-/,
}),
]);
/** The module script a built index.html loads, relative to dist ('assets/…'), or null. */
export function entryFromHtml(html) {
const scripts = String(html).match(/<script\b[^>]*>/g) ?? [];
for (const tag of scripts) {
if (!/\btype\s*=\s*["']module["']/.test(tag)) continue;
const src = tag.match(/\bsrc\s*=\s*["']([^"']+)["']/);
if (src) return src[1].replace(/^\.?\//, '');
}
return null;
}
/** The file of the manifest chunk marked isEntry for index.html (or any entry), or null. */
export function entryFromManifest(manifest) {
const chunks = Object.values(manifest ?? {});
const entry =
chunks.find((chunk) => chunk.isEntry && chunk.src === 'index.html') ??
chunks.find((chunk) => chunk.isEntry);
return entry?.file ?? null;
}
/** Sibling chunks a chunk imports statically (`from"./x.js"`, `import"./x.js"`). */
export function staticImportsOf(code) {
const found = [];
const pattern = /(?:\bfrom|\bimport)\s*(["'])\.\/([^"']+\.js)\1/g;
for (const match of String(code).matchAll(pattern)) {
if (!found.includes(match[2])) found.push(match[2]);
}
return found;
}
/**
* The entry and every chunk reachable from it by static imports, in
* discovery order. `read(file)` returns a chunk's code (file relative to
* dist, in the entry's directory).
*/
export function initialChunks(entry, read) {
const directory = entry.includes('/')
? entry.slice(0, entry.lastIndexOf('/') + 1)
: '';
const seen = [entry];
for (let index = 0; index < seen.length; index++) {
const code = read(seen[index]);
if (code == null) continue;
for (const name of staticImportsOf(code)) {
const file = directory + name;
if (!seen.includes(file)) seen.push(file);
}
}
return seen;
}
/** The names of FORBIDDEN_MARKERS found in code. */
export function forbiddenMarkersIn(code) {
const text = String(code);
return FORBIDDEN_MARKERS.filter(({ pattern }) => pattern.test(text)).map(
({ name }) => name,
);
}
+86
View File
@@ -0,0 +1,86 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
FORBIDDEN_MARKERS,
entryFromHtml,
entryFromManifest,
forbiddenMarkersIn,
initialChunks,
staticImportsOf,
} from './mainChunk.js';
test('the entry is the module script the built index.html loads', () => {
const html = `<!doctype html><head>
<script src="/cesium/Cesium.js"></script>
<script type="module" crossorigin src="/assets/index--RGa-KXE.js"></script>
<link rel="modulepreload" crossorigin href="/assets/vendor-Ab.js">
</head>`;
assert.equal(entryFromHtml(html), 'assets/index--RGa-KXE.js');
});
test('an index.html without a module script has no entry', () => {
assert.equal(
entryFromHtml('<script src="/cesium/Cesium.js"></script>'),
null,
);
});
test('the entry of a Vite manifest is the chunk marked isEntry for index.html', () => {
const manifest = {
'src/app/navaraMode.js': {
file: 'assets/navaraMode-B.js',
isDynamicEntry: true,
},
'index.html': {
file: 'assets/index-A.js',
isEntry: true,
src: 'index.html',
imports: ['_vendor-C.js'],
},
'_vendor-C.js': { file: 'assets/vendor-C.js' },
};
assert.equal(entryFromManifest(manifest), 'assets/index-A.js');
});
test('static imports are found, dynamic imports are not', () => {
const code =
'import{a as b}from"./vendor-C.js";import"./side-D.js";' +
'const x=()=>import("./navaraMode-B.js");' +
"import { y } from './spaced-E.js';";
assert.deepEqual(staticImportsOf(code), [
'vendor-C.js',
'side-D.js',
'spaced-E.js',
]);
});
test('the initial chunks are the entry and everything it imports statically', () => {
const files = {
'assets/index-A.js': 'import"./vendor-C.js";import("./lazy-L.js")',
'assets/vendor-C.js': 'import{z}from"./deep-D.js"',
'assets/deep-D.js': 'import"./vendor-C.js"',
'assets/lazy-L.js': 'THREE.WebGLRenderer',
};
assert.deepEqual(
initialChunks('assets/index-A.js', (file) => files[file]),
['assets/index-A.js', 'assets/vendor-C.js', 'assets/deep-D.js'],
);
});
test('three and Navara markers are reported, ordinary engine names are not', () => {
assert.deepEqual(
forbiddenMarkersIn(
'B4=Object.freeze(["cesium","navara"]);FP={cesium:49,navara:80}',
),
[],
);
assert.deepEqual(
forbiddenMarkersIn('console.warn("THREE.WebGLRenderer: Context Lost.")'),
['three'],
);
assert.deepEqual(forbiddenMarkersIn('import "@navaramap/three"'), ['navara']);
assert.deepEqual(forbiddenMarkersIn('new URL("navara_wasm_bg.wasm")'), [
'navara',
]);
assert.ok(FORBIDDEN_MARKERS.length >= 2);
});