Files
ycode/scripts/runtime-bundle.mjs
T

44 lines
2.5 KiB
JavaScript

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})`)
}
}