@kungfu-tech/buildchain 2.1.1-alpha.0 → 2.2.0-alpha.1
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.
- package/README.md +8 -0
- package/bin/buildchain.mjs +146 -0
- package/docs/MAP.md +6 -0
- package/docs/cli.md +38 -0
- package/docs/release-passport.md +99 -0
- package/docs/versioning.md +57 -0
- package/package.json +4 -1
- package/packages/core/index.js +21 -0
- package/packages/core/logging.js +84 -0
- package/packages/core/release-passport.js +587 -0
- package/scripts/build-standalone-binary.mjs +377 -0
- package/scripts/check-inventory.mjs +85 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
8
|
+
import { createBuildchainLogger } from "../packages/core/logging.js";
|
|
9
|
+
|
|
10
|
+
function readArg(name, fallback = "") {
|
|
11
|
+
const index = process.argv.indexOf(`--${name}`);
|
|
12
|
+
if (index === -1) {
|
|
13
|
+
return fallback;
|
|
14
|
+
}
|
|
15
|
+
return process.argv[index + 1] || "";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function timed(logger, eventName, details, callback) {
|
|
19
|
+
const spanId = crypto.randomUUID();
|
|
20
|
+
const startedAt = Date.now();
|
|
21
|
+
logger.info(`${eventName}.start`, { ...details, spanId });
|
|
22
|
+
try {
|
|
23
|
+
const result = callback();
|
|
24
|
+
logger.info(`${eventName}.end`, {
|
|
25
|
+
...details,
|
|
26
|
+
spanId,
|
|
27
|
+
durationMs: Date.now() - startedAt,
|
|
28
|
+
});
|
|
29
|
+
return result;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
logger.error(`${eventName}.error`, {
|
|
32
|
+
...details,
|
|
33
|
+
spanId,
|
|
34
|
+
durationMs: Date.now() - startedAt,
|
|
35
|
+
message: error.message,
|
|
36
|
+
attributes: {
|
|
37
|
+
...(details.attributes || {}),
|
|
38
|
+
errorName: error.name,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const WINDOWS_CMD_SHIMS = new Set(["corepack", "npm", "npx", "pnpm", "yarn"]);
|
|
46
|
+
|
|
47
|
+
export function resolveSpawnCommand(command, platform = process.platform) {
|
|
48
|
+
if (platform !== "win32") {
|
|
49
|
+
return command;
|
|
50
|
+
}
|
|
51
|
+
if (!WINDOWS_CMD_SHIMS.has(command)) {
|
|
52
|
+
return command;
|
|
53
|
+
}
|
|
54
|
+
return `${command}.cmd`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function run(command, args, options = {}) {
|
|
58
|
+
const { logger, event = "process.run", phase = "process", attributes = {}, ...spawnOptions } = options;
|
|
59
|
+
const resolvedCommand = resolveSpawnCommand(command);
|
|
60
|
+
const runCommand = () => {
|
|
61
|
+
const result = spawnSync(resolvedCommand, args, {
|
|
62
|
+
stdio: "inherit",
|
|
63
|
+
shell: false,
|
|
64
|
+
...spawnOptions,
|
|
65
|
+
});
|
|
66
|
+
if (result.error) {
|
|
67
|
+
throw result.error;
|
|
68
|
+
}
|
|
69
|
+
if (result.status !== 0) {
|
|
70
|
+
throw new Error(`${command} ${args.join(" ")} exited with ${result.status}`);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
if (logger) {
|
|
74
|
+
return timed(logger, event, {
|
|
75
|
+
phase,
|
|
76
|
+
attributes: {
|
|
77
|
+
command,
|
|
78
|
+
resolvedCommand,
|
|
79
|
+
args: args.join(" "),
|
|
80
|
+
...attributes,
|
|
81
|
+
},
|
|
82
|
+
}, runCommand);
|
|
83
|
+
}
|
|
84
|
+
return runCommand();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function relativePath(cwd, targetPath) {
|
|
88
|
+
return path.relative(cwd, targetPath).split(path.sep).join("/");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function writeLogSummary(logger, cwd, outputDir, archiveBase) {
|
|
92
|
+
if (!logger.path) {
|
|
93
|
+
return "";
|
|
94
|
+
}
|
|
95
|
+
const summaryPath = path.join(outputDir, `${archiveBase}.log-summary.json`);
|
|
96
|
+
const summary = logger.summary();
|
|
97
|
+
fs.writeFileSync(summaryPath, `${JSON.stringify(summary, null, 2)}\n`);
|
|
98
|
+
return relativePath(cwd, summaryPath);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function readLogPath(value) {
|
|
102
|
+
if (value === false || value === "false") {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
if (typeof value === "string" && value) {
|
|
106
|
+
return value;
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function noopLogger() {
|
|
112
|
+
return {
|
|
113
|
+
path: "",
|
|
114
|
+
info: () => undefined,
|
|
115
|
+
error: () => undefined,
|
|
116
|
+
summary: () => ({
|
|
117
|
+
schemaVersion: 1,
|
|
118
|
+
contract: "kungfu-buildchain-log-summary",
|
|
119
|
+
eventCount: 0,
|
|
120
|
+
warningCount: 0,
|
|
121
|
+
errorCount: 0,
|
|
122
|
+
durationMs: 0,
|
|
123
|
+
sources: {},
|
|
124
|
+
phases: {},
|
|
125
|
+
components: {},
|
|
126
|
+
}),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function platformTriple() {
|
|
131
|
+
const platform = process.platform;
|
|
132
|
+
const arch = process.arch;
|
|
133
|
+
if (platform === "darwin") {
|
|
134
|
+
return arch === "arm64" ? "aarch64-apple-darwin" : "x86_64-apple-darwin";
|
|
135
|
+
}
|
|
136
|
+
if (platform === "win32") {
|
|
137
|
+
return "x86_64-pc-windows-msvc";
|
|
138
|
+
}
|
|
139
|
+
if (platform === "linux") {
|
|
140
|
+
return arch === "arm64" ? "aarch64-unknown-linux-gnu" : "x86_64-unknown-linux-gnu";
|
|
141
|
+
}
|
|
142
|
+
return `${arch}-${platform}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function copyNodeBinary(destination) {
|
|
146
|
+
fs.copyFileSync(process.execPath, destination);
|
|
147
|
+
if (process.platform !== "win32") {
|
|
148
|
+
fs.chmodSync(destination, 0o755);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function packageVersion(cwd) {
|
|
153
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf8"));
|
|
154
|
+
return packageJson.version;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function bundleCli({ cwd, tempDir, version, logger }) {
|
|
158
|
+
const outDir = path.join(tempDir, "bundle");
|
|
159
|
+
const configPath = path.join(tempDir, "tsup.config.mjs");
|
|
160
|
+
fs.writeFileSync(configPath, `export default {
|
|
161
|
+
entry: {
|
|
162
|
+
buildchain: ${JSON.stringify(path.join(cwd, "bin", "buildchain.mjs"))},
|
|
163
|
+
},
|
|
164
|
+
format: ["cjs"],
|
|
165
|
+
platform: "node",
|
|
166
|
+
target: "node24",
|
|
167
|
+
outDir: ${JSON.stringify(outDir)},
|
|
168
|
+
clean: true,
|
|
169
|
+
silent: true,
|
|
170
|
+
splitting: false,
|
|
171
|
+
sourcemap: false,
|
|
172
|
+
dts: false,
|
|
173
|
+
shims: true,
|
|
174
|
+
noExternal: ["smol-toml"],
|
|
175
|
+
define: {
|
|
176
|
+
"process.env.BUILDCHAIN_EMBEDDED_PACKAGE_VERSION": ${JSON.stringify(JSON.stringify(version || packageVersion(cwd)))},
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
`);
|
|
180
|
+
run("pnpm", ["exec", "tsup", "--config", configPath], {
|
|
181
|
+
cwd,
|
|
182
|
+
logger,
|
|
183
|
+
event: "standalone.cli-bundle.create",
|
|
184
|
+
phase: "prepare",
|
|
185
|
+
attributes: {
|
|
186
|
+
outDir,
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
return path.join(outDir, "buildchain.cjs");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function postjectArgs(binaryPath, blobPath) {
|
|
193
|
+
const args = [
|
|
194
|
+
"--yes",
|
|
195
|
+
"postject",
|
|
196
|
+
binaryPath,
|
|
197
|
+
"NODE_SEA_BLOB",
|
|
198
|
+
blobPath,
|
|
199
|
+
"--sentinel-fuse",
|
|
200
|
+
"NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2",
|
|
201
|
+
];
|
|
202
|
+
if (process.platform === "darwin") {
|
|
203
|
+
args.push("--macho-segment-name", "NODE_SEA");
|
|
204
|
+
}
|
|
205
|
+
return args;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function buildStandaloneBinary({
|
|
209
|
+
cwd = process.cwd(),
|
|
210
|
+
outputDir = "dist/binary",
|
|
211
|
+
name = "buildchain",
|
|
212
|
+
version = "",
|
|
213
|
+
packageManagerInstall = false,
|
|
214
|
+
logPath = undefined,
|
|
215
|
+
} = {}) {
|
|
216
|
+
const resolvedOutputDir = path.resolve(cwd, outputDir);
|
|
217
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "buildchain-sea-"));
|
|
218
|
+
const triple = platformTriple();
|
|
219
|
+
const archiveBase = `${name}-${triple}`;
|
|
220
|
+
const logger = createBuildchainLogger({
|
|
221
|
+
cwd,
|
|
222
|
+
path: readLogPath(logPath),
|
|
223
|
+
source: "buildchain",
|
|
224
|
+
component: "standalone-binary",
|
|
225
|
+
phase: "binary",
|
|
226
|
+
attributes: {
|
|
227
|
+
name,
|
|
228
|
+
version,
|
|
229
|
+
platform: triple,
|
|
230
|
+
outputDir: relativePath(cwd, resolvedOutputDir),
|
|
231
|
+
},
|
|
232
|
+
}) || noopLogger();
|
|
233
|
+
logger.info("standalone.build.requested", {
|
|
234
|
+
attributes: {
|
|
235
|
+
packageManagerInstall,
|
|
236
|
+
node: process.version,
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
fs.mkdirSync(resolvedOutputDir, { recursive: true });
|
|
240
|
+
if (packageManagerInstall) {
|
|
241
|
+
run("pnpm", ["install", "--frozen-lockfile"], {
|
|
242
|
+
cwd,
|
|
243
|
+
logger,
|
|
244
|
+
event: "standalone.dependencies.install",
|
|
245
|
+
phase: "setup",
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
const blobPath = path.join(tempDir, "sea-prep.blob");
|
|
249
|
+
const configPath = path.join(tempDir, "sea-config.json");
|
|
250
|
+
const bundledCliPath = bundleCli({
|
|
251
|
+
cwd,
|
|
252
|
+
tempDir,
|
|
253
|
+
version,
|
|
254
|
+
logger,
|
|
255
|
+
});
|
|
256
|
+
timed(logger, "standalone.sea-config.write", {
|
|
257
|
+
phase: "prepare",
|
|
258
|
+
attributes: {
|
|
259
|
+
configPath,
|
|
260
|
+
blobPath,
|
|
261
|
+
bundledCliPath,
|
|
262
|
+
},
|
|
263
|
+
}, () => {
|
|
264
|
+
fs.writeFileSync(configPath, `${JSON.stringify({
|
|
265
|
+
main: bundledCliPath,
|
|
266
|
+
mainFormat: "commonjs",
|
|
267
|
+
output: blobPath,
|
|
268
|
+
disableExperimentalSEAWarning: true,
|
|
269
|
+
useSnapshot: false,
|
|
270
|
+
useCodeCache: false,
|
|
271
|
+
}, null, 2)}\n`);
|
|
272
|
+
});
|
|
273
|
+
run(process.execPath, ["--experimental-sea-config", configPath], {
|
|
274
|
+
cwd,
|
|
275
|
+
logger,
|
|
276
|
+
event: "standalone.sea-blob.create",
|
|
277
|
+
phase: "prepare",
|
|
278
|
+
});
|
|
279
|
+
const extension = process.platform === "win32" ? ".exe" : "";
|
|
280
|
+
const binaryPath = path.join(resolvedOutputDir, `${name}${extension}`);
|
|
281
|
+
timed(logger, "standalone.node.copy", {
|
|
282
|
+
phase: "prepare",
|
|
283
|
+
attributes: {
|
|
284
|
+
source: process.execPath,
|
|
285
|
+
destination: relativePath(cwd, binaryPath),
|
|
286
|
+
},
|
|
287
|
+
}, () => copyNodeBinary(binaryPath));
|
|
288
|
+
if (process.platform === "darwin") {
|
|
289
|
+
timed(logger, "standalone.codesign.remove", {
|
|
290
|
+
phase: "sign",
|
|
291
|
+
attributes: {
|
|
292
|
+
binary: relativePath(cwd, binaryPath),
|
|
293
|
+
},
|
|
294
|
+
}, () => {
|
|
295
|
+
spawnSync("codesign", ["--remove-signature", binaryPath], { stdio: "ignore" });
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
run("npx", postjectArgs(binaryPath, blobPath), {
|
|
299
|
+
cwd,
|
|
300
|
+
logger,
|
|
301
|
+
event: "standalone.sea-blob.inject",
|
|
302
|
+
phase: "package",
|
|
303
|
+
});
|
|
304
|
+
if (process.platform === "darwin") {
|
|
305
|
+
run("codesign", ["--sign", "-", binaryPath], {
|
|
306
|
+
logger,
|
|
307
|
+
event: "standalone.codesign.adhoc",
|
|
308
|
+
phase: "sign",
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
const archiveName = process.platform === "win32" ? `${archiveBase}.zip` : `${archiveBase}.tar.gz`;
|
|
312
|
+
const archivePath = path.join(resolvedOutputDir, archiveName);
|
|
313
|
+
if (process.platform === "win32") {
|
|
314
|
+
run("powershell", [
|
|
315
|
+
"-NoProfile",
|
|
316
|
+
"-Command",
|
|
317
|
+
`Compress-Archive -Path '${binaryPath.replaceAll("'", "''")}' -DestinationPath '${archivePath.replaceAll("'", "''")}' -Force`,
|
|
318
|
+
], {
|
|
319
|
+
logger,
|
|
320
|
+
event: "standalone.archive.create",
|
|
321
|
+
phase: "archive",
|
|
322
|
+
});
|
|
323
|
+
} else {
|
|
324
|
+
run("tar", ["-czf", archivePath, "-C", resolvedOutputDir, path.basename(binaryPath)], {
|
|
325
|
+
logger,
|
|
326
|
+
event: "standalone.archive.create",
|
|
327
|
+
phase: "archive",
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
const manifest = {
|
|
331
|
+
schemaVersion: 1,
|
|
332
|
+
contract: "kungfu-buildchain-standalone-binary",
|
|
333
|
+
name,
|
|
334
|
+
version,
|
|
335
|
+
platform: triple,
|
|
336
|
+
binary: relativePath(cwd, binaryPath),
|
|
337
|
+
archive: relativePath(cwd, archivePath),
|
|
338
|
+
node: process.version,
|
|
339
|
+
observability: {
|
|
340
|
+
eventLog: logger.path ? relativePath(cwd, logger.path) : "",
|
|
341
|
+
summary: logger.path ? relativePath(cwd, path.join(resolvedOutputDir, `${archiveBase}.log-summary.json`)) : "",
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
timed(logger, "standalone.manifest.write", {
|
|
345
|
+
phase: "evidence",
|
|
346
|
+
attributes: {
|
|
347
|
+
manifest: `${archiveBase}.json`,
|
|
348
|
+
},
|
|
349
|
+
}, () => {
|
|
350
|
+
fs.writeFileSync(path.join(resolvedOutputDir, `${archiveBase}.json`), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
351
|
+
});
|
|
352
|
+
logger.info("standalone.build.complete", {
|
|
353
|
+
attributes: {
|
|
354
|
+
archive: manifest.archive,
|
|
355
|
+
binary: manifest.binary,
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
writeLogSummary(logger, cwd, resolvedOutputDir, archiveBase);
|
|
359
|
+
return manifest;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
363
|
+
try {
|
|
364
|
+
const result = buildStandaloneBinary({
|
|
365
|
+
cwd: path.resolve(readArg("cwd", process.cwd())),
|
|
366
|
+
outputDir: readArg("output-dir", "dist/binary"),
|
|
367
|
+
name: readArg("name", "buildchain"),
|
|
368
|
+
version: readArg("version", ""),
|
|
369
|
+
packageManagerInstall: process.argv.includes("--install"),
|
|
370
|
+
logPath: readArg("log-path", ""),
|
|
371
|
+
});
|
|
372
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
373
|
+
} catch (error) {
|
|
374
|
+
console.error(`buildchain binary: ${error.message}`);
|
|
375
|
+
process.exitCode = 1;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
@@ -16,7 +16,10 @@ const requiredPaths = [
|
|
|
16
16
|
"bin/buildchain.mjs",
|
|
17
17
|
"docs/MAP.md",
|
|
18
18
|
"docs/cli.md",
|
|
19
|
+
"docs/release-passport.md",
|
|
20
|
+
"docs/versioning.md",
|
|
19
21
|
"scripts/release-line-dry-run.mjs",
|
|
22
|
+
"scripts/build-standalone-binary.mjs",
|
|
20
23
|
"scripts/npm-publish-dry-run.mjs",
|
|
21
24
|
"scripts/npm-publish-transaction.mjs",
|
|
22
25
|
"docs/migration-inventory.md",
|
|
@@ -28,6 +31,7 @@ const requiredPaths = [
|
|
|
28
31
|
".github/workflows/self-hosted-runner-smoke.yml",
|
|
29
32
|
".github/workflows/buildchain-ref-promotion.yml",
|
|
30
33
|
".github/workflows/npm-publish.yml",
|
|
34
|
+
".github/workflows/binary-distribution.yml",
|
|
31
35
|
".github/workflows/verify.yml",
|
|
32
36
|
".github/workflows/.build.yml",
|
|
33
37
|
".github/workflows/build-surface-fixture.yml",
|
|
@@ -58,6 +62,9 @@ if (rootPackage.exports?.["."] !== "./packages/core/index.js") {
|
|
|
58
62
|
if (rootPackage.exports?.["./logging"] !== "./packages/core/logging.js") {
|
|
59
63
|
throw new Error("root package must export @kungfu-tech/buildchain/logging");
|
|
60
64
|
}
|
|
65
|
+
if (rootPackage.exports?.["./release-passport"] !== "./packages/core/release-passport.js") {
|
|
66
|
+
throw new Error("root package must export @kungfu-tech/buildchain/release-passport");
|
|
67
|
+
}
|
|
61
68
|
if (rootPackage.publishConfig?.access !== "public") {
|
|
62
69
|
throw new Error("root package publishConfig.access must be public");
|
|
63
70
|
}
|
|
@@ -70,13 +77,29 @@ for (const expectedFile of ["bin/", "scripts/*.mjs", "packages/core/", "docs/MAP
|
|
|
70
77
|
}
|
|
71
78
|
}
|
|
72
79
|
const cliSource = fs.readFileSync(path.join(root, "bin/buildchain.mjs"), "utf8");
|
|
80
|
+
const coreIndexSource = fs.readFileSync(path.join(root, "packages/core/index.js"), "utf8");
|
|
81
|
+
const versioningDoc = fs.readFileSync(path.join(root, "docs/versioning.md"), "utf8");
|
|
73
82
|
if (!cliSource.startsWith("#!/usr/bin/env node")) {
|
|
74
83
|
throw new Error("bin/buildchain.mjs must be executable with a node shebang");
|
|
75
84
|
}
|
|
76
85
|
if (commonJsSourcePattern.test(cliSource)) {
|
|
77
86
|
throw new Error("bin/buildchain.mjs must use ESM syntax");
|
|
78
87
|
}
|
|
88
|
+
if (!coreIndexSource.includes("verifyBuildchainLogEvents")) {
|
|
89
|
+
throw new Error("packages/core/index.js must export verifyBuildchainLogEvents");
|
|
90
|
+
}
|
|
91
|
+
for (const requiredSnippet of [
|
|
92
|
+
"Release passport and binary distribution are a minor surface.",
|
|
93
|
+
"`v2.2`",
|
|
94
|
+
"GitHub-hosted runners for production",
|
|
95
|
+
"Self-hosted runners remain compatibility fixtures",
|
|
96
|
+
]) {
|
|
97
|
+
if (!versioningDoc.includes(requiredSnippet)) {
|
|
98
|
+
throw new Error(`versioning doc missing required snippet: ${requiredSnippet}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
79
101
|
const releaseLineDryRunScript = fs.readFileSync(path.join(root, "scripts/release-line-dry-run.mjs"), "utf8");
|
|
102
|
+
const standaloneBinaryScript = fs.readFileSync(path.join(root, "scripts/build-standalone-binary.mjs"), "utf8");
|
|
80
103
|
for (const requiredSnippet of [
|
|
81
104
|
"explainReleaseLineDryRun",
|
|
82
105
|
"formatReleaseLineDryRun",
|
|
@@ -89,8 +112,30 @@ for (const requiredSnippet of [
|
|
|
89
112
|
if (commonJsSourcePattern.test(releaseLineDryRunScript)) {
|
|
90
113
|
throw new Error("scripts/release-line-dry-run.mjs must use ESM syntax");
|
|
91
114
|
}
|
|
115
|
+
for (const requiredSnippet of [
|
|
116
|
+
"../packages/core/logging.js",
|
|
117
|
+
"standalone.cli-bundle.create",
|
|
118
|
+
"BUILDCHAIN_EMBEDDED_PACKAGE_VERSION",
|
|
119
|
+
"noExternal: [\"smol-toml\"]",
|
|
120
|
+
"--macho-segment-name",
|
|
121
|
+
"mainFormat: \"commonjs\"",
|
|
122
|
+
"standalone.sea-blob.create",
|
|
123
|
+
"standalone.sea-blob.inject",
|
|
124
|
+
"standalone.archive.create",
|
|
125
|
+
"standalone.build.complete",
|
|
126
|
+
".log-summary.json",
|
|
127
|
+
]) {
|
|
128
|
+
if (!standaloneBinaryScript.includes(requiredSnippet)) {
|
|
129
|
+
throw new Error(`standalone binary script missing observability dogfood snippet: ${requiredSnippet}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (commonJsSourcePattern.test(standaloneBinaryScript)) {
|
|
133
|
+
throw new Error("scripts/build-standalone-binary.mjs must use ESM syntax");
|
|
134
|
+
}
|
|
92
135
|
const npmPublishWorkflow = fs.readFileSync(path.join(root, ".github/workflows/npm-publish.yml"), "utf8");
|
|
93
136
|
const buildchainRefPromotionWorkflow = fs.readFileSync(path.join(root, ".github/workflows/buildchain-ref-promotion.yml"), "utf8");
|
|
137
|
+
const binaryDistributionWorkflow = fs.readFileSync(path.join(root, ".github/workflows/binary-distribution.yml"), "utf8");
|
|
138
|
+
const selfHostedRunnerSmokeWorkflow = fs.readFileSync(path.join(root, ".github/workflows/self-hosted-runner-smoke.yml"), "utf8");
|
|
94
139
|
const npmDryRunScript = fs.readFileSync(path.join(root, "scripts/npm-publish-dry-run.mjs"), "utf8");
|
|
95
140
|
const npmPublishTransactionScript = fs.readFileSync(path.join(root, "scripts/npm-publish-transaction.mjs"), "utf8");
|
|
96
141
|
for (const requiredSnippet of [
|
|
@@ -147,6 +192,32 @@ if (commonJsSourcePattern.test(npmPublishTransactionScript)) {
|
|
|
147
192
|
if (/runs-on:\s*self-hosted/.test(npmPublishWorkflow)) {
|
|
148
193
|
throw new Error("npm publish workflow must use GitHub-hosted runners for trusted publishing");
|
|
149
194
|
}
|
|
195
|
+
for (const requiredSnippet of [
|
|
196
|
+
"name: Binary Distribution",
|
|
197
|
+
"ubuntu-24.04",
|
|
198
|
+
"macos-latest",
|
|
199
|
+
"windows-2022",
|
|
200
|
+
"BUILDCHAIN_LOG_PATH",
|
|
201
|
+
"buildchain-log-events",
|
|
202
|
+
"buildchain-log-summary",
|
|
203
|
+
"bin/buildchain.mjs mark",
|
|
204
|
+
"bin/buildchain.mjs span",
|
|
205
|
+
"verify observability-log",
|
|
206
|
+
"bin/buildchain.mjs log summary",
|
|
207
|
+
"collect github-release",
|
|
208
|
+
"verify release-passport",
|
|
209
|
+
"gh release upload",
|
|
210
|
+
]) {
|
|
211
|
+
if (!binaryDistributionWorkflow.includes(requiredSnippet)) {
|
|
212
|
+
throw new Error(`binary distribution workflow missing required snippet: ${requiredSnippet}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (/runs-on:\s*self-hosted/.test(binaryDistributionWorkflow)) {
|
|
216
|
+
throw new Error("binary distribution production workflow must not require self-hosted runners");
|
|
217
|
+
}
|
|
218
|
+
if (!selfHostedRunnerSmokeWorkflow.includes("BUILDCHAIN_RUNNER_KIND: self-hosted")) {
|
|
219
|
+
throw new Error("self-hosted smoke must remain a release-passport compatibility fixture");
|
|
220
|
+
}
|
|
150
221
|
|
|
151
222
|
const inventory = JSON.parse(
|
|
152
223
|
fs.readFileSync(path.join(root, "tests/buildchain-inventory.json"), "utf8")
|
|
@@ -167,6 +238,20 @@ if (inventory.stableRefs?.actions !== "kungfu-systems/buildchain/actions/<name>@
|
|
|
167
238
|
if (inventory.stableRefs?.workflows !== "kungfu-systems/buildchain/.github/workflows/<workflow>.yml@v2") {
|
|
168
239
|
throw new Error("inventory stable workflow ref must point at @v2");
|
|
169
240
|
}
|
|
241
|
+
if (inventory.safety?.releasePassport?.line !== "v2.2") {
|
|
242
|
+
throw new Error("release passport inventory must be registered as a v2.2 surface");
|
|
243
|
+
}
|
|
244
|
+
if (inventory.safety?.releasePassport?.binaryDistribution?.productionRunnerDefault !== "github-hosted") {
|
|
245
|
+
throw new Error("release passport binary distribution must default to GitHub-hosted production runners");
|
|
246
|
+
}
|
|
247
|
+
if (inventory.safety?.releasePassport?.binaryDistribution?.selfHostedRole !== "compatibility-fixture") {
|
|
248
|
+
throw new Error("self-hosted runners must remain release passport compatibility fixtures");
|
|
249
|
+
}
|
|
250
|
+
for (const artifact of ["buildchain.release.json", "artifact-evidence.json", "impact.json", "agent-index.json", "llms.txt"]) {
|
|
251
|
+
if (!inventory.safety?.releasePassport?.protocolArtifacts?.includes(artifact)) {
|
|
252
|
+
throw new Error(`release passport inventory missing protocol artifact ${artifact}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
170
255
|
|
|
171
256
|
if (!Array.isArray(inventory.workflowSources) || inventory.workflowSources.length < 1) {
|
|
172
257
|
throw new Error("workflowSources must include the migrated workflows repository");
|