@kungfu-tech/buildchain 2.3.1-alpha.0 → 2.3.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.
@@ -47,13 +47,42 @@ export function aggregateBuildSummaryCli() {
47
47
  consumerVersion: readEnv("BUILDCHAIN_PUBLISH_SOURCE_CONSUMER_VERSION", ""),
48
48
  releaseManifest: readEnv("BUILDCHAIN_RELEASE_MANIFEST_JSON", ""),
49
49
  },
50
+ runtime: {
51
+ workflowShellRef: readEnv("BUILDCHAIN_WORKFLOW_SHELL_REF", ""),
52
+ requestedRef: readEnv("BUILDCHAIN_RUNTIME_REQUESTED_REF", ""),
53
+ ref: readEnv("BUILDCHAIN_RUNTIME_REF", ""),
54
+ sha: readEnv("BUILDCHAIN_RUNTIME_SHA", ""),
55
+ class: readEnv("BUILDCHAIN_RUNTIME_CLASS", ""),
56
+ override: readEnv("BUILDCHAIN_RUNTIME_OVERRIDE", "false") === "true",
57
+ trustDecision: readEnv("BUILDCHAIN_RUNTIME_TRUST_DECISION", ""),
58
+ rollbackRef: readEnv("BUILDCHAIN_ROLLBACK_REF", ""),
59
+ },
50
60
  platformCount: manifests.length,
51
61
  fileCount: manifests.reduce((sum, manifest) => sum + Number(manifest.summary?.fileCount || 0), 0),
52
62
  totalBytes: manifests.reduce((sum, manifest) => sum + Number(manifest.summary?.totalBytes || 0), 0),
63
+ observability: {
64
+ lifecycle: {
65
+ stages: manifests.reduce((acc, manifest) => {
66
+ for (const [stage, value] of Object.entries(manifest.observability?.lifecycle?.stages || {})) {
67
+ acc[stage] = acc[stage] || { durationMs: 0, eventCount: 0 };
68
+ acc[stage].durationMs += Number(value.durationMs || 0);
69
+ acc[stage].eventCount += Number(value.eventCount || 0);
70
+ }
71
+ return acc;
72
+ }, {}),
73
+ topSlowSpans: manifests
74
+ .flatMap((manifest) => manifest.observability?.lifecycle?.topSlowSpans || [])
75
+ .sort((left, right) => Number(right.durationMs || 0) - Number(left.durationMs || 0))
76
+ .slice(0, 10),
77
+ warningCount: manifests.reduce((sum, manifest) => sum + Number(manifest.observability?.lifecycle?.warningCount || 0), 0),
78
+ errorCount: manifests.reduce((sum, manifest) => sum + Number(manifest.observability?.lifecycle?.errorCount || 0), 0),
79
+ },
80
+ },
53
81
  platforms: manifests.map((manifest, index) => ({
54
82
  artifactName: manifest.artifactName,
55
83
  platform: manifest.platform,
56
84
  summary: manifest.summary,
85
+ observability: manifest.observability,
57
86
  expectedArtifacts: manifest.expectedArtifacts,
58
87
  manifestPath: manifestFiles[index],
59
88
  })),
@@ -73,6 +102,7 @@ export function aggregateBuildSummaryCli() {
73
102
  totalBytes: summary.totalBytes,
74
103
  publishGate: summary.publishGate,
75
104
  publishSource: summary.publishSource,
105
+ runtime: summary.runtime,
76
106
  }),
77
107
  });
78
108
  return summary;
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import { findJsonFiles, writeGitHubOutputs } from "./build-contract-core.mjs";
6
+ import {
7
+ formatDiagnosticsSummaryTable,
8
+ summarizeDiagnosticsArtifacts,
9
+ } from "../packages/core/diagnostics.js";
10
+
11
+ function readEnv(name, fallback = "") {
12
+ return process.env[name] || fallback;
13
+ }
14
+
15
+ export function aggregateDiagnosticsSummaryCli() {
16
+ const inputRoot = path.resolve(readEnv("BUILDCHAIN_DIAGNOSTICS_INPUT", ".buildchain/downloaded-diagnostics"));
17
+ const outputPath = path.resolve(readEnv("BUILDCHAIN_DIAGNOSTICS_OUTPUT", ".buildchain/artifacts/diagnostics-summary.json"));
18
+ const expectedPlatformCount = Number(readEnv("BUILDCHAIN_PLATFORM_COUNT", "0"));
19
+ const diagnosticsFiles = findJsonFiles(inputRoot)
20
+ .filter((file) => path.basename(file) === "diagnostics.json")
21
+ .sort();
22
+ if (expectedPlatformCount > 0 && diagnosticsFiles.length !== expectedPlatformCount) {
23
+ throw new Error(
24
+ `expected ${expectedPlatformCount} platform diagnostics artifacts, found ${diagnosticsFiles.length} under ${inputRoot}`,
25
+ );
26
+ }
27
+ const summary = summarizeDiagnosticsArtifacts(diagnosticsFiles);
28
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
29
+ fs.writeFileSync(outputPath, `${JSON.stringify(summary, null, 2)}\n`);
30
+ process.stdout.write(`${formatDiagnosticsSummaryTable(summary)}\n`);
31
+ writeGitHubOutputs({
32
+ "diagnostics-summary-path": path.relative(process.cwd(), outputPath).split(path.sep).join("/"),
33
+ "diagnostics-platform-count": String(summary.count),
34
+ "diagnostics-summary-json": JSON.stringify({
35
+ contract: summary.contract,
36
+ count: summary.count,
37
+ totalWarningCount: summary.totalWarningCount,
38
+ totalErrorCount: summary.totalErrorCount,
39
+ diagnosticsManifestWarningCount: summary.diagnosticsManifestWarningCount,
40
+ diagnosticsContractWarningCount: summary.diagnosticsContractWarningCount,
41
+ slowestPlatforms: summary.slowestPlatforms,
42
+ }),
43
+ });
44
+ return summary;
45
+ }
46
+
47
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
48
+ try {
49
+ aggregateDiagnosticsSummaryCli();
50
+ } catch (error) {
51
+ console.error(`::error::${String(error.message || error).replace(/\r?\n/g, "%0A")}`);
52
+ process.exitCode = 1;
53
+ }
54
+ }
@@ -66,6 +66,9 @@ if (rootPackage.bin?.buildchain !== "./bin/buildchain.mjs") {
66
66
  if (rootPackage.exports?.["."] !== "./packages/core/index.js") {
67
67
  throw new Error("root package must export packages/core/index.js");
68
68
  }
69
+ if (rootPackage.exports?.["./diagnostics"] !== "./packages/core/diagnostics.js") {
70
+ throw new Error("root package must export @kungfu-tech/buildchain/diagnostics");
71
+ }
69
72
  if (rootPackage.exports?.["./logging"] !== "./packages/core/logging.js") {
70
73
  throw new Error("root package must export @kungfu-tech/buildchain/logging");
71
74
  }
@@ -91,6 +94,8 @@ for (const expectedFile of ["dist/site/", "docs/install.md", "docs/binary-distri
91
94
  const cliSource = fs.readFileSync(path.join(root, "bin/buildchain.mjs"), "utf8");
92
95
  const coreIndexSource = fs.readFileSync(path.join(root, "packages/core/index.js"), "utf8");
93
96
  const versioningDoc = fs.readFileSync(path.join(root, "docs/versioning.md"), "utf8");
97
+ const cliDoc = fs.readFileSync(path.join(root, "docs/cli.md"), "utf8");
98
+ const installDoc = fs.readFileSync(path.join(root, "docs/install.md"), "utf8");
94
99
  if (!cliSource.startsWith("#!/usr/bin/env node")) {
95
100
  throw new Error("bin/buildchain.mjs must be executable with a node shebang");
96
101
  }
@@ -100,6 +105,9 @@ if (commonJsSourcePattern.test(cliSource)) {
100
105
  if (!coreIndexSource.includes("verifyBuildchainLogEvents")) {
101
106
  throw new Error("packages/core/index.js must export verifyBuildchainLogEvents");
102
107
  }
108
+ if (!coreIndexSource.includes("collectBuildchainDiagnostics")) {
109
+ throw new Error("packages/core/index.js must export collectBuildchainDiagnostics");
110
+ }
103
111
  for (const requiredSnippet of [
104
112
  "Release passport and binary distribution are a minor surface.",
105
113
  "`v2.2`",
@@ -110,6 +118,17 @@ for (const requiredSnippet of [
110
118
  throw new Error(`versioning doc missing required snippet: ${requiredSnippet}`);
111
119
  }
112
120
  }
121
+ for (const [docName, docSource] of Object.entries({ "docs/cli.md": cliDoc, "docs/install.md": installDoc })) {
122
+ for (const requiredSnippet of [
123
+ "minimumReleaseAgeExclude",
124
+ "@kungfu-tech/buildchain@2.2.5",
125
+ "package/version-specific",
126
+ ]) {
127
+ if (!docSource.includes(requiredSnippet)) {
128
+ throw new Error(`${docName} missing fresh Buildchain package pin guidance: ${requiredSnippet}`);
129
+ }
130
+ }
131
+ }
113
132
  const releaseLineDryRunScript = fs.readFileSync(path.join(root, "scripts/release-line-dry-run.mjs"), "utf8");
114
133
  const standaloneBinaryScript = fs.readFileSync(path.join(root, "scripts/build-standalone-binary.mjs"), "utf8");
115
134
  for (const requiredSnippet of [
@@ -69,6 +69,7 @@ function buildSiteBundle() {
69
69
  { id: "collect-github-release", usage: "buildchain collect github-release --tag <tag>", purpose: "Collect release assets into a release passport." },
70
70
  { id: "verify-release-passport", usage: "buildchain verify release-passport <file-or-url>", purpose: "Fail closed unless a release passport and its evidence are complete." },
71
71
  { id: "logging", usage: "buildchain log|mark|span|verify observability-log", purpose: "Emit timestamped build events, summarize logs, and enforce required phases." },
72
+ { id: "diagnostics-summary", usage: "buildchain diagnostics summary <diagnostics.json>...", purpose: "Summarize small diagnostics artifacts into JSON and a cross-platform lifecycle timing table." },
72
73
  { id: "npm-dry-run", usage: "buildchain npm dry-run --json", purpose: "Verify npm publish shape before a release transaction." },
73
74
  ],
74
75
  };
@@ -112,6 +112,13 @@ commands = [
112
112
  commands = [
113
113
  "ctest --test-dir build --output-on-failure",
114
114
  ]
115
+
116
+ [diagnostics.native]
117
+ enabled = true
118
+ sample_process_tree = false
119
+ compiler_cache = "auto"
120
+ expected_tools = ["ccache", "sccache", "clang", "cl", "cmake", "ninja"]
121
+ artifact_dirs = ["build", "dist"]
115
122
  `;
116
123
  }
117
124
 
@@ -209,6 +216,12 @@ function workflowYaml({ runnerPreset, artifactName }) {
209
216
  return `name: Build
210
217
 
211
218
  on:
219
+ workflow_dispatch:
220
+ inputs:
221
+ buildchain-ref:
222
+ description: "Temporary Buildchain runtime ref for trusted manual validation"
223
+ required: false
224
+ default: ""
212
225
  pull_request:
213
226
  push:
214
227
  branches:
@@ -225,6 +238,7 @@ jobs:
225
238
  uses: ${BUILDCHAIN_WORKFLOW_REF}
226
239
  with:
227
240
  working-directory: "."
241
+ buildchain-ref: \${{ inputs.buildchain-ref || '' }}
228
242
  runner-preset: "${runnerPreset}"
229
243
  artifact-name-template: "${artifactName}"
230
244
  artifact-paths: |
@@ -50,6 +50,11 @@ function parsePackResult(stdout) {
50
50
  const pack = Array.isArray(parsed) ? parsed[0] : parsed;
51
51
  const files = Array.isArray(pack?.files) ? pack.files : [];
52
52
  const bin = files.find((file) => file.path === "bin/buildchain.mjs");
53
+ const fileEntries = files.map((file) => ({
54
+ path: file.path,
55
+ size: file.size,
56
+ mode: file.mode,
57
+ }));
53
58
  return {
54
59
  filename: pack?.filename || "",
55
60
  name: pack?.name || "",
@@ -58,6 +63,7 @@ function parsePackResult(stdout) {
58
63
  unpackedSize: pack?.unpackedSize || 0,
59
64
  entryCount: pack?.entryCount || files.length,
60
65
  bundled: pack?.bundled || [],
66
+ files: fileEntries,
61
67
  binMode: bin?.mode,
62
68
  };
63
69
  }