38 lines
1.8 KiB
JavaScript
38 lines
1.8 KiB
JavaScript
// Fast, dependency-free frontend gate. The application deliberately shares one
|
|
// global scope, so duplicate top-level declarations are a runtime collision.
|
|
// Nested callbacks are ignored; vendor/PDF bundles are outside the manifest.
|
|
import { execFileSync } from 'node:child_process';
|
|
import { readFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const root = join(fileURLToPath(new URL('.', import.meta.url)), '..');
|
|
const manifest = JSON.parse(readFileSync(join(root, 'static', 'module-manifest.json'), 'utf8'));
|
|
const modules = [...new Set([...(manifest.core || []), ...Object.values(manifest.lazy || {}).flat()])];
|
|
const seenModules = new Set();
|
|
const declarations = new Map();
|
|
const declarationPattern = /^(?:async\s+)?(?:function|class|const|let|var)\s+([A-Za-z_$][\w$]*)/gm;
|
|
|
|
for (const name of modules) {
|
|
if (seenModules.has(name)) throw new Error(`duplicate module in manifest: ${name}`);
|
|
seenModules.add(name);
|
|
const file = join(root, 'static', 'js', name + '.js');
|
|
try {
|
|
execFileSync(process.execPath, ['--check', file], { stdio: 'pipe' });
|
|
} catch (error) {
|
|
throw new Error(`JavaScript syntax check failed for ${file}: ${error.stderr?.toString().trim() || error.message}`);
|
|
}
|
|
const source = readFileSync(file, 'utf8');
|
|
for (const match of source.matchAll(declarationPattern)) {
|
|
const files = declarations.get(match[1]) || [];
|
|
files.push(file);
|
|
declarations.set(match[1], files);
|
|
}
|
|
}
|
|
|
|
const collisions = [...declarations.entries()].filter(([, files]) => files.length > 1);
|
|
if (collisions.length) {
|
|
throw new Error('duplicate top-level declarations:\n' + collisions.map(([name, files]) => ` ${name}: ${files.join(', ')}`).join('\n'));
|
|
}
|
|
console.log(`Frontend gate passed: ${modules.length} modules, no syntax errors or top-level declaration collisions.`);
|