mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 07:42:23 +00:00
With a satellite tracked and no aircraft tracked, Cockpit can ride the satellite. Entry needs only the Satellites layer; debris can be tracked but not entered, and voice stays aircraft-only. Double-clicking the tracked satellite or the entry buttons (reading ORBIT) enter it. The camera sits on the satellite's per-frame SGP4 position and looks straight down, turned to the ground track (north-up for a satellite with none). The optical zoom steps 1x to 8x with a focus pull, the arrows tilt the view up to 45 degrees off nadir, and Home or 0 returns to nadir. Off nadir the tilt reads its total angle first and the swath readout becomes an approximation marked with ≈. While in orbit the map leases flat Esri imagery, the cloud pass stays off and the view opens in the optical style. The orbital HUD names the satellite with its owner's flag (from the flag-icons package, loaded per country) or an organisation badge, its operator, class, NORAD and COSPAR ids and TLE epoch with the element lines on request, altitude, orbital speed and the sub-satellite point. CURRENTLY OVER names the country and its first-level division, or the sea, and from 8x the most prominent populated place nearby, looked up offline from the bundled Natural Earth packs (populated places are a new pack). The satellites layer hides the ridden satellite's own marks, dims the other orbit rings and ignores scene clicks and Escape while held. Exit restores the camera's field of view and the satellite's follow camera. C no longer enters or leaves Cockpit, bridge or orbit: it is left to the app-wide CCTV shortcut, and Escape or the exit button leave first person.
170 lines
5.9 KiB
JavaScript
170 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 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}`);
|
|
}
|
|
const allowed = new Set();
|
|
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}`);
|
|
}
|
|
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('./') ||
|
|
!allowed.has(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];
|
|
// A declared dependency also owns the asset files it ships (a
|
|
// flag-icons SVG imported as a URL) that a glob resolves in place.
|
|
const dependencyAsset = group.external.some((external) =>
|
|
id.startsWith(
|
|
`${normalizePath(path.join(root, 'node_modules', external))}/`,
|
|
),
|
|
);
|
|
if (!allowed.has(id) && !dependencyAsset) {
|
|
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;
|
|
}
|
|
}
|