// 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.`);