114 lines
4.4 KiB
JavaScript
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
|
|
})
|