@pumpkin-plugins/build 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,154 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
4
+ import { release as osRelease } from 'node:os';
5
+ import { join, resolve } from 'node:path';
6
+ import { Argument, Command } from 'commander';
7
+
8
+ const runtimeDir = import.meta.dirname;
9
+ const expected = JSON.parse(readFileSync(join(runtimeDir, 'provenance.json'), 'utf8'));
10
+ const patchPath = join(runtimeDir, 'componentize-qjs-bigint.patch');
11
+ const artifactName = 'quickjs-runtime-bigint.wasm';
12
+ const hash = (path) => createHash('sha256').update(readFileSync(path)).digest('hex');
13
+ const run = (command, args) =>
14
+ execFileSync(command, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
15
+ const requireMatch = (actual, wanted, message) => {
16
+ if (actual !== wanted) throw new Error(message);
17
+ };
18
+
19
+ function checkPatch() {
20
+ requireMatch(hash(patchPath), expected.patchSha256, 'Patch hash mismatch');
21
+ }
22
+
23
+ function checkInputs(sourceDir, sdkDir) {
24
+ checkPatch();
25
+ const git = (...args) => run('git', ['-C', sourceDir, ...args]);
26
+ requireMatch(
27
+ git('rev-parse', 'HEAD'),
28
+ expected.sourceCommit,
29
+ `Expected componentize-qjs at ${expected.sourceCommit}`,
30
+ );
31
+ requireMatch(hash(join(sourceDir, 'Cargo.lock')), expected.cargoLockSha256, 'Cargo.lock hash mismatch');
32
+ // Include ignored files: .cargo/config and local source overrides are also build inputs.
33
+ if (
34
+ git('ls-files', '--others', '--exclude-standard') ||
35
+ git('ls-files', '--others', '--ignored', '--exclude-standard')
36
+ ) {
37
+ throw new Error('Untracked source inputs');
38
+ }
39
+ const diff = git(
40
+ 'diff',
41
+ '--no-ext-diff',
42
+ '--no-textconv',
43
+ '--binary',
44
+ '--src-prefix=a/',
45
+ '--dst-prefix=b/',
46
+ 'HEAD',
47
+ );
48
+ // Git writes a space on empty context lines; the checked-in patch omits it.
49
+ const normalize = (text) => text.replace(/^ $/gm, '').trim();
50
+ if (diff && normalize(diff) !== normalize(readFileSync(patchPath, 'utf8'))) {
51
+ throw new Error('Source changes differ from the declared patch');
52
+ }
53
+ requireMatch(run('rustc', ['--version']), expected.rustVersion, 'Rust version mismatch');
54
+ requireMatch(run('cargo', ['--version']), expected.cargoVersion, 'Cargo version mismatch');
55
+ requireMatch(
56
+ readFileSync(join(sdkDir, 'VERSION'), 'utf8').trim(),
57
+ expected.sdkVersion,
58
+ 'wasi-sdk version mismatch',
59
+ );
60
+ requireMatch(
61
+ run(join(sdkDir, 'bin/clang'), ['--version']).split('\n')[0],
62
+ expected.clangVersion,
63
+ 'Clang version mismatch',
64
+ );
65
+ const targetLib = run('rustc', ['--print', 'target-libdir', '--target', expected.target]);
66
+ if (!existsSync(targetLib)) throw new Error(`Rust target ${expected.target} is not installed`);
67
+ }
68
+
69
+ function checkArtifact(outputDir, wanted = expected.outputSha256) {
70
+ const actual = hash(join(outputDir, artifactName));
71
+ requireMatch(actual, wanted, `Runtime output hash mismatch: expected ${wanted}, got ${actual}`);
72
+ }
73
+
74
+ function verifyShipped(outputDir) {
75
+ const manifest = JSON.parse(readFileSync(join(outputDir, 'provenance.json'), 'utf8'));
76
+ if ('buildHost' in manifest || 'buildHostRelease' in manifest) {
77
+ throw new Error('Candidate provenance cannot verify as shipped runtime');
78
+ }
79
+ for (const [key, value] of Object.entries(expected)) {
80
+ requireMatch(manifest[key], value, `Provenance mismatch: ${key}`);
81
+ }
82
+ checkArtifact(outputDir);
83
+ }
84
+
85
+ function verifyCandidate(outputDir) {
86
+ const manifest = JSON.parse(readFileSync(join(outputDir, 'provenance.json'), 'utf8'));
87
+ for (const [key, value] of Object.entries(expected)) {
88
+ if (key !== 'outputSha256') requireMatch(manifest[key], value, `Provenance mismatch: ${key}`);
89
+ }
90
+ if (typeof manifest.outputSha256 !== 'string' || !/^[a-f0-9]{64}$/.test(manifest.outputSha256)) {
91
+ throw new Error('Invalid runtime candidate output SHA-256');
92
+ }
93
+ if (typeof manifest.buildHost !== 'string' || !manifest.buildHost.trim()) {
94
+ throw new Error('Missing runtime candidate build host');
95
+ }
96
+ if (typeof manifest.buildHostRelease !== 'string' || !manifest.buildHostRelease.trim()) {
97
+ throw new Error('Missing runtime candidate host release');
98
+ }
99
+ checkArtifact(outputDir, manifest.outputSha256);
100
+ }
101
+
102
+ try {
103
+ const program = new Command().name('provenance.mjs').description('Inspect and record QuickJS runtime provenance.');
104
+ program
105
+ .command('value')
106
+ .description('Print one pinned build value.')
107
+ .addArgument(new Argument('<name>', 'Pinned value name').choices(['target', 'features', 'rustFlags', 'cFlags']))
108
+ .action((name) => process.stdout.write(`${expected[name]}\n`));
109
+ program
110
+ .command('inputs')
111
+ .description('Verify the pinned source tree and WASI SDK inputs.')
112
+ .argument('<source>', 'QuickJS source directory')
113
+ .argument('<sdk>', 'WASI SDK directory')
114
+ .action((source, sdk) => checkInputs(resolve(source), resolve(sdk)));
115
+ program
116
+ .command('record')
117
+ .description('Verify inputs and record the generated runtime candidate hash.')
118
+ .argument('<source>', 'QuickJS source directory')
119
+ .argument('<sdk>', 'WASI SDK directory')
120
+ .argument('<output>', 'Runtime output directory')
121
+ .action((source, sdk, output) => {
122
+ checkInputs(resolve(source), resolve(sdk));
123
+ const outputSha256 = hash(join(resolve(output), artifactName));
124
+ const buildHost = `${process.platform}-${process.arch}`;
125
+ const buildHostRelease = osRelease();
126
+ writeFileSync(
127
+ join(output, 'provenance.json'),
128
+ `${JSON.stringify({ ...expected, outputSha256, buildHost, buildHostRelease }, null, 4)}\n`,
129
+ );
130
+ process.stdout.write(
131
+ `Runtime candidate output SHA-256: ${outputSha256} (${buildHost}, ${buildHostRelease})\n`,
132
+ );
133
+ });
134
+ program
135
+ .command('verify')
136
+ .description('Verify a shipped runtime directory against the pinned provenance.')
137
+ .argument('<output>', 'Shipped runtime directory')
138
+ .action((output) => {
139
+ checkPatch();
140
+ verifyShipped(resolve(output));
141
+ });
142
+ program
143
+ .command('verify-candidate')
144
+ .description('Verify a build candidate directory and its recorded host hash.')
145
+ .argument('<output>', 'Runtime candidate directory')
146
+ .action((output) => {
147
+ checkPatch();
148
+ verifyCandidate(resolve(output));
149
+ });
150
+ await program.parseAsync(process.argv);
151
+ } catch (error) {
152
+ process.stderr.write(`${error instanceof Error ? error.message : error}\n`);
153
+ process.exitCode = 1;
154
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "version": "0.2.3",
3
+ "files": {
4
+ "clocks/monotonic-clock.wit": "ffb4d4e52b0b3fbce90efa365fcbd1b63f724696dac9f269ed70d798b64b0f47",
5
+ "clocks/timezone.wit": "661469d5c0d1bd943c8e07e37927ba569c9d2813ff2de9823f089e3802af0848",
6
+ "clocks/wall-clock.wit": "1e8e6bf52e76aaebd471112bb8eaa70ef1e11c183cd28b9eb012de5382370cc9",
7
+ "clocks/world.wit": "4ac7cd7eae10eaaf98b93d760350eece7cd4e30cc7d23d1a029b0fc4cd417925",
8
+ "filesystem/preopens.wit": "0e838ee8abba3bdc0a7be20549312514ca8a28770fd3997d1f1f1407acf6f1fc",
9
+ "filesystem/types.wit": "26e069bade72e3c98da948fe9668d9fc43e9f8e465a3e0cde61f2c6dfaf01110",
10
+ "filesystem/world.wit": "04f42e76fb1322db234f394f427b3ee2bd777f0378ee4a642f888aa424387b50",
11
+ "io/error.wit": "910ed309534a26ac004ae882fbb3be82ccb05d16ae646ae351fde66bbf14e4bd",
12
+ "io/poll.wit": "e8069d860b0e837a81f820ab13c700a90bd71da9e1f774bbfaeca31daf920371",
13
+ "io/streams.wit": "b8cc539eb831695e3340243d7f0dd643c47d9adbbd355866bc604a25a5765dff",
14
+ "io/world.wit": "acc9fc0d7f96658d7bdf88e0a8c01d51db3ee35d5cff07f9f4b2f4a5c47d83b1",
15
+ "sockets/instance-network.wit": "1e4a5d97df44421503a169e1d514eca2cf160f545c44d320ce8a82f726a40cf8",
16
+ "sockets/ip-name-lookup.wit": "2b9db4b6c6eae692c8285ae57adb318750061a02a850e37410bef87d5909e69c",
17
+ "sockets/network.wit": "bdcf7ba3a0d04c0a071f06cba8a4072eca884d30f6d89c2e55e6047f657694cf",
18
+ "sockets/tcp-create-socket.wit": "96a6a1a93b859127ee60273f0a77cda8d60930f7952174fb22469293b5da6a3a",
19
+ "sockets/tcp.wit": "2ec54e09277bd8f917154696c812a943f0f2ea365feb6e32d88c65871362f1dd",
20
+ "sockets/udp-create-socket.wit": "c52b9bf91ef4e30e7ec08ad1fc9e77ef005a0a87648c5d2e416dd6e35369f08c",
21
+ "sockets/udp.wit": "63ea1696a3c9dd500ba8d0f518c526ff0e04e5cbeca4f56950756b0b217487c2",
22
+ "sockets/world.wit": "6e2bd764df0037ebcec03c63f92bd47f4e69679cf608f6b3d2073cf10fb3672f",
23
+ "http/handler.wit": "1f66174b4058b102367e69ce8aa657b3e9dbc767f6ade960108faaa1731f0945",
24
+ "http/package.wit": "03af060b69ccf1d6ac5cbe1ff626b3ed734214cf2829a3d6d23d5c6223633119",
25
+ "http/types.wit": "a5501f024e9ced633153b5247d67d5d50a621c3cc09abc348d997040fd7355b3"
26
+ },
27
+ "sources": {
28
+ "http/handler.wit": "https://raw.githubusercontent.com/WebAssembly/wasi-http/v0.2.3/wit/handler.wit",
29
+ "http/types.wit": "https://raw.githubusercontent.com/WebAssembly/wasi-http/v0.2.3/wit/types.wit"
30
+ },
31
+ "generatedFiles": {
32
+ "http/package.wit": "package wasi:http@0.2.3;\n"
33
+ }
34
+ }