feat: retain free-only startup and provider verification
This commit is contained in:
parent
c9ec6da894
commit
ef7b760668
|
|
@ -0,0 +1,42 @@
|
|||
// Read-only source smoke check. Never prints or submits API credentials.
|
||||
const checks = [
|
||||
['Free configuration', '/api/free-providers'],
|
||||
['Place search', '/api/free-geocode?q=Reykjavik'],
|
||||
['Flights', '/api/opensky'],
|
||||
['Military flights', '/api/adsblol/mil'],
|
||||
['Satellites', '/api/celestrak/stations'],
|
||||
['Space missions', '/api/launches'],
|
||||
['Camera directory', '/api/cctv/sources'],
|
||||
['Traffic configuration', '/api/tomtom/status'],
|
||||
['Fire configuration', '/api/firms/status'],
|
||||
['Ships', '/api/ais-live'],
|
||||
['Weather', '/api/weather-effects?latitude=30.2672&longitude=-97.7431'],
|
||||
];
|
||||
const results = [];
|
||||
for (let i = 0; i < checks.length; i += 3) {
|
||||
await Promise.all(checks.slice(i, i + 3).map(async ([name, path]) => {
|
||||
const start = Date.now();
|
||||
try {
|
||||
const response = await fetch(`http://localhost:4173${path}`, { signal: AbortSignal.timeout(30000) });
|
||||
const text = await response.text();
|
||||
const body = name === 'Satellites' && !text.trim().startsWith('{')
|
||||
? { results: text.trim().split('\n').filter(line => line.startsWith('1 ')) }
|
||||
: JSON.parse(text);
|
||||
const rows = body.states || body.ac || body.results || body.sources || body.vessels;
|
||||
const result = { name, http: response.status, seconds: ((Date.now() - start) / 1000).toFixed(1),
|
||||
...(Array.isArray(rows) ? { count: rows.length } : {}),
|
||||
...(body.status ? { status: body.status } : {}),
|
||||
...(typeof body.hasKey === 'boolean' ? { hasKey: body.hasKey } : {}),
|
||||
...(body.place ? { place: body.place.label } : {}),
|
||||
...(path === '/api/free-providers' ? { configuration: body } : {}),
|
||||
};
|
||||
results.push(result);
|
||||
console.log(JSON.stringify(result));
|
||||
} catch (error) {
|
||||
const result = { name, error: error.name === 'TimeoutError' ? 'timeout' : 'request failed' };
|
||||
results.push(result);
|
||||
console.log(JSON.stringify(result));
|
||||
}
|
||||
}));
|
||||
}
|
||||
if (results.some(r => r.error || (r.http >= 400 && r.name !== 'Ships'))) process.exitCode = 1;
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import { spawn } from 'node:child_process';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
const root=fileURLToPath(new URL('../',import.meta.url));
|
||||
const vite=fileURLToPath(new URL('../node_modules/vite/bin/vite.js',import.meta.url));
|
||||
const [major,minor]=process.versions.node.split('.').map(Number);
|
||||
if(!((major===24&&minor>=14)||major===26)){
|
||||
console.error('Install Node 24.14+ (24.x) or Node 26 before starting Gods Eye.');
|
||||
process.exit(1);
|
||||
}
|
||||
if(!existsSync(vite)){
|
||||
console.error('Run npm ci first to install dependencies.');
|
||||
process.exit(1);
|
||||
}
|
||||
const child=spawn(process.execPath,[vite,'--host','localhost','--port','4173','--strictPort','--open'],{
|
||||
cwd:root,stdio:'inherit',
|
||||
env:{...process.env,GEV_FREE_ONLY:'1',GOOGLE_MAPS_API_KEY:'',OPENAI_API_KEY:'',HOST:'localhost',PORT:'4173'}
|
||||
});
|
||||
child.on('error',()=>{console.error('Unable to start the local server.');process.exitCode=1;});
|
||||
child.on('exit',code=>{process.exitCode=code??1;});
|
||||
for(const signal of ['SIGINT','SIGTERM'])process.on(signal,()=>child.kill(signal));
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import { lonLatToTile } from '../src/data/tomtomTiles.js';
|
||||
import { decodeFlowTile } from '../src/data/flowTiles.js';
|
||||
const root = 'http://localhost:4173';
|
||||
const { x, y } = lonLatToTile(-97.7431, 30.2672, 12);
|
||||
const checks = [
|
||||
['Traffic tile', `/api/tomtom/flow/12/${x}/${y}.pbf`, 'tile'],
|
||||
['Fire detections', '/api/firms', 'fires'],
|
||||
['Ships', '/api/ais-live?maxRows=5000', 'ships'],
|
||||
['Camera health', '/api/cctv/health', 'cameras'],
|
||||
['Mapped installations', '/api/military-installations?south=30.15&west=-97.85&north=30.35&east=-97.65', 'mapped'],
|
||||
];
|
||||
await Promise.all(checks.map(async ([name, path, kind]) => {
|
||||
const start = Date.now();
|
||||
try {
|
||||
const r = await fetch(root + path, { signal: AbortSignal.timeout(90000) });
|
||||
const bytes = new Uint8Array(await r.arrayBuffer());
|
||||
const result = { name, http: r.status, seconds: Math.round((Date.now() - start) / 1000) };
|
||||
if (kind === 'tile' && r.ok) {
|
||||
result.bytes = bytes.length;
|
||||
result.contentType = r.headers.get('content-type');
|
||||
result.cache = r.headers.get('x-tomtom-cache');
|
||||
result.decodedRoadSegments = decodeFlowTile(bytes, 12, x, y).length;
|
||||
} else {
|
||||
const body = JSON.parse(new TextDecoder().decode(bytes));
|
||||
if (kind === 'fires') Object.assign(result, { count: body.fires?.length, stale: body.stale, sourceCount: body.sources?.length });
|
||||
if (kind === 'ships') Object.assign(result, { count: body.rows?.length, status: body.status, lastMessageAt: body.lastMessageAt });
|
||||
if (kind === 'mapped') Object.assign(result, { count: body.elements?.length, status: body.status });
|
||||
if (kind === 'cameras') {
|
||||
const cameras = Array.isArray(body.cameras) ? body.cameras : Object.values(body.cameras || {});
|
||||
result.observed = cameras.length;
|
||||
result.statusCounts = cameras.reduce((counts, camera) => {
|
||||
const status = camera.status || 'unknown'; counts[status] = (counts[status] || 0) + 1; return counts;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
console.log(JSON.stringify(result));
|
||||
if (!r.ok) process.exitCode = 1;
|
||||
} catch (e) {
|
||||
console.log(JSON.stringify({ name, error: e.name === 'TimeoutError' ? 'timeout' : 'request failed' }));
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}));
|
||||
Loading…
Reference in New Issue