@kungfu-tech/buildchain 2.8.17 → 2.9.0
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/README.md +2 -0
- package/bin/buildchain.mjs +119 -8
- package/dist/site/buildchain-contract.json +8 -8
- package/dist/site/buildchain-site.json +77 -28
- package/dist/site/cli-registry.json +5 -0
- package/dist/site/kfd-claims.json +37 -1
- package/dist/site/manual-registry.json +14 -6
- package/dist/site/node-api-registry.json +24 -7
- package/dist/site/page-registry.json +58 -17
- package/dist/site/release-provenance.json +2 -0
- package/dist/site/site-manifest.json +18 -10
- package/docs/MAP.md +7 -3
- package/docs/build-facts.md +164 -0
- package/docs/cli.md +34 -2
- package/docs/readme-badges.md +40 -3
- package/docs/release-passport.md +11 -1
- package/docs/versioning.md +1 -0
- package/docs/web-surface-deployments.md +19 -7
- package/package.json +3 -1
- package/packages/core/README.md +23 -7
- package/packages/core/badges.js +18 -0
- package/packages/core/build-facts.js +567 -0
- package/packages/core/buildchain-config.js +115 -0
- package/packages/core/buildchain-kfd-claims.js +2 -0
- package/packages/core/index.js +24 -0
- package/packages/core/readme-badges.js +166 -2
- package/packages/core/release-passport.js +21 -0
- package/scripts/check-inventory.mjs +37 -6
- package/scripts/generate-site-bundle.mjs +3 -0
- package/scripts/web-surface-core.mjs +48 -0
|
@@ -42,6 +42,8 @@ const SUPPORTED_INFRA_ADOPTION_MODES = new Set([
|
|
|
42
42
|
"managed-apply",
|
|
43
43
|
]);
|
|
44
44
|
const SUPPORTED_INFRA_APPLY_MODES = new Set(["disabled", "manual-approval", "environment-approval"]);
|
|
45
|
+
const SUPPORTED_FACT_VERSION_SOURCE_TYPES = new Set(["static", "json", "toml", "regex", "command"]);
|
|
46
|
+
const SUPPORTED_FACT_LEGACY_PROJECTIONS = new Set(["kungfu-buildinfo"]);
|
|
45
47
|
|
|
46
48
|
function posixPath(value) {
|
|
47
49
|
return String(value || "").split(path.sep).join("/");
|
|
@@ -154,11 +156,123 @@ export function normalizeBuildchainConfig(config) {
|
|
|
154
156
|
if (normalized.diagnostics !== undefined) {
|
|
155
157
|
normalized.diagnostics = normalizeDiagnosticsSection(normalized.diagnostics);
|
|
156
158
|
}
|
|
159
|
+
if (normalized.facts !== undefined) {
|
|
160
|
+
normalized.facts = normalizeFactsSection(normalized.facts);
|
|
161
|
+
}
|
|
157
162
|
validateWebSurfaceConfig(normalized);
|
|
158
163
|
validateInfraContractConfig(normalized);
|
|
159
164
|
return normalized;
|
|
160
165
|
}
|
|
161
166
|
|
|
167
|
+
function normalizeFactsSection(facts) {
|
|
168
|
+
assertPlainObject(facts, "facts");
|
|
169
|
+
return {
|
|
170
|
+
outputDir: facts.output_dir === undefined ? ".buildchain/facts" : posixPath(assertString(facts.output_dir, "facts.output_dir")),
|
|
171
|
+
versionSources: normalizeFactVersionSources(facts.version_sources),
|
|
172
|
+
modules: normalizeFactModules(facts.modules),
|
|
173
|
+
products: normalizeFactProducts(facts.products),
|
|
174
|
+
legacyProjections: normalizeFactLegacyProjections(facts.legacy_projections),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function normalizeFactVersionSources(sources = []) {
|
|
179
|
+
if (sources === undefined) {
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
if (!Array.isArray(sources)) {
|
|
183
|
+
throw new Error("facts.version_sources must be an array of tables");
|
|
184
|
+
}
|
|
185
|
+
return sources.map((source, index) => {
|
|
186
|
+
assertPlainObject(source, `facts.version_sources[${index}]`);
|
|
187
|
+
const id = assertString(source.id, `facts.version_sources[${index}].id`);
|
|
188
|
+
const type = source.type === undefined ? "static" : assertString(source.type, `facts.version_sources[${index}].type`);
|
|
189
|
+
if (!SUPPORTED_FACT_VERSION_SOURCE_TYPES.has(type)) {
|
|
190
|
+
throw new Error("facts.version_sources[].type must be one of static, json, toml, regex, or command");
|
|
191
|
+
}
|
|
192
|
+
if (["json", "toml", "regex"].includes(type) && source.path === undefined) {
|
|
193
|
+
throw new Error(`facts.version_sources[${index}].path is required for ${type}`);
|
|
194
|
+
}
|
|
195
|
+
if (["json", "toml"].includes(type) && source.key === undefined) {
|
|
196
|
+
throw new Error(`facts.version_sources[${index}].key is required for ${type}`);
|
|
197
|
+
}
|
|
198
|
+
if (type === "regex" && source.pattern === undefined) {
|
|
199
|
+
throw new Error(`facts.version_sources[${index}].pattern is required for regex`);
|
|
200
|
+
}
|
|
201
|
+
if (type === "command" && source.command === undefined) {
|
|
202
|
+
throw new Error(`facts.version_sources[${index}].command is required for command`);
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
id,
|
|
206
|
+
type,
|
|
207
|
+
value: source.value === undefined ? undefined : String(source.value),
|
|
208
|
+
path: source.path === undefined ? "" : posixPath(assertString(source.path, `facts.version_sources[${index}].path`)),
|
|
209
|
+
key: source.key === undefined ? "" : assertString(source.key, `facts.version_sources[${index}].key`),
|
|
210
|
+
pattern: source.pattern === undefined ? "" : assertString(source.pattern, `facts.version_sources[${index}].pattern`),
|
|
211
|
+
command: source.command === undefined ? "" : assertString(source.command, `facts.version_sources[${index}].command`),
|
|
212
|
+
trust: source.trust === undefined ? "" : assertString(source.trust, `facts.version_sources[${index}].trust`),
|
|
213
|
+
reproducible: optionalBoolean(source.reproducible, type !== "command"),
|
|
214
|
+
};
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function normalizeFactModules(modules = []) {
|
|
219
|
+
if (modules === undefined) {
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
if (!Array.isArray(modules)) {
|
|
223
|
+
throw new Error("facts.modules must be an array of tables");
|
|
224
|
+
}
|
|
225
|
+
return modules.map((module, index) => {
|
|
226
|
+
assertPlainObject(module, `facts.modules[${index}]`);
|
|
227
|
+
return {
|
|
228
|
+
id: assertString(module.id, `facts.modules[${index}].id`),
|
|
229
|
+
root: module.root === undefined ? "." : posixPath(assertString(module.root, `facts.modules[${index}].root`)),
|
|
230
|
+
scope: module.scope === undefined ? "" : assertString(module.scope, `facts.modules[${index}].scope`),
|
|
231
|
+
versionSource: module.version_source === undefined ? "" : assertString(module.version_source, `facts.modules[${index}].version_source`),
|
|
232
|
+
lifecycle: module.lifecycle === undefined ? "" : assertString(module.lifecycle, `facts.modules[${index}].lifecycle`),
|
|
233
|
+
outputs: normalizeStringArray(module.outputs, `facts.modules[${index}].outputs`).map(posixPath),
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function normalizeFactProducts(products = []) {
|
|
239
|
+
if (products === undefined) {
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
if (!Array.isArray(products)) {
|
|
243
|
+
throw new Error("facts.products must be an array of tables");
|
|
244
|
+
}
|
|
245
|
+
return products.map((product, index) => {
|
|
246
|
+
assertPlainObject(product, `facts.products[${index}]`);
|
|
247
|
+
return {
|
|
248
|
+
id: assertString(product.id, `facts.products[${index}].id`),
|
|
249
|
+
moduleFacts: normalizeStringArray(product.module_facts, `facts.products[${index}].module_facts`).map(posixPath),
|
|
250
|
+
artifacts: normalizeStringArray(product.artifacts, `facts.products[${index}].artifacts`).map(posixPath),
|
|
251
|
+
};
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function normalizeFactLegacyProjections(projections = []) {
|
|
256
|
+
if (projections === undefined) {
|
|
257
|
+
return [];
|
|
258
|
+
}
|
|
259
|
+
if (!Array.isArray(projections)) {
|
|
260
|
+
throw new Error("facts.legacy_projections must be an array of tables");
|
|
261
|
+
}
|
|
262
|
+
return projections.map((projection, index) => {
|
|
263
|
+
assertPlainObject(projection, `facts.legacy_projections[${index}]`);
|
|
264
|
+
const type = assertString(projection.type, `facts.legacy_projections[${index}].type`);
|
|
265
|
+
if (!SUPPORTED_FACT_LEGACY_PROJECTIONS.has(type)) {
|
|
266
|
+
throw new Error("facts.legacy_projections[].type must be kungfu-buildinfo");
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
type,
|
|
270
|
+
module: projection.module === undefined ? "" : assertString(projection.module, `facts.legacy_projections[${index}].module`),
|
|
271
|
+
path: posixPath(assertString(projection.path, `facts.legacy_projections[${index}].path`)),
|
|
272
|
+
};
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
162
276
|
function normalizeDiagnosticsSection(diagnostics) {
|
|
163
277
|
assertPlainObject(diagnostics, "diagnostics");
|
|
164
278
|
const normalized = { ...diagnostics };
|
|
@@ -1062,6 +1176,7 @@ export function validateBuildchainConfig(
|
|
|
1062
1176
|
})),
|
|
1063
1177
|
lifecycleStages,
|
|
1064
1178
|
publish: loadedConfig.config.publish,
|
|
1179
|
+
facts: loadedConfig.config.facts,
|
|
1065
1180
|
};
|
|
1066
1181
|
}
|
|
1067
1182
|
|
|
@@ -22,6 +22,7 @@ export const BUILDCHAIN_AGENT_MANUALS = Object.freeze([
|
|
|
22
22
|
{ id: "ownership", title: "Ownership", path: "docs/ownership.md", plane: "why" },
|
|
23
23
|
{ id: "product-mechanism", title: "Product mechanism", path: "docs/product-mechanism.md", plane: "why" },
|
|
24
24
|
{ id: "cli", title: "CLI and npm package", path: "docs/cli.md", plane: "use" },
|
|
25
|
+
{ id: "build-facts", title: "Build Facts", path: "docs/build-facts.md", plane: "use" },
|
|
25
26
|
{ id: "lifecycle-protocol", title: "Lifecycle protocol", path: "docs/lifecycle-protocol.md", plane: "use" },
|
|
26
27
|
{ id: "reusable-build-surface", title: "Reusable build surface", path: "docs/reusable-build-surface.md", plane: "use" },
|
|
27
28
|
{ id: "publish-transaction", title: "Publish transaction", path: "docs/publish-transaction.md", plane: "verify" },
|
|
@@ -72,6 +73,7 @@ const EXTRA_KFD1_FILES = Object.freeze([
|
|
|
72
73
|
"bin/buildchain.mjs",
|
|
73
74
|
"packages/core/index.js",
|
|
74
75
|
"packages/core/homebrew.js",
|
|
76
|
+
"packages/core/build-facts.js",
|
|
75
77
|
"packages/core/release-propagation.js",
|
|
76
78
|
"scripts/generate-site-bundle.mjs",
|
|
77
79
|
"scripts/ensure-github-release.mjs",
|
package/packages/core/index.js
CHANGED
|
@@ -86,6 +86,24 @@ export {
|
|
|
86
86
|
writeDiagnosticsArtifact,
|
|
87
87
|
} from "./diagnostics.js";
|
|
88
88
|
|
|
89
|
+
export {
|
|
90
|
+
BUILD_FACTS_GIT_CONTRACT,
|
|
91
|
+
BUILD_FACTS_LEGACY_KUNGFU_BUILDINFO_CONTRACT,
|
|
92
|
+
BUILD_FACTS_MODULE_CONTRACT,
|
|
93
|
+
BUILD_FACTS_PRODUCT_CONTRACT,
|
|
94
|
+
BUILD_FACTS_VERIFY_CONTRACT,
|
|
95
|
+
BUILD_FACTS_VERSION_CONTRACT,
|
|
96
|
+
aggregateBuildFacts,
|
|
97
|
+
buildFactsDigest,
|
|
98
|
+
collectGitSourceFacts,
|
|
99
|
+
collectModuleBuildFacts,
|
|
100
|
+
collectVersionSourceFact,
|
|
101
|
+
createKungfuBuildInfoProjection,
|
|
102
|
+
verifyBuildFacts,
|
|
103
|
+
writeBuildFacts,
|
|
104
|
+
writeKungfuBuildInfoProjection,
|
|
105
|
+
} from "./build-facts.js";
|
|
106
|
+
|
|
89
107
|
export {
|
|
90
108
|
RELEASE_CANDIDATE_PASSPORT_CONTRACT,
|
|
91
109
|
createReleaseCandidatePassport,
|
|
@@ -194,15 +212,21 @@ export {
|
|
|
194
212
|
} from "./issue-reporting.js";
|
|
195
213
|
|
|
196
214
|
export {
|
|
215
|
+
BADGE_BUNDLE_DEFAULT_CLAIMS,
|
|
216
|
+
BADGE_BUNDLE_FACTS_CONTRACT,
|
|
197
217
|
README_BADGE_BLOCK_END,
|
|
198
218
|
README_BADGE_BLOCK_START,
|
|
199
219
|
README_BADGE_FACTS_CONTRACT,
|
|
200
220
|
README_BADGE_HOSTED_BASE_URL,
|
|
221
|
+
checkBadgeBundleBlock,
|
|
201
222
|
checkReadmeBadgeBlock,
|
|
223
|
+
collectBadgeBundleFacts,
|
|
202
224
|
collectReadmeBadgeFacts,
|
|
203
225
|
createReadmeBadgeEndpointRegistry,
|
|
204
226
|
readReadme,
|
|
227
|
+
renderBadgeBundleBlock,
|
|
205
228
|
renderReadmeBadgeBlock,
|
|
229
|
+
updateBadgeBundleBlock,
|
|
206
230
|
updateReadmeBadgeBlock,
|
|
207
231
|
} from "./readme-badges.js";
|
|
208
232
|
|
|
@@ -10,9 +10,16 @@ import {
|
|
|
10
10
|
} from "./release-passport.js";
|
|
11
11
|
|
|
12
12
|
export const README_BADGE_FACTS_CONTRACT = "kungfu-buildchain-readme-badge-facts";
|
|
13
|
+
export const BADGE_BUNDLE_FACTS_CONTRACT = "kungfu-buildchain-badge-bundle-facts";
|
|
13
14
|
export const README_BADGE_BLOCK_START = "<!-- buildchain:badges:start -->";
|
|
14
15
|
export const README_BADGE_BLOCK_END = "<!-- buildchain:badges:end -->";
|
|
15
16
|
export const README_BADGE_HOSTED_BASE_URL = "https://buildchain.libkungfu.dev/badges/v1";
|
|
17
|
+
export const BADGE_BUNDLE_DEFAULT_CLAIMS = [
|
|
18
|
+
"kfd-1",
|
|
19
|
+
"kfd-2",
|
|
20
|
+
"kfd-3",
|
|
21
|
+
"release-passport",
|
|
22
|
+
];
|
|
16
23
|
|
|
17
24
|
const KFD_KEYS = [
|
|
18
25
|
{ key: "kfd-1", id: "kfd1", label: "KFD-1", text: "contract world" },
|
|
@@ -47,6 +54,22 @@ const BUILDCHAIN_BADGE_IDS = [
|
|
|
47
54
|
"buildchain-release-passport",
|
|
48
55
|
];
|
|
49
56
|
|
|
57
|
+
const BADGE_BUNDLE_CLAIM_ALIASES = {
|
|
58
|
+
kfd1: "kfd-1",
|
|
59
|
+
"kfd_1": "kfd-1",
|
|
60
|
+
"kfd-1": "kfd-1",
|
|
61
|
+
kfd2: "kfd-2",
|
|
62
|
+
"kfd_2": "kfd-2",
|
|
63
|
+
"kfd-2": "kfd-2",
|
|
64
|
+
kfd3: "kfd-3",
|
|
65
|
+
"kfd_3": "kfd-3",
|
|
66
|
+
"kfd-3": "kfd-3",
|
|
67
|
+
passport: "release-passport",
|
|
68
|
+
"release_passport": "release-passport",
|
|
69
|
+
"release-passport": "release-passport",
|
|
70
|
+
"buildchain-release-passport": "release-passport",
|
|
71
|
+
};
|
|
72
|
+
|
|
50
73
|
const BUILDCHAIN_BADGE_LOGO_PLACEHOLDER = {
|
|
51
74
|
contract: "kungfu-buildchain-badge-logo-policy",
|
|
52
75
|
mode: "hosted-placeholder",
|
|
@@ -83,6 +106,10 @@ function sha256File(filePath) {
|
|
|
83
106
|
return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
|
|
84
107
|
}
|
|
85
108
|
|
|
109
|
+
function hashText(value) {
|
|
110
|
+
return crypto.createHash("sha256").update(String(value || "")).digest("hex");
|
|
111
|
+
}
|
|
112
|
+
|
|
86
113
|
function posixPath(value) {
|
|
87
114
|
return String(value || "").split(path.sep).join("/");
|
|
88
115
|
}
|
|
@@ -174,6 +201,51 @@ function normalizeStringArray(value) {
|
|
|
174
201
|
.filter(Boolean);
|
|
175
202
|
}
|
|
176
203
|
|
|
204
|
+
function splitClaimList(value) {
|
|
205
|
+
if (Array.isArray(value)) {
|
|
206
|
+
return value;
|
|
207
|
+
}
|
|
208
|
+
if (typeof value === "string") {
|
|
209
|
+
return value.split(",");
|
|
210
|
+
}
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function normalizeBadgeBundleClaims(value, fallback = BADGE_BUNDLE_DEFAULT_CLAIMS) {
|
|
215
|
+
const selected = [];
|
|
216
|
+
const unknown = [];
|
|
217
|
+
for (const claim of splitClaimList(value)) {
|
|
218
|
+
const key = String(claim || "").trim().toLowerCase();
|
|
219
|
+
if (!key) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const normalized = BADGE_BUNDLE_CLAIM_ALIASES[key];
|
|
223
|
+
if (!normalized) {
|
|
224
|
+
unknown.push(key);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (!selected.includes(normalized)) {
|
|
228
|
+
selected.push(normalized);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (unknown.length > 0) {
|
|
232
|
+
throw new Error(`unsupported badge bundle claim(s): ${unknown.join(", ")}`);
|
|
233
|
+
}
|
|
234
|
+
return selected.length > 0 ? selected : [...fallback];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function badgeBundleConfig(badgeConfig = {}) {
|
|
238
|
+
const configured = badgeConfig.bundle && typeof badgeConfig.bundle === "object"
|
|
239
|
+
? badgeConfig.bundle
|
|
240
|
+
: {};
|
|
241
|
+
return {
|
|
242
|
+
enabled: configured.enabled === undefined ? true : Boolean(configured.enabled),
|
|
243
|
+
claims: normalizeBadgeBundleClaims(configured.claims || badgeConfig.bundle_claims || badgeConfig.bundleClaims),
|
|
244
|
+
mode: String(configured.mode || "readme"),
|
|
245
|
+
hosted: configured.hosted === undefined ? true : Boolean(configured.hosted),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
177
249
|
function resolveLocalFactPath({ cwd, location }) {
|
|
178
250
|
const value = String(location || "").trim();
|
|
179
251
|
if (!value || /^https?:\/\//.test(value)) {
|
|
@@ -531,6 +603,46 @@ function buildBadgeEntries(facts, { badgeConfig = {} } = {}) {
|
|
|
531
603
|
return entries;
|
|
532
604
|
}
|
|
533
605
|
|
|
606
|
+
function buildBadgeBundleEntries(readmeFacts, bundle) {
|
|
607
|
+
const selected = new Set(bundle.claims);
|
|
608
|
+
const entries = [];
|
|
609
|
+
for (const kfd of readmeFacts.kfd) {
|
|
610
|
+
if (!selected.has(kfd.key)) {
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
entries.push({
|
|
614
|
+
id: kfd.key,
|
|
615
|
+
claim: kfd.key,
|
|
616
|
+
alt: `${kfd.label}: ${kfd.state}`,
|
|
617
|
+
image: readmeFacts.badges.find((badge) => badge.id === kfd.key)?.image || "",
|
|
618
|
+
link: kfd.url,
|
|
619
|
+
state: kfd.state,
|
|
620
|
+
source: kfd.source,
|
|
621
|
+
standard: {
|
|
622
|
+
title: kfd.title,
|
|
623
|
+
documentUrl: kfd.standardDocumentUrl,
|
|
624
|
+
documentSha256: kfd.standardDocumentSha256,
|
|
625
|
+
interfaceContract: kfd.interfaceContract,
|
|
626
|
+
schemaId: kfd.schemaId,
|
|
627
|
+
},
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
if (selected.has("release-passport")) {
|
|
631
|
+
const badge = readmeFacts.badges.find((entry) => entry.id === "buildchain-release-passport");
|
|
632
|
+
entries.push({
|
|
633
|
+
id: "buildchain-release-passport",
|
|
634
|
+
claim: "release-passport",
|
|
635
|
+
alt: `Buildchain Release Passport: ${readmeFacts.releasePassport.state}`,
|
|
636
|
+
image: badge?.image || "",
|
|
637
|
+
link: readmeFacts.releasePassport.url,
|
|
638
|
+
state: readmeFacts.releasePassport.state,
|
|
639
|
+
source: readmeFacts.releasePassport.verified ? "release-passport" : "declaration",
|
|
640
|
+
releasePassport: readmeFacts.releasePassport,
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
return entries;
|
|
644
|
+
}
|
|
645
|
+
|
|
534
646
|
export async function collectReadmeBadgeFacts({ cwd = process.cwd() } = {}) {
|
|
535
647
|
const resolvedCwd = path.resolve(cwd);
|
|
536
648
|
const loadedConfig = loadBuildchainConfig(resolvedCwd);
|
|
@@ -610,13 +722,46 @@ export async function collectReadmeBadgeFacts({ cwd = process.cwd() } = {}) {
|
|
|
610
722
|
hostedBadgeIds: BUILDCHAIN_BADGE_IDS,
|
|
611
723
|
logoPolicy: BUILDCHAIN_BADGE_LOGO_PLACEHOLDER,
|
|
612
724
|
},
|
|
725
|
+
badgeBundle: badgeBundleConfig(badgeConfig),
|
|
613
726
|
badges: [],
|
|
614
727
|
};
|
|
615
728
|
facts.badges = buildBadgeEntries(facts, { badgeConfig });
|
|
616
729
|
return facts;
|
|
617
730
|
}
|
|
618
731
|
|
|
619
|
-
export function
|
|
732
|
+
export async function collectBadgeBundleFacts({ cwd = process.cwd(), claims = undefined } = {}) {
|
|
733
|
+
const readmeFacts = await collectReadmeBadgeFacts({ cwd });
|
|
734
|
+
const config = {
|
|
735
|
+
...readmeFacts.badgeBundle,
|
|
736
|
+
claims: normalizeBadgeBundleClaims(claims, readmeFacts.badgeBundle.claims),
|
|
737
|
+
};
|
|
738
|
+
const badges = buildBadgeBundleEntries(readmeFacts, config);
|
|
739
|
+
return {
|
|
740
|
+
schemaVersion: 1,
|
|
741
|
+
contract: BADGE_BUNDLE_FACTS_CONTRACT,
|
|
742
|
+
cwd: readmeFacts.cwd,
|
|
743
|
+
repository: readmeFacts.repository,
|
|
744
|
+
package: readmeFacts.package,
|
|
745
|
+
sourceFactsContract: readmeFacts.contract,
|
|
746
|
+
sourceFactsSha256: hashText(JSON.stringify(readmeFacts)),
|
|
747
|
+
policy: {
|
|
748
|
+
defaultClaims: [...BADGE_BUNDLE_DEFAULT_CLAIMS],
|
|
749
|
+
claims: config.claims,
|
|
750
|
+
hosted: config.hosted,
|
|
751
|
+
mode: config.mode,
|
|
752
|
+
passedRequiresRepositoryPassport: true,
|
|
753
|
+
consumerActionForLogoChange: "none",
|
|
754
|
+
},
|
|
755
|
+
releasePassport: readmeFacts.releasePassport,
|
|
756
|
+
kfdStandards: readmeFacts.kfdStandards,
|
|
757
|
+
kfdClaimRegistry: readmeFacts.kfdClaimRegistry,
|
|
758
|
+
productMechanism: readmeFacts.productMechanism,
|
|
759
|
+
badgeRuntime: readmeFacts.badgeRuntime,
|
|
760
|
+
badges,
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function renderBadgeMarkdownBlock(facts) {
|
|
620
765
|
const lines = [
|
|
621
766
|
README_BADGE_BLOCK_START,
|
|
622
767
|
...facts.badges.map((badge) => (
|
|
@@ -629,12 +774,20 @@ export function renderReadmeBadgeBlock(facts) {
|
|
|
629
774
|
return `${lines.join("\n")}\n`;
|
|
630
775
|
}
|
|
631
776
|
|
|
777
|
+
export function renderReadmeBadgeBlock(facts) {
|
|
778
|
+
return renderBadgeMarkdownBlock(facts);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
export function renderBadgeBundleBlock(facts) {
|
|
782
|
+
return renderBadgeMarkdownBlock(facts);
|
|
783
|
+
}
|
|
784
|
+
|
|
632
785
|
function badgeBlockRegex() {
|
|
633
786
|
return new RegExp(`${README_BADGE_BLOCK_START.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${README_BADGE_BLOCK_END.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`);
|
|
634
787
|
}
|
|
635
788
|
|
|
636
789
|
export function checkReadmeBadgeBlock({ readmeText, facts } = {}) {
|
|
637
|
-
const expected =
|
|
790
|
+
const expected = renderBadgeMarkdownBlock(facts);
|
|
638
791
|
const match = String(readmeText || "").match(badgeBlockRegex());
|
|
639
792
|
const actual = match ? match[0] : "";
|
|
640
793
|
const normalizedActual = actual.endsWith("\n") ? actual : `${actual}\n`;
|
|
@@ -652,6 +805,13 @@ export function checkReadmeBadgeBlock({ readmeText, facts } = {}) {
|
|
|
652
805
|
};
|
|
653
806
|
}
|
|
654
807
|
|
|
808
|
+
export function checkBadgeBundleBlock({ readmeText, facts } = {}) {
|
|
809
|
+
return {
|
|
810
|
+
...checkReadmeBadgeBlock({ readmeText, facts }),
|
|
811
|
+
contract: "kungfu-buildchain-badge-bundle-check",
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
|
|
655
815
|
export function updateReadmeBadgeBlock({ readmeText, facts } = {}) {
|
|
656
816
|
const source = String(readmeText || "");
|
|
657
817
|
const block = renderReadmeBadgeBlock(facts);
|
|
@@ -665,6 +825,10 @@ export function updateReadmeBadgeBlock({ readmeText, facts } = {}) {
|
|
|
665
825
|
return `${block}\n${source}`;
|
|
666
826
|
}
|
|
667
827
|
|
|
828
|
+
export function updateBadgeBundleBlock({ readmeText, facts } = {}) {
|
|
829
|
+
return updateReadmeBadgeBlock({ readmeText, facts });
|
|
830
|
+
}
|
|
831
|
+
|
|
668
832
|
export function readReadme({ cwd = process.cwd(), readmePath = "README.md" } = {}) {
|
|
669
833
|
return readTextIfExists(path.resolve(cwd, readmePath));
|
|
670
834
|
}
|
|
@@ -930,6 +930,7 @@ export function createReleasePassport({
|
|
|
930
930
|
trustedPublishing = undefined,
|
|
931
931
|
transaction = undefined,
|
|
932
932
|
buildSummary = undefined,
|
|
933
|
+
buildFacts = [],
|
|
933
934
|
platformArtifactManifests = [],
|
|
934
935
|
distTagPromotionEvidence = undefined,
|
|
935
936
|
release = {},
|
|
@@ -947,6 +948,9 @@ export function createReleasePassport({
|
|
|
947
948
|
const normalizedTrustedPublishing = normalizeTrustedPublishing(trustedPublishing, { workflow, publish });
|
|
948
949
|
const normalizedTransaction = normalizeTransaction(transaction);
|
|
949
950
|
const normalizedBuildSummary = buildSummary ? normalizeEvidenceDocument(buildSummary, "buildSummary") : undefined;
|
|
951
|
+
const normalizedBuildFacts = (buildFacts || [])
|
|
952
|
+
.map((fact, index) => normalizeEvidenceDocument(fact, `buildFacts[${index}]`))
|
|
953
|
+
.filter(Boolean);
|
|
950
954
|
const normalizedPlatformArtifactManifests = (platformArtifactManifests || [])
|
|
951
955
|
.map((manifest, index) => normalizePlatformArtifactManifest(manifest, index))
|
|
952
956
|
.filter(Boolean);
|
|
@@ -1078,6 +1082,7 @@ export function createReleasePassport({
|
|
|
1078
1082
|
...(normalizedTrustedPublishing ? { trustedPublishing: normalizedTrustedPublishing } : {}),
|
|
1079
1083
|
...(normalizedTransaction ? { transaction: normalizedTransaction } : {}),
|
|
1080
1084
|
...(normalizedBuildSummary ? { buildSummary: normalizedBuildSummary } : {}),
|
|
1085
|
+
...(normalizedBuildFacts.length > 0 ? { buildFacts: normalizedBuildFacts } : {}),
|
|
1081
1086
|
...(normalizedPlatformArtifactManifests.length > 0 ? { platformArtifactManifests: normalizedPlatformArtifactManifests } : {}),
|
|
1082
1087
|
...(normalizedDistTagPromotionEvidence ? { distTagPromotion: normalizedDistTagPromotionEvidence } : {}),
|
|
1083
1088
|
...(normalizedKfd1 ? { [normalizedKfd1.key || kfd1Metadata.key]: normalizedKfd1.passportSection } : {}),
|
|
@@ -1111,6 +1116,13 @@ export function createReleasePassport({
|
|
|
1111
1116
|
publishEvidence: publishEvidencePath,
|
|
1112
1117
|
transactionState: transactionStatePath,
|
|
1113
1118
|
buildSummary: normalizedBuildSummary?.path || "",
|
|
1119
|
+
buildFacts: normalizedBuildFacts.map((fact) => ({
|
|
1120
|
+
path: fact.path || "",
|
|
1121
|
+
sha256: fact.sha256 || "",
|
|
1122
|
+
contract: fact.fields?.contract || "",
|
|
1123
|
+
id: fact.fields?.id || "",
|
|
1124
|
+
digest: fact.fields?.digest || "",
|
|
1125
|
+
})),
|
|
1114
1126
|
platformArtifactManifests: normalizedPlatformArtifactManifests.map((manifest) => ({
|
|
1115
1127
|
path: manifest.path,
|
|
1116
1128
|
sha256: manifest.sha256,
|
|
@@ -1152,6 +1164,7 @@ export function collectGitHubReleasePassport({
|
|
|
1152
1164
|
anchorManifestJson = "",
|
|
1153
1165
|
impactJson = "",
|
|
1154
1166
|
buildSummaryJson = "",
|
|
1167
|
+
buildFactsJsons = [],
|
|
1155
1168
|
platformManifestJsons = [],
|
|
1156
1169
|
distTagEvidenceJson = "",
|
|
1157
1170
|
kfd1WitnessJsons = [],
|
|
@@ -1175,6 +1188,10 @@ export function collectGitHubReleasePassport({
|
|
|
1175
1188
|
const anchorManifest = normalizeAnchorManifest(parseJsonInputWithMeta(anchorManifestJson, undefined));
|
|
1176
1189
|
const impactMeta = parseJsonInputWithMeta(impactJson, undefined);
|
|
1177
1190
|
const buildSummaryMeta = parseJsonInputWithMeta(buildSummaryJson, undefined);
|
|
1191
|
+
const buildFactMetas = (buildFactsJsons || [])
|
|
1192
|
+
.filter(Boolean)
|
|
1193
|
+
.map((buildFactsJson) => parseJsonInputWithMeta(buildFactsJson, undefined))
|
|
1194
|
+
.filter((meta) => meta.value);
|
|
1178
1195
|
const platformManifestMetas = (platformManifestJsons || [])
|
|
1179
1196
|
.filter(Boolean)
|
|
1180
1197
|
.map((manifestJson) => parseJsonInputWithMeta(manifestJson, undefined));
|
|
@@ -1255,6 +1272,10 @@ export function collectGitHubReleasePassport({
|
|
|
1255
1272
|
path: buildSummaryMeta.path ? path.relative(resolvedOutputDir, buildSummaryMeta.path).split(path.sep).join("/") : "",
|
|
1256
1273
|
}
|
|
1257
1274
|
: undefined,
|
|
1275
|
+
buildFacts: buildFactMetas.map((meta) => ({
|
|
1276
|
+
...meta,
|
|
1277
|
+
path: meta.path ? path.relative(resolvedOutputDir, meta.path).split(path.sep).join("/") : "",
|
|
1278
|
+
})),
|
|
1258
1279
|
platformArtifactManifests: platformManifestMetas
|
|
1259
1280
|
.filter((meta) => meta.value)
|
|
1260
1281
|
.map((meta) => ({
|
|
@@ -15,7 +15,9 @@ const requiredPaths = [
|
|
|
15
15
|
".github/pull_request_template.md",
|
|
16
16
|
"bin/buildchain.mjs",
|
|
17
17
|
"packages/core/homebrew.js",
|
|
18
|
+
"packages/core/build-facts.js",
|
|
18
19
|
"docs/MAP.md",
|
|
20
|
+
"docs/build-facts.md",
|
|
19
21
|
"docs/binary-distribution.md",
|
|
20
22
|
"docs/cli.md",
|
|
21
23
|
"docs/consumer-issue-reporting.md",
|
|
@@ -120,6 +122,12 @@ if (rootPackage.exports?.["./issue-reporting"] !== "./packages/core/issue-report
|
|
|
120
122
|
if (rootPackage.exports?.["./readme-badges"] !== "./packages/core/readme-badges.js") {
|
|
121
123
|
throw new Error("root package must export @kungfu-tech/buildchain/readme-badges");
|
|
122
124
|
}
|
|
125
|
+
if (rootPackage.exports?.["./badges"] !== "./packages/core/badges.js") {
|
|
126
|
+
throw new Error("root package must export @kungfu-tech/buildchain/badges");
|
|
127
|
+
}
|
|
128
|
+
if (rootPackage.exports?.["./build-facts"] !== "./packages/core/build-facts.js") {
|
|
129
|
+
throw new Error("root package must export @kungfu-tech/buildchain/build-facts");
|
|
130
|
+
}
|
|
123
131
|
if (rootPackage.exports?.["./logging"] !== "./packages/core/logging.js") {
|
|
124
132
|
throw new Error("root package must export @kungfu-tech/buildchain/logging");
|
|
125
133
|
}
|
|
@@ -210,6 +218,20 @@ if (!coreIndexSource.includes("createSurfaceTimestampPolicy")) {
|
|
|
210
218
|
throw new Error("packages/core/index.js must export surface manifest timestamp policy APIs");
|
|
211
219
|
}
|
|
212
220
|
for (const requiredSnippet of [
|
|
221
|
+
"collectModuleBuildFacts",
|
|
222
|
+
"aggregateBuildFacts",
|
|
223
|
+
"verifyBuildFacts",
|
|
224
|
+
"writeKungfuBuildInfoProjection",
|
|
225
|
+
]) {
|
|
226
|
+
if (!coreIndexSource.includes(requiredSnippet)) {
|
|
227
|
+
throw new Error(`packages/core/index.js must export Build Facts API: ${requiredSnippet}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
for (const requiredSnippet of [
|
|
231
|
+
"collectBadgeBundleFacts",
|
|
232
|
+
"renderBadgeBundleBlock",
|
|
233
|
+
"checkBadgeBundleBlock",
|
|
234
|
+
"updateBadgeBundleBlock",
|
|
213
235
|
"collectReadmeBadgeFacts",
|
|
214
236
|
"renderReadmeBadgeBlock",
|
|
215
237
|
"checkReadmeBadgeBlock",
|
|
@@ -387,6 +409,8 @@ for (const requiredSnippet of [
|
|
|
387
409
|
"Verification fails closed",
|
|
388
410
|
"KFD-2 release trust passport audit",
|
|
389
411
|
"Unbound public claims fail",
|
|
412
|
+
"buildFacts[]",
|
|
413
|
+
"--build-facts-json",
|
|
390
414
|
"Floating Buildchain contract lock",
|
|
391
415
|
"buildchain.contract-lock.json",
|
|
392
416
|
"--kfd-3-prebuild-witness-json",
|
|
@@ -403,6 +427,7 @@ for (const requiredSnippet of [
|
|
|
403
427
|
"KFD-1 / KFD-2 / KFD-3",
|
|
404
428
|
"floating `@v2`",
|
|
405
429
|
"npm publish transactions",
|
|
430
|
+
"Git/source/version/module/product build facts",
|
|
406
431
|
"GitHub Release",
|
|
407
432
|
"release propagation",
|
|
408
433
|
"Homebrew tap distribution indexes",
|
|
@@ -416,6 +441,11 @@ for (const requiredSnippet of [
|
|
|
416
441
|
}
|
|
417
442
|
const readmeBadgesDoc = fs.readFileSync(path.join(root, "docs/readme-badges.md"), "utf8");
|
|
418
443
|
for (const requiredSnippet of [
|
|
444
|
+
"buildchain badges bundle --check",
|
|
445
|
+
"collectBadgeBundleFacts",
|
|
446
|
+
"kungfu-buildchain-badge-bundle-facts",
|
|
447
|
+
"@kungfu-tech/buildchain/badges",
|
|
448
|
+
"[badges.bundle]",
|
|
419
449
|
"buildchain badges readme --check",
|
|
420
450
|
"collectReadmeBadgeFacts",
|
|
421
451
|
"kungfu-buildchain-readme-badge-facts",
|
|
@@ -472,8 +502,11 @@ for (const [docName, docSource] of Object.entries({ "docs/cli.md": cliDoc, "docs
|
|
|
472
502
|
}
|
|
473
503
|
}
|
|
474
504
|
for (const requiredSnippet of [
|
|
505
|
+
"buildchain badges bundle --check",
|
|
506
|
+
"buildchain badges bundle --write",
|
|
475
507
|
"buildchain badges readme --check",
|
|
476
508
|
"buildchain badges readme --write",
|
|
509
|
+
"@kungfu-tech/buildchain/badges",
|
|
477
510
|
"@kungfu-tech/buildchain/readme-badges",
|
|
478
511
|
"buildchain homebrew update-formula",
|
|
479
512
|
"buildchain homebrew check",
|
|
@@ -553,12 +586,10 @@ for (const requiredSnippet of [
|
|
|
553
586
|
"release-passport-buildchain-self-kfd: true",
|
|
554
587
|
"publish-required-artifacts-json: \"[]\"",
|
|
555
588
|
"release-passport-impact-json: >-",
|
|
556
|
-
"Buildchain v2.
|
|
557
|
-
"
|
|
558
|
-
"
|
|
559
|
-
"
|
|
560
|
-
"publish-source-lock-enforcement",
|
|
561
|
-
"required-check-protection",
|
|
589
|
+
"Buildchain v2.9 adds Build Facts as a public contract",
|
|
590
|
+
"build-facts-contract",
|
|
591
|
+
"trust-badge-bundle",
|
|
592
|
+
"web-surface-directory-index-aliases",
|
|
562
593
|
"\"surfaceImpacts\":[",
|
|
563
594
|
]) {
|
|
564
595
|
if (!buildchainRefPromotionWorkflow.includes(requiredSnippet)) {
|
|
@@ -294,6 +294,7 @@ function buildSiteBundle() {
|
|
|
294
294
|
{ id: "verify-infra-contract-evidence-bundle", usage: "buildchain verify infra-contract-evidence-bundle <file>", purpose: "Fail closed unless an infra-contract lifecycle evidence bundle is complete, hash-bound, and validation-consistent." },
|
|
295
295
|
{ id: "logging", usage: "buildchain log|mark|span|verify observability-log", purpose: "Emit timestamped build events, summarize logs, and enforce required phases." },
|
|
296
296
|
{ id: "diagnostics-summary", usage: "buildchain diagnostics summary <diagnostics.json>...", purpose: "Summarize small diagnostics artifacts into JSON and a cross-platform lifecycle timing table." },
|
|
297
|
+
{ id: "build-facts", usage: "buildchain facts module|aggregate|verify", purpose: "Collect and verify Git source, version, module output, product artifact, and legacy Kungfu buildinfo facts." },
|
|
297
298
|
{ id: "npm-dry-run", usage: "buildchain npm dry-run --json", purpose: "Verify npm publish shape before a release transaction." },
|
|
298
299
|
{ id: "infra-contract", usage: "buildchain infra-contract --mode validate|ci|plan|contract|propagation-plan|propagation-apply|apply|evidence-bundle", purpose: "Validate and publish provider-neutral infrastructure contract evidence with a mutation-free CI evidence chain, provider command plans, configured provider command execution, saved-plan apply gates, dry-run-first propagation, and lifecycle evidence bundles." },
|
|
299
300
|
{ id: "homebrew", usage: "buildchain homebrew update-formula|check", purpose: "Generate and verify Homebrew tap Formula metadata as a distribution-index projection of upstream release passport evidence." },
|
|
@@ -316,6 +317,7 @@ function buildSiteBundle() {
|
|
|
316
317
|
"docs/MAP.md",
|
|
317
318
|
"docs/install.md",
|
|
318
319
|
"docs/cli.md",
|
|
320
|
+
"docs/build-facts.md",
|
|
319
321
|
"docs/reusable-build-surface.md",
|
|
320
322
|
"docs/release-candidate.md",
|
|
321
323
|
"docs/release-governance.md",
|
|
@@ -361,6 +363,7 @@ function buildSiteBundle() {
|
|
|
361
363
|
})),
|
|
362
364
|
docs: [
|
|
363
365
|
{ id: "cli-and-node-package", path: "docs/cli.md", digest: sha256File("docs/cli.md") },
|
|
366
|
+
{ id: "build-facts", path: "docs/build-facts.md", digest: sha256File("docs/build-facts.md") },
|
|
364
367
|
{ id: "readme-badges", path: "docs/readme-badges.md", digest: sha256File("docs/readme-badges.md") },
|
|
365
368
|
{ id: "homebrew", path: "docs/homebrew.md", digest: sha256File("docs/homebrew.md") },
|
|
366
369
|
{ id: "site-bundle-contract", path: "docs/site-bundle-contract.md", digest: sha256File("docs/site-bundle-contract.md") },
|