e32d59ca44
- scripts/make-verify-json.js: 산출물 폴더 → extras/assets JSON 생성 - scripts/verify.js: JSON 입력으로 무결성 검증(종료코드 반영) - package.json: verify 스크립트 추가 - .gitea/workflows/ci.yml: ./artifacts/export 존재 시 검증 스텝 실행
79 lines
3.2 KiB
JavaScript
79 lines
3.2 KiB
JavaScript
#!/usr/bin/env node
|
|
/*
|
|
Verify bundle integrity using extras/assets JSON maps.
|
|
Usage: node scripts/verify.js --extras <extras.json> --assets <assets.json>
|
|
*/
|
|
const fs = require('fs/promises');
|
|
const crypto = require('crypto');
|
|
|
|
function parseArgs(argv) {
|
|
const out = { extras: '', assets: '' };
|
|
for (let i = 2; i < argv.length; i++) {
|
|
const a = argv[i];
|
|
if (a === '--extras') out.extras = argv[++i];
|
|
else if (a === '--assets') out.assets = argv[++i];
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function fromB64(b64) { return Buffer.from(b64, 'base64'); }
|
|
function sha256b64(buf) { return crypto.createHash('sha256').update(buf).digest('base64'); }
|
|
|
|
(async () => {
|
|
try {
|
|
const { extras, assets } = parseArgs(process.argv);
|
|
if (!extras || !assets) {
|
|
console.log('Usage: node scripts/verify.js --extras <extras.json> --assets <assets.json>');
|
|
process.exit(2);
|
|
}
|
|
const extrasJson = await fs.readFile(extras, 'utf-8');
|
|
const assetsJson = await fs.readFile(assets, 'utf-8');
|
|
const extrasMap = JSON.parse(extrasJson);
|
|
const assetsMap = JSON.parse(assetsJson);
|
|
|
|
const result = { ok: true, errors: [], summary: { total: 0, missing: 0, integrityMismatches: 0, sizeMismatches: 0, bundleHashMismatch: false } };
|
|
|
|
if (!Object.prototype.hasOwnProperty.call(extrasMap, 'assets.integrity.index.json')) {
|
|
result.ok = false; result.errors.push('Missing assets.integrity.index.json in extras');
|
|
console.log(JSON.stringify(result, null, 2)); process.exit(1);
|
|
}
|
|
|
|
let parsed;
|
|
try {
|
|
parsed = JSON.parse(fromB64(extrasMap['assets.integrity.index.json']).toString('utf-8'));
|
|
} catch {
|
|
result.ok = false; result.errors.push('Invalid assets.integrity.index.json JSON');
|
|
console.log(JSON.stringify(result, null, 2)); process.exit(1);
|
|
}
|
|
|
|
const recomputedLines = [];
|
|
let missing = 0, integrityMismatches = 0, sizeMismatches = 0;
|
|
for (const e of parsed.entries) {
|
|
const b64 = assetsMap[e.path];
|
|
if (!b64) { result.errors.push(`Missing asset for path in integrity index: ${e.path}`); missing++; continue; }
|
|
const buf = fromB64(b64);
|
|
if (typeof e.size === 'number' && e.size !== buf.length) {
|
|
result.errors.push(`Size mismatch for ${e.path}: expected ${e.size}, got ${buf.length}`); sizeMismatches++; }
|
|
const integ = 'sha256-' + sha256b64(buf);
|
|
if (integ !== e.integrity) { result.errors.push(`Integrity mismatch for ${e.path}`); integrityMismatches++; }
|
|
recomputedLines.push(e.path); recomputedLines.push(integ);
|
|
}
|
|
const bundle = 'sha256-' + sha256b64(Buffer.from(recomputedLines.join('\n'), 'utf-8'));
|
|
const bundleHashMismatch = bundle !== parsed.bundleHash;
|
|
if (bundleHashMismatch) result.errors.push('BundleHash mismatch');
|
|
|
|
result.summary.total = parsed.entries.length;
|
|
result.summary.missing = missing;
|
|
result.summary.integrityMismatches = integrityMismatches;
|
|
result.summary.sizeMismatches = sizeMismatches;
|
|
result.summary.bundleHashMismatch = bundleHashMismatch;
|
|
result.ok = result.errors.length === 0;
|
|
|
|
console.log(JSON.stringify(result, null, 2));
|
|
process.exit(result.ok ? 0 : 1);
|
|
} catch (e) {
|
|
console.error(e && e.stack || String(e));
|
|
process.exit(1);
|
|
}
|
|
})();
|