mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
Restrict runtime SDK imports to their engine directories, including package subpaths, re-exports and dynamic imports. Keep three measured, reasoned exceptions for the Bhote Koshi property-record models and reject stale exceptions. Move the Cesium annotation renderers and both wind adapters to their engine folders, preserving their implementations and lazy loading. Load all 35 manifest display entries on a fake engine with an unknown kind and no native extensions. Draw small fixtures and verify resource and subscription cleanup before destroying the engine. Name the three entries without display factories as skips. Update the architecture, infrastructure lifecycle and engine integration documentation to describe the code today. Cesium behavior is unchanged, and Navara keeps its existing behavior. The weather comparison harness supplies the Navara flow-mesh factory and accepts the previous backend location for comparison runs. No user-visible strings change. Cesium layer-renderer lines fall from 14 to 0, Navara layer-renderer lines from 314 to 189, outside-engine Cesium import files from 5 to 3, viewer lines from 933 to 910, extension reads from 4 to 3, and engine-name lines from 36 to 33. No coupling count grows. Targeted checks pass: 845 tests, three named rehearsal skips. Import directions, package boundaries, formatting and syntax checks pass. Checked in the browser, keyless: the contract suite passes on Navara and on Cesium apart from the camera-events check that also fails intermittently before; Navara annotations pass across engine switches; the ground-look comparison is pixel-identical. qa-draw-tool's hidden-line check and the wind engine switch fail the same way before this change.
173 lines
5.9 KiB
JavaScript
173 lines
5.9 KiB
JavaScript
import { readFile, realpath } from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
import vue from '@vitejs/plugin-vue';
|
|
import { VUE_PLUGIN_OPTIONS } from '../build/vueOptions.js';
|
|
import { build, normalizePath } from 'vite';
|
|
|
|
/** Build each browser or Node export group and reject imports outside its declared ownership. */
|
|
export async function checkPackageBoundaries(root) {
|
|
root = await realpath(root);
|
|
const readJson = async (name) =>
|
|
JSON.parse(await readFile(path.join(root, name), 'utf8'));
|
|
const pkg = await readJson('package.json');
|
|
const { engineImportExceptions, ...groups } = await readJson(
|
|
'scripts/package-boundaries.json',
|
|
);
|
|
const declaredExports = Object.keys(pkg.exports || {}).sort();
|
|
const classifiedExports = Object.values(groups)
|
|
.flatMap((group) => group.exports)
|
|
.sort();
|
|
if (
|
|
!declaredExports.length ||
|
|
JSON.stringify(declaredExports) !== JSON.stringify(classifiedExports)
|
|
) {
|
|
throw new Error(
|
|
'Every package export must belong to exactly one boundary group',
|
|
);
|
|
}
|
|
const reports = [];
|
|
for (const [name, group] of Object.entries(groups)) {
|
|
const node = group.runtime === 'node';
|
|
if (group.runtime && !['browser', 'node'].includes(group.runtime)) {
|
|
throw new Error(`Invalid boundary runtime: ${name}`);
|
|
}
|
|
if (
|
|
!Array.isArray(group.modules) ||
|
|
!group.modules.length ||
|
|
!Array.isArray(group.external)
|
|
) {
|
|
throw new Error(`Invalid package boundary: ${name}`);
|
|
}
|
|
// A module entry ending in "/" owns every file under that directory.
|
|
const allowed = new Set();
|
|
const allowedDirectories = [];
|
|
const owns = (id) =>
|
|
allowed.has(id) || allowedDirectories.some((dir) => id.startsWith(dir));
|
|
for (const module of group.modules) {
|
|
if (
|
|
typeof module !== 'string' ||
|
|
path.isAbsolute(module) ||
|
|
module.includes('\\') ||
|
|
module.split('/').includes('..')
|
|
) {
|
|
throw new Error(`Boundary modules must be repository paths: ${name}`);
|
|
}
|
|
const resolved = await realpath(path.join(root, module));
|
|
const relative = path.relative(root, resolved);
|
|
if (
|
|
relative === '..' ||
|
|
relative.startsWith(`..${path.sep}`) ||
|
|
path.isAbsolute(relative)
|
|
) {
|
|
throw new Error(`Boundary module escapes repository: ${name}`);
|
|
}
|
|
if (module.endsWith('/')) {
|
|
allowedDirectories.push(`${normalizePath(resolved)}/`);
|
|
} else {
|
|
allowed.add(normalizePath(resolved));
|
|
}
|
|
}
|
|
for (const external of group.external) {
|
|
if (
|
|
!Object.hasOwn(pkg.dependencies || {}, external) &&
|
|
!Object.hasOwn(pkg.peerDependencies || {}, external) &&
|
|
!(node && Object.hasOwn(pkg.devDependencies || {}, external))
|
|
) {
|
|
throw new Error(
|
|
`Boundary external must be a declared dependency for its runtime: ${external}`,
|
|
);
|
|
}
|
|
}
|
|
const input = group.exports.map((key) => {
|
|
const declaration = pkg.exports[key];
|
|
const target = node ? declaration?.node : declaration;
|
|
if (
|
|
node &&
|
|
(typeof declaration !== 'object' ||
|
|
Object.keys(declaration).join() !== 'node')
|
|
) {
|
|
throw new Error(`Node export must have only a node condition: ${key}`);
|
|
}
|
|
if (
|
|
typeof target !== 'string' ||
|
|
!target.startsWith('./') ||
|
|
!owns(normalizePath(path.resolve(root, target)))
|
|
) {
|
|
throw new Error(`Export must point to an owned module: ${key}`);
|
|
}
|
|
return path.resolve(root, target);
|
|
});
|
|
const seen = new Set();
|
|
await build({
|
|
root,
|
|
// Keep this gate out of a running dev server's dependency cache
|
|
// (node_modules/.vite): a build there re-optimizes dependencies and the
|
|
// dev server's workers then fail with 504 "Outdated Optimize Dep".
|
|
cacheDir: path.join(root, 'node_modules/.vite-boundaries'),
|
|
configFile: false,
|
|
envFile: false,
|
|
publicDir: false,
|
|
ssr: node ? { noExternal: true } : undefined,
|
|
logLevel: 'silent',
|
|
plugins: [
|
|
vue(VUE_PLUGIN_OPTIONS),
|
|
{
|
|
name: 'check-package-ownership',
|
|
moduleParsed(info) {
|
|
// Plugin helpers are virtual; a single-file component's style and
|
|
// template blocks belong to the component file itself.
|
|
if (info.id.startsWith('\0')) return;
|
|
const id = info.id.split('?')[0];
|
|
if (!owns(id)) {
|
|
throw new Error(
|
|
`Package boundary ${name} imports an unowned module: ${path.relative(root, id)}`,
|
|
);
|
|
}
|
|
seen.add(id);
|
|
},
|
|
},
|
|
],
|
|
build: {
|
|
ssr: node,
|
|
lib: { entry: input, formats: ['es'] },
|
|
write: false,
|
|
minify: false,
|
|
assetsInlineLimit: 0,
|
|
rollupOptions: {
|
|
input,
|
|
// A declared dependency also owns its package subpath exports
|
|
// (for example `@jtarrio/webrtlsdr/rtlsdr.js`).
|
|
external: (id) =>
|
|
group.external.some(
|
|
(name) => id === name || id.startsWith(`${name}/`),
|
|
),
|
|
// Unused imports must still obey ownership; tree shaking is not a boundary.
|
|
treeshake: false,
|
|
preserveEntrySignatures: 'strict',
|
|
},
|
|
},
|
|
});
|
|
reports.push({ name, exports: input.length, modules: seen.size });
|
|
}
|
|
return reports;
|
|
}
|
|
|
|
const invoked = process.argv[1]
|
|
? pathToFileURL(path.resolve(process.argv[1])).href
|
|
: '';
|
|
if (import.meta.url === invoked) {
|
|
try {
|
|
const reports = await checkPackageBoundaries(
|
|
fileURLToPath(new URL('../', import.meta.url)),
|
|
);
|
|
for (const report of reports)
|
|
console.log(
|
|
`Checked ${report.name}: ${report.exports} exports, ${report.modules} owned modules.`,
|
|
);
|
|
} catch (error) {
|
|
console.error(error.message);
|
|
process.exitCode = 1;
|
|
}
|
|
}
|