|
| 1 | +import fs from 'node:fs'; |
| 2 | +import path from 'node:path'; |
| 3 | +import { parseSync } from 'oxc-parser'; |
| 4 | +import { walkFiles } from './lib/walk-files.ts'; |
| 5 | + |
| 6 | +const repoRoot = path.resolve(import.meta.dirname, '..'); |
| 7 | +const distRoot = path.join(repoRoot, 'dist', 'src'); |
| 8 | + |
| 9 | +function moduleSpecifiers(file: string, source: string): string[] { |
| 10 | + const record = parseSync(file, source).module; |
| 11 | + return [ |
| 12 | + ...record.staticImports.map((entry) => entry.moduleRequest.value), |
| 13 | + ...record.staticExports.flatMap((entry) => |
| 14 | + entry.entries.flatMap((exported) => moduleRequestValue(exported.moduleRequest)), |
| 15 | + ), |
| 16 | + ...record.dynamicImports.flatMap((entry) => |
| 17 | + dynamicModuleRequestValue(source, entry.moduleRequest), |
| 18 | + ), |
| 19 | + ]; |
| 20 | +} |
| 21 | + |
| 22 | +function moduleRequestValue(request: { value?: string } | undefined): string[] { |
| 23 | + return request?.value ? [request.value] : []; |
| 24 | +} |
| 25 | + |
| 26 | +function dynamicModuleRequestValue( |
| 27 | + source: string, |
| 28 | + request: { start: number; end: number }, |
| 29 | +): string[] { |
| 30 | + const raw = source.slice(request.start, request.end); |
| 31 | + const literal = /^(['"])([^'"]*)\1$/.exec(raw); |
| 32 | + return literal?.[2] ? [literal[2]] : []; |
| 33 | +} |
| 34 | + |
| 35 | +const bundleFiles = walkFiles(distRoot).filter( |
| 36 | + (file) => file.endsWith('.js') || file.endsWith('.d.ts'), |
| 37 | +); |
| 38 | +if (bundleFiles.length === 0) { |
| 39 | + throw new Error('No dist/src JavaScript files found. Run `pnpm build` first.'); |
| 40 | +} |
| 41 | + |
| 42 | +const leaks = bundleFiles.flatMap((file) => { |
| 43 | + const source = fs.readFileSync(file, 'utf8'); |
| 44 | + return moduleSpecifiers(file, source) |
| 45 | + .filter((specifier) => specifier.startsWith('@agent-device/')) |
| 46 | + .map((specifier) => ({ file: path.relative(repoRoot, file), specifier })); |
| 47 | +}); |
| 48 | + |
| 49 | +if (leaks.length > 0) { |
| 50 | + const details = leaks.map(({ file, specifier }) => `- ${specifier} in ${file}`).join('\n'); |
| 51 | + throw new Error( |
| 52 | + `Private workspace dependencies escaped the production bundle:\n${details}\n` + |
| 53 | + 'Published installs cannot resolve private @agent-device packages.', |
| 54 | + ); |
| 55 | +} |
| 56 | + |
| 57 | +process.stdout.write( |
| 58 | + `Verified ${bundleFiles.length} production module files contain no private workspace imports.\n`, |
| 59 | +); |
0 commit comments