@kungfu-tech/buildchain 2.3.0 → 2.3.1-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/AGENTS.md +11 -0
- package/CONTRIBUTING.md +12 -2
- package/README.md +21 -0
- package/actions/promote-buildchain-ref/README.md +39 -1
- package/actions/run-lifecycle/README.md +20 -0
- package/bin/buildchain.mjs +253 -8
- package/dist/site/cli-registry.json +5 -0
- package/dist/site/release-provenance.json +1 -0
- package/docs/MAP.md +4 -0
- package/docs/cli.md +125 -2
- package/docs/install.md +25 -1
- package/docs/publish-transaction.md +10 -0
- package/docs/release-passport.md +35 -0
- package/docs/reusable-build-surface.md +152 -3
- package/docs/toolkit-observability.md +235 -0
- package/docs/web-surface-deployments.md +35 -2
- package/fixtures/libnode-shaped/README.md +2 -1
- package/package.json +2 -1
- package/packages/core/README.md +22 -0
- package/packages/core/buildchain-config.js +42 -0
- package/packages/core/diagnostics.js +1392 -0
- package/packages/core/index.js +32 -0
- package/packages/core/logging.js +51 -0
- package/packages/core/release-passport.js +581 -13
- package/scripts/aggregate-build-summary.mjs +30 -0
- package/scripts/aggregate-diagnostics-summary.mjs +54 -0
- package/scripts/check-inventory.mjs +19 -0
- package/scripts/generate-site-bundle.mjs +1 -0
- package/scripts/init-repo.mjs +14 -0
- package/scripts/npm-publish-dry-run.mjs +6 -0
- package/scripts/run-lifecycle-core.mjs +391 -17
- package/scripts/run-lifecycle.mjs +16 -0
- package/scripts/runtime-ref-core.mjs +109 -0
- package/scripts/web-surface-core.mjs +8 -0
- package/scripts/web-surface.mjs +4 -0
|
@@ -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
|
};
|
package/scripts/init-repo.mjs
CHANGED
|
@@ -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
|
}
|