@kungfu-tech/buildchain 3.0.2 → 3.0.3-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 +1 -1
- package/CONTRIBUTING.md +1 -1
- package/README.md +27 -1
- package/actions/promote-buildchain-ref/README.md +90 -19
- package/actions/report-buildchain-issue/README.md +20 -2
- package/actions/run-lifecycle/README.md +5 -0
- package/actions/validate-config/README.md +19 -1
- package/bin/buildchain.mjs +56 -2
- package/bin/internal/command-registry.mjs +82 -0
- package/bin/internal/trust-release-cli.mjs +1 -0
- package/contracts/auditable-demo-media-profiles-v1.json +116 -1
- package/contracts/buildchain-v2-residuals-v1.json +146 -0
- package/contracts/evidence/auditable-demo-responsive-web-delivery-v1.json +143 -0
- package/contracts/evidence/auditable-demo-web-delivery-v1.json +7 -3
- package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/complete-transcript.txt +2 -0
- package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/public-projection.json +16 -0
- package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/scene.json +12 -0
- package/dist/site/buildchain-contract.json +188 -46
- package/dist/site/buildchain-site.json +181 -90
- package/dist/site/capability-registry.json +5 -5
- package/dist/site/cli-registry.json +860 -98
- package/dist/site/controller-registry.json +118 -5
- package/dist/site/kfd-claims.json +233 -19
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +20 -20
- package/dist/site/node-api-registry.json +374 -59
- package/dist/site/page-registry.json +156 -65
- package/dist/site/public-surface-audit.json +231 -22
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +3 -0
- package/dist/site/site-manifest.json +24 -24
- package/dist/site/workflow-registry.json +575 -79
- package/docs/MAP.md +22 -4
- package/docs/auditable-demo.md +54 -13
- package/docs/aws-us-elastic-runner-burst-plane.md +381 -0
- package/docs/cli.md +119 -12
- package/docs/consumer-issue-reporting.md +20 -2
- package/docs/dev-alpha-candidate-patrol.md +24 -10
- package/docs/github-governance-authority.md +24 -31
- package/docs/lifecycle-protocol.md +20 -2
- package/docs/migration-inventory.md +24 -5
- package/docs/observed-evidence-patrol.md +28 -12
- package/docs/ownership.md +19 -1
- package/docs/publication-artifacts.md +158 -18
- package/docs/publication-authority.md +4 -4
- package/docs/publish-transaction.md +78 -31
- package/docs/release-candidate.md +24 -0
- package/docs/release-flow.md +50 -32
- package/docs/release-governance.md +39 -32
- package/docs/release-passport.md +4 -0
- package/docs/release-propagation.md +57 -12
- package/docs/reusable-build-surface.md +218 -90
- package/docs/runtime-train-validation.md +20 -2
- package/docs/shifu-gate-profiles.md +7 -1
- package/docs/stable-candidate-patrol.md +6 -6
- package/docs/toolkit-observability.md +22 -0
- package/docs/versioning.md +46 -24
- package/docs/web-surface-deployments.md +82 -1
- package/package.json +7 -5
- package/packages/core/README.md +60 -4
- package/packages/core/artifact-signing.js +1 -0
- package/packages/core/buildchain-contract.js +9 -4
- package/packages/core/cache-evidence.js +1 -0
- package/packages/core/controller-evidence.js +2 -0
- package/packages/core/diagnostics.js +105 -30
- package/packages/core/github-governance-authority.js +25 -17
- package/packages/core/index.js +40 -0
- package/packages/core/paper.js +3226 -0
- package/packages/core/public-surface-audit.js +3 -0
- package/packages/core/publication-artifact.js +1 -6
- package/packages/core/publication-authority.js +1 -0
- package/packages/core/publication-control-plane-audit.js +28 -0
- package/packages/core/publication-reproducibility.js +947 -0
- package/packages/core/publication-sealed-bundle.js +190 -0
- package/packages/core/publish-transaction.js +147 -17
- package/packages/core/release-candidate.js +234 -0
- package/packages/core/release-passport.js +519 -228
- package/packages/core/release-propagation.js +175 -1
- package/packages/core/stable-release-gate.js +4 -1
- package/scripts/artifact-signing-delegation.mjs +268 -0
- package/scripts/audit-github-governance.mjs +32 -13
- package/scripts/audit-publication-control-plane.mjs +17 -8
- package/scripts/auditable-demo.mjs +266 -7
- package/scripts/aws-codebuild-toolchain.mjs +285 -0
- package/scripts/aws-macos-jit-core.mjs +378 -0
- package/scripts/aws-macos-jit.mjs +123 -0
- package/scripts/aws-runner-burst-core.mjs +248 -0
- package/scripts/aws-runner-burst.mjs +79 -0
- package/scripts/aws-windows-jit-core.mjs +374 -0
- package/scripts/aws-windows-jit.mjs +121 -0
- package/scripts/build-contract-core.mjs +69 -6
- package/scripts/build-standalone-binary.mjs +23 -0
- package/scripts/buildchain-contract-lock.mjs +1 -1
- package/scripts/buildchain-patrol.mjs +1 -1
- package/scripts/check-internal-architecture.mjs +69 -2
- package/scripts/check-inventory.mjs +70 -2
- package/scripts/check-javascript-syntax.mjs +31 -0
- package/scripts/check-maintainability.mjs +371 -0
- package/scripts/compiler-cache-evidence.mjs +221 -0
- package/scripts/dev-alpha-candidate-patrol.mjs +288 -43
- package/scripts/dispatch-artifact-signing-authority.mjs +7 -1
- package/scripts/finalize-native-artifact-signing-result.mjs +185 -31
- package/scripts/gate-profile-core.mjs +6 -1
- package/scripts/generate-channel-promotion-workflow.mjs +3 -12
- package/scripts/generate-release-candidate-passport.mjs +4 -0
- package/scripts/generate-site-bundle.mjs +84 -18
- package/scripts/import-artifact-signing-results.mjs +64 -5
- package/scripts/inspect-artifact-signing-requests.mjs +52 -14
- package/scripts/maintainability-metrics.mjs +339 -0
- package/scripts/npm-publish-transaction.mjs +57 -6
- package/scripts/observed-evidence.mjs +151 -33
- package/scripts/paper.mjs +645 -0
- package/scripts/promotion-channel-router.mjs +16 -4
- package/scripts/publication-reproducibility.mjs +62 -0
- package/scripts/reconcile-github-governance.mjs +8 -1
- package/scripts/release-propagation.mjs +39 -0
- package/scripts/resolve-artifact-signing-upload-route.mjs +55 -0
- package/scripts/resolve-build-contract.mjs +6 -0
- package/scripts/route-offline-runners.mjs +236 -0
- package/scripts/run-candidate-body-prefix-renderer.mjs +187 -0
- package/scripts/run-lifecycle-core.mjs +62 -1
- package/scripts/runtime-ref-core.mjs +36 -4
- package/scripts/seal-artifact-signing-requests.mjs +64 -12
- package/scripts/stable-candidate-qualification.mjs +50 -0
- package/scripts/verify-artifact-signing-results.mjs +26 -1
- package/scripts/web-surface-core.mjs +272 -86
- package/scripts/web-surface-production-decision.mjs +19 -3
|
@@ -38,7 +38,8 @@ export function inspectArtifactSigningRequests({
|
|
|
38
38
|
} = {}) {
|
|
39
39
|
const root = path.resolve(required(inputRoot, "signing request root"));
|
|
40
40
|
const indexes = walk(root, "index.json");
|
|
41
|
-
if (indexes.length === 0)
|
|
41
|
+
if (indexes.length === 0)
|
|
42
|
+
throw new Error("no artifact signing request indexes found");
|
|
42
43
|
const seen = new Set();
|
|
43
44
|
const matrices = {
|
|
44
45
|
detached: [],
|
|
@@ -47,41 +48,73 @@ export function inspectArtifactSigningRequests({
|
|
|
47
48
|
};
|
|
48
49
|
for (const indexPath of indexes) {
|
|
49
50
|
const index = JSON.parse(fs.readFileSync(indexPath, "utf8"));
|
|
50
|
-
if (
|
|
51
|
+
if (
|
|
52
|
+
index.contract !== "kungfu-buildchain-artifact-signing-request-index/v1"
|
|
53
|
+
)
|
|
54
|
+
continue;
|
|
51
55
|
for (const entry of index.requests || []) {
|
|
52
56
|
const requestPath = path.resolve(path.dirname(indexPath), entry.path);
|
|
53
57
|
const relative = path.relative(root, requestPath);
|
|
54
58
|
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
55
|
-
throw new Error(
|
|
59
|
+
throw new Error(
|
|
60
|
+
"signing request path escapes the authority intake root",
|
|
61
|
+
);
|
|
56
62
|
}
|
|
57
63
|
const request = JSON.parse(fs.readFileSync(requestPath, "utf8"));
|
|
58
64
|
const check = validateArtifactSigningRequest(request);
|
|
59
65
|
if (!check.ok || request.digest !== entry.digest) {
|
|
60
66
|
throw new Error(`invalid artifact signing request: ${entry.id}`);
|
|
61
67
|
}
|
|
62
|
-
if (
|
|
68
|
+
if (
|
|
69
|
+
expectedRepository &&
|
|
70
|
+
request.source.repository !== expectedRepository
|
|
71
|
+
) {
|
|
63
72
|
throw new Error("signing request source repository mismatch");
|
|
64
73
|
}
|
|
65
74
|
if (expectedRuntimeSha && request.runtime.sha !== expectedRuntimeSha) {
|
|
66
75
|
throw new Error("signing request runtime SHA mismatch");
|
|
67
76
|
}
|
|
68
77
|
const key = `${request.source.sha}:${request.artifact.id}:${request.artifact.platform}`;
|
|
69
|
-
if (seen.has(key))
|
|
78
|
+
if (seen.has(key))
|
|
79
|
+
throw new Error(`duplicate artifact signing request: ${key}`);
|
|
70
80
|
seen.add(key);
|
|
71
81
|
const item = {
|
|
72
82
|
id: request.artifact.id,
|
|
73
83
|
slug: safeId(request.artifact.id),
|
|
74
84
|
request: path.relative(root, requestPath).split(path.sep).join("/"),
|
|
75
|
-
directory: path
|
|
76
|
-
|
|
85
|
+
directory: path
|
|
86
|
+
.relative(root, path.dirname(requestPath))
|
|
87
|
+
.split(path.sep)
|
|
88
|
+
.join("/"),
|
|
89
|
+
indexRoot:
|
|
90
|
+
path
|
|
91
|
+
.relative(root, path.dirname(indexPath))
|
|
92
|
+
.split(path.sep)
|
|
93
|
+
.join("/") || ".",
|
|
94
|
+
kind: request.artifact.kind,
|
|
95
|
+
platform: request.artifact.platform,
|
|
96
|
+
arch: request.artifact.arch || "",
|
|
97
|
+
platformId: [request.artifact.platform, request.artifact.arch]
|
|
98
|
+
.filter(Boolean)
|
|
99
|
+
.join("-"),
|
|
100
|
+
sourceSha: request.source.sha,
|
|
101
|
+
sourceTreeSha: request.source.treeSha,
|
|
102
|
+
transportFormat: request.artifact.transport?.format || "",
|
|
77
103
|
};
|
|
78
|
-
if (request.signature.profile === "detached-signature-v1")
|
|
79
|
-
|
|
80
|
-
else if (request.signature.profile === "
|
|
81
|
-
|
|
104
|
+
if (request.signature.profile === "detached-signature-v1")
|
|
105
|
+
matrices.detached.push(item);
|
|
106
|
+
else if (request.signature.profile === "apple-developer-id")
|
|
107
|
+
matrices.macos.push(item);
|
|
108
|
+
else if (request.signature.profile === "windows-authenticode")
|
|
109
|
+
matrices.windows.push(item);
|
|
110
|
+
else
|
|
111
|
+
throw new Error(
|
|
112
|
+
`unsupported signing authority profile: ${request.signature.profile}`,
|
|
113
|
+
);
|
|
82
114
|
}
|
|
83
115
|
}
|
|
84
|
-
for (const entries of Object.values(matrices))
|
|
116
|
+
for (const entries of Object.values(matrices))
|
|
117
|
+
entries.sort((a, b) => a.request.localeCompare(b.request));
|
|
85
118
|
writeGitHubOutputs({
|
|
86
119
|
"detached-matrix": JSON.stringify(matrices.detached),
|
|
87
120
|
"macos-matrix": JSON.stringify(matrices.macos),
|
|
@@ -91,11 +124,16 @@ export function inspectArtifactSigningRequests({
|
|
|
91
124
|
return matrices;
|
|
92
125
|
}
|
|
93
126
|
|
|
94
|
-
if (
|
|
127
|
+
if (
|
|
128
|
+
process.argv[1] &&
|
|
129
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
130
|
+
) {
|
|
95
131
|
try {
|
|
96
132
|
inspectArtifactSigningRequests();
|
|
97
133
|
} catch (error) {
|
|
98
|
-
console.error(
|
|
134
|
+
console.error(
|
|
135
|
+
`::error::${String(error?.message || error).replace(/\r?\n/gu, "%0A")}`,
|
|
136
|
+
);
|
|
99
137
|
process.exitCode = 1;
|
|
100
138
|
}
|
|
101
139
|
}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { execFileSync } from "node:child_process";
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
7
|
+
import ts from "typescript";
|
|
8
|
+
import {
|
|
9
|
+
dependencyCycles,
|
|
10
|
+
relativeImports,
|
|
11
|
+
} from "./check-internal-architecture.mjs";
|
|
12
|
+
|
|
13
|
+
const JS_EXTENSIONS = new Set([".js", ".mjs", ".cjs"]);
|
|
14
|
+
const GENERATED_PATTERN = /^actions\/[^/]+\/dist\/.*\.js$/u;
|
|
15
|
+
|
|
16
|
+
function gitText(root, args, encoding = "utf8") {
|
|
17
|
+
return execFileSync("git", args, {
|
|
18
|
+
cwd: root,
|
|
19
|
+
encoding,
|
|
20
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function trackedFiles(root, revision = "") {
|
|
25
|
+
const args = revision
|
|
26
|
+
? ["ls-tree", "-r", "-z", "--name-only", revision]
|
|
27
|
+
: ["ls-files", "-z", "--cached", "--others", "--exclude-standard"];
|
|
28
|
+
return gitText(root, args).split("\0").filter(Boolean).sort();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readTrackedFile(root, file, revision = "") {
|
|
32
|
+
return revision
|
|
33
|
+
? gitText(root, ["show", `${revision}:${file}`])
|
|
34
|
+
: fs.readFileSync(path.join(root, file), "utf8");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function lineCount(source) {
|
|
38
|
+
if (!source) return 0;
|
|
39
|
+
const lines = source.split("\n");
|
|
40
|
+
return lines.at(-1) === "" ? lines.length - 1 : lines.length;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isTestFile(file) {
|
|
44
|
+
return file.startsWith("tests/") || /(?:^|\/)test(?:s)?\//u.test(file);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isGeneratedFile(file) {
|
|
48
|
+
return GENERATED_PATTERN.test(file);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isHandMaintainedSource(file) {
|
|
52
|
+
return (
|
|
53
|
+
JS_EXTENSIONS.has(path.extname(file)) &&
|
|
54
|
+
!isTestFile(file) &&
|
|
55
|
+
!isGeneratedFile(file)
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isFunctionNode(node) {
|
|
60
|
+
return (
|
|
61
|
+
ts.isFunctionDeclaration(node) ||
|
|
62
|
+
ts.isFunctionExpression(node) ||
|
|
63
|
+
ts.isArrowFunction(node) ||
|
|
64
|
+
ts.isMethodDeclaration(node) ||
|
|
65
|
+
ts.isConstructorDeclaration(node) ||
|
|
66
|
+
ts.isGetAccessorDeclaration(node) ||
|
|
67
|
+
ts.isSetAccessorDeclaration(node)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function propertyName(node) {
|
|
72
|
+
if (!node) return "";
|
|
73
|
+
if (ts.isIdentifier(node) || ts.isPrivateIdentifier(node)) return node.text;
|
|
74
|
+
if (ts.isStringLiteral(node) || ts.isNumericLiteral(node)) return node.text;
|
|
75
|
+
return node.getText();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function functionName(node, sourceFile) {
|
|
79
|
+
if (node.name) return propertyName(node.name);
|
|
80
|
+
const parent = node.parent;
|
|
81
|
+
if (ts.isVariableDeclaration(parent)) return propertyName(parent.name);
|
|
82
|
+
if (ts.isPropertyAssignment(parent)) return propertyName(parent.name);
|
|
83
|
+
if (ts.isExportAssignment(parent)) return "default";
|
|
84
|
+
const start =
|
|
85
|
+
sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line +
|
|
86
|
+
1;
|
|
87
|
+
return `<anonymous@${start}>`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isLogicalDecision(node) {
|
|
91
|
+
return (
|
|
92
|
+
ts.isBinaryExpression(node) &&
|
|
93
|
+
[
|
|
94
|
+
ts.SyntaxKind.AmpersandAmpersandToken,
|
|
95
|
+
ts.SyntaxKind.BarBarToken,
|
|
96
|
+
ts.SyntaxKind.QuestionQuestionToken,
|
|
97
|
+
].includes(node.operatorToken.kind)
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isDecision(node) {
|
|
102
|
+
return (
|
|
103
|
+
ts.isIfStatement(node) ||
|
|
104
|
+
ts.isForStatement(node) ||
|
|
105
|
+
ts.isForInStatement(node) ||
|
|
106
|
+
ts.isForOfStatement(node) ||
|
|
107
|
+
ts.isWhileStatement(node) ||
|
|
108
|
+
ts.isDoStatement(node) ||
|
|
109
|
+
ts.isCatchClause(node) ||
|
|
110
|
+
ts.isConditionalExpression(node) ||
|
|
111
|
+
ts.isCaseClause(node) ||
|
|
112
|
+
isLogicalDecision(node)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function functionComplexity(node) {
|
|
117
|
+
let complexity = 1;
|
|
118
|
+
const visit = (child) => {
|
|
119
|
+
if (child !== node && isFunctionNode(child)) return;
|
|
120
|
+
if (child !== node && isDecision(child)) complexity += 1;
|
|
121
|
+
ts.forEachChild(child, visit);
|
|
122
|
+
};
|
|
123
|
+
visit(node);
|
|
124
|
+
return complexity;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function analyzeJavaScript(file, source) {
|
|
128
|
+
const sourceFile = ts.createSourceFile(
|
|
129
|
+
file,
|
|
130
|
+
source,
|
|
131
|
+
ts.ScriptTarget.Latest,
|
|
132
|
+
true,
|
|
133
|
+
file.endsWith(".cjs") ? ts.ScriptKind.JS : ts.ScriptKind.JS,
|
|
134
|
+
);
|
|
135
|
+
const functions = [];
|
|
136
|
+
const visit = (node) => {
|
|
137
|
+
if (isFunctionNode(node)) {
|
|
138
|
+
const start =
|
|
139
|
+
sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile))
|
|
140
|
+
.line + 1;
|
|
141
|
+
const end =
|
|
142
|
+
sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1;
|
|
143
|
+
functions.push({
|
|
144
|
+
name: functionName(node, sourceFile),
|
|
145
|
+
start,
|
|
146
|
+
end,
|
|
147
|
+
lines: end - start + 1,
|
|
148
|
+
complexity: functionComplexity(node),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
ts.forEachChild(node, visit);
|
|
152
|
+
};
|
|
153
|
+
visit(sourceFile);
|
|
154
|
+
functions.sort(
|
|
155
|
+
(left, right) =>
|
|
156
|
+
left.start - right.start || left.name.localeCompare(right.name),
|
|
157
|
+
);
|
|
158
|
+
return {
|
|
159
|
+
lines: lineCount(source),
|
|
160
|
+
complexity: functions.reduce((total, entry) => total + entry.complexity, 0),
|
|
161
|
+
functions,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function countRegistryEntries(root, file, field, revision = "") {
|
|
166
|
+
const value = JSON.parse(readTrackedFile(root, file, revision));
|
|
167
|
+
return Array.isArray(value[field]) ? value[field].length : 0;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function architectureSummary(root, revision = "") {
|
|
171
|
+
const index = JSON.parse(
|
|
172
|
+
readTrackedFile(root, "architecture/internal-capabilities.json", revision),
|
|
173
|
+
);
|
|
174
|
+
const implementationPaths = new Set(
|
|
175
|
+
index.capabilities.flatMap((entry) => entry.implementation || []),
|
|
176
|
+
);
|
|
177
|
+
const graph = new Map(
|
|
178
|
+
[...implementationPaths].map((file) => [file, new Set()]),
|
|
179
|
+
);
|
|
180
|
+
for (const file of implementationPaths) {
|
|
181
|
+
if (!JS_EXTENSIONS.has(path.extname(file))) continue;
|
|
182
|
+
for (const specifier of relativeImports(
|
|
183
|
+
readTrackedFile(root, file, revision),
|
|
184
|
+
)) {
|
|
185
|
+
const base = path.posix.normalize(
|
|
186
|
+
path.posix.join(path.posix.dirname(file), specifier),
|
|
187
|
+
);
|
|
188
|
+
const target = [
|
|
189
|
+
base,
|
|
190
|
+
`${base}.js`,
|
|
191
|
+
`${base}.mjs`,
|
|
192
|
+
`${base}.cjs`,
|
|
193
|
+
`${base}/index.js`,
|
|
194
|
+
`${base}/index.mjs`,
|
|
195
|
+
].find((candidate) => implementationPaths.has(candidate));
|
|
196
|
+
if (target) graph.get(file).add(target);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
capabilities: index.capabilities.length,
|
|
201
|
+
implementationMappings: index.capabilities.reduce(
|
|
202
|
+
(total, entry) => total + (entry.implementation || []).length,
|
|
203
|
+
0,
|
|
204
|
+
),
|
|
205
|
+
testMappings: index.capabilities.reduce(
|
|
206
|
+
(total, entry) => total + (entry.tests || []).length,
|
|
207
|
+
0,
|
|
208
|
+
),
|
|
209
|
+
dependencyRules: index.dependencyRules.length,
|
|
210
|
+
dependencyCycles: dependencyCycles(graph).length,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function selectedFunction(files, file, name) {
|
|
215
|
+
const entry = files[file];
|
|
216
|
+
const matches =
|
|
217
|
+
entry?.functions.filter((candidate) => candidate.name === name) || [];
|
|
218
|
+
if (matches.length !== 1) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`${file} must contain exactly one ${name} function; found ${matches.length}`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
return matches[0];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function collectMaintainabilityMetrics({
|
|
227
|
+
root = process.cwd(),
|
|
228
|
+
revision = "",
|
|
229
|
+
} = {}) {
|
|
230
|
+
const files = trackedFiles(root, revision);
|
|
231
|
+
const sourceFiles = files.filter(isHandMaintainedSource);
|
|
232
|
+
const testFiles = files.filter(
|
|
233
|
+
(file) => JS_EXTENSIONS.has(path.extname(file)) && isTestFile(file),
|
|
234
|
+
);
|
|
235
|
+
const workflowFiles = files.filter((file) =>
|
|
236
|
+
/^\.github\/workflows\/.*\.ya?ml$/u.test(file),
|
|
237
|
+
);
|
|
238
|
+
const generatedFiles = files.filter(isGeneratedFile);
|
|
239
|
+
const documentationFiles = files.filter((file) => file.endsWith(".md"));
|
|
240
|
+
const sourceMetrics = Object.fromEntries(
|
|
241
|
+
sourceFiles.map((file) => {
|
|
242
|
+
const source = readTrackedFile(root, file, revision);
|
|
243
|
+
return [file, analyzeJavaScript(file, source)];
|
|
244
|
+
}),
|
|
245
|
+
);
|
|
246
|
+
const sumLines = (entries) =>
|
|
247
|
+
entries.reduce(
|
|
248
|
+
(total, file) => total + lineCount(readTrackedFile(root, file, revision)),
|
|
249
|
+
0,
|
|
250
|
+
);
|
|
251
|
+
const generatedBytes = generatedFiles.reduce(
|
|
252
|
+
(total, file) =>
|
|
253
|
+
total + Buffer.byteLength(readTrackedFile(root, file, revision)),
|
|
254
|
+
0,
|
|
255
|
+
);
|
|
256
|
+
const workflowRegistry = JSON.parse(
|
|
257
|
+
readTrackedFile(root, "dist/site/workflow-registry.json", revision),
|
|
258
|
+
);
|
|
259
|
+
const reverseAudit = JSON.parse(
|
|
260
|
+
readTrackedFile(root, "dist/site/public-surface-audit.json", revision),
|
|
261
|
+
);
|
|
262
|
+
const head = revision || gitText(root, ["rev-parse", "HEAD"]).trim();
|
|
263
|
+
return {
|
|
264
|
+
schemaVersion: 1,
|
|
265
|
+
contract: "kungfu-buildchain-maintainability-metrics",
|
|
266
|
+
revision: head,
|
|
267
|
+
repository: {
|
|
268
|
+
trackedFiles: files.length,
|
|
269
|
+
handMaintainedSourceFiles: sourceFiles.length,
|
|
270
|
+
handMaintainedSourceLines: Object.values(sourceMetrics).reduce(
|
|
271
|
+
(total, entry) => total + entry.lines,
|
|
272
|
+
0,
|
|
273
|
+
),
|
|
274
|
+
testFiles: testFiles.length,
|
|
275
|
+
testLines: sumLines(testFiles),
|
|
276
|
+
workflowFiles: workflowFiles.length,
|
|
277
|
+
workflowLines: sumLines(workflowFiles),
|
|
278
|
+
documentationFiles: documentationFiles.length,
|
|
279
|
+
documentationLines: sumLines(documentationFiles),
|
|
280
|
+
generatedFiles: generatedFiles.length,
|
|
281
|
+
generatedBytes,
|
|
282
|
+
},
|
|
283
|
+
publicSurface: {
|
|
284
|
+
cliCommands: countRegistryEntries(
|
|
285
|
+
root,
|
|
286
|
+
"dist/site/cli-registry.json",
|
|
287
|
+
"commands",
|
|
288
|
+
revision,
|
|
289
|
+
),
|
|
290
|
+
nodeApiExports: countRegistryEntries(
|
|
291
|
+
root,
|
|
292
|
+
"dist/site/node-api-registry.json",
|
|
293
|
+
"exports",
|
|
294
|
+
revision,
|
|
295
|
+
),
|
|
296
|
+
workflows: Array.isArray(workflowRegistry.workflows)
|
|
297
|
+
? workflowRegistry.workflows.length
|
|
298
|
+
: 0,
|
|
299
|
+
actions: Array.isArray(workflowRegistry.actions)
|
|
300
|
+
? workflowRegistry.actions.length
|
|
301
|
+
: 0,
|
|
302
|
+
reverseAuditFailures: Number(reverseAudit.summary?.failureCount || 0),
|
|
303
|
+
},
|
|
304
|
+
architecture: architectureSummary(root, revision),
|
|
305
|
+
hotspots: {
|
|
306
|
+
promoteBuildchainRefs: {
|
|
307
|
+
file: "actions/promote-buildchain-ref/lib.js",
|
|
308
|
+
...selectedFunction(
|
|
309
|
+
sourceMetrics,
|
|
310
|
+
"actions/promote-buildchain-ref/lib.js",
|
|
311
|
+
"promoteBuildchainRefs",
|
|
312
|
+
),
|
|
313
|
+
},
|
|
314
|
+
createReleaseCheckReport: {
|
|
315
|
+
file: "packages/core/release-passport.js",
|
|
316
|
+
...selectedFunction(
|
|
317
|
+
sourceMetrics,
|
|
318
|
+
"packages/core/release-passport.js",
|
|
319
|
+
"createReleaseCheckReport",
|
|
320
|
+
),
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
files: sourceMetrics,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (
|
|
328
|
+
process.argv[1] &&
|
|
329
|
+
import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href
|
|
330
|
+
) {
|
|
331
|
+
console.log(JSON.stringify(collectMaintainabilityMetrics(), null, 2));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export {
|
|
335
|
+
analyzeJavaScript,
|
|
336
|
+
collectMaintainabilityMetrics,
|
|
337
|
+
isHandMaintainedSource,
|
|
338
|
+
lineCount,
|
|
339
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
+
import crypto from "node:crypto";
|
|
3
4
|
import fs from "node:fs";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { pathToFileURL } from "node:url";
|
|
@@ -71,6 +72,39 @@ function artifactDigest(pack) {
|
|
|
71
72
|
throw new Error("npm pack did not return integrity or shasum");
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
function sealedPackResult() {
|
|
76
|
+
const tarballPath = readEnv("BUILDCHAIN_SEALED_NPM_TARBALL");
|
|
77
|
+
if (!tarballPath) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
const resolved = path.resolve(tarballPath);
|
|
81
|
+
if (!fs.existsSync(resolved) || !fs.statSync(resolved).isFile()) {
|
|
82
|
+
throw new Error(`sealed npm tarball is missing: ${resolved}`);
|
|
83
|
+
}
|
|
84
|
+
const bytes = fs.readFileSync(resolved);
|
|
85
|
+
const integrity = `sha512-${crypto.createHash("sha512").update(bytes).digest("base64")}`;
|
|
86
|
+
const shasum = crypto.createHash("sha1").update(bytes).digest("hex");
|
|
87
|
+
const sha256 = crypto.createHash("sha256").update(bytes).digest("hex");
|
|
88
|
+
const expectedIntegrity = readEnv("BUILDCHAIN_SEALED_NPM_INTEGRITY");
|
|
89
|
+
const expectedSha256 = readEnv("BUILDCHAIN_SEALED_NPM_SHA256").replace(/^sha256:/, "");
|
|
90
|
+
if (expectedIntegrity && integrity !== expectedIntegrity) {
|
|
91
|
+
throw new Error(`sealed npm tarball integrity mismatch: expected ${expectedIntegrity}, got ${integrity}`);
|
|
92
|
+
}
|
|
93
|
+
if (expectedSha256 && sha256 !== expectedSha256) {
|
|
94
|
+
throw new Error(`sealed npm tarball sha256 mismatch: expected ${expectedSha256}, got ${sha256}`);
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
filename: path.basename(resolved),
|
|
98
|
+
integrity,
|
|
99
|
+
shasum,
|
|
100
|
+
sha256,
|
|
101
|
+
bytes: bytes.length,
|
|
102
|
+
entryCount: 0,
|
|
103
|
+
tarballPath: resolved,
|
|
104
|
+
sealed: true,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
74
108
|
function parseNpmView(stdout) {
|
|
75
109
|
const raw = String(stdout || "").trim();
|
|
76
110
|
if (!raw) {
|
|
@@ -154,10 +188,14 @@ export function npmPublishTransaction({
|
|
|
154
188
|
const exactTag = assertPackageVersion({ pkg, expectedVersion });
|
|
155
189
|
const distTag = readEnv("BUILDCHAIN_NPM_DIST_TAG", pkg.version.includes("-") ? "alpha" : "latest");
|
|
156
190
|
|
|
157
|
-
const pack =
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
191
|
+
const pack =
|
|
192
|
+
sealedPackResult() ||
|
|
193
|
+
parsePackResult(
|
|
194
|
+
runNpm({
|
|
195
|
+
cwd: resolvedCwd,
|
|
196
|
+
args: ["pack", "--dry-run", "--json", `--registry=${registry}`],
|
|
197
|
+
}).stdout,
|
|
198
|
+
);
|
|
161
199
|
const digest = artifactDigest(pack);
|
|
162
200
|
|
|
163
201
|
const existingDigest = skipRegistryLookup
|
|
@@ -178,7 +216,15 @@ export function npmPublishTransaction({
|
|
|
178
216
|
} else {
|
|
179
217
|
runNpm({
|
|
180
218
|
cwd: resolvedCwd,
|
|
181
|
-
args: [
|
|
219
|
+
args: [
|
|
220
|
+
"publish",
|
|
221
|
+
...(pack.tarballPath ? [pack.tarballPath] : []),
|
|
222
|
+
"--access",
|
|
223
|
+
access,
|
|
224
|
+
"--tag",
|
|
225
|
+
distTag,
|
|
226
|
+
`--registry=${registry}`,
|
|
227
|
+
],
|
|
182
228
|
});
|
|
183
229
|
publishAction = "published";
|
|
184
230
|
const registryDigest = skipRegistryLookup
|
|
@@ -215,7 +261,11 @@ export function npmPublishTransaction({
|
|
|
215
261
|
digest,
|
|
216
262
|
}],
|
|
217
263
|
};
|
|
218
|
-
const resolvedEvidencePath = writeEvidence({
|
|
264
|
+
const resolvedEvidencePath = writeEvidence({
|
|
265
|
+
cwd: resolvedCwd,
|
|
266
|
+
evidencePath,
|
|
267
|
+
evidence,
|
|
268
|
+
});
|
|
219
269
|
writeGitHubOutputs({
|
|
220
270
|
version: pkg.version,
|
|
221
271
|
"exact-tag": exactTag,
|
|
@@ -235,6 +285,7 @@ export function npmPublishTransaction({
|
|
|
235
285
|
registry,
|
|
236
286
|
publishAction,
|
|
237
287
|
pack,
|
|
288
|
+
sealedBundleRoot: readEnv("BUILDCHAIN_SEALED_BUNDLE_ROOT"),
|
|
238
289
|
evidencePath: resolvedEvidencePath,
|
|
239
290
|
evidence,
|
|
240
291
|
};
|