verify-runtime-manifest.mjs
57 lines 2.4 KB
Raw
sha256:bd1eb24434d281fe65142038c1bf217493421f5a86880c22dff7e6037d2d4700 feat(companion): add production macOS distribution path Human minor ⚠ breaking 25 days ago
1 #!/usr/bin/env node
2 import crypto from 'node:crypto';
3 import fs from 'node:fs';
4 import path from 'node:path';
5
6 function option(name) {
7 const index = process.argv.indexOf(`--${name}`);
8 if (index < 0 || !process.argv[index + 1]) throw new Error(`missing --${name}`);
9 return path.resolve(process.argv[index + 1]);
10 }
11
12 function sha256(file) {
13 return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
14 }
15
16 function walk(root, relative = '') {
17 const entries = [];
18 for (const name of fs.readdirSync(path.join(root, relative)).sort()) {
19 const rel = relative ? `${relative}/${name}` : name;
20 if (rel === 'runtime-manifest.json') continue;
21 const absolute = path.join(root, rel);
22 const stat = fs.lstatSync(absolute);
23 if (stat.isSymbolicLink()) throw new Error(`symbolic link in runtime closure: ${rel}`);
24 if (stat.isDirectory()) entries.push(...walk(root, rel));
25 else if (stat.isFile()) entries.push({
26 path: rel,
27 sha256: sha256(absolute),
28 bytes: stat.size,
29 executable: (stat.mode & 0o111) !== 0,
30 });
31 else throw new Error(`unsupported runtime entry: ${rel}`);
32 }
33 return entries;
34 }
35
36 const app = option('app');
37 const resources = path.join(app, 'Contents/Resources');
38 const manifest = JSON.parse(fs.readFileSync(path.join(resources, 'runtime-manifest.json'), 'utf8'));
39 if (manifest.schema !== 1 || manifest.releaseVersion !== '0.1.0' || manifest.releaseBuild !== 100 ||
40 manifest.architecture !== 'arm64') {
41 throw new Error('runtime manifest release identity mismatch');
42 }
43 if (process.argv.includes('--require-committed-source') && manifest.sourceRevision?.status !== 'committed') {
44 throw new Error('runtime manifest is not bound to committed Muse and Git revisions');
45 }
46 const lockFile = path.join(resources, 'release-lock.json');
47 const lock = JSON.parse(fs.readFileSync(lockFile, 'utf8'));
48 if (manifest.releaseLockSHA256 !== sha256(lockFile) || manifest.w3BundleDigest !== lock.model.w3BundleDigest) {
49 throw new Error('runtime manifest release lock mismatch');
50 }
51 const actual = walk(resources);
52 const fields = (entry) => [entry.path, entry.sha256, entry.bytes, entry.executable];
53 if (!Array.isArray(manifest.files) ||
54 JSON.stringify(actual.map(fields)) !== JSON.stringify(manifest.files.map(fields))) {
55 throw new Error('runtime manifest file closure mismatch');
56 }
57 process.stdout.write(`runtime manifest verified (${actual.length} files)\n`);
File History 1 commit
sha256:bd1eb24434d281fe65142038c1bf217493421f5a86880c22dff7e6037d2d4700 feat(companion): add production macOS distribution path Human minor ⚠ 25 days ago