@kungfu-tech/buildchain 3.0.2 → 3.0.3-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.
Files changed (127) hide show
  1. package/AGENTS.md +1 -1
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +27 -1
  4. package/actions/promote-buildchain-ref/README.md +90 -19
  5. package/actions/report-buildchain-issue/README.md +20 -2
  6. package/actions/run-lifecycle/README.md +5 -0
  7. package/actions/validate-config/README.md +19 -1
  8. package/bin/buildchain.mjs +56 -2
  9. package/bin/internal/command-registry.mjs +82 -0
  10. package/bin/internal/trust-release-cli.mjs +1 -0
  11. package/contracts/auditable-demo-media-profiles-v1.json +116 -1
  12. package/contracts/buildchain-v2-residuals-v1.json +146 -0
  13. package/contracts/evidence/auditable-demo-responsive-web-delivery-v1.json +143 -0
  14. package/contracts/evidence/auditable-demo-web-delivery-v1.json +7 -3
  15. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/complete-transcript.txt +2 -0
  16. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/public-projection.json +16 -0
  17. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/scene.json +12 -0
  18. package/dist/site/buildchain-contract.json +188 -46
  19. package/dist/site/buildchain-site.json +181 -90
  20. package/dist/site/capability-registry.json +5 -5
  21. package/dist/site/cli-registry.json +860 -98
  22. package/dist/site/controller-registry.json +118 -5
  23. package/dist/site/kfd-claims.json +233 -19
  24. package/dist/site/kfd-upstream-aggregate.json +1 -1
  25. package/dist/site/manual-registry.json +20 -20
  26. package/dist/site/node-api-registry.json +374 -59
  27. package/dist/site/page-registry.json +156 -65
  28. package/dist/site/public-surface-audit.json +231 -22
  29. package/dist/site/publication-registry.json +4 -4
  30. package/dist/site/release-provenance.json +3 -0
  31. package/dist/site/site-manifest.json +24 -24
  32. package/dist/site/workflow-registry.json +575 -79
  33. package/docs/MAP.md +22 -4
  34. package/docs/auditable-demo.md +54 -13
  35. package/docs/aws-us-elastic-runner-burst-plane.md +381 -0
  36. package/docs/cli.md +119 -12
  37. package/docs/consumer-issue-reporting.md +20 -2
  38. package/docs/dev-alpha-candidate-patrol.md +24 -10
  39. package/docs/github-governance-authority.md +24 -31
  40. package/docs/lifecycle-protocol.md +20 -2
  41. package/docs/migration-inventory.md +24 -5
  42. package/docs/observed-evidence-patrol.md +28 -12
  43. package/docs/ownership.md +19 -1
  44. package/docs/publication-artifacts.md +158 -18
  45. package/docs/publication-authority.md +4 -4
  46. package/docs/publish-transaction.md +78 -31
  47. package/docs/release-candidate.md +24 -0
  48. package/docs/release-flow.md +50 -32
  49. package/docs/release-governance.md +39 -32
  50. package/docs/release-passport.md +4 -0
  51. package/docs/release-propagation.md +57 -12
  52. package/docs/reusable-build-surface.md +218 -90
  53. package/docs/runtime-train-validation.md +20 -2
  54. package/docs/shifu-gate-profiles.md +7 -1
  55. package/docs/stable-candidate-patrol.md +6 -6
  56. package/docs/toolkit-observability.md +22 -0
  57. package/docs/versioning.md +46 -24
  58. package/docs/web-surface-deployments.md +82 -1
  59. package/package.json +7 -5
  60. package/packages/core/README.md +60 -4
  61. package/packages/core/artifact-signing.js +1 -0
  62. package/packages/core/buildchain-contract.js +9 -4
  63. package/packages/core/cache-evidence.js +1 -0
  64. package/packages/core/controller-evidence.js +2 -0
  65. package/packages/core/diagnostics.js +105 -30
  66. package/packages/core/github-governance-authority.js +25 -17
  67. package/packages/core/index.js +40 -0
  68. package/packages/core/paper.js +3226 -0
  69. package/packages/core/public-surface-audit.js +3 -0
  70. package/packages/core/publication-artifact.js +1 -6
  71. package/packages/core/publication-authority.js +1 -0
  72. package/packages/core/publication-control-plane-audit.js +28 -0
  73. package/packages/core/publication-reproducibility.js +947 -0
  74. package/packages/core/publication-sealed-bundle.js +190 -0
  75. package/packages/core/publish-transaction.js +147 -17
  76. package/packages/core/release-candidate.js +234 -0
  77. package/packages/core/release-passport.js +519 -228
  78. package/packages/core/release-propagation.js +175 -1
  79. package/packages/core/stable-release-gate.js +4 -1
  80. package/scripts/artifact-signing-delegation.mjs +268 -0
  81. package/scripts/audit-github-governance.mjs +32 -13
  82. package/scripts/audit-publication-control-plane.mjs +17 -8
  83. package/scripts/auditable-demo.mjs +266 -7
  84. package/scripts/aws-codebuild-toolchain.mjs +285 -0
  85. package/scripts/aws-macos-jit-core.mjs +378 -0
  86. package/scripts/aws-macos-jit.mjs +123 -0
  87. package/scripts/aws-runner-burst-core.mjs +248 -0
  88. package/scripts/aws-runner-burst.mjs +79 -0
  89. package/scripts/aws-windows-jit-core.mjs +374 -0
  90. package/scripts/aws-windows-jit.mjs +121 -0
  91. package/scripts/build-contract-core.mjs +69 -6
  92. package/scripts/build-standalone-binary.mjs +23 -0
  93. package/scripts/buildchain-contract-lock.mjs +1 -1
  94. package/scripts/buildchain-patrol.mjs +1 -1
  95. package/scripts/check-internal-architecture.mjs +69 -2
  96. package/scripts/check-inventory.mjs +70 -2
  97. package/scripts/check-javascript-syntax.mjs +31 -0
  98. package/scripts/check-maintainability.mjs +371 -0
  99. package/scripts/compiler-cache-evidence.mjs +221 -0
  100. package/scripts/dev-alpha-candidate-patrol.mjs +288 -43
  101. package/scripts/dispatch-artifact-signing-authority.mjs +7 -1
  102. package/scripts/finalize-native-artifact-signing-result.mjs +185 -31
  103. package/scripts/gate-profile-core.mjs +6 -1
  104. package/scripts/generate-channel-promotion-workflow.mjs +3 -12
  105. package/scripts/generate-release-candidate-passport.mjs +4 -0
  106. package/scripts/generate-site-bundle.mjs +84 -18
  107. package/scripts/import-artifact-signing-results.mjs +64 -5
  108. package/scripts/inspect-artifact-signing-requests.mjs +52 -14
  109. package/scripts/maintainability-metrics.mjs +339 -0
  110. package/scripts/npm-publish-transaction.mjs +57 -6
  111. package/scripts/observed-evidence.mjs +151 -33
  112. package/scripts/paper.mjs +645 -0
  113. package/scripts/promotion-channel-router.mjs +16 -4
  114. package/scripts/publication-reproducibility.mjs +62 -0
  115. package/scripts/reconcile-github-governance.mjs +8 -1
  116. package/scripts/release-propagation.mjs +39 -0
  117. package/scripts/resolve-artifact-signing-upload-route.mjs +55 -0
  118. package/scripts/resolve-build-contract.mjs +6 -0
  119. package/scripts/route-offline-runners.mjs +236 -0
  120. package/scripts/run-candidate-body-prefix-renderer.mjs +187 -0
  121. package/scripts/run-lifecycle-core.mjs +62 -1
  122. package/scripts/runtime-ref-core.mjs +36 -4
  123. package/scripts/seal-artifact-signing-requests.mjs +64 -12
  124. package/scripts/stable-candidate-qualification.mjs +50 -0
  125. package/scripts/verify-artifact-signing-results.mjs +26 -1
  126. package/scripts/web-surface-core.mjs +272 -86
  127. package/scripts/web-surface-production-decision.mjs +19 -3
@@ -38,7 +38,8 @@ export function inspectArtifactSigningRequests({
38
38
  } = {}) {
39
39
  const root = path.resolve(required(inputRoot, "signing request root"));
40
40
  const indexes = walk(root, "index.json");
41
- if (indexes.length === 0) throw new Error("no artifact signing request indexes found");
41
+ if (indexes.length === 0)
42
+ throw new Error("no artifact signing request indexes found");
42
43
  const seen = new Set();
43
44
  const matrices = {
44
45
  detached: [],
@@ -47,41 +48,73 @@ export function inspectArtifactSigningRequests({
47
48
  };
48
49
  for (const indexPath of indexes) {
49
50
  const index = JSON.parse(fs.readFileSync(indexPath, "utf8"));
50
- if (index.contract !== "kungfu-buildchain-artifact-signing-request-index/v1") continue;
51
+ if (
52
+ index.contract !== "kungfu-buildchain-artifact-signing-request-index/v1"
53
+ )
54
+ continue;
51
55
  for (const entry of index.requests || []) {
52
56
  const requestPath = path.resolve(path.dirname(indexPath), entry.path);
53
57
  const relative = path.relative(root, requestPath);
54
58
  if (relative.startsWith("..") || path.isAbsolute(relative)) {
55
- throw new Error("signing request path escapes the authority intake root");
59
+ throw new Error(
60
+ "signing request path escapes the authority intake root",
61
+ );
56
62
  }
57
63
  const request = JSON.parse(fs.readFileSync(requestPath, "utf8"));
58
64
  const check = validateArtifactSigningRequest(request);
59
65
  if (!check.ok || request.digest !== entry.digest) {
60
66
  throw new Error(`invalid artifact signing request: ${entry.id}`);
61
67
  }
62
- if (expectedRepository && request.source.repository !== expectedRepository) {
68
+ if (
69
+ expectedRepository &&
70
+ request.source.repository !== expectedRepository
71
+ ) {
63
72
  throw new Error("signing request source repository mismatch");
64
73
  }
65
74
  if (expectedRuntimeSha && request.runtime.sha !== expectedRuntimeSha) {
66
75
  throw new Error("signing request runtime SHA mismatch");
67
76
  }
68
77
  const key = `${request.source.sha}:${request.artifact.id}:${request.artifact.platform}`;
69
- if (seen.has(key)) throw new Error(`duplicate artifact signing request: ${key}`);
78
+ if (seen.has(key))
79
+ throw new Error(`duplicate artifact signing request: ${key}`);
70
80
  seen.add(key);
71
81
  const item = {
72
82
  id: request.artifact.id,
73
83
  slug: safeId(request.artifact.id),
74
84
  request: path.relative(root, requestPath).split(path.sep).join("/"),
75
- directory: path.relative(root, path.dirname(requestPath)).split(path.sep).join("/"),
76
- indexRoot: path.relative(root, path.dirname(indexPath)).split(path.sep).join("/") || ".",
85
+ directory: path
86
+ .relative(root, path.dirname(requestPath))
87
+ .split(path.sep)
88
+ .join("/"),
89
+ indexRoot:
90
+ path
91
+ .relative(root, path.dirname(indexPath))
92
+ .split(path.sep)
93
+ .join("/") || ".",
94
+ kind: request.artifact.kind,
95
+ platform: request.artifact.platform,
96
+ arch: request.artifact.arch || "",
97
+ platformId: [request.artifact.platform, request.artifact.arch]
98
+ .filter(Boolean)
99
+ .join("-"),
100
+ sourceSha: request.source.sha,
101
+ sourceTreeSha: request.source.treeSha,
102
+ transportFormat: request.artifact.transport?.format || "",
77
103
  };
78
- if (request.signature.profile === "detached-signature-v1") matrices.detached.push(item);
79
- else if (request.signature.profile === "apple-developer-id") matrices.macos.push(item);
80
- else if (request.signature.profile === "windows-authenticode") matrices.windows.push(item);
81
- else throw new Error(`unsupported signing authority profile: ${request.signature.profile}`);
104
+ if (request.signature.profile === "detached-signature-v1")
105
+ matrices.detached.push(item);
106
+ else if (request.signature.profile === "apple-developer-id")
107
+ matrices.macos.push(item);
108
+ else if (request.signature.profile === "windows-authenticode")
109
+ matrices.windows.push(item);
110
+ else
111
+ throw new Error(
112
+ `unsupported signing authority profile: ${request.signature.profile}`,
113
+ );
82
114
  }
83
115
  }
84
- for (const entries of Object.values(matrices)) entries.sort((a, b) => a.request.localeCompare(b.request));
116
+ for (const entries of Object.values(matrices))
117
+ entries.sort((a, b) => a.request.localeCompare(b.request));
85
118
  writeGitHubOutputs({
86
119
  "detached-matrix": JSON.stringify(matrices.detached),
87
120
  "macos-matrix": JSON.stringify(matrices.macos),
@@ -91,11 +124,16 @@ export function inspectArtifactSigningRequests({
91
124
  return matrices;
92
125
  }
93
126
 
94
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
127
+ if (
128
+ process.argv[1] &&
129
+ import.meta.url === pathToFileURL(process.argv[1]).href
130
+ ) {
95
131
  try {
96
132
  inspectArtifactSigningRequests();
97
133
  } catch (error) {
98
- console.error(`::error::${String(error?.message || error).replace(/\r?\n/gu, "%0A")}`);
134
+ console.error(
135
+ `::error::${String(error?.message || error).replace(/\r?\n/gu, "%0A")}`,
136
+ );
99
137
  process.exitCode = 1;
100
138
  }
101
139
  }
@@ -0,0 +1,339 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { execFileSync } from "node:child_process";
6
+ import { pathToFileURL } from "node:url";
7
+ import ts from "typescript";
8
+ import {
9
+ dependencyCycles,
10
+ relativeImports,
11
+ } from "./check-internal-architecture.mjs";
12
+
13
+ const JS_EXTENSIONS = new Set([".js", ".mjs", ".cjs"]);
14
+ const GENERATED_PATTERN = /^actions\/[^/]+\/dist\/.*\.js$/u;
15
+
16
+ function gitText(root, args, encoding = "utf8") {
17
+ return execFileSync("git", args, {
18
+ cwd: root,
19
+ encoding,
20
+ maxBuffer: 16 * 1024 * 1024,
21
+ });
22
+ }
23
+
24
+ function trackedFiles(root, revision = "") {
25
+ const args = revision
26
+ ? ["ls-tree", "-r", "-z", "--name-only", revision]
27
+ : ["ls-files", "-z", "--cached", "--others", "--exclude-standard"];
28
+ return gitText(root, args).split("\0").filter(Boolean).sort();
29
+ }
30
+
31
+ function readTrackedFile(root, file, revision = "") {
32
+ return revision
33
+ ? gitText(root, ["show", `${revision}:${file}`])
34
+ : fs.readFileSync(path.join(root, file), "utf8");
35
+ }
36
+
37
+ function lineCount(source) {
38
+ if (!source) return 0;
39
+ const lines = source.split("\n");
40
+ return lines.at(-1) === "" ? lines.length - 1 : lines.length;
41
+ }
42
+
43
+ function isTestFile(file) {
44
+ return file.startsWith("tests/") || /(?:^|\/)test(?:s)?\//u.test(file);
45
+ }
46
+
47
+ function isGeneratedFile(file) {
48
+ return GENERATED_PATTERN.test(file);
49
+ }
50
+
51
+ function isHandMaintainedSource(file) {
52
+ return (
53
+ JS_EXTENSIONS.has(path.extname(file)) &&
54
+ !isTestFile(file) &&
55
+ !isGeneratedFile(file)
56
+ );
57
+ }
58
+
59
+ function isFunctionNode(node) {
60
+ return (
61
+ ts.isFunctionDeclaration(node) ||
62
+ ts.isFunctionExpression(node) ||
63
+ ts.isArrowFunction(node) ||
64
+ ts.isMethodDeclaration(node) ||
65
+ ts.isConstructorDeclaration(node) ||
66
+ ts.isGetAccessorDeclaration(node) ||
67
+ ts.isSetAccessorDeclaration(node)
68
+ );
69
+ }
70
+
71
+ function propertyName(node) {
72
+ if (!node) return "";
73
+ if (ts.isIdentifier(node) || ts.isPrivateIdentifier(node)) return node.text;
74
+ if (ts.isStringLiteral(node) || ts.isNumericLiteral(node)) return node.text;
75
+ return node.getText();
76
+ }
77
+
78
+ function functionName(node, sourceFile) {
79
+ if (node.name) return propertyName(node.name);
80
+ const parent = node.parent;
81
+ if (ts.isVariableDeclaration(parent)) return propertyName(parent.name);
82
+ if (ts.isPropertyAssignment(parent)) return propertyName(parent.name);
83
+ if (ts.isExportAssignment(parent)) return "default";
84
+ const start =
85
+ sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line +
86
+ 1;
87
+ return `<anonymous@${start}>`;
88
+ }
89
+
90
+ function isLogicalDecision(node) {
91
+ return (
92
+ ts.isBinaryExpression(node) &&
93
+ [
94
+ ts.SyntaxKind.AmpersandAmpersandToken,
95
+ ts.SyntaxKind.BarBarToken,
96
+ ts.SyntaxKind.QuestionQuestionToken,
97
+ ].includes(node.operatorToken.kind)
98
+ );
99
+ }
100
+
101
+ function isDecision(node) {
102
+ return (
103
+ ts.isIfStatement(node) ||
104
+ ts.isForStatement(node) ||
105
+ ts.isForInStatement(node) ||
106
+ ts.isForOfStatement(node) ||
107
+ ts.isWhileStatement(node) ||
108
+ ts.isDoStatement(node) ||
109
+ ts.isCatchClause(node) ||
110
+ ts.isConditionalExpression(node) ||
111
+ ts.isCaseClause(node) ||
112
+ isLogicalDecision(node)
113
+ );
114
+ }
115
+
116
+ function functionComplexity(node) {
117
+ let complexity = 1;
118
+ const visit = (child) => {
119
+ if (child !== node && isFunctionNode(child)) return;
120
+ if (child !== node && isDecision(child)) complexity += 1;
121
+ ts.forEachChild(child, visit);
122
+ };
123
+ visit(node);
124
+ return complexity;
125
+ }
126
+
127
+ function analyzeJavaScript(file, source) {
128
+ const sourceFile = ts.createSourceFile(
129
+ file,
130
+ source,
131
+ ts.ScriptTarget.Latest,
132
+ true,
133
+ file.endsWith(".cjs") ? ts.ScriptKind.JS : ts.ScriptKind.JS,
134
+ );
135
+ const functions = [];
136
+ const visit = (node) => {
137
+ if (isFunctionNode(node)) {
138
+ const start =
139
+ sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile))
140
+ .line + 1;
141
+ const end =
142
+ sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1;
143
+ functions.push({
144
+ name: functionName(node, sourceFile),
145
+ start,
146
+ end,
147
+ lines: end - start + 1,
148
+ complexity: functionComplexity(node),
149
+ });
150
+ }
151
+ ts.forEachChild(node, visit);
152
+ };
153
+ visit(sourceFile);
154
+ functions.sort(
155
+ (left, right) =>
156
+ left.start - right.start || left.name.localeCompare(right.name),
157
+ );
158
+ return {
159
+ lines: lineCount(source),
160
+ complexity: functions.reduce((total, entry) => total + entry.complexity, 0),
161
+ functions,
162
+ };
163
+ }
164
+
165
+ function countRegistryEntries(root, file, field, revision = "") {
166
+ const value = JSON.parse(readTrackedFile(root, file, revision));
167
+ return Array.isArray(value[field]) ? value[field].length : 0;
168
+ }
169
+
170
+ function architectureSummary(root, revision = "") {
171
+ const index = JSON.parse(
172
+ readTrackedFile(root, "architecture/internal-capabilities.json", revision),
173
+ );
174
+ const implementationPaths = new Set(
175
+ index.capabilities.flatMap((entry) => entry.implementation || []),
176
+ );
177
+ const graph = new Map(
178
+ [...implementationPaths].map((file) => [file, new Set()]),
179
+ );
180
+ for (const file of implementationPaths) {
181
+ if (!JS_EXTENSIONS.has(path.extname(file))) continue;
182
+ for (const specifier of relativeImports(
183
+ readTrackedFile(root, file, revision),
184
+ )) {
185
+ const base = path.posix.normalize(
186
+ path.posix.join(path.posix.dirname(file), specifier),
187
+ );
188
+ const target = [
189
+ base,
190
+ `${base}.js`,
191
+ `${base}.mjs`,
192
+ `${base}.cjs`,
193
+ `${base}/index.js`,
194
+ `${base}/index.mjs`,
195
+ ].find((candidate) => implementationPaths.has(candidate));
196
+ if (target) graph.get(file).add(target);
197
+ }
198
+ }
199
+ return {
200
+ capabilities: index.capabilities.length,
201
+ implementationMappings: index.capabilities.reduce(
202
+ (total, entry) => total + (entry.implementation || []).length,
203
+ 0,
204
+ ),
205
+ testMappings: index.capabilities.reduce(
206
+ (total, entry) => total + (entry.tests || []).length,
207
+ 0,
208
+ ),
209
+ dependencyRules: index.dependencyRules.length,
210
+ dependencyCycles: dependencyCycles(graph).length,
211
+ };
212
+ }
213
+
214
+ function selectedFunction(files, file, name) {
215
+ const entry = files[file];
216
+ const matches =
217
+ entry?.functions.filter((candidate) => candidate.name === name) || [];
218
+ if (matches.length !== 1) {
219
+ throw new Error(
220
+ `${file} must contain exactly one ${name} function; found ${matches.length}`,
221
+ );
222
+ }
223
+ return matches[0];
224
+ }
225
+
226
+ function collectMaintainabilityMetrics({
227
+ root = process.cwd(),
228
+ revision = "",
229
+ } = {}) {
230
+ const files = trackedFiles(root, revision);
231
+ const sourceFiles = files.filter(isHandMaintainedSource);
232
+ const testFiles = files.filter(
233
+ (file) => JS_EXTENSIONS.has(path.extname(file)) && isTestFile(file),
234
+ );
235
+ const workflowFiles = files.filter((file) =>
236
+ /^\.github\/workflows\/.*\.ya?ml$/u.test(file),
237
+ );
238
+ const generatedFiles = files.filter(isGeneratedFile);
239
+ const documentationFiles = files.filter((file) => file.endsWith(".md"));
240
+ const sourceMetrics = Object.fromEntries(
241
+ sourceFiles.map((file) => {
242
+ const source = readTrackedFile(root, file, revision);
243
+ return [file, analyzeJavaScript(file, source)];
244
+ }),
245
+ );
246
+ const sumLines = (entries) =>
247
+ entries.reduce(
248
+ (total, file) => total + lineCount(readTrackedFile(root, file, revision)),
249
+ 0,
250
+ );
251
+ const generatedBytes = generatedFiles.reduce(
252
+ (total, file) =>
253
+ total + Buffer.byteLength(readTrackedFile(root, file, revision)),
254
+ 0,
255
+ );
256
+ const workflowRegistry = JSON.parse(
257
+ readTrackedFile(root, "dist/site/workflow-registry.json", revision),
258
+ );
259
+ const reverseAudit = JSON.parse(
260
+ readTrackedFile(root, "dist/site/public-surface-audit.json", revision),
261
+ );
262
+ const head = revision || gitText(root, ["rev-parse", "HEAD"]).trim();
263
+ return {
264
+ schemaVersion: 1,
265
+ contract: "kungfu-buildchain-maintainability-metrics",
266
+ revision: head,
267
+ repository: {
268
+ trackedFiles: files.length,
269
+ handMaintainedSourceFiles: sourceFiles.length,
270
+ handMaintainedSourceLines: Object.values(sourceMetrics).reduce(
271
+ (total, entry) => total + entry.lines,
272
+ 0,
273
+ ),
274
+ testFiles: testFiles.length,
275
+ testLines: sumLines(testFiles),
276
+ workflowFiles: workflowFiles.length,
277
+ workflowLines: sumLines(workflowFiles),
278
+ documentationFiles: documentationFiles.length,
279
+ documentationLines: sumLines(documentationFiles),
280
+ generatedFiles: generatedFiles.length,
281
+ generatedBytes,
282
+ },
283
+ publicSurface: {
284
+ cliCommands: countRegistryEntries(
285
+ root,
286
+ "dist/site/cli-registry.json",
287
+ "commands",
288
+ revision,
289
+ ),
290
+ nodeApiExports: countRegistryEntries(
291
+ root,
292
+ "dist/site/node-api-registry.json",
293
+ "exports",
294
+ revision,
295
+ ),
296
+ workflows: Array.isArray(workflowRegistry.workflows)
297
+ ? workflowRegistry.workflows.length
298
+ : 0,
299
+ actions: Array.isArray(workflowRegistry.actions)
300
+ ? workflowRegistry.actions.length
301
+ : 0,
302
+ reverseAuditFailures: Number(reverseAudit.summary?.failureCount || 0),
303
+ },
304
+ architecture: architectureSummary(root, revision),
305
+ hotspots: {
306
+ promoteBuildchainRefs: {
307
+ file: "actions/promote-buildchain-ref/lib.js",
308
+ ...selectedFunction(
309
+ sourceMetrics,
310
+ "actions/promote-buildchain-ref/lib.js",
311
+ "promoteBuildchainRefs",
312
+ ),
313
+ },
314
+ createReleaseCheckReport: {
315
+ file: "packages/core/release-passport.js",
316
+ ...selectedFunction(
317
+ sourceMetrics,
318
+ "packages/core/release-passport.js",
319
+ "createReleaseCheckReport",
320
+ ),
321
+ },
322
+ },
323
+ files: sourceMetrics,
324
+ };
325
+ }
326
+
327
+ if (
328
+ process.argv[1] &&
329
+ import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href
330
+ ) {
331
+ console.log(JSON.stringify(collectMaintainabilityMetrics(), null, 2));
332
+ }
333
+
334
+ export {
335
+ analyzeJavaScript,
336
+ collectMaintainabilityMetrics,
337
+ isHandMaintainedSource,
338
+ lineCount,
339
+ };
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from "node:child_process";
3
+ import crypto from "node:crypto";
3
4
  import fs from "node:fs";
4
5
  import path from "node:path";
5
6
  import { pathToFileURL } from "node:url";
@@ -71,6 +72,39 @@ function artifactDigest(pack) {
71
72
  throw new Error("npm pack did not return integrity or shasum");
72
73
  }
73
74
 
75
+ function sealedPackResult() {
76
+ const tarballPath = readEnv("BUILDCHAIN_SEALED_NPM_TARBALL");
77
+ if (!tarballPath) {
78
+ return undefined;
79
+ }
80
+ const resolved = path.resolve(tarballPath);
81
+ if (!fs.existsSync(resolved) || !fs.statSync(resolved).isFile()) {
82
+ throw new Error(`sealed npm tarball is missing: ${resolved}`);
83
+ }
84
+ const bytes = fs.readFileSync(resolved);
85
+ const integrity = `sha512-${crypto.createHash("sha512").update(bytes).digest("base64")}`;
86
+ const shasum = crypto.createHash("sha1").update(bytes).digest("hex");
87
+ const sha256 = crypto.createHash("sha256").update(bytes).digest("hex");
88
+ const expectedIntegrity = readEnv("BUILDCHAIN_SEALED_NPM_INTEGRITY");
89
+ const expectedSha256 = readEnv("BUILDCHAIN_SEALED_NPM_SHA256").replace(/^sha256:/, "");
90
+ if (expectedIntegrity && integrity !== expectedIntegrity) {
91
+ throw new Error(`sealed npm tarball integrity mismatch: expected ${expectedIntegrity}, got ${integrity}`);
92
+ }
93
+ if (expectedSha256 && sha256 !== expectedSha256) {
94
+ throw new Error(`sealed npm tarball sha256 mismatch: expected ${expectedSha256}, got ${sha256}`);
95
+ }
96
+ return {
97
+ filename: path.basename(resolved),
98
+ integrity,
99
+ shasum,
100
+ sha256,
101
+ bytes: bytes.length,
102
+ entryCount: 0,
103
+ tarballPath: resolved,
104
+ sealed: true,
105
+ };
106
+ }
107
+
74
108
  function parseNpmView(stdout) {
75
109
  const raw = String(stdout || "").trim();
76
110
  if (!raw) {
@@ -154,10 +188,14 @@ export function npmPublishTransaction({
154
188
  const exactTag = assertPackageVersion({ pkg, expectedVersion });
155
189
  const distTag = readEnv("BUILDCHAIN_NPM_DIST_TAG", pkg.version.includes("-") ? "alpha" : "latest");
156
190
 
157
- const pack = parsePackResult(runNpm({
158
- cwd: resolvedCwd,
159
- args: ["pack", "--dry-run", "--json", `--registry=${registry}`],
160
- }).stdout);
191
+ const pack =
192
+ sealedPackResult() ||
193
+ parsePackResult(
194
+ runNpm({
195
+ cwd: resolvedCwd,
196
+ args: ["pack", "--dry-run", "--json", `--registry=${registry}`],
197
+ }).stdout,
198
+ );
161
199
  const digest = artifactDigest(pack);
162
200
 
163
201
  const existingDigest = skipRegistryLookup
@@ -178,7 +216,15 @@ export function npmPublishTransaction({
178
216
  } else {
179
217
  runNpm({
180
218
  cwd: resolvedCwd,
181
- args: ["publish", "--access", access, "--tag", distTag, `--registry=${registry}`],
219
+ args: [
220
+ "publish",
221
+ ...(pack.tarballPath ? [pack.tarballPath] : []),
222
+ "--access",
223
+ access,
224
+ "--tag",
225
+ distTag,
226
+ `--registry=${registry}`,
227
+ ],
182
228
  });
183
229
  publishAction = "published";
184
230
  const registryDigest = skipRegistryLookup
@@ -215,7 +261,11 @@ export function npmPublishTransaction({
215
261
  digest,
216
262
  }],
217
263
  };
218
- const resolvedEvidencePath = writeEvidence({ cwd: resolvedCwd, evidencePath, evidence });
264
+ const resolvedEvidencePath = writeEvidence({
265
+ cwd: resolvedCwd,
266
+ evidencePath,
267
+ evidence,
268
+ });
219
269
  writeGitHubOutputs({
220
270
  version: pkg.version,
221
271
  "exact-tag": exactTag,
@@ -235,6 +285,7 @@ export function npmPublishTransaction({
235
285
  registry,
236
286
  publishAction,
237
287
  pack,
288
+ sealedBundleRoot: readEnv("BUILDCHAIN_SEALED_BUNDLE_ROOT"),
238
289
  evidencePath: resolvedEvidencePath,
239
290
  evidence,
240
291
  };