|
| 1 | +import { readdir, stat } from 'node:fs/promises'; |
| 2 | +import { join, resolve, sep } from 'node:path'; |
| 3 | +import { fileURLToPath } from 'node:url'; |
| 4 | +import { createRequire } from 'node:module'; |
| 5 | + |
| 6 | +const require = createRequire(import.meta.url); |
| 7 | +const { listPackage } = require('@electron/asar'); |
| 8 | + |
| 9 | +const repoRoot = resolve(fileURLToPath(new URL('..', import.meta.url))); |
| 10 | +const inputRoots = process.argv.slice(2).map((input) => resolve(repoRoot, input)); |
| 11 | +const scanRoots = inputRoots.length ? inputRoots : [resolve(repoRoot, 'release')]; |
| 12 | + |
| 13 | +const requiredAsarEntries = [ |
| 14 | + 'node_modules/ai/dist/index.mjs', |
| 15 | + 'node_modules/@opentelemetry/api/package.json', |
| 16 | + 'node_modules/@opentelemetry/api/build/src/index.js' |
| 17 | +]; |
| 18 | + |
| 19 | +async function collectAsars(directory, found = []) { |
| 20 | + let entries; |
| 21 | + try { |
| 22 | + entries = await readdir(directory, { withFileTypes: true }); |
| 23 | + } catch { |
| 24 | + return found; |
| 25 | + } |
| 26 | + |
| 27 | + for (const entry of entries) { |
| 28 | + const path = join(directory, entry.name); |
| 29 | + if (entry.isDirectory()) { |
| 30 | + await collectAsars(path, found); |
| 31 | + } else if (entry.isFile() && entry.name === 'app.asar') { |
| 32 | + found.push(path); |
| 33 | + } |
| 34 | + } |
| 35 | + return found; |
| 36 | +} |
| 37 | + |
| 38 | +function normalizeEntry(entry) { |
| 39 | + return entry.replace(/^\/+/, '').split(sep).join('/'); |
| 40 | +} |
| 41 | + |
| 42 | +async function verifyAsar(asarPath) { |
| 43 | + const entries = new Set(listPackage(asarPath).map(normalizeEntry)); |
| 44 | + const missing = requiredAsarEntries.filter((entry) => !entries.has(entry)); |
| 45 | + if (missing.length) { |
| 46 | + throw new Error(`${asarPath} is missing runtime dependency entries:\n${missing.map((entry) => `- ${entry}`).join('\n')}`); |
| 47 | + } |
| 48 | +} |
| 49 | + |
| 50 | +const asars = []; |
| 51 | +for (const scanRoot of scanRoots) { |
| 52 | + const scanStats = await stat(scanRoot).catch(() => null); |
| 53 | + if (!scanStats?.isDirectory()) { |
| 54 | + throw new Error(`${scanRoot} does not exist or is not a directory. Build packaged artifacts before verifying runtime dependencies.`); |
| 55 | + } |
| 56 | + await collectAsars(scanRoot, asars); |
| 57 | +} |
| 58 | +if (!asars.length) { |
| 59 | + throw new Error('No app.asar files found under release/. Build packaged artifacts before verifying runtime dependencies.'); |
| 60 | +} |
| 61 | + |
| 62 | +for (const asarPath of asars) { |
| 63 | + await verifyAsar(asarPath); |
| 64 | +} |
| 65 | + |
| 66 | +console.log(`Packaged runtime dependency verification passed for ${asars.length} app.asar file(s).`); |
0 commit comments