mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +00:00
refactor: separate standalone Vite wiring and provider middleware
This commit is contained in:
1 parent
493acfc6aa
commit
8de1018c21
35 files changed
+8078
-7919
No files matched your search
@@ -3,7 +3,7 @@ import fs from 'node:fs';
|
||||
import test from 'node:test';
|
||||
import { filterTrailing24h, parseFirmsCsv } from './firmsCsv.js';
|
||||
|
||||
const config = fs.readFileSync(new URL('../../vite.config.js', import.meta.url), 'utf8');
|
||||
const config = fs.readFileSync(new URL('../../server/providers/local.js', import.meta.url), 'utf8');
|
||||
const start = config.indexOf(' async function refreshUpstream(key) {');
|
||||
assert.notEqual(start, -1, 'FIRMS refresh function exists');
|
||||
const end = config.indexOf('\n }', start);
|
||||
|
||||
@@ -20,8 +20,8 @@ async function launch(overrides = {}, dotenv = '') {
|
||||
for (const name of ['dev-cctv.sh', 'dev-fresh.sh', 'read-dotenv-value.mjs']) {
|
||||
await fs.copyFile(new URL(`../scripts/${name}`, import.meta.url), path.join(root, 'scripts', name));
|
||||
}
|
||||
await fs.mkdir(path.join(root, 'src', 'editions', 'local'), { recursive: true });
|
||||
await fs.copyFile(new URL('./editions/local/data.js', import.meta.url), path.join(root, 'src', 'editions', 'local', 'data.js'));
|
||||
await fs.mkdir(path.join(root, 'src', 'standalone'), { recursive: true });
|
||||
await fs.copyFile(new URL('./standalone/data.js', import.meta.url), path.join(root, 'src', 'standalone', 'data.js'));
|
||||
await fs.mkdir(path.join(root, 'node_modules'));
|
||||
await fs.symlink(fileURLToPath(new URL('../node_modules/vite', import.meta.url)), path.join(root, 'node_modules', 'vite'), 'dir');
|
||||
await fs.writeFile(path.join(root, '.env'), dotenv);
|
||||
|
||||
@@ -546,7 +546,7 @@ test('the decision table is written down where the next editor will read it', ()
|
||||
|
||||
test('markup, startup ordering and accessibility remain pinned', () => {
|
||||
const html = fs.readFileSync(new URL('../index.html', import.meta.url), 'utf8');
|
||||
const startup = fs.readFileSync(new URL('./editions/local/startupChrome.js', import.meta.url), 'utf8');
|
||||
const startup = fs.readFileSync(new URL('./standalone/startupChrome.js', import.meta.url), 'utf8');
|
||||
const css = fs.readFileSync(new URL('../style.css', import.meta.url), 'utf8');
|
||||
|
||||
assert.match(html, /id="first-run-launcher" role="dialog"[^>]*aria-labelledby="first-run-title"[^>]*hidden/);
|
||||
@@ -648,7 +648,7 @@ test('the DISPLAY rail starts collapsed on a first run, and a stored choice wins
|
||||
// ── Voice: instruction-only, tool schema byte-unchanged ─────────────────────
|
||||
|
||||
test('the voice TOOL SCHEMA is byte-identical to main — the mission mapping is instructions only', () => {
|
||||
const src = fs.readFileSync(new URL('../vite.config.js', import.meta.url), 'utf8');
|
||||
const src = fs.readFileSync(new URL('../server/providers/local.js', import.meta.url), 'utf8');
|
||||
const start = src.indexOf('const GEV_REALTIME_TOOLS = [');
|
||||
assert.ok(start > 0, 'GEV_REALTIME_TOOLS must still be a single literal array');
|
||||
const end = src.indexOf('\n];\n', start);
|
||||
@@ -687,7 +687,7 @@ test('the voice TOOL SCHEMA is byte-identical to main — the mission mapping is
|
||||
});
|
||||
|
||||
test('every layer a mission drives is already in the shipped set_layer_visibility enum', () => {
|
||||
const src = fs.readFileSync(new URL('../vite.config.js', import.meta.url), 'utf8');
|
||||
const src = fs.readFileSync(new URL('../server/providers/local.js', import.meta.url), 'utf8');
|
||||
const tool = src.slice(src.indexOf("name: 'set_layer_visibility'"), src.indexOf("name: 'show_data_layers_menu'"));
|
||||
const missionLayerIds = Object.values(FIRST_RUN_MISSIONS).flatMap((mission) => mission.layerIds || []);
|
||||
assert.ok(missionLayerIds.length > 0);
|
||||
|
||||
@@ -28,7 +28,7 @@ test('the boot provenance snapshot survives in-process Vite config re-evaluation
|
||||
// panel save has already set its values live on process.env. A recomputed
|
||||
// snapshot would classify the panel's own keys as external (read-only) until
|
||||
// a full process relaunch, so the first evaluation's snapshot must win.
|
||||
const source = readFileSync(new URL('../vite.config.js', import.meta.url), 'utf8');
|
||||
const source = readFileSync(new URL('../server/providers/local.js', import.meta.url), 'utf8');
|
||||
assert.match(
|
||||
source,
|
||||
/const PROVIDER_ENV_AT_BOOT = globalThis\.__GEV_PROVIDER_ENV_AT_BOOT \?\?= Object\.freeze\(/,
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import { createLocalApplication } from './editions/local/application.js';
|
||||
import { describeError } from './editions/local/errors.js';
|
||||
import { createStandaloneApplication } from './standalone/application.js';
|
||||
import { describeError } from './standalone/errors.js';
|
||||
|
||||
const application = createLocalApplication({
|
||||
const application = createStandaloneApplication({
|
||||
googleApiKey: import.meta.env.GOOGLE_MAPS_API_KEY,
|
||||
cesiumToken: import.meta.env.CESIUM_ION_TOKEN,
|
||||
allowQaRegistration: import.meta.env.DEV,
|
||||
|
||||
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
const source = readFileSync(new URL('../vite.config.js', import.meta.url), 'utf8');
|
||||
const source = readFileSync(new URL('../server/providers/local.js', import.meta.url), 'utf8');
|
||||
const detail = 'fixture-secret-token /internal/example <html>';
|
||||
|
||||
// Execute the production middleware with isolated upstreams and cache storage.
|
||||
|
||||
@@ -8,7 +8,7 @@ const ui = readFileSync(new URL('./ui.js', import.meta.url), 'utf8');
|
||||
const radio = readFileSync(new URL('./data/radio.js', import.meta.url), 'utf8');
|
||||
const rocketLaunches = readFileSync(new URL('./data/rocketLaunches.js', import.meta.url), 'utf8');
|
||||
const realtime = readFileSync(new URL('./voice/gevRealtime.js', import.meta.url), 'utf8');
|
||||
const voice = readFileSync(new URL('../vite.config.js', import.meta.url), 'utf8');
|
||||
const voice = readFileSync(new URL('../server/providers/local.js', import.meta.url), 'utf8');
|
||||
const css = readFileSync(new URL('../style.css', import.meta.url), 'utf8');
|
||||
|
||||
/** Parse the Realtime tool array out of the Vite config as real data. */
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { createApplication } from '../../app/application.js';
|
||||
import { createLocalScene } from './scene.js';
|
||||
import { createLocalControls } from './controls.js';
|
||||
import { createLocalData } from './data.js';
|
||||
import { createLocalTools } from './tools.js';
|
||||
import { createApplication } from '../app/application.js';
|
||||
import { createStandaloneScene } from './scene.js';
|
||||
import { createStandaloneControls } from './controls.js';
|
||||
import { createStandaloneData } from './data.js';
|
||||
import { createStandaloneTools } from './tools.js';
|
||||
|
||||
// The existing controls and layer catalog contain page-scoped state.
|
||||
let constructed = false;
|
||||
|
||||
/** Compose the standalone application once per page. Reload to start again. */
|
||||
export function createLocalApplication({
|
||||
export function createStandaloneApplication({
|
||||
googleApiKey,
|
||||
cesiumToken,
|
||||
allowQaRegistration = false,
|
||||
@@ -20,11 +20,11 @@ export function createLocalApplication({
|
||||
const loaderStatus = loadingScreen.querySelector('.loader-status');
|
||||
return createApplication({
|
||||
createScene: (context) =>
|
||||
createLocalScene({ ...context, googleApiKey, cesiumToken, loaderStatus }),
|
||||
createStandaloneScene({ ...context, googleApiKey, cesiumToken, loaderStatus }),
|
||||
createControls: (context) =>
|
||||
createLocalControls({ ...context, loaderStatus }),
|
||||
createStandaloneControls({ ...context, loaderStatus }),
|
||||
createData: (context) =>
|
||||
createLocalData({ ...context, allowQaRegistration }),
|
||||
createTools: (context) => createLocalTools({ ...context, loadingScreen }),
|
||||
createStandaloneData({ ...context, allowQaRegistration }),
|
||||
createTools: (context) => createStandaloneTools({ ...context, loadingScreen }),
|
||||
});
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { StyleManager } from '../../ui.js';
|
||||
import { flyToAustin } from '../../camera.js';
|
||||
import { initCockpitCloudEffects } from '../../cockpitCloudEffects.js';
|
||||
import { StyleManager } from '../ui.js';
|
||||
import { flyToAustin } from '../camera.js';
|
||||
import { initCockpitCloudEffects } from '../cockpitCloudEffects.js';
|
||||
|
||||
/** Construct the existing controls and camera presentation. */
|
||||
export function createLocalControls({
|
||||
export function createStandaloneControls({
|
||||
scene: { viewer, mapStackController },
|
||||
loaderStatus,
|
||||
defer,
|
||||
@@ -1,21 +1,21 @@
|
||||
import { DataLayerManager } from '../../data/manager.js';
|
||||
import flightsLayer from '../../data/flights.js';
|
||||
import militaryFlightsLayer from '../../data/militaryFlights.js';
|
||||
import earthquakesLayer from '../../data/earthquakes.js';
|
||||
import satellitesLayer from '../../data/satellites.js';
|
||||
import rocketLaunchesLayer from '../../data/rocketLaunches.js';
|
||||
import trafficLayer from '../../data/traffic.js';
|
||||
import cctvLayer from '../../data/cctv.js';
|
||||
import radioLayer from '../../data/radio.js';
|
||||
import bikeshareLayer from '../../data/bikeshare.js';
|
||||
import aisLiveVesselsLayer from '../../data/aisLiveVessels.js';
|
||||
import militaryInstallationsLayer from '../../data/militaryInstallations.js';
|
||||
import militaryAwarenessLayer from '../../data/militaryAwareness.js';
|
||||
import localDataLayers from '../../data/localLayers.js';
|
||||
import { LAYER_STATE_REGISTRY } from '../../data/layerState.js';
|
||||
import { DataLayerManager } from '../data/manager.js';
|
||||
import flightsLayer from '../data/flights.js';
|
||||
import militaryFlightsLayer from '../data/militaryFlights.js';
|
||||
import earthquakesLayer from '../data/earthquakes.js';
|
||||
import satellitesLayer from '../data/satellites.js';
|
||||
import rocketLaunchesLayer from '../data/rocketLaunches.js';
|
||||
import trafficLayer from '../data/traffic.js';
|
||||
import cctvLayer from '../data/cctv.js';
|
||||
import radioLayer from '../data/radio.js';
|
||||
import bikeshareLayer from '../data/bikeshare.js';
|
||||
import aisLiveVesselsLayer from '../data/aisLiveVessels.js';
|
||||
import militaryInstallationsLayer from '../data/militaryInstallations.js';
|
||||
import militaryAwarenessLayer from '../data/militaryAwareness.js';
|
||||
import localDataLayers from '../data/localLayers.js';
|
||||
import { LAYER_STATE_REGISTRY } from '../data/layerState.js';
|
||||
|
||||
/** Register the standalone layer catalog before allowing state restoration. */
|
||||
export function createLocalData({
|
||||
export function createStandaloneData({
|
||||
scene: { viewer },
|
||||
controls: { styleManager },
|
||||
allowQaRegistration,
|
||||
File renamed without changes.
@@ -1,15 +1,15 @@
|
||||
import * as Cesium from 'cesium';
|
||||
import { createApplicationViewer } from '../../app/viewer.js';
|
||||
import { registerDataCredits } from '../../data/dataCredits.js';
|
||||
import { configureCreditKeyboardAccess } from '../../creditKeyboard.js';
|
||||
import { MapStackController } from '../../mapStackController.js';
|
||||
import { loadPhotorealisticTileset } from '../../mapStartup.js';
|
||||
import { initLogoGaze } from '../../logoGaze.js';
|
||||
import { uninstallRenderGovernor } from '../../renderGovernor.js';
|
||||
import { createApplicationViewer } from '../app/viewer.js';
|
||||
import { registerDataCredits } from '../data/dataCredits.js';
|
||||
import { configureCreditKeyboardAccess } from '../creditKeyboard.js';
|
||||
import { MapStackController } from '../mapStackController.js';
|
||||
import { loadPhotorealisticTileset } from '../mapStartup.js';
|
||||
import { initLogoGaze } from '../logoGaze.js';
|
||||
import { uninstallRenderGovernor } from '../renderGovernor.js';
|
||||
import { describeError } from './errors.js';
|
||||
|
||||
/** Construct the standalone globe using the caller's local configuration. */
|
||||
export async function createLocalScene({
|
||||
export async function createStandaloneScene({
|
||||
googleApiKey,
|
||||
cesiumToken,
|
||||
loaderStatus,
|
||||
@@ -1,8 +1,8 @@
|
||||
import { initFirstRunExperience } from '../../firstRunExperience.js';
|
||||
import { initKeySetup } from '../../keySetup.js';
|
||||
import { initFirstRunExperience } from '../firstRunExperience.js';
|
||||
import { initKeySetup } from '../keySetup.js';
|
||||
|
||||
/** Reveal welcome controls only after restoration and the loading transition. */
|
||||
export function startLocalChrome({
|
||||
export function startStandaloneChrome({
|
||||
loadingScreen,
|
||||
styleManager,
|
||||
dataManager,
|
||||
@@ -37,7 +37,7 @@ function fixture() {
|
||||
};
|
||||
vm.createContext(context);
|
||||
vm.runInContext(source, context);
|
||||
const stop = context.startLocalChrome({
|
||||
const stop = context.startStandaloneChrome({
|
||||
loadingScreen: {
|
||||
classList: { add: (value) => events.push(value) },
|
||||
addEventListener: (type, listener) => listeners.set(type, listener),
|
||||
@@ -1,18 +1,18 @@
|
||||
import { SceneDirector } from '../../scenes/director.js';
|
||||
import { initAnnotations } from '../../annotations/index.js';
|
||||
import { initGevVoiceCommands } from '../../voice/gevRealtime.js';
|
||||
import { installScopeMask, destroyScopeMask } from '../../scopeMask.js';
|
||||
import { SceneDirector } from '../scenes/director.js';
|
||||
import { initAnnotations } from '../annotations/index.js';
|
||||
import { initGevVoiceCommands } from '../voice/gevRealtime.js';
|
||||
import { installScopeMask, destroyScopeMask } from '../scopeMask.js';
|
||||
import {
|
||||
installRenderGovernor,
|
||||
getRenderGovernorDiagnostics,
|
||||
governorRequestRender,
|
||||
holdContinuousRender,
|
||||
releaseContinuousRender,
|
||||
} from '../../renderGovernor.js';
|
||||
import { startLocalChrome } from './startupChrome.js';
|
||||
} from '../renderGovernor.js';
|
||||
import { startStandaloneChrome } from './startupChrome.js';
|
||||
|
||||
/** Attach scene tools, rendering listeners and the standalone debug handle. */
|
||||
export function createLocalTools({
|
||||
export function createStandaloneTools({
|
||||
scene,
|
||||
controls,
|
||||
data,
|
||||
@@ -30,7 +30,7 @@ export function createLocalTools({
|
||||
if (window.__gevAnnotations === annotations) delete window.__gevAnnotations;
|
||||
annotations.destroy();
|
||||
});
|
||||
defer(startLocalChrome({ loadingScreen, styleManager, dataManager, signal }));
|
||||
defer(startStandaloneChrome({ loadingScreen, styleManager, dataManager, signal }));
|
||||
// Idle render governor: flips the scene into requestRenderMode whenever
|
||||
// nothing animates per frame. Installed AFTER every module above has had
|
||||
// its chance to register pre-install holds. (perf wave 2)
|
||||
@@ -75,3 +75,17 @@ test('dynamic imports obey the same component ownership rule', async (t) => {
|
||||
await writeFile(path.join(root, 'startup.js'), 'export const app = 2;');
|
||||
await assert.rejects(checkPackageBoundaries(root), /unowned module.*startup/);
|
||||
});
|
||||
|
||||
test('a Node build export must be explicitly classified and scoped', async (t) => {
|
||||
const root = await fixture(t);
|
||||
await writeFile(path.join(root, 'package.json'), JSON.stringify({
|
||||
name: 'boundary-fixture', type: 'module', exports: { './feature': { node: './entry.js' } },
|
||||
}));
|
||||
await writeFile(path.join(root, 'scripts/package-boundaries.json'), JSON.stringify({
|
||||
feature: { runtime: 'node', exports: ['./feature'], modules: ['entry.js'], external: [] },
|
||||
}));
|
||||
assert.deepEqual(await checkPackageBoundaries(root), [{ name: 'feature', exports: 1, modules: 1 }]);
|
||||
await writeFile(path.join(root, 'entry.js'), "import './startup.js'; export const value = 1;");
|
||||
await writeFile(path.join(root, 'startup.js'), 'export const app = 2;');
|
||||
await assert.rejects(checkPackageBoundaries(root), /unowned module.*startup/);
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { createBrowserViteConfig } from '../../build/vite.js';
|
||||
import standaloneConfig, * as compatibility from '../../vite.config.js';
|
||||
import * as providers from '../../server/providers/local.js';
|
||||
|
||||
test('explicit build inputs preserve browser-only defines, plugin order and loopback protections', () => {
|
||||
const plugin = { name: 'fixture-provider' };
|
||||
const config = createBrowserViteConfig({ plugins: [plugin], googleApiKey: 'browser-fixture', cesiumToken: 'ion-fixture' });
|
||||
assert.equal(config.plugins[1], plugin);
|
||||
assert.equal(config.server.host, 'localhost');
|
||||
assert.equal(config.server.port, 4173);
|
||||
assert.deepEqual(config.server.allowedHosts, ['localhost', '127.0.0.1', '.local']);
|
||||
assert.ok(config.server.fs.deny.includes('**/ENVIRONMENT'));
|
||||
assert.ok(config.server.fs.deny.includes('.env.*'));
|
||||
assert.equal(config.server.headers['X-Frame-Options'], 'DENY');
|
||||
assert.equal(config.server.headers['Content-Security-Policy'], "frame-ancestors 'none'");
|
||||
assert.deepEqual(config.define, {
|
||||
'import.meta.env.GOOGLE_MAPS_API_KEY': '"browser-fixture"',
|
||||
'import.meta.env.CESIUM_ION_TOKEN': '"ion-fixture"',
|
||||
});
|
||||
assert.equal(createBrowserViteConfig({ host: '0.0.0.0', port: '4800' }).server.allowedHosts, true);
|
||||
assert.equal(createBrowserViteConfig({ host: '::', port: '4800' }).server.port, 4800);
|
||||
});
|
||||
|
||||
test('build helper does not discover environment values or construct local providers', () => {
|
||||
const before = process.env.GOOGLE_MAPS_API_KEY;
|
||||
process.env.GOOGLE_MAPS_API_KEY = 'environment-fixture';
|
||||
try {
|
||||
const config = createBrowserViteConfig();
|
||||
assert.equal(config.define['import.meta.env.GOOGLE_MAPS_API_KEY'], undefined);
|
||||
assert.equal(config.plugins.length, 1);
|
||||
} finally {
|
||||
if (before === undefined) delete process.env.GOOGLE_MAPS_API_KEY;
|
||||
else process.env.GOOGLE_MAPS_API_KEY = before;
|
||||
}
|
||||
});
|
||||
|
||||
test('root config retains existing named exports and standalone provider order', () => {
|
||||
for (const [name, value] of Object.entries(providers)) assert.equal(compatibility[name], value, name);
|
||||
const config = standaloneConfig({ mode: 'test' });
|
||||
assert.deepEqual(config.plugins.slice(1).map((plugin) => plugin.name), providers.localProviderPlugins().map((plugin) => plugin.name));
|
||||
assert.equal(config.plugins.at(-1).name, 'gev-key-setup');
|
||||
});
|
||||
|
||||
test('build export resolves in Node and has no browser fallback', async () => {
|
||||
const exported = await import('gods-eye-view/build/vite');
|
||||
assert.equal(exported.createBrowserViteConfig, createBrowserViteConfig);
|
||||
const pkg = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url)));
|
||||
assert.deepEqual(pkg.exports['./build/vite'], { node: './build/vite.js' });
|
||||
});
|
||||
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import test from 'node:test';
|
||||
|
||||
const voiceConfig = readFileSync(new URL('../../vite.config.js', import.meta.url), 'utf8');
|
||||
const voiceConfig = readFileSync(new URL('../../server/providers/local.js', import.meta.url), 'utf8');
|
||||
const realtime = readFileSync(new URL('./gevRealtime.js', import.meta.url), 'utf8');
|
||||
|
||||
test('aircraft identity narration acknowledges missing enrichment', () => {
|
||||
|
||||
@@ -35,7 +35,7 @@ test('every live basemap is reachable by its own id — no enum value without a
|
||||
assert.equal(normalizeStackId('Esri'), 'esri-imagery');
|
||||
assert.equal(normalizeStackId('esri imagery'), 'esri-imagery');
|
||||
// And the voice tool's enum must equal the set of live ids — no drift either way.
|
||||
const config = readFileSync(new URL('../../vite.config.js', import.meta.url), 'utf8');
|
||||
const config = readFileSync(new URL('../../server/providers/local.js', import.meta.url), 'utf8');
|
||||
const enumMatch = config.match(/enum: \[('photoreal'[^\]]*)\],\s*\n\s*description: 'photoreal = Google 3D/);
|
||||
assert.ok(enumMatch, 'set_map_stack enum literal must still be findable');
|
||||
const enumIds = enumMatch[1].split(',').map((s) => s.trim().replace(/^'|'$/g, ''));
|
||||
|
||||
Reference in new issue
Block a user