feat: complete WorkBuddy parity and Windows release workflow
This commit is contained in:
1 parent
143516b3a8
commit
d5512df549
434 files changed
+54977
-1854
No files matched your search
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* 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
|
||||
})
|
||||
Reference in new issue
Block a user