@kungfu-tech/buildchain 3.0.1-alpha.2 → 3.0.1-alpha.3
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/bin/buildchain.mjs +7 -0
- package/dist/site/buildchain-contract.json +10 -10
- package/dist/site/buildchain-site.json +10 -10
- package/dist/site/kfd-claims.json +2 -2
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +2 -2
- package/dist/site/node-api-registry.json +3 -3
- package/dist/site/page-registry.json +4 -4
- package/dist/site/public-surface-audit.json +1 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +6 -6
- package/docs/cli.md +6 -0
- package/docs/toolkit-observability.md +14 -0
- package/package.json +1 -1
- package/packages/core/issue-reporting.js +8 -3
- package/packages/core/logging.js +101 -0
- package/scripts/check-inventory.mjs +29 -10
- package/scripts/web-surface-production-release-pr.mjs +99 -0
- package/scripts/workflow-friction-report.mjs +10 -0
|
@@ -19,6 +19,28 @@ import {
|
|
|
19
19
|
const root = process.cwd();
|
|
20
20
|
const sharedActionTsupConfig = fs.readFileSync(path.join(root, "scripts/tsup-action.config.mjs"), "utf8");
|
|
21
21
|
const commonJsSourcePattern = /\b(require\s*\(|module\.exports|exports\.|require\.main|createRequire)\b/;
|
|
22
|
+
|
|
23
|
+
function assertSelfReleaseImpactContract(impact, { expectedVersion = "" } = {}) {
|
|
24
|
+
if (!impact || typeof impact !== "object" || Array.isArray(impact)) {
|
|
25
|
+
throw new Error("Buildchain self release impact must be a JSON object");
|
|
26
|
+
}
|
|
27
|
+
const version = String(impact.release?.version || "").trim();
|
|
28
|
+
const versionMatch = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/);
|
|
29
|
+
if (!versionMatch) throw new Error("Buildchain self release impact release.version must be a semantic version");
|
|
30
|
+
if (expectedVersion && version !== expectedVersion) {
|
|
31
|
+
throw new Error("Buildchain self release impact version must match package.json version");
|
|
32
|
+
}
|
|
33
|
+
const expectedLine = `v${versionMatch[1]}.${versionMatch[2]}`;
|
|
34
|
+
const line = String(impact.release?.line || "").trim();
|
|
35
|
+
if (line !== expectedLine) {
|
|
36
|
+
throw new Error(`Buildchain self release impact line must be ${expectedLine} for release.version ${version}`);
|
|
37
|
+
}
|
|
38
|
+
const summary = String(impact.summary || "").trim();
|
|
39
|
+
if (!summary.startsWith(`Buildchain ${line} `)) {
|
|
40
|
+
throw new Error(`Buildchain self release impact summary must describe the current ${line} line`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
22
44
|
const requiredPaths = [
|
|
23
45
|
"AGENTS.md",
|
|
24
46
|
"CONTRIBUTING.md",
|
|
@@ -844,19 +866,16 @@ const selfHostedRunnerSmokeWorkflow = fs.readFileSync(path.join(root, ".github/w
|
|
|
844
866
|
const npmDryRunScript = fs.readFileSync(path.join(root, "scripts/npm-publish-dry-run.mjs"), "utf8");
|
|
845
867
|
const npmPublishTransactionScript = fs.readFileSync(path.join(root, "scripts/npm-publish-transaction.mjs"), "utf8");
|
|
846
868
|
const rootPackageJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
847
|
-
const
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
}
|
|
853
|
-
if (!expectedSelfReleaseLine || selfReleaseImpact.release?.line !== expectedSelfReleaseLine) {
|
|
854
|
-
throw new Error("Buildchain self release impact line must match package.json major/minor line");
|
|
855
|
-
}
|
|
869
|
+
const selfReleaseImpactPath = path.resolve(
|
|
870
|
+
root,
|
|
871
|
+
process.env.BUILDCHAIN_SELF_RELEASE_IMPACT_PATH || ".buildchain/release-impact.json",
|
|
872
|
+
);
|
|
873
|
+
const selfReleaseImpact = JSON.parse(fs.readFileSync(selfReleaseImpactPath, "utf8"));
|
|
874
|
+
assertSelfReleaseImpactContract(selfReleaseImpact, { expectedVersion: rootPackageJson.version });
|
|
856
875
|
if (!["patch", "minor", "major"].includes(selfReleaseImpact.classification)) {
|
|
857
876
|
throw new Error("Buildchain self release impact classification must be patch, minor, or major");
|
|
858
877
|
}
|
|
859
|
-
if (!
|
|
878
|
+
if (!Array.isArray(selfReleaseImpact.surfaceImpacts) || selfReleaseImpact.surfaceImpacts.length === 0) {
|
|
860
879
|
throw new Error("Buildchain self release impact requires a summary and surfaceImpacts[]");
|
|
861
880
|
}
|
|
862
881
|
for (const requiredSnippet of [
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
defaultBuildchainLogPath,
|
|
6
|
+
recordBuildchainControlPlaneOutcome,
|
|
7
|
+
} from "../packages/core/logging.js";
|
|
4
8
|
|
|
5
9
|
function requiredString(value, name) {
|
|
6
10
|
const normalized = String(value || "").trim();
|
|
@@ -70,6 +74,34 @@ function runUrl({ serverUrl = "", repository = "", runId = "" } = {}) {
|
|
|
70
74
|
return `${serverUrl.replace(/\/$/, "")}/${repository}/actions/runs/${runId}`;
|
|
71
75
|
}
|
|
72
76
|
|
|
77
|
+
export function recordProductionReleasePrOutcome(result, env = process.env) {
|
|
78
|
+
const outcome = result.action === "created"
|
|
79
|
+
? "created"
|
|
80
|
+
: result.action === "updated"
|
|
81
|
+
? "reused"
|
|
82
|
+
: result.action === "suppressed-merged-release-pr"
|
|
83
|
+
? "suppressed"
|
|
84
|
+
: ["permission-denied", "app-token-unavailable", "failed"].includes(result.action)
|
|
85
|
+
? "failed"
|
|
86
|
+
: "skipped";
|
|
87
|
+
return recordBuildchainControlPlaneOutcome({
|
|
88
|
+
domain: "release-intent",
|
|
89
|
+
action: result.action,
|
|
90
|
+
outcome,
|
|
91
|
+
reason: result.suppressionReason || result.status,
|
|
92
|
+
attributes: {
|
|
93
|
+
repository: result.repository,
|
|
94
|
+
channel: result.productionReleaseChannel,
|
|
95
|
+
pullNumber: result.pullNumber,
|
|
96
|
+
sourceSha: result.sourceSha,
|
|
97
|
+
},
|
|
98
|
+
}, {
|
|
99
|
+
path: optionalString(env.BUILDCHAIN_LOG_PATH) ||
|
|
100
|
+
(env.GITHUB_ACTIONS === "true" ? defaultBuildchainLogPath() : false),
|
|
101
|
+
console: false,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
73
105
|
function urlsFromResult(result = {}) {
|
|
74
106
|
const urls = result.urls && typeof result.urls === "object" ? result.urls : {};
|
|
75
107
|
if (Object.keys(urls).length > 0) return urls;
|
|
@@ -374,6 +406,33 @@ async function createOrUpdateBranch({ apiUrl, token, owner, repo, branchName, so
|
|
|
374
406
|
return createdCommit.sha;
|
|
375
407
|
}
|
|
376
408
|
|
|
409
|
+
function findMergedProductionReleasePr({
|
|
410
|
+
pullRequests = [],
|
|
411
|
+
repository,
|
|
412
|
+
productionReleaseLabel = "buildchain-release",
|
|
413
|
+
productionReleaseHeadPrefix = "release/",
|
|
414
|
+
base = "main",
|
|
415
|
+
} = {}) {
|
|
416
|
+
const fullName = requiredString(repository, "repository");
|
|
417
|
+
const label = requiredString(productionReleaseLabel, "productionReleaseLabel");
|
|
418
|
+
const headPrefix = optionalString(productionReleaseHeadPrefix);
|
|
419
|
+
const candidates = (Array.isArray(pullRequests) ? pullRequests : []).filter((pull) => {
|
|
420
|
+
const labels = Array.isArray(pull?.labels) ? pull.labels.map((entry) => entry?.name || entry) : [];
|
|
421
|
+
const headRef = optionalString(pull?.head?.ref);
|
|
422
|
+
return Boolean(pull?.merged_at) &&
|
|
423
|
+
pull?.base?.ref === base &&
|
|
424
|
+
pull?.head?.repo?.full_name === fullName &&
|
|
425
|
+
labels.includes(label) &&
|
|
426
|
+
(!headPrefix || headRef.startsWith(headPrefix));
|
|
427
|
+
});
|
|
428
|
+
if (candidates.length > 1) {
|
|
429
|
+
throw new Error(
|
|
430
|
+
`multiple merged production release PRs matched the source commit: ${candidates.map((pull) => `#${pull.number}`).join(", ")}`,
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
return candidates[0];
|
|
434
|
+
}
|
|
435
|
+
|
|
377
436
|
export async function openProductionReleasePr({
|
|
378
437
|
apiUrl = "https://api.github.com",
|
|
379
438
|
token,
|
|
@@ -400,6 +459,43 @@ export async function openProductionReleasePr({
|
|
|
400
459
|
});
|
|
401
460
|
const { owner, repo, branchName, title, body, head } = handoff;
|
|
402
461
|
const normalizedToken = requiredString(token, "token");
|
|
462
|
+
const associated = await githubJson({
|
|
463
|
+
apiUrl,
|
|
464
|
+
token: normalizedToken,
|
|
465
|
+
path: `/repos/${owner}/${repo}/commits/${encodeURIComponent(handoff.sourceSha)}/pulls?per_page=100`,
|
|
466
|
+
});
|
|
467
|
+
let mergedReleasePull = findMergedProductionReleasePr({
|
|
468
|
+
pullRequests: associated,
|
|
469
|
+
repository,
|
|
470
|
+
productionReleaseLabel,
|
|
471
|
+
productionReleaseHeadPrefix,
|
|
472
|
+
base: handoff.base,
|
|
473
|
+
});
|
|
474
|
+
if (!mergedReleasePull) {
|
|
475
|
+
const closedByDeterministicHead = await githubJson({
|
|
476
|
+
apiUrl,
|
|
477
|
+
token: normalizedToken,
|
|
478
|
+
path: `/repos/${owner}/${repo}/pulls?state=closed&base=${encodeURIComponent(handoff.base)}&head=${encodeURIComponent(head)}&per_page=100`,
|
|
479
|
+
});
|
|
480
|
+
mergedReleasePull = findMergedProductionReleasePr({
|
|
481
|
+
pullRequests: closedByDeterministicHead,
|
|
482
|
+
repository,
|
|
483
|
+
productionReleaseLabel,
|
|
484
|
+
productionReleaseHeadPrefix,
|
|
485
|
+
base: handoff.base,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
if (mergedReleasePull) {
|
|
489
|
+
return {
|
|
490
|
+
action: "suppressed-merged-release-pr",
|
|
491
|
+
status: "suppressed-merged-release-pr",
|
|
492
|
+
...handoff,
|
|
493
|
+
branchName,
|
|
494
|
+
pullNumber: mergedReleasePull.number,
|
|
495
|
+
pullUrl: mergedReleasePull.html_url || mergedReleasePull.url || "",
|
|
496
|
+
suppressionReason: "source-commit-already-has-qualifying-merged-release-pr",
|
|
497
|
+
};
|
|
498
|
+
}
|
|
403
499
|
const existing = await githubJson({
|
|
404
500
|
apiUrl,
|
|
405
501
|
token: normalizedToken,
|
|
@@ -524,6 +620,7 @@ export async function webSurfaceProductionReleasePrCli(env = process.env) {
|
|
|
524
620
|
if (failOnReleasePrError) {
|
|
525
621
|
writeJsonFile(summaryPath, result);
|
|
526
622
|
if (env.GITHUB_STEP_SUMMARY) fs.appendFileSync(env.GITHUB_STEP_SUMMARY, renderStepSummary(result));
|
|
623
|
+
recordProductionReleasePrOutcome(result, env);
|
|
527
624
|
throw new Error(result.error.message);
|
|
528
625
|
}
|
|
529
626
|
} else {
|
|
@@ -562,12 +659,14 @@ export async function webSurfaceProductionReleasePrCli(env = process.env) {
|
|
|
562
659
|
if (status !== "permission-denied" || failOnReleasePrError) {
|
|
563
660
|
writeJsonFile(summaryPath, result);
|
|
564
661
|
if (env.GITHUB_STEP_SUMMARY) fs.appendFileSync(env.GITHUB_STEP_SUMMARY, renderStepSummary(result));
|
|
662
|
+
recordProductionReleasePrOutcome(result, env);
|
|
565
663
|
throw error;
|
|
566
664
|
}
|
|
567
665
|
}
|
|
568
666
|
}
|
|
569
667
|
}
|
|
570
668
|
|
|
669
|
+
recordProductionReleasePrOutcome(result, env);
|
|
571
670
|
writeJsonFile(summaryPath, result);
|
|
572
671
|
if (env.GITHUB_STEP_SUMMARY) {
|
|
573
672
|
fs.appendFileSync(env.GITHUB_STEP_SUMMARY, renderStepSummary(result));
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { recordBuildchainControlPlaneOutcome } from "../packages/core/logging.js";
|
|
4
5
|
import { pathToFileURL } from "node:url";
|
|
5
6
|
import { writeGitHubOutputs } from "./build-contract-core.mjs";
|
|
6
7
|
|
|
@@ -346,6 +347,15 @@ export async function workflowFrictionReportCli() {
|
|
|
346
347
|
})}\n`);
|
|
347
348
|
console.error(`::warning::${diagnosis.replace(/\r?\n/g, "%0A")}`);
|
|
348
349
|
}
|
|
350
|
+
recordBuildchainControlPlaneOutcome({
|
|
351
|
+
domain: "workflow-friction",
|
|
352
|
+
action: "classified",
|
|
353
|
+
outcome: "classified",
|
|
354
|
+
attributes: {
|
|
355
|
+
frictionClass: result.frictionClass,
|
|
356
|
+
pullRequest: result.pullRequest,
|
|
357
|
+
},
|
|
358
|
+
});
|
|
349
359
|
writeGitHubOutputs({
|
|
350
360
|
"friction-class": result.frictionClass,
|
|
351
361
|
summary: result.summary,
|