@kungfu-tech/buildchain 2.3.1-alpha.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
package/AGENTS.md
CHANGED
|
@@ -36,6 +36,10 @@ See [`docs/cli.md`](docs/cli.md), [`docs/lifecycle-protocol.md`](docs/lifecycle-
|
|
|
36
36
|
and [`docs/reusable-build-surface.md`](docs/reusable-build-surface.md) for the
|
|
37
37
|
consumer contract.
|
|
38
38
|
|
|
39
|
+
For temporary validation of an unreleased Buildchain runtime, keep the committed
|
|
40
|
+
workflow ref on `@v2` and use the trusted `workflow_dispatch` `buildchain-ref`
|
|
41
|
+
pass-through. See [`docs/runtime-train-validation.md`](docs/runtime-train-validation.md).
|
|
42
|
+
|
|
39
43
|
## Building this repo
|
|
40
44
|
|
|
41
45
|
Buildchain is a pnpm workspace running on Node 24:
|
|
@@ -52,6 +56,13 @@ and rebuilds every action bundle.
|
|
|
52
56
|
## Proposing changes
|
|
53
57
|
|
|
54
58
|
- Open pull requests against the relevant `dev/*` channel branch.
|
|
59
|
+
- If a Buildchain change needs downstream validation before stable refs move,
|
|
60
|
+
publish a `train/v2/v2.3/<capability>` ref and include the validation request
|
|
61
|
+
described in [`docs/runtime-train-validation.md`](docs/runtime-train-validation.md).
|
|
62
|
+
After validation succeeds, do not leave the train as a pending merge item:
|
|
63
|
+
merge the pull request into the active `dev/*` mainline and run the requested
|
|
64
|
+
alpha or release promotion. The train may remain for a retention window as a
|
|
65
|
+
fast-use and rollback channel; periodic cleanup handles old trains.
|
|
55
66
|
- Write commit messages and PR descriptions in English, using lightweight
|
|
56
67
|
[Conventional Commits](https://www.conventionalcommits.org/)
|
|
57
68
|
(`type(scope): summary`).
|
package/CONTRIBUTING.md
CHANGED
|
@@ -103,14 +103,24 @@ release/vX/vX.Y -> publish-gate/major
|
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
- Open normal changes against the relevant `dev/*` branch.
|
|
106
|
+
- When a Buildchain change needs downstream validation before stable refs move,
|
|
107
|
+
publish a temporary runtime train ref such as
|
|
108
|
+
`train/v2/v2.3/<capability>` and ask consumers to run trusted
|
|
109
|
+
`workflow_dispatch` with `buildchain-ref` set to that train. Keep the pull
|
|
110
|
+
request against the `dev/*` branch; the train is only a validation pointer,
|
|
111
|
+
not a pending merge target. After validation succeeds, merge into the active
|
|
112
|
+
`dev/*` mainline and run the requested alpha or release promotion. The train
|
|
113
|
+
may remain for a retention window as a fast-use and rollback channel; old
|
|
114
|
+
trains are cleaned by a separate periodic cleanup task.
|
|
106
115
|
- Merging into `alpha/*`, `release/*`, or `publish-gate/major` expresses a
|
|
107
116
|
release intent. Buildchain promotion then creates version-state commits,
|
|
108
117
|
exact tags, floating tags, npm publish evidence, and next-alpha state.
|
|
109
118
|
- Manual promotion dispatch is dry-run only; non-dry-run promotion follows a
|
|
110
119
|
successful protected workflow path.
|
|
111
120
|
|
|
112
|
-
See [`docs/release-governance.md`](docs/release-governance.md)
|
|
113
|
-
[`docs/release-flow.md`](docs/release-flow.md)
|
|
121
|
+
See [`docs/release-governance.md`](docs/release-governance.md),
|
|
122
|
+
[`docs/release-flow.md`](docs/release-flow.md), and
|
|
123
|
+
[`docs/runtime-train-validation.md`](docs/runtime-train-validation.md).
|
|
114
124
|
|
|
115
125
|
## License
|
|
116
126
|
|
package/README.md
CHANGED
|
@@ -85,6 +85,27 @@ const report = verifyBuildchainLogEvents({
|
|
|
85
85
|
The package also ships `dist/site/` as the Buildchain-owned fact source for
|
|
86
86
|
`buildchain.libkungfu.dev`.
|
|
87
87
|
|
|
88
|
+
Native build consumers can import the diagnostics toolkit instead of copying
|
|
89
|
+
repository-local probes:
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
import {
|
|
93
|
+
collectBuildchainDiagnostics,
|
|
94
|
+
collectRunnerDiagnostics,
|
|
95
|
+
writeDiagnosticsArtifact,
|
|
96
|
+
} from "@kungfu-tech/buildchain/diagnostics";
|
|
97
|
+
|
|
98
|
+
writeDiagnosticsArtifact(".buildchain/artifacts/diagnostics.json", {
|
|
99
|
+
contract: "consumer-build-diagnostics",
|
|
100
|
+
buildchain: collectBuildchainDiagnostics({ cwd: process.cwd() }),
|
|
101
|
+
runner: collectRunnerDiagnostics(),
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`buildchain lifecycle run` writes a small `diagnostics.json` next to the
|
|
106
|
+
platform manifest. It includes lifecycle-wide observability, runner/tool/cache
|
|
107
|
+
snapshots, Git state, and links to the larger manifest and artifact outputs.
|
|
108
|
+
|
|
88
109
|
## Use Buildchain
|
|
89
110
|
|
|
90
111
|
Bootstrap a repository:
|
|
@@ -101,6 +101,31 @@ trusted channel workflow:
|
|
|
101
101
|
]
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
For anchored/manual package repositories that build through the reusable
|
|
105
|
+
workflow, keep the publish entrypoint on the `publish-gate/*` source-lock
|
|
106
|
+
contract:
|
|
107
|
+
|
|
108
|
+
```yaml
|
|
109
|
+
- uses: kungfu-systems/buildchain/actions/promote-buildchain-ref@v2
|
|
110
|
+
with:
|
|
111
|
+
token: ${{ secrets.BUILDCHAIN_PROMOTION_TOKEN }}
|
|
112
|
+
sha: ${{ needs.build.outputs.publish-source-sha }}
|
|
113
|
+
target-ref: release/v22/v22.22
|
|
114
|
+
require-publish-source-lock: "true"
|
|
115
|
+
publish-source-ref: ${{ needs.build.outputs.publish-source-ref }}
|
|
116
|
+
publish-source-sha: ${{ needs.build.outputs.publish-source-sha }}
|
|
117
|
+
publish-source-locked: ${{ needs.build.outputs.publish-source-locked }}
|
|
118
|
+
publish-transaction: "true"
|
|
119
|
+
publish-mode: publish-final-version
|
|
120
|
+
publish-auth: trusted-publishing
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`target-ref` remains the channel promotion target that must point at `sha`.
|
|
124
|
+
`publish-source-ref` is the reviewed source-lock branch that authorized this
|
|
125
|
+
specific package publication. Direct `alpha/*` or `release/*` channel refs are
|
|
126
|
+
not valid publish source locks when `require-publish-source-lock` is enabled,
|
|
127
|
+
and a mismatched `publish-source-sha` fails before any promotion or publish side effects begin.
|
|
128
|
+
|
|
104
129
|
When enabled, the action creates or resumes a release transaction keyed by
|
|
105
130
|
repository, version, source SHA, and target ref. It persists that transaction to
|
|
106
131
|
a machine-managed branch under `buildchain/release-state/<version>`, with
|
|
@@ -144,10 +169,23 @@ BUILDCHAIN_PUBLISH_EVIDENCE
|
|
|
144
169
|
The action outputs `transaction-id`, `transaction-state`,
|
|
145
170
|
`transaction-exact-tag`, `transaction-release-sha`, `transaction-state-ref`,
|
|
146
171
|
`transaction-state-sha`, `transaction-state-path`, `publish-evidence-path`, and
|
|
147
|
-
`
|
|
172
|
+
`release-passport-path`, `release-passport-output-dir`,
|
|
173
|
+
`release-passport-state-sha`, and `finalization-needed`.
|
|
174
|
+
`transaction-state-ref` is the durable recovery location.
|
|
175
|
+
`release-passport-state-sha` is the durable ref commit after the generated
|
|
176
|
+
`release-passport/*` files have been uploaded into that recovery ref.
|
|
148
177
|
`finalization-needed=true` means publish evidence is valid, but protected branch
|
|
149
178
|
or ref finalization needs a later promotion run.
|
|
150
179
|
|
|
180
|
+
After a publish transaction reaches `complete`, the action generates the unified
|
|
181
|
+
`buildchain-release-passport` in `.buildchain/release-passport` by default and
|
|
182
|
+
persists those files under `release-passport/` in the durable release-state ref.
|
|
183
|
+
When present, the passport includes the aggregate build summary, platform
|
|
184
|
+
artifact manifests, npm publish evidence, dist-tag promotion evidence, the
|
|
185
|
+
release-state ref, trusted publishing metadata, and the Buildchain transaction
|
|
186
|
+
result. Set `release-passport: "false"` only for a controlled recovery run that
|
|
187
|
+
must skip passport generation.
|
|
188
|
+
|
|
151
189
|
Finalization recovery is anchored to the durable transaction, not to a single
|
|
152
190
|
workflow run SHA. After a generated version-state PR is merged, the current
|
|
153
191
|
channel head can be a merge commit that contains or corresponds to the recorded
|
|
@@ -39,3 +39,23 @@ with:
|
|
|
39
39
|
expected-artifacts-json: >-
|
|
40
40
|
{"minFiles":2,"requiredPaths":["dist/app.tar.gz","dist/checksums.txt"]}
|
|
41
41
|
```
|
|
42
|
+
|
|
43
|
+
For long native builds, set `sample-process-tree: "true"` and pass
|
|
44
|
+
`process-summary-path`. The action wraps either the explicit `command` or the
|
|
45
|
+
configured lifecycle stage with `buildchain sample process-tree`, then embeds
|
|
46
|
+
the produced summary in the lifecycle diagnostics artifact:
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
with:
|
|
50
|
+
stage: build
|
|
51
|
+
sample-process-tree: "true"
|
|
52
|
+
process-summary-path: .buildchain/diagnostics/process-summary.json
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Custom wrappers can still pass an existing sampler report or summary with
|
|
56
|
+
`process-summary-path`; the action reads it after the lifecycle command
|
|
57
|
+
finishes, so commands may generate the file during the same invocation.
|
|
58
|
+
Set `process-summary-required: "false"` when the summary is an optional sidecar,
|
|
59
|
+
for example when a reusable workflow may skip an optional build stage.
|
|
60
|
+
The diagnostics directory also includes `diagnostics-manifest.json`, a compact
|
|
61
|
+
inventory of the diagnostics sidecars with byte counts and sha256 hashes.
|
package/bin/buildchain.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
3
3
|
import crypto from "node:crypto";
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
@@ -24,6 +24,14 @@ import {
|
|
|
24
24
|
explainReleasePassport,
|
|
25
25
|
verifyReleasePassport,
|
|
26
26
|
} from "../packages/core/release-passport.js";
|
|
27
|
+
import {
|
|
28
|
+
BUILDCHAIN_PROCESS_SAMPLE_REPORT_CONTRACT,
|
|
29
|
+
formatDiagnosticsSummaryTable,
|
|
30
|
+
startProcessSampler,
|
|
31
|
+
summarizeDiagnosticsArtifacts,
|
|
32
|
+
summarizeProcessSamples,
|
|
33
|
+
validateAnchoredPackageRelease,
|
|
34
|
+
} from "../packages/core/diagnostics.js";
|
|
27
35
|
|
|
28
36
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
29
37
|
const embeddedPackageVersion = process.env.BUILDCHAIN_EMBEDDED_PACKAGE_VERSION || "";
|
|
@@ -39,6 +47,7 @@ function usage() {
|
|
|
39
47
|
[--require-lifecycle-stages <comma-list>]
|
|
40
48
|
buildchain lifecycle run <stage> [--cwd <dir>] [--required]
|
|
41
49
|
[--artifact-name <name>] [--artifact-path <path>]...
|
|
50
|
+
[--process-summary <json>]
|
|
42
51
|
buildchain npm dry-run [--cwd <dir>] [--expected-tag <tag>] [--registry <url>]
|
|
43
52
|
[--dist-tag <tag>] [--skip-npm-publish-dry-run] [--json]
|
|
44
53
|
buildchain release --dry-run --target-ref <ref> [--sha <sha>] [--source-ref <ref>]
|
|
@@ -51,7 +60,17 @@ function usage() {
|
|
|
51
60
|
buildchain transaction inspect ...
|
|
52
61
|
buildchain collect github-release --tag <tag> [--repository <owner/repo>]
|
|
53
62
|
[--assets-dir <dir>] [--assets-json <json-or-path>]
|
|
54
|
-
[--release-json <json-or-path>] [--
|
|
63
|
+
[--release-json <json-or-path>] [--package-set-json <json-or-path>]
|
|
64
|
+
[--product-name <name>]
|
|
65
|
+
[--publish-evidence-json <json-or-path>]
|
|
66
|
+
[--trusted-publishing-json <json-or-path>]
|
|
67
|
+
[--transaction-json <json-or-path>]
|
|
68
|
+
[--anchor-manifest-json <json-or-path>]
|
|
69
|
+
[--build-summary-json <json-or-path>]
|
|
70
|
+
[--platform-manifest-json <json-or-path>]...
|
|
71
|
+
[--dist-tag-evidence-json <json-or-path>]
|
|
72
|
+
[--release-extra-json <json-or-path>]
|
|
73
|
+
[--publish-json <json-or-path>] [--output-dir <dir>] [--json]
|
|
55
74
|
buildchain verify release-passport <file-or-url> [--json]
|
|
56
75
|
buildchain verify observability-log <jsonl> [--min-events <n>]
|
|
57
76
|
[--require-phase <csv>]
|
|
@@ -59,17 +78,23 @@ function usage() {
|
|
|
59
78
|
[--require-event <csv>] [--allow-errors] [--json]
|
|
60
79
|
buildchain explain release --passport <file-or-url> [--for human|agent] [--json]
|
|
61
80
|
buildchain inspect release --passport <file-or-url> [--json]
|
|
62
|
-
buildchain doctor [--cwd <dir>] [--json]
|
|
81
|
+
buildchain doctor [--cwd <dir>] [--require-publish-source-lock] [--json]
|
|
63
82
|
buildchain log <info|warn|error> --event <name> [--phase <phase>]
|
|
64
83
|
[--component <name>] [--source <name>] [--attribute key=value]...
|
|
65
84
|
[--path <jsonl>] [--json]
|
|
66
85
|
buildchain log summary [--path <jsonl>] [--json]
|
|
86
|
+
buildchain diagnostics summary <diagnostics.json>... [--artifact <file>]...
|
|
87
|
+
[--output <file>] [--json]
|
|
88
|
+
buildchain sample process-tree [--interval-ms <n>] [--label <name>]
|
|
89
|
+
[--output <jsonl>] [--summary-output <json>]
|
|
90
|
+
[--requested-parallelism <n>] [--json]
|
|
91
|
+
-- <command> [args...]
|
|
67
92
|
buildchain mark --event <name> [--phase <phase>] [--component <name>]
|
|
68
93
|
[--attribute key=value]... [--path <jsonl>] [--json]
|
|
69
94
|
buildchain span --event <name> [--phase <phase>] [--component <name>]
|
|
70
95
|
[--path <jsonl>] -- <command> [args...]
|
|
71
96
|
buildchain web-surface ...
|
|
72
|
-
buildchain publish-source <lock|manifest|verify-lock> ...
|
|
97
|
+
buildchain publish-source <lock|manifest|verify-lock|validate-anchored-release> ...
|
|
73
98
|
buildchain build-contract ...
|
|
74
99
|
|
|
75
100
|
Examples:
|
|
@@ -140,14 +165,15 @@ function checkStatus(ok, id, message, details = {}) {
|
|
|
140
165
|
return { id, status: ok ? "pass" : "fail", message, details };
|
|
141
166
|
}
|
|
142
167
|
|
|
143
|
-
function runDoctor({ cwd = process.cwd() } = {}) {
|
|
168
|
+
function runDoctor({ cwd = process.cwd(), requirePublishSourceLock = false } = {}) {
|
|
144
169
|
const resolvedCwd = path.resolve(cwd);
|
|
145
170
|
const checks = [];
|
|
146
171
|
checks.push(checkStatus(fs.existsSync(resolvedCwd), "cwd.exists", "working directory exists", { cwd: resolvedCwd }));
|
|
172
|
+
let validation;
|
|
147
173
|
try {
|
|
148
|
-
|
|
174
|
+
validation = validateBuildchainConfig(resolvedCwd);
|
|
149
175
|
checks.push(checkStatus(true, "config.valid", "buildchain.toml is valid", {
|
|
150
|
-
projectType: validation.project
|
|
176
|
+
projectType: validation.project?.type || "",
|
|
151
177
|
lifecycleStages: validation.lifecycleStages.map((stage) => stage.name),
|
|
152
178
|
}));
|
|
153
179
|
} catch (error) {
|
|
@@ -168,6 +194,22 @@ function runDoctor({ cwd = process.cwd() } = {}) {
|
|
|
168
194
|
checks.push(checkStatus(fs.existsSync(workflowPath), "workflow.build", "reusable workflow caller exists", {
|
|
169
195
|
path: ".github/workflows/build.yml",
|
|
170
196
|
}));
|
|
197
|
+
if (validation?.version?.strategy === "anchored" && validation.version.next === "manual") {
|
|
198
|
+
const anchored = validateAnchoredPackageRelease({
|
|
199
|
+
cwd: resolvedCwd,
|
|
200
|
+
requirePublishGateSourceLock: requirePublishSourceLock,
|
|
201
|
+
});
|
|
202
|
+
checks.push(checkStatus(
|
|
203
|
+
anchored.ok,
|
|
204
|
+
"anchored-package-release.valid",
|
|
205
|
+
"anchored package release contract is valid",
|
|
206
|
+
{
|
|
207
|
+
contract: anchored.contract,
|
|
208
|
+
summary: anchored.summary,
|
|
209
|
+
checks: anchored.checks,
|
|
210
|
+
},
|
|
211
|
+
));
|
|
212
|
+
}
|
|
171
213
|
return {
|
|
172
214
|
schemaVersion: 1,
|
|
173
215
|
contract: "kungfu-buildchain-doctor",
|
|
@@ -195,6 +237,61 @@ function printJson(value) {
|
|
|
195
237
|
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
196
238
|
}
|
|
197
239
|
|
|
240
|
+
function writeJsonFile(filePath, value) {
|
|
241
|
+
if (!filePath) {
|
|
242
|
+
return "";
|
|
243
|
+
}
|
|
244
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
245
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
246
|
+
return filePath;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function appendJsonLine(filePath, value) {
|
|
250
|
+
if (!filePath) {
|
|
251
|
+
return "";
|
|
252
|
+
}
|
|
253
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
254
|
+
fs.appendFileSync(filePath, `${JSON.stringify(value)}\n`);
|
|
255
|
+
return filePath;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function readIntegerFlag(args, name, fallback = 0) {
|
|
259
|
+
const value = readFlag(args, name, "");
|
|
260
|
+
if (!value) {
|
|
261
|
+
return fallback;
|
|
262
|
+
}
|
|
263
|
+
const parsed = Number(value);
|
|
264
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
265
|
+
throw new Error(`--${name} must be a non-negative integer`);
|
|
266
|
+
}
|
|
267
|
+
return parsed;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function readDiagnosticsArtifactInputs(args) {
|
|
271
|
+
const values = [];
|
|
272
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
273
|
+
const entry = args[index];
|
|
274
|
+
if (entry === "--artifact") {
|
|
275
|
+
const value = args[index + 1];
|
|
276
|
+
if (!value || value.startsWith("--")) {
|
|
277
|
+
throw new Error("buildchain diagnostics summary --artifact requires a file path");
|
|
278
|
+
}
|
|
279
|
+
values.push(value);
|
|
280
|
+
index += 1;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (entry === "--output") {
|
|
284
|
+
index += 1;
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
if (entry === "--json") {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
values.push(entry);
|
|
291
|
+
}
|
|
292
|
+
return values;
|
|
293
|
+
}
|
|
294
|
+
|
|
198
295
|
function packageVersion() {
|
|
199
296
|
if (embeddedPackageVersion) {
|
|
200
297
|
return embeddedPackageVersion;
|
|
@@ -203,6 +300,81 @@ function packageVersion() {
|
|
|
203
300
|
return packageJson.version;
|
|
204
301
|
}
|
|
205
302
|
|
|
303
|
+
async function runProcessTreeSample(sampleArgs = []) {
|
|
304
|
+
const separator = sampleArgs.indexOf("--");
|
|
305
|
+
const optionArgs = separator === -1 ? sampleArgs : sampleArgs.slice(0, separator);
|
|
306
|
+
const commandArgs = separator === -1 ? [] : sampleArgs.slice(separator + 1);
|
|
307
|
+
if (commandArgs.length === 0) {
|
|
308
|
+
throw new Error("usage: buildchain sample process-tree -- <command> [args...]");
|
|
309
|
+
}
|
|
310
|
+
const command = commandArgs[0];
|
|
311
|
+
const args = commandArgs.slice(1);
|
|
312
|
+
const label = readFlag(optionArgs, "label", "process-tree");
|
|
313
|
+
const intervalMs = readIntegerFlag(optionArgs, "interval-ms", 15000);
|
|
314
|
+
const requestedParallelism = readIntegerFlag(optionArgs, "requested-parallelism", 0);
|
|
315
|
+
const outputPath = readFlag(optionArgs, "output", ".buildchain/diagnostics/process-samples.jsonl");
|
|
316
|
+
const summaryOutputPath = readFlag(optionArgs, "summary-output", ".buildchain/diagnostics/process-summary.json");
|
|
317
|
+
const startedAt = Date.now();
|
|
318
|
+
const child = spawn(command, args, {
|
|
319
|
+
cwd: process.cwd(),
|
|
320
|
+
env: process.env,
|
|
321
|
+
stdio: "inherit",
|
|
322
|
+
});
|
|
323
|
+
const sampler = startProcessSampler({
|
|
324
|
+
rootPid: child.pid || process.pid,
|
|
325
|
+
intervalMs,
|
|
326
|
+
label,
|
|
327
|
+
command,
|
|
328
|
+
args,
|
|
329
|
+
env: process.env,
|
|
330
|
+
requestedParallelism,
|
|
331
|
+
onSample(sample) {
|
|
332
|
+
appendJsonLine(outputPath, sample);
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
const result = await new Promise((resolve) => {
|
|
336
|
+
child.on("error", (error) => resolve({ error, status: 1, signal: "" }));
|
|
337
|
+
child.on("close", (status, signal) => resolve({ status, signal: signal || "" }));
|
|
338
|
+
});
|
|
339
|
+
const samples = sampler.stop();
|
|
340
|
+
const summary = summarizeProcessSamples({
|
|
341
|
+
samples,
|
|
342
|
+
command,
|
|
343
|
+
args,
|
|
344
|
+
env: process.env,
|
|
345
|
+
requestedParallelism,
|
|
346
|
+
});
|
|
347
|
+
const report = {
|
|
348
|
+
schemaVersion: 1,
|
|
349
|
+
contract: BUILDCHAIN_PROCESS_SAMPLE_REPORT_CONTRACT,
|
|
350
|
+
label,
|
|
351
|
+
command: path.basename(command),
|
|
352
|
+
argsCount: args.length,
|
|
353
|
+
exit: {
|
|
354
|
+
status: result.status ?? 0,
|
|
355
|
+
signal: result.signal || "",
|
|
356
|
+
error: result.error?.message || "",
|
|
357
|
+
},
|
|
358
|
+
durationMs: Date.now() - startedAt,
|
|
359
|
+
samplesPath: outputPath,
|
|
360
|
+
summaryPath: summaryOutputPath,
|
|
361
|
+
summary,
|
|
362
|
+
};
|
|
363
|
+
writeJsonFile(summaryOutputPath, report);
|
|
364
|
+
if (readBooleanFlag(optionArgs, "json")) {
|
|
365
|
+
printJson(report);
|
|
366
|
+
} else {
|
|
367
|
+
process.stdout.write(`buildchain process sample: ${summary.sampleCount} samples\n`);
|
|
368
|
+
process.stdout.write(`observed concurrency max: ${summary.observedConcurrency.max}\n`);
|
|
369
|
+
process.stdout.write(`wrote: ${outputPath}\n`);
|
|
370
|
+
process.stdout.write(`wrote: ${summaryOutputPath}\n`);
|
|
371
|
+
}
|
|
372
|
+
if (result.error || result.status !== 0) {
|
|
373
|
+
process.exitCode = result.status || 1;
|
|
374
|
+
}
|
|
375
|
+
return report;
|
|
376
|
+
}
|
|
377
|
+
|
|
206
378
|
async function main(argv = process.argv.slice(2)) {
|
|
207
379
|
const [command, ...args] = argv;
|
|
208
380
|
if (!command || command === "-h" || command === "--help" || command === "help") {
|
|
@@ -241,7 +413,10 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
241
413
|
}
|
|
242
414
|
|
|
243
415
|
if (command === "doctor") {
|
|
244
|
-
const result = runDoctor({
|
|
416
|
+
const result = runDoctor({
|
|
417
|
+
cwd: readFlag(args, "cwd", process.cwd()),
|
|
418
|
+
requirePublishSourceLock: readBooleanFlag(args, "require-publish-source-lock"),
|
|
419
|
+
});
|
|
245
420
|
if (readBooleanFlag(args, "json")) {
|
|
246
421
|
printJson(result);
|
|
247
422
|
} else {
|
|
@@ -285,6 +460,46 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
285
460
|
return;
|
|
286
461
|
}
|
|
287
462
|
|
|
463
|
+
if (command === "diagnostics") {
|
|
464
|
+
const [subcommand = "", ...diagnosticsArgs] = args;
|
|
465
|
+
if (subcommand !== "summary") {
|
|
466
|
+
throw new Error("usage: buildchain diagnostics summary <diagnostics.json>...");
|
|
467
|
+
}
|
|
468
|
+
const inputs = readDiagnosticsArtifactInputs(diagnosticsArgs);
|
|
469
|
+
if (inputs.length === 0) {
|
|
470
|
+
throw new Error("buildchain diagnostics summary requires at least one artifact");
|
|
471
|
+
}
|
|
472
|
+
const summary = summarizeDiagnosticsArtifacts(inputs);
|
|
473
|
+
if (summary.count !== inputs.length) {
|
|
474
|
+
throw new Error(`buildchain diagnostics summary read ${summary.count}/${inputs.length} artifacts`);
|
|
475
|
+
}
|
|
476
|
+
const outputPath = readFlag(diagnosticsArgs, "output", "");
|
|
477
|
+
writeJsonFile(outputPath, summary);
|
|
478
|
+
if (readBooleanFlag(diagnosticsArgs, "json")) {
|
|
479
|
+
printJson(summary);
|
|
480
|
+
} else {
|
|
481
|
+
process.stdout.write(`buildchain diagnostics summary: ${summary.count} platforms\n`);
|
|
482
|
+
process.stdout.write(`warnings: ${summary.totalWarningCount} errors: ${summary.totalErrorCount}\n`);
|
|
483
|
+
if (summary.diagnosticsManifestWarningCount) {
|
|
484
|
+
process.stdout.write(`diagnostics manifest warnings: ${summary.diagnosticsManifestWarningCount}\n`);
|
|
485
|
+
}
|
|
486
|
+
process.stdout.write(`${formatDiagnosticsSummaryTable(summary)}\n`);
|
|
487
|
+
if (outputPath) {
|
|
488
|
+
process.stdout.write(`wrote: ${outputPath}\n`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (command === "sample") {
|
|
495
|
+
const [subcommand = "", ...sampleArgs] = args;
|
|
496
|
+
if (subcommand !== "process-tree") {
|
|
497
|
+
throw new Error("usage: buildchain sample process-tree -- <command> [args...]");
|
|
498
|
+
}
|
|
499
|
+
await runProcessTreeSample(sampleArgs);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
288
503
|
if (command === "mark") {
|
|
289
504
|
const eventName = readFlag(args, "event", "");
|
|
290
505
|
if (!eventName) {
|
|
@@ -359,6 +574,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
359
574
|
artifactPaths,
|
|
360
575
|
expectedArtifactsJson: readFlag(lifecycleArgs, "expected-artifacts-json", ""),
|
|
361
576
|
logPath: readFlag(lifecycleArgs, "log-path", process.env.BUILDCHAIN_LOG_PATH || ".buildchain/logs/events.jsonl"),
|
|
577
|
+
processSummaryPath: readFlag(lifecycleArgs, "process-summary", ""),
|
|
362
578
|
workspace: process.cwd(),
|
|
363
579
|
});
|
|
364
580
|
printJson(manifest);
|
|
@@ -446,8 +662,19 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
446
662
|
assetsDir: readFlag(collectArgs, "assets-dir", ""),
|
|
447
663
|
assetsJson: readFlag(collectArgs, "assets-json", ""),
|
|
448
664
|
releaseJson: readFlag(collectArgs, "release-json", ""),
|
|
665
|
+
productName: readFlag(collectArgs, "product-name", "Buildchain"),
|
|
449
666
|
packageName: readFlag(collectArgs, "package-name", "@kungfu-tech/buildchain"),
|
|
450
667
|
packageVersion: readFlag(collectArgs, "package-version", packageVersion()),
|
|
668
|
+
packageSetJson: readFlag(collectArgs, "package-set-json", ""),
|
|
669
|
+
publishEvidenceJson: readFlag(collectArgs, "publish-evidence-json", ""),
|
|
670
|
+
trustedPublishingJson: readFlag(collectArgs, "trusted-publishing-json", ""),
|
|
671
|
+
transactionJson: readFlag(collectArgs, "transaction-json", ""),
|
|
672
|
+
anchorManifestJson: readFlag(collectArgs, "anchor-manifest-json", ""),
|
|
673
|
+
buildSummaryJson: readFlag(collectArgs, "build-summary-json", ""),
|
|
674
|
+
platformManifestJsons: readRepeatedFlag(collectArgs, "platform-manifest-json"),
|
|
675
|
+
distTagEvidenceJson: readFlag(collectArgs, "dist-tag-evidence-json", ""),
|
|
676
|
+
releaseJsonExtra: readFlag(collectArgs, "release-extra-json", ""),
|
|
677
|
+
publishJson: readFlag(collectArgs, "publish-json", ""),
|
|
451
678
|
workflow,
|
|
452
679
|
});
|
|
453
680
|
if (readBooleanFlag(collectArgs, "json")) {
|
|
@@ -562,6 +789,24 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
562
789
|
runScript("verify-publish-source-lock.mjs", publishArgs);
|
|
563
790
|
return;
|
|
564
791
|
}
|
|
792
|
+
if (mode === "validate-anchored-release") {
|
|
793
|
+
const report = validateAnchoredPackageRelease({
|
|
794
|
+
cwd: readFlag(publishArgs, "cwd", process.cwd()),
|
|
795
|
+
requirePublishGateSourceLock: true,
|
|
796
|
+
});
|
|
797
|
+
if (readBooleanFlag(publishArgs, "json")) {
|
|
798
|
+
printJson(report);
|
|
799
|
+
} else {
|
|
800
|
+
process.stdout.write(`anchored release source lock: ${report.ok ? "ok" : "failed"}\n`);
|
|
801
|
+
for (const entry of report.checks) {
|
|
802
|
+
process.stdout.write(`- ${entry.status}: ${entry.id}: ${entry.message}\n`);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
if (!report.ok) {
|
|
806
|
+
process.exitCode = 1;
|
|
807
|
+
}
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
565
810
|
throw new Error(`unsupported publish-source command: ${mode}`);
|
|
566
811
|
}
|
|
567
812
|
|
|
@@ -49,6 +49,11 @@
|
|
|
49
49
|
"usage": "buildchain log|mark|span|verify observability-log",
|
|
50
50
|
"purpose": "Emit timestamped build events, summarize logs, and enforce required phases."
|
|
51
51
|
},
|
|
52
|
+
{
|
|
53
|
+
"id": "diagnostics-summary",
|
|
54
|
+
"usage": "buildchain diagnostics summary <diagnostics.json>...",
|
|
55
|
+
"purpose": "Summarize small diagnostics artifacts into JSON and a cross-platform lifecycle timing table."
|
|
56
|
+
},
|
|
52
57
|
{
|
|
53
58
|
"id": "npm-dry-run",
|
|
54
59
|
"usage": "buildchain npm dry-run --json",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"exports": {
|
|
11
11
|
".": "./packages/core/index.js",
|
|
12
12
|
"./core": "./packages/core/index.js",
|
|
13
|
+
"./diagnostics": "./packages/core/diagnostics.js",
|
|
13
14
|
"./logging": "./packages/core/logging.js",
|
|
14
15
|
"./release-passport": "./packages/core/release-passport.js",
|
|
15
16
|
"./site/buildchain-site.json": "./dist/site/buildchain-site.json",
|
package/docs/MAP.md
CHANGED
|
@@ -35,6 +35,7 @@ running artifact), *use* (consume / extend) - and a **status**:
|
|
|
35
35
|
| How do I add timestamped logs inside build scripts? | [`toolkit-observability.md`](toolkit-observability.md) | use | stable |
|
|
36
36
|
| What package-owned facts should buildchain.libkungfu.dev render? | [`site-bundle-contract.md`](site-bundle-contract.md) | use | stable |
|
|
37
37
|
| How do I call the reusable build workflow? | [`reusable-build-surface.md`](reusable-build-surface.md) | use | stable |
|
|
38
|
+
| How do I validate an unreleased Buildchain runtime train while keeping `@v2`? | [`runtime-train-validation.md`](runtime-train-validation.md) | use | stable |
|
|
38
39
|
| How do I deploy a site/app preview, staging, or production surface? | [`web-surface-deployments.md`](web-surface-deployments.md) | use | stable |
|
|
39
40
|
| How do I use the active actions directly? | [`../actions/validate-config/README.md`](../actions/validate-config/README.md), [`../actions/run-lifecycle/README.md`](../actions/run-lifecycle/README.md), [`../actions/promote-buildchain-ref/README.md`](../actions/promote-buildchain-ref/README.md) | use | stable |
|
|
40
41
|
| What do the fixture repositories demonstrate? | [`../fixtures/libnode-shaped/README.md`](../fixtures/libnode-shaped/README.md), [`../fixtures/publish-transaction-shaped/README.md`](../fixtures/publish-transaction-shaped/README.md), [`../fixtures/web-surface-shaped/README.md`](../fixtures/web-surface-shaped/README.md) | verify | stable |
|
|
@@ -59,6 +60,9 @@ running artifact), *use* (consume / extend) - and a **status**:
|
|
|
59
60
|
- **libnode / native artifacts / self-hosted runner matrix** ->
|
|
60
61
|
[`reusable-build-surface.md`](reusable-build-surface.md) and
|
|
61
62
|
[`../fixtures/libnode-shaped/README.md`](../fixtures/libnode-shaped/README.md).
|
|
63
|
+
- **runtime train validation / temporary `buildchain-ref` override** ->
|
|
64
|
+
[`runtime-train-validation.md`](runtime-train-validation.md) and
|
|
65
|
+
[`reusable-build-surface.md`](reusable-build-surface.md).
|
|
62
66
|
- **standalone binary install / platform archives / GitHub Release bundle** ->
|
|
63
67
|
[`install.md`](install.md), [`binary-distribution.md`](binary-distribution.md),
|
|
64
68
|
and [`release-passport.md`](release-passport.md).
|