Files
ycode/scripts/verify-bundled-omp.cjs
T

114 lines
4.4 KiB
JavaScript

/**
* Verifies the staged (or packaged) OMP kernel assets: lock schema, sizes,
* SHA-256 digests, and that the binary actually launches with the pinned version.
*
* Usage:
* node scripts/verify-bundled-omp.cjs # resources/omp
* node scripts/verify-bundled-omp.cjs --packaged # release/win-unpacked/resources/omp
* node scripts/verify-bundled-omp.cjs <directory>
*/
const { createHash } = require('node:crypto')
const { createReadStream, existsSync, promises: fs } = require('node:fs')
const path = require('node:path')
const { spawn } = require('node:child_process')
const project = path.resolve(__dirname, '..')
const hashPattern = /^[a-f0-9]{64}$/
const lockName = 'runtime.lock.json'
const maxAssetBytes = 512 * 1024 * 1024
function fail(message) {
throw new Error(message)
}
function asset(value, label) {
if (!value || typeof value !== 'object' || typeof value.name !== 'string' || !/^[A-Za-z0-9._-]+$/.test(value.name)
|| !Number.isSafeInteger(value.bytes) || value.bytes <= 0 || value.bytes > maxAssetBytes
|| typeof value.sha256 !== 'string' || !hashPattern.test(value.sha256)) {
fail(`Invalid ${label} entry in ${lockName}`)
}
return value
}
async function sha256File(file) {
const hash = createHash('sha256')
for await (const chunk of createReadStream(file)) hash.update(chunk)
return hash.digest('hex')
}
async function verifyAsset(directory, spec) {
const file = path.join(directory, spec.name)
const stat = await fs.stat(file).catch(() => null)
if (!stat?.isFile()) fail(`Missing asset: ${file}`)
if (stat.size !== spec.bytes) fail(`Size mismatch for ${spec.name}: ${stat.size} != ${spec.bytes}`)
const digest = await sha256File(file)
if (digest !== spec.sha256) fail(`SHA-256 mismatch for ${spec.name}`)
console.log(`PASS ${spec.name} ${spec.bytes} bytes ${digest.slice(0, 12)}…`)
}
function readVersion(exe, timeoutMs = 30_000) {
return new Promise((resolve, reject) => {
const child = spawn(exe, ['--version'], { windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] })
let stdout = ''
let stderr = ''
const timer = setTimeout(() => {
child.kill()
reject(new Error(`Timed out launching ${exe}`))
}, timeoutMs)
child.stdout.on('data', (chunk) => { stdout += chunk })
child.stderr.on('data', (chunk) => { stderr += chunk })
child.on('error', (error) => {
clearTimeout(timer)
reject(error)
})
child.on('exit', (code) => {
clearTimeout(timer)
if (code !== 0) reject(new Error(`OMP exited with ${code}: ${stderr.trim()}`))
else resolve(stdout.trim())
})
})
}
async function main() {
const args = process.argv.slice(2)
const packaged = args.includes('--packaged')
const positional = args.find((arg) => !arg.startsWith('--'))
const directory = positional
? path.resolve(positional)
: packaged
? path.join(project, 'release', 'win-unpacked', 'resources', 'omp')
: path.join(project, 'resources', 'omp')
if (!existsSync(directory)) fail(`OMP directory does not exist: ${directory}`)
const lock = JSON.parse((await fs.readFile(path.join(directory, lockName), 'utf8')).replace(/^\uFEFF/, ''))
if (lock.format !== 1 || lock.kernel !== 'omp' || lock.upstream !== 'oh-my-pi' || lock.platform !== 'win32-x64'
|| typeof lock.version !== 'string' || !/^\d+\.\d+\.\d+$/.test(lock.version)
|| lock.releaseUrl !== `https://github.com/can1357/oh-my-pi/releases/tag/v${lock.version}`
|| !Number.isFinite(Date.parse(lock.publishedAt))) {
fail(`Invalid ${lockName}`)
}
const binary = asset(lock.binary, 'binary')
const license = asset(lock.license, 'license')
const notices = asset(lock.thirdPartyNotices, 'thirdPartyNotices')
if (binary.name !== 'omp.exe' || license.name !== 'LICENSE' || notices.name !== 'THIRD-PARTY-NOTICES.txt') {
fail(`Unexpected asset names in ${lockName}`)
}
console.log(`Checking OMP ${lock.version} in ${directory}`)
await verifyAsset(directory, binary)
await verifyAsset(directory, license)
await verifyAsset(directory, notices)
const output = await readVersion(path.join(directory, binary.name))
if (output !== `omp/${lock.version}`) fail(`Unexpected OMP version output: ${output}`)
console.log(`PASS omp.exe --version => ${output}`)
console.log(`OMP ${lock.version} bundle is valid.`)
}
main().catch((error) => {
console.error(`FAIL ${error.message}`)
process.exitCode = 1
})