Files
lingniu-vehicle-ingest/vehicle-data-platform/apps/web/scripts/verify-build.mjs
2026-07-16 06:04:30 +08:00

65 lines
4.1 KiB
JavaScript

import { readFileSync, readdirSync, statSync } from 'node:fs';
import { resolve } from 'node:path';
const assetsDirectory = resolve(process.cwd(), 'dist/assets');
const assets = readdirSync(assetsDirectory);
const excelAssets = assets.filter((name) => /^exceljs(?:\.min)?-[\w-]+\.js$/.test(name));
const mileageWorkers = assets.filter((name) => /^mileageExport\.worker-[\w-]+\.js$/.test(name));
const monitorAssets = assets.filter((name) => /^MonitorPage-[\w-]+\.js$/.test(name));
const runtimeConfig = readFileSync(resolve(process.cwd(), 'dist/app-config.js'), 'utf8');
const safeRuntimeTemplate = readFileSync(resolve(process.cwd(), 'public/app-config.example.js'), 'utf8');
const builtIndex = readFileSync(resolve(process.cwd(), 'dist/index.html'), 'utf8');
const releaseManifest = readFileSync(resolve(process.cwd(), 'dist/.release-assets'), 'utf8').split(/\r?\n/).filter(Boolean);
if (excelAssets.length !== 1) {
throw new Error(`production build must contain exactly one ExcelJS asset, found ${excelAssets.length}: ${excelAssets.join(', ') || 'none'}`);
}
if (mileageWorkers.length !== 1) {
throw new Error(`production build must contain exactly one mileage export worker, found ${mileageWorkers.length}: ${mileageWorkers.join(', ') || 'none'}`);
}
if (monitorAssets.length !== 1) {
throw new Error(`production build must contain exactly one monitor page asset, found ${monitorAssets.length}: ${monitorAssets.join(', ') || 'none'}`);
}
if (runtimeConfig !== safeRuntimeTemplate) {
throw new Error('production dist/app-config.js must match the credential-free runtime template');
}
if (!builtIndex.includes('id="platform-boot"')
|| !builtIndex.includes('vehicle-platform:ready')
|| !builtIndex.includes('window.setTimeout(fail, 10_000)')) {
throw new Error('production index must retain the static boot recovery boundary');
}
const mainAsset = builtIndex.match(/<script[^>]+type="module"[^>]+src="\/assets\/([^"/]+\.js)"/)?.[1];
if (!mainAsset || !assets.includes(mainAsset)
|| !readFileSync(resolve(assetsDirectory, mainAsset), 'utf8').includes('vehicle-platform:ready')) {
throw new Error('production entry must retain the React boot-ready handshake');
}
if (releaseManifest.length !== assets.length || releaseManifest.some((name, index) => name !== [...assets].sort()[index])) {
throw new Error('production dist/.release-assets must list every generated asset exactly once in sorted order');
}
if (releaseManifest.some((name) => name.includes('/') || name === '.' || name === '..')) {
throw new Error('production dist/.release-assets contains an unsafe asset name');
}
const configuredSecurityCode = runtimeConfig.match(/["']?amapSecurityJsCode["']?\s*:\s*(["'])(.*?)\1/s)?.[2].trim();
if (configuredSecurityCode) {
throw new Error('production dist/app-config.js must not contain an AMap security code');
}
const excelBytes = statSync(resolve(assetsDirectory, excelAssets[0])).size;
const workerBytes = statSync(resolve(assetsDirectory, mileageWorkers[0])).size;
const monitorSource = readFileSync(resolve(assetsDirectory, monitorAssets[0]), 'utf8');
const dynamicMonitorAssets = [...monitorSource.matchAll(/import\("\.\/([^"/]+\.js)"\)/g)].map((match) => match[1]);
const qrAssets = dynamicMonitorAssets.filter((name) => {
if (!assets.includes(name)) return false;
const source = readFileSync(resolve(assetsDirectory, name), 'utf8');
return source.includes('No input text') && source.includes('toDataURL');
});
if (qrAssets.length !== 1) {
throw new Error(`monitor QR generator must be one deferred dynamic asset, found ${qrAssets.length}: ${qrAssets.join(', ') || 'none'}`);
}
const monitorBytes = statSync(resolve(assetsDirectory, monitorAssets[0])).size;
if (monitorBytes > 35_000) {
throw new Error(`monitor critical-path asset exceeds 35000 bytes: ${monitorBytes}`);
}
const qrBytes = statSync(resolve(assetsDirectory, qrAssets[0])).size;
process.stdout.write(`web_build_gate=ok release_assets=${releaseManifest.length} exceljs_assets=1 exceljs_bytes=${excelBytes} mileage_workers=1 worker_bytes=${workerBytes} monitor_bytes=${monitorBytes} qr_deferred=1 qr_bytes=${qrBytes} runtime_config_sanitized=1 boot_recovery=1\n`);