import fs from 'node:fs/promises' import path from 'node:path' import crypto from 'node:crypto' import { fileURLToPath } from 'node:url' export const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') export const runtimeRoot = path.join(projectRoot, 'resources/runtimes') export const manifest = JSON.parse(await fs.readFile(path.join(runtimeRoot, 'manifest.json'), 'utf8')) export const digest = (bytes, algorithm = 'sha256', encoding = 'hex') => crypto.createHash(algorithm).update(bytes).digest(encoding) export async function inventory(root) { const files = {} async function visit(dir) { for (const entry of (await fs.readdir(dir, { withFileTypes: true })).sort((a,b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) { const absolute = path.join(dir, entry.name) if (entry.isSymbolicLink()) throw new Error(`Runtime symlink is not allowed: ${absolute}`) if (entry.isDirectory()) await visit(absolute) else if (entry.isFile()) files[path.relative(root, absolute).split(path.sep).join('/')] = digest(await fs.readFile(absolute)) else throw new Error(`Unsupported runtime entry: ${absolute}`) } } await visit(root) return files } export async function verifyRuntimeBundle(root = path.join(runtimeRoot, 'win32-x64')) { for (const runtime of manifest.runtimes) { const dir = path.join(root, runtime.id) for (const required of runtime.required) { const file = path.join(dir, required) const bytes = await fs.readFile(file).catch(() => { throw new Error(`Missing bundled ${runtime.id}: ${required}. Run npm run runtimes:prepare.`) }) if (!bytes.length) throw new Error(`Empty runtime asset: ${file}`) if (required.endsWith('.exe')) { if (bytes.length < 64 || bytes.subarray(0, 2).toString() !== 'MZ') throw new Error(`Invalid Windows executable: ${file}`) const pe = bytes.readUInt32LE(0x3c) if (pe > bytes.length - 6 || bytes.subarray(pe, pe + 4).toString('hex') !== '50450000' || bytes.readUInt16LE(pe + 4) !== 0x8664) { throw new Error(`Runtime executable is not Windows x64 PE: ${file}`) } } } const files = await inventory(dir) const actual = digest(JSON.stringify(files)) if (!runtime.treeSha256 || actual !== runtime.treeSha256) throw new Error(`Bundled ${runtime.id} tree checksum mismatch: ${actual}; expected ${runtime.treeSha256}. Run npm run runtimes:prepare.`) console.log(`${runtime.id} ${runtime.version}: verified ${Object.keys(files).length} files (${actual})`) } }