@kungfu-tech/buildchain 2.9.1 → 2.10.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.
@@ -59,6 +59,7 @@ function commandId(first = "", second = "") {
59
59
  if (head === "log" && sub) return "logging";
60
60
  if (head === "diagnostics" && sub) return `diagnostics-${sub}`;
61
61
  if (head === "facts" && sub) return "build-facts";
62
+ if (head === "kfd-3" && sub) return `kfd-3-${sub}`;
62
63
  if (head === "sample" && sub) return `sample-${sub}`;
63
64
  if (head === "badges" && sub) return `badges-${sub}`;
64
65
  if (head === "homebrew" && sub) return `homebrew-${sub}`;
@@ -126,6 +126,9 @@ if (rootPackage.exports?.["./buildchain-contract"] !== "./packages/core/buildcha
126
126
  if (rootPackage.exports?.["./issue-reporting"] !== "./packages/core/issue-reporting.js") {
127
127
  throw new Error("root package must export @kungfu-tech/buildchain/issue-reporting");
128
128
  }
129
+ if (rootPackage.exports?.["./kfd-3-surfaces"] !== "./packages/core/kfd3-surface-register.js") {
130
+ throw new Error("root package must export @kungfu-tech/buildchain/kfd-3-surfaces");
131
+ }
129
132
  if (rootPackage.exports?.["./release-line-bootstrap"] !== "./packages/core/release-line-bootstrap.js") {
130
133
  throw new Error("root package must export @kungfu-tech/buildchain/release-line-bootstrap");
131
134
  }
@@ -244,6 +247,17 @@ for (const requiredSnippet of [
244
247
  throw new Error(`packages/core/index.js must export Build Facts API: ${requiredSnippet}`);
245
248
  }
246
249
  }
250
+ for (const requiredSnippet of [
251
+ "detectKfd3Surfaces",
252
+ "registerKfd3Surfaces",
253
+ "auditKfd3Surfaces",
254
+ "createKfd3SurfaceWitness",
255
+ "queryKfd3Capabilities",
256
+ ]) {
257
+ if (!coreIndexSource.includes(requiredSnippet)) {
258
+ throw new Error(`packages/core/index.js must export KFD-3 surface API: ${requiredSnippet}`);
259
+ }
260
+ }
247
261
  for (const requiredSnippet of [
248
262
  "collectBadgeBundleFacts",
249
263
  "renderBadgeBundleBlock",
@@ -308,6 +308,11 @@ function buildSiteBundle() {
308
308
  ["span", "Run a command inside a Buildchain log span."],
309
309
  ["diagnostics-summary", "Summarize diagnostics artifacts into JSON and cross-platform lifecycle timing tables."],
310
310
  ["build-facts", "Collect and verify Git source, version, module output, product artifact, and legacy Kungfu buildinfo facts."],
311
+ ["kfd-3-detect", "Detect standard KFD-3 public surface candidates from source and artifact metadata."],
312
+ ["kfd-3-register", "Declare detected KFD-3 public surfaces into a product-owned surface registry."],
313
+ ["kfd-3-audit", "Compare detected, declared, and enforced KFD-3 public surfaces."],
314
+ ["kfd-3-witness", "Generate release-passport-compatible KFD-3 surface witnesses from a product registry."],
315
+ ["kfd-3-query", "Return an agent-readable KFD-3 capability map for a product, registry, or release passport."],
311
316
  ["sample-process-tree", "Sample process-tree diagnostics for a wrapped command."],
312
317
  ["npm-dry-run", "Verify npm publish shape before a release transaction."],
313
318
  ["infra-contract", "Validate and publish provider-neutral infrastructure contract evidence."],
@@ -348,6 +353,7 @@ function buildSiteBundle() {
348
353
  "docs/install.md",
349
354
  "docs/cli.md",
350
355
  "docs/build-facts.md",
356
+ "docs/kfd-support.md",
351
357
  "docs/reusable-build-surface.md",
352
358
  "docs/release-candidate.md",
353
359
  "docs/release-governance.md",
@@ -394,6 +400,7 @@ function buildSiteBundle() {
394
400
  docs: [
395
401
  { id: "cli-and-node-package", path: "docs/cli.md", digest: sha256File("docs/cli.md") },
396
402
  { id: "build-facts", path: "docs/build-facts.md", digest: sha256File("docs/build-facts.md") },
403
+ { id: "kfd-support", path: "docs/kfd-support.md", digest: sha256File("docs/kfd-support.md") },
397
404
  { id: "readme-badges", path: "docs/readme-badges.md", digest: sha256File("docs/readme-badges.md") },
398
405
  { id: "homebrew", path: "docs/homebrew.md", digest: sha256File("docs/homebrew.md") },
399
406
  { id: "site-bundle-contract", path: "docs/site-bundle-contract.md", digest: sha256File("docs/site-bundle-contract.md") },
@@ -324,70 +324,6 @@ function directoryIndexAliasOperations({ surfaceArtifactRoot, bucket, binding })
324
324
  });
325
325
  }
326
326
 
327
- function cloudfrontDefaultRootObjectOperation({ distributionId, directoryIndex = "index.html" }) {
328
- const normalizedDistribution = String(distributionId || "").trim();
329
- const normalizedDirectoryIndex = normalizeS3Key(directoryIndex || "index.html");
330
- if (!normalizedDistribution || !normalizedDirectoryIndex || normalizedDirectoryIndex.includes("/")) {
331
- return null;
332
- }
333
- return {
334
- action: "ensure-cloudfront-default-root-object",
335
- surface: "__distribution__",
336
- command: "bash",
337
- args: [
338
- "-euo",
339
- "pipefail",
340
- "-c",
341
- [
342
- 'distribution_id="$1"',
343
- 'root_object="$2"',
344
- 'tmp_dir="$(mktemp -d)"',
345
- 'trap \'rm -rf "$tmp_dir"\' EXIT',
346
- 'aws cloudfront get-distribution-config --id "$distribution_id" > "$tmp_dir/distribution.json"',
347
- 'etag="$(jq -r \'.ETag\' "$tmp_dir/distribution.json")"',
348
- 'current="$(jq -r \'.DistributionConfig.DefaultRootObject // ""\' "$tmp_dir/distribution.json")"',
349
- 'if [ "$current" = "$root_object" ]; then',
350
- ' echo "cloudfront default root object already $root_object for $distribution_id"',
351
- ' exit 0',
352
- 'fi',
353
- 'jq --arg root "$root_object" \'.DistributionConfig | .DefaultRootObject = $root\' "$tmp_dir/distribution.json" > "$tmp_dir/distribution-config.json"',
354
- 'aws cloudfront update-distribution --id "$distribution_id" --if-match "$etag" --distribution-config "file://$tmp_dir/distribution-config.json" >/dev/null',
355
- 'aws cloudfront wait distribution-deployed --id "$distribution_id"',
356
- 'echo "cloudfront default root object set to $root_object for $distribution_id"',
357
- ].join("\n"),
358
- "buildchain-cloudfront-default-root-object",
359
- normalizedDistribution,
360
- normalizedDirectoryIndex,
361
- ],
362
- routing: {
363
- contract: "kungfu-buildchain-web-surface-cloudfront-default-root-object",
364
- distributionId: normalizedDistribution,
365
- defaultRootObject: normalizedDirectoryIndex,
366
- reason: "Surface host root requests must resolve to index.html before host/path prefix rewrite reaches the S3 origin.",
367
- },
368
- };
369
- }
370
-
371
- function cloudfrontDefaultRootObjectOperations({ bindings }) {
372
- const seen = new Set();
373
- return (bindings || []).flatMap((binding) => {
374
- if (binding.directoryIndexResolution === false || !binding.distributionId) {
375
- return [];
376
- }
377
- const directoryIndex = binding.directoryIndex || "index.html";
378
- const key = `${binding.distributionId}:${directoryIndex}`;
379
- if (seen.has(key)) {
380
- return [];
381
- }
382
- seen.add(key);
383
- const operation = cloudfrontDefaultRootObjectOperation({
384
- distributionId: binding.distributionId,
385
- directoryIndex,
386
- });
387
- return operation ? [operation] : [];
388
- });
389
- }
390
-
391
327
  function defaultCommandRunner({ command, args, stdin = "" }) {
392
328
  const result = spawnSync(command, args, {
393
329
  encoding: "utf8",
@@ -1006,7 +942,6 @@ export function applyWebSurfaceDeploy({
1006
942
  }
1007
943
  }
1008
944
  const operations = [
1009
- ...cloudfrontDefaultRootObjectOperations({ bindings }),
1010
945
  ...bindings.flatMap((binding) => deployBindingOperations({
1011
946
  artifactRoot,
1012
947
  deployConfig,
@@ -1495,12 +1430,6 @@ function deployBindingOperations({ artifactRoot, deployConfig, manifest, binding
1495
1430
  function planAdapterSteps(adapter, deployConfig, manifest) {
1496
1431
  if (adapter === "aws-s3-cloudfront") {
1497
1432
  return [
1498
- ...cloudfrontDefaultRootObjectOperations({ bindings: manifest.surfaceBindings }).map((operation) => ({
1499
- action: operation.action,
1500
- surface: operation.surface,
1501
- distribution: operation.routing.distributionId,
1502
- defaultRootObject: operation.routing.defaultRootObject,
1503
- })),
1504
1433
  ...manifest.surfaceBindings.flatMap((binding) => [
1505
1434
  {
1506
1435
  action: "sync-static-artifact",
@@ -50,10 +50,59 @@ function readJsonFileArg(name) {
50
50
 
51
51
  function assertApplySucceeded(result) {
52
52
  if (result.status === "failed") {
53
+ printApplyFailureSummary(result);
53
54
  throw new Error(`web-surface ${result.contract} failed; see apply result for operation details`);
54
55
  }
55
56
  }
56
57
 
58
+ function operationFailureSummary(result) {
59
+ const failed = (result.operations || []).find((operation) => operation.status === "failed");
60
+ if (!failed) {
61
+ return "";
62
+ }
63
+ const stderr = String(failed.stderr || "").trim();
64
+ const stdout = String(failed.stdout || "").trim();
65
+ return [
66
+ `web-surface failed operation: ${failed.action || "unknown"}`,
67
+ `surface: ${failed.surface || "unknown"}`,
68
+ failed.command ? `command: ${failed.command} ${(failed.args || []).join(" ")}` : "",
69
+ failed.exitCode !== null && failed.exitCode !== undefined ? `exitCode: ${failed.exitCode}` : "",
70
+ stderr ? `stderr: ${stderr}` : "",
71
+ stdout ? `stdout: ${stdout}` : "",
72
+ ].filter(Boolean).join("\n");
73
+ }
74
+
75
+ function printApplyFailureSummary(result) {
76
+ const summary = operationFailureSummary(result);
77
+ if (summary) {
78
+ console.error(summary);
79
+ }
80
+ }
81
+
82
+ function writeFailureResult({ output, mode, cwd, error }) {
83
+ if (!output) {
84
+ return null;
85
+ }
86
+ const resolvedOutput = path.resolve(output);
87
+ if (fs.existsSync(resolvedOutput)) {
88
+ return JSON.parse(fs.readFileSync(resolvedOutput, "utf8"));
89
+ }
90
+ const result = {
91
+ schemaVersion: 1,
92
+ contract: mode === "cleanup-apply"
93
+ ? "kungfu-buildchain-web-surface-cleanup-apply"
94
+ : "kungfu-buildchain-web-surface-deploy-apply",
95
+ status: "failed",
96
+ error: {
97
+ message: String(error?.message || error),
98
+ },
99
+ cwd,
100
+ generatedAt: new Date().toISOString(),
101
+ };
102
+ writeJson(result, resolvedOutput);
103
+ return result;
104
+ }
105
+
57
106
  export function webSurfaceCli() {
58
107
  const mode = readArg("mode", process.env.BUILDCHAIN_WEB_SURFACE_MODE || "validate");
59
108
  const cwd = readArg("cwd", process.env.BUILDCHAIN_WORKDIR || process.cwd());
@@ -120,34 +169,39 @@ export function webSurfaceCli() {
120
169
  "published-at",
121
170
  process.env.BUILDCHAIN_SITE_PUBLISHED_AT || process.env.BUILDCHAIN_SURFACE_PUBLISHED_AT || "",
122
171
  );
123
- const result = applyWebSurfaceDeploy({
124
- cwd,
125
- channel,
126
- alias,
127
- sourceSha,
128
- artifactHash: readArg("artifact-hash", process.env.BUILDCHAIN_WEB_SURFACE_ARTIFACT_HASH || ""),
129
- artifactPath: readArg("artifact-path", process.env.BUILDCHAIN_WEB_SURFACE_ARTIFACT_PATH || ""),
130
- plan,
131
- dryRun: readBooleanArg("dry-run", true),
132
- actor: readArg("actor", process.env.GITHUB_ACTOR || ""),
133
- runId: readArg("run-id", process.env.GITHUB_RUN_ID || ""),
134
- ...(publishedAt ? { appliedAt: publishedAt } : {}),
135
- });
136
- writeJson(result, output);
137
- writeGitHubOutputs({
138
- "web-surface-channel": result.channel,
139
- "web-surface-alias": result.alias,
140
- "web-surface-url": result.url,
141
- "web-surface-urls-json": JSON.stringify(result.urls || {}),
142
- "web-surface-artifact-hash": result.artifactHash,
143
- "web-surface-apply-mode": result.applyMode,
144
- "web-surface-apply-status": result.status,
145
- "web-surface-manifest-json": JSON.stringify(result.manifest),
146
- "web-surface-bindings-json": JSON.stringify(result.surfaceBindings || []),
147
- "web-surface-apply-result-json": JSON.stringify(result),
148
- });
149
- assertApplySucceeded(result);
150
- return result;
172
+ try {
173
+ const result = applyWebSurfaceDeploy({
174
+ cwd,
175
+ channel,
176
+ alias,
177
+ sourceSha,
178
+ artifactHash: readArg("artifact-hash", process.env.BUILDCHAIN_WEB_SURFACE_ARTIFACT_HASH || ""),
179
+ artifactPath: readArg("artifact-path", process.env.BUILDCHAIN_WEB_SURFACE_ARTIFACT_PATH || ""),
180
+ plan,
181
+ dryRun: readBooleanArg("dry-run", true),
182
+ actor: readArg("actor", process.env.GITHUB_ACTOR || ""),
183
+ runId: readArg("run-id", process.env.GITHUB_RUN_ID || ""),
184
+ ...(publishedAt ? { appliedAt: publishedAt } : {}),
185
+ });
186
+ writeJson(result, output);
187
+ writeGitHubOutputs({
188
+ "web-surface-channel": result.channel,
189
+ "web-surface-alias": result.alias,
190
+ "web-surface-url": result.url,
191
+ "web-surface-urls-json": JSON.stringify(result.urls || {}),
192
+ "web-surface-artifact-hash": result.artifactHash,
193
+ "web-surface-apply-mode": result.applyMode,
194
+ "web-surface-apply-status": result.status,
195
+ "web-surface-manifest-json": JSON.stringify(result.manifest),
196
+ "web-surface-bindings-json": JSON.stringify(result.surfaceBindings || []),
197
+ "web-surface-apply-result-json": JSON.stringify(result),
198
+ });
199
+ assertApplySucceeded(result);
200
+ return result;
201
+ } catch (error) {
202
+ writeFailureResult({ output, mode, cwd, error });
203
+ throw error;
204
+ }
151
205
  }
152
206
 
153
207
  if (mode === "production-preflight") {