@kungfu-tech/buildchain 2.8.15 → 2.8.16-alpha.2
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 +19 -11
- package/bin/buildchain.mjs +150 -0
- package/dist/site/buildchain-contract.json +83 -11
- package/dist/site/buildchain-site.json +121 -20
- package/dist/site/cli-registry.json +5 -0
- package/dist/site/kfd-claims.json +85 -3
- package/dist/site/manual-registry.json +18 -3
- package/dist/site/node-api-registry.json +28 -6
- package/dist/site/page-registry.json +88 -11
- package/dist/site/release-model.json +8 -0
- package/dist/site/release-provenance.json +2 -0
- package/dist/site/site-manifest.json +23 -7
- package/docs/MAP.md +8 -0
- package/docs/cli.md +40 -3
- package/docs/homebrew.md +116 -0
- package/docs/readme-badges.md +124 -0
- package/docs/web-surface-deployments.md +46 -8
- package/package.json +3 -1
- package/packages/core/README.md +12 -0
- package/packages/core/buildchain-config.js +2 -2
- package/packages/core/buildchain-contract.js +56 -0
- package/packages/core/buildchain-kfd-claims.js +37 -0
- package/packages/core/homebrew.js +414 -0
- package/packages/core/index.js +21 -0
- package/packages/core/readme-badges.js +450 -0
- package/packages/core/release-passport.js +24 -9
- package/scripts/check-inventory.mjs +69 -0
- package/scripts/generate-site-bundle.mjs +12 -0
- package/scripts/web-surface-core.mjs +177 -5
|
@@ -11,6 +11,8 @@ export const BUILDCHAIN_AGENT_MANUALS = Object.freeze([
|
|
|
11
11
|
{ id: "install", title: "Install and verify Buildchain", path: "docs/install.md", plane: "use" },
|
|
12
12
|
{ id: "release-passport", title: "Release Passport protocol", path: "docs/release-passport.md", plane: "verify" },
|
|
13
13
|
{ id: "release-propagation", title: "Release propagation", path: "docs/release-propagation.md", plane: "use" },
|
|
14
|
+
{ id: "readme-badges", title: "README badge blocks", path: "docs/readme-badges.md", plane: "use" },
|
|
15
|
+
{ id: "homebrew", title: "Homebrew distribution indexes", path: "docs/homebrew.md", plane: "use" },
|
|
14
16
|
{ id: "binary-distribution", title: "Binary distribution contract", path: "docs/binary-distribution.md", plane: "verify" },
|
|
15
17
|
{ id: "consumer-issue-reporting", title: "Consumer issue reporting", path: "docs/consumer-issue-reporting.md", plane: "use" },
|
|
16
18
|
{ id: "infra-contract", title: "Infra Contract", path: "docs/infra-contract.md", plane: "use" },
|
|
@@ -69,6 +71,7 @@ const EXTRA_KFD1_FILES = Object.freeze([
|
|
|
69
71
|
"package.json",
|
|
70
72
|
"bin/buildchain.mjs",
|
|
71
73
|
"packages/core/index.js",
|
|
74
|
+
"packages/core/homebrew.js",
|
|
72
75
|
"packages/core/release-propagation.js",
|
|
73
76
|
"scripts/generate-site-bundle.mjs",
|
|
74
77
|
"scripts/ensure-github-release.mjs",
|
|
@@ -195,9 +198,11 @@ export function createBuildchainPublicClaimDefinitions() {
|
|
|
195
198
|
sourcePaths: [
|
|
196
199
|
"scripts/generate-site-bundle.mjs",
|
|
197
200
|
"packages/core/buildchain-kfd-claims.js",
|
|
201
|
+
"packages/core/readme-badges.js",
|
|
198
202
|
"docs/MAP.md",
|
|
199
203
|
"docs/site-bundle-contract.md",
|
|
200
204
|
"docs/cli.md",
|
|
205
|
+
"docs/readme-badges.md",
|
|
201
206
|
],
|
|
202
207
|
artifactPaths: [
|
|
203
208
|
"dist/site/manual-registry.json",
|
|
@@ -207,6 +212,38 @@ export function createBuildchainPublicClaimDefinitions() {
|
|
|
207
212
|
"dist/site/site-manifest.json",
|
|
208
213
|
],
|
|
209
214
|
},
|
|
215
|
+
{
|
|
216
|
+
id: "claim:buildchain-readme-badge-facts",
|
|
217
|
+
claim: "Buildchain README status badges are generated from repository-owned facts and verified release-passport evidence, not hand-maintained README prose.",
|
|
218
|
+
sourcePaths: [
|
|
219
|
+
"packages/core/readme-badges.js",
|
|
220
|
+
"bin/buildchain.mjs",
|
|
221
|
+
"docs/readme-badges.md",
|
|
222
|
+
"docs/cli.md",
|
|
223
|
+
"buildchain.toml",
|
|
224
|
+
],
|
|
225
|
+
artifactPaths: [
|
|
226
|
+
"dist/site/node-api-registry.json",
|
|
227
|
+
"dist/site/manual-registry.json",
|
|
228
|
+
"dist/site/buildchain-contract.json",
|
|
229
|
+
],
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
id: "claim:buildchain-homebrew-distribution-index",
|
|
233
|
+
claim: "Buildchain Homebrew tap support generates and verifies Formula metadata as a distribution-index projection of upstream release passport evidence.",
|
|
234
|
+
sourcePaths: [
|
|
235
|
+
"packages/core/homebrew.js",
|
|
236
|
+
"bin/buildchain.mjs",
|
|
237
|
+
"docs/homebrew.md",
|
|
238
|
+
"docs/cli.md",
|
|
239
|
+
"packages/core/buildchain-config.js",
|
|
240
|
+
],
|
|
241
|
+
artifactPaths: [
|
|
242
|
+
"dist/site/node-api-registry.json",
|
|
243
|
+
"dist/site/manual-registry.json",
|
|
244
|
+
"dist/site/buildchain-contract.json",
|
|
245
|
+
],
|
|
246
|
+
},
|
|
210
247
|
{
|
|
211
248
|
id: "claim:buildchain-floating-contract-lock",
|
|
212
249
|
claim: "Consumers using floating Buildchain refs can lock the observed runtime contract and receive drift checks before expensive build or publish work proceeds.",
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { readJsonFromLocation, sha256Text, verifyReleasePassport } from "./release-passport.js";
|
|
4
|
+
|
|
5
|
+
export const HOMEBREW_TAP_FACTS_CONTRACT = "kungfu-buildchain-homebrew-tap-facts";
|
|
6
|
+
export const HOMEBREW_TAP_CHECK_CONTRACT = "kungfu-buildchain-homebrew-tap-check";
|
|
7
|
+
export const HOMEBREW_TAP_MANIFEST_CONTRACT = "kungfu-buildchain-homebrew-tap-manifest";
|
|
8
|
+
|
|
9
|
+
const DEFAULT_MANIFEST_PATH = "tap-manifest.json";
|
|
10
|
+
const DEFAULT_FORMULA_PATH = "Formula/buildchain.rb";
|
|
11
|
+
const KFD_KEYS = ["kfd-1", "kfd-2", "kfd-3"];
|
|
12
|
+
const SUPPORTED_FORMULA_PLATFORMS = new Set(["darwin-arm64", "linux-x64"]);
|
|
13
|
+
|
|
14
|
+
function readJsonFile(filePath, fallback = undefined) {
|
|
15
|
+
if (!fs.existsSync(filePath)) {
|
|
16
|
+
return fallback;
|
|
17
|
+
}
|
|
18
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function writeJsonFile(filePath, value) {
|
|
22
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
23
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function writeTextFile(filePath, value) {
|
|
27
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
28
|
+
fs.writeFileSync(filePath, value.endsWith("\n") ? value : `${value}\n`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function optionalString(value) {
|
|
32
|
+
return value === undefined || value === null ? "" : String(value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function nonEmptyString(value, label) {
|
|
36
|
+
const normalized = optionalString(value).trim();
|
|
37
|
+
if (!normalized) {
|
|
38
|
+
throw new Error(`${label} must be a non-empty string`);
|
|
39
|
+
}
|
|
40
|
+
return normalized;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function digestWithoutPrefix(value) {
|
|
44
|
+
return optionalString(value).replace(/^sha256:/, "");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function stableJson(value) {
|
|
48
|
+
if (Array.isArray(value)) {
|
|
49
|
+
return `[${value.map(stableJson).join(",")}]`;
|
|
50
|
+
}
|
|
51
|
+
if (value && typeof value === "object") {
|
|
52
|
+
return `{${Object.keys(value)
|
|
53
|
+
.sort()
|
|
54
|
+
.map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
|
|
55
|
+
.join(",")}}`;
|
|
56
|
+
}
|
|
57
|
+
return JSON.stringify(value);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function formulaClassName(name) {
|
|
61
|
+
return nonEmptyString(name, "package name")
|
|
62
|
+
.replace(/^@[^/]+\//, "")
|
|
63
|
+
.split(/[^A-Za-z0-9]+/)
|
|
64
|
+
.filter(Boolean)
|
|
65
|
+
.map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`)
|
|
66
|
+
.join("");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function rubyString(value) {
|
|
70
|
+
return optionalString(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function inferPlatformFromName(name) {
|
|
74
|
+
const lower = optionalString(name).toLowerCase();
|
|
75
|
+
if (lower.includes("apple-darwin") || lower.includes("darwin") || lower.includes("macos")) {
|
|
76
|
+
return lower.includes("aarch64") || lower.includes("arm64") ? "darwin-arm64" : "darwin-x64";
|
|
77
|
+
}
|
|
78
|
+
if (lower.includes("windows") || lower.includes("pc-windows") || lower.endsWith(".zip")) {
|
|
79
|
+
return "windows-x64";
|
|
80
|
+
}
|
|
81
|
+
if (lower.includes("linux") || lower.includes("unknown-linux")) {
|
|
82
|
+
return lower.includes("aarch64") || lower.includes("arm64") ? "linux-arm64" : "linux-x64";
|
|
83
|
+
}
|
|
84
|
+
return "";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function normalizeRepository(value = "") {
|
|
88
|
+
const raw = optionalString(value).trim();
|
|
89
|
+
if (!raw) return "";
|
|
90
|
+
const githubMatch = raw.match(/github\.com[:/]([^/\s]+\/[^/\s#]+?)(?:\.git)?(?:[#/?].*)?$/);
|
|
91
|
+
if (githubMatch) return githubMatch[1].replace(/\.git$/, "");
|
|
92
|
+
if (/^[^/\s]+\/[^/\s]+$/.test(raw)) return raw.replace(/\.git$/, "");
|
|
93
|
+
return raw;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function releaseDirectoryUrl(releasePassportLocation = "") {
|
|
97
|
+
if (!/^https?:\/\//.test(releasePassportLocation)) return "";
|
|
98
|
+
const url = new URL(releasePassportLocation);
|
|
99
|
+
url.pathname = url.pathname.replace(/\/[^/]*$/, "/");
|
|
100
|
+
return url.toString();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function githubAssetUrl(repository, tag, name) {
|
|
104
|
+
const repo = normalizeRepository(repository);
|
|
105
|
+
if (!repo || !tag || !name) return "";
|
|
106
|
+
return `https://github.com/${repo}/releases/download/${encodeURIComponent(tag)}/${encodeURIComponent(name)}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function siblingAssetUrl(releasePassportLocation, name) {
|
|
110
|
+
const base = releaseDirectoryUrl(releasePassportLocation);
|
|
111
|
+
return base ? new URL(encodeURIComponent(name), base).toString() : "";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function normalizeArtifact(artifact, { releasePassportLocation = "", repository = "", tag = "" } = {}) {
|
|
115
|
+
const name = nonEmptyString(artifact.name || artifact.filename, "artifact.name");
|
|
116
|
+
const platform = optionalString(artifact.platform || inferPlatformFromName(name));
|
|
117
|
+
return {
|
|
118
|
+
name,
|
|
119
|
+
platform,
|
|
120
|
+
url: optionalString(artifact.url || artifact.browser_download_url || artifact.downloadUrl)
|
|
121
|
+
|| siblingAssetUrl(releasePassportLocation, name)
|
|
122
|
+
|| githubAssetUrl(repository, tag, name),
|
|
123
|
+
sha256: digestWithoutPrefix(artifact.sha256 || artifact.digest || artifact.checksum),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function formulaArchiveArtifacts(passport, context) {
|
|
128
|
+
return (passport.artifacts || [])
|
|
129
|
+
.map((artifact) => normalizeArtifact(artifact, context))
|
|
130
|
+
.filter((artifact) => SUPPORTED_FORMULA_PLATFORMS.has(artifact.platform))
|
|
131
|
+
.filter((artifact) => /\.(?:tar\.gz|tgz)$/i.test(artifact.name))
|
|
132
|
+
.sort((left, right) => left.platform.localeCompare(right.platform));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function readTapManifest(cwd, manifestPath = DEFAULT_MANIFEST_PATH) {
|
|
136
|
+
const filePath = path.join(cwd, manifestPath);
|
|
137
|
+
const manifest = readJsonFile(filePath, {
|
|
138
|
+
schema: 1,
|
|
139
|
+
contract: HOMEBREW_TAP_MANIFEST_CONTRACT,
|
|
140
|
+
name: "",
|
|
141
|
+
kind: "homebrew-tap",
|
|
142
|
+
entries: [],
|
|
143
|
+
});
|
|
144
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
145
|
+
throw new Error(`${manifestPath} must be a JSON object`);
|
|
146
|
+
}
|
|
147
|
+
if (manifest.schema !== undefined && manifest.schema !== 1) {
|
|
148
|
+
throw new Error(`${manifestPath} schema must be 1`);
|
|
149
|
+
}
|
|
150
|
+
if (manifest.schemaVersion !== undefined && manifest.schemaVersion !== 1) {
|
|
151
|
+
throw new Error(`${manifestPath} schemaVersion must be 1`);
|
|
152
|
+
}
|
|
153
|
+
if (manifest.entries !== undefined && !Array.isArray(manifest.entries)) {
|
|
154
|
+
throw new Error(`${manifestPath} entries must be an array`);
|
|
155
|
+
}
|
|
156
|
+
return manifest;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function findManifestEntry(manifest, packageName) {
|
|
160
|
+
return (manifest.entries || []).find((entry) => entry?.type === "formula" && entry.name === packageName);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function verifyPassportLocation(location) {
|
|
164
|
+
try {
|
|
165
|
+
const report = await verifyReleasePassport({ passportLocation: location });
|
|
166
|
+
return { ok: report.ok === true, report, error: "" };
|
|
167
|
+
} catch (error) {
|
|
168
|
+
return { ok: false, report: undefined, error: error.message };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function createKfdProjection(passport, verified) {
|
|
173
|
+
return Object.fromEntries(KFD_KEYS.map((key) => [
|
|
174
|
+
key,
|
|
175
|
+
verified && passport?.[key]?.status === "passed" ? "passed" : "unverified",
|
|
176
|
+
]));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function createManifestProjection({
|
|
180
|
+
manifest,
|
|
181
|
+
entry,
|
|
182
|
+
packageName,
|
|
183
|
+
formulaPath,
|
|
184
|
+
releasePassportLocation,
|
|
185
|
+
passport,
|
|
186
|
+
artifacts,
|
|
187
|
+
kfd,
|
|
188
|
+
repository,
|
|
189
|
+
} = {}) {
|
|
190
|
+
const tag = nonEmptyString(passport.release?.tag, "release.tag");
|
|
191
|
+
const version = nonEmptyString(passport.release?.publishedVersion || passport.release?.versionLabel, "release.publishedVersion");
|
|
192
|
+
const repo = normalizeRepository(repository || entry?.upstream?.repository || passport.product?.repository);
|
|
193
|
+
const latestReleasePassportUrl = optionalString(entry?.upstream?.latestReleasePassportUrl)
|
|
194
|
+
|| (repo ? `https://github.com/${repo}/releases/latest/download/buildchain.release.json` : "");
|
|
195
|
+
return {
|
|
196
|
+
schema: 1,
|
|
197
|
+
contract: HOMEBREW_TAP_MANIFEST_CONTRACT,
|
|
198
|
+
name: optionalString(manifest.name || (repo ? `${repo.replace(/\/[^/]+$/, "")}/homebrew-tap` : "")),
|
|
199
|
+
kind: "homebrew-tap",
|
|
200
|
+
entries: [
|
|
201
|
+
{
|
|
202
|
+
type: "formula",
|
|
203
|
+
name: packageName,
|
|
204
|
+
path: formulaPath,
|
|
205
|
+
upstream: {
|
|
206
|
+
repository: repo,
|
|
207
|
+
tag,
|
|
208
|
+
releasePassportUrl: releasePassportLocation,
|
|
209
|
+
latestReleasePassportUrl,
|
|
210
|
+
},
|
|
211
|
+
version,
|
|
212
|
+
kfd,
|
|
213
|
+
artifacts: artifacts.map((artifact) => ({
|
|
214
|
+
platform: artifact.platform,
|
|
215
|
+
url: artifact.url,
|
|
216
|
+
sha256: artifact.sha256,
|
|
217
|
+
})),
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export async function collectHomebrewTapFacts({
|
|
224
|
+
cwd = process.cwd(),
|
|
225
|
+
packageName = "buildchain",
|
|
226
|
+
releasePassport = "",
|
|
227
|
+
manifestPath = DEFAULT_MANIFEST_PATH,
|
|
228
|
+
formulaPath = "",
|
|
229
|
+
} = {}) {
|
|
230
|
+
const resolvedCwd = path.resolve(cwd);
|
|
231
|
+
const manifest = readTapManifest(resolvedCwd, manifestPath);
|
|
232
|
+
const entry = findManifestEntry(manifest, packageName);
|
|
233
|
+
const selectedFormulaPath = formulaPath || entry?.path || (packageName === "buildchain" ? DEFAULT_FORMULA_PATH : `Formula/${packageName}.rb`);
|
|
234
|
+
const releasePassportLocation = nonEmptyString(
|
|
235
|
+
releasePassport || entry?.upstream?.releasePassportUrl || entry?.releasePassport,
|
|
236
|
+
"release passport",
|
|
237
|
+
);
|
|
238
|
+
const passport = await readJsonFromLocation(releasePassportLocation);
|
|
239
|
+
const verification = await verifyPassportLocation(releasePassportLocation);
|
|
240
|
+
const repository = normalizeRepository(entry?.upstream?.repository || passport.product?.repository);
|
|
241
|
+
const artifacts = formulaArchiveArtifacts(passport, {
|
|
242
|
+
releasePassportLocation,
|
|
243
|
+
repository,
|
|
244
|
+
tag: passport.release?.tag,
|
|
245
|
+
});
|
|
246
|
+
const kfd = createKfdProjection(passport, verification.ok);
|
|
247
|
+
const manifestProjection = createManifestProjection({
|
|
248
|
+
manifest,
|
|
249
|
+
entry,
|
|
250
|
+
packageName,
|
|
251
|
+
formulaPath: selectedFormulaPath,
|
|
252
|
+
releasePassportLocation,
|
|
253
|
+
passport,
|
|
254
|
+
artifacts,
|
|
255
|
+
kfd,
|
|
256
|
+
repository,
|
|
257
|
+
});
|
|
258
|
+
return {
|
|
259
|
+
schemaVersion: 1,
|
|
260
|
+
contract: HOMEBREW_TAP_FACTS_CONTRACT,
|
|
261
|
+
cwd: resolvedCwd,
|
|
262
|
+
package: {
|
|
263
|
+
name: packageName,
|
|
264
|
+
formulaClass: formulaClassName(packageName),
|
|
265
|
+
desc: optionalString(entry?.formula?.desc || "Release passport and build evidence toolkit"),
|
|
266
|
+
homepage: optionalString(entry?.formula?.homepage || passport.product?.homepage || "https://buildchain.libkungfu.dev"),
|
|
267
|
+
license: optionalString(entry?.formula?.license || "Apache-2.0"),
|
|
268
|
+
},
|
|
269
|
+
releasePassport: {
|
|
270
|
+
location: releasePassportLocation,
|
|
271
|
+
verified: verification.ok,
|
|
272
|
+
verificationError: verification.error,
|
|
273
|
+
report: verification.report,
|
|
274
|
+
},
|
|
275
|
+
release: {
|
|
276
|
+
tag: passport.release?.tag || "",
|
|
277
|
+
version: passport.release?.publishedVersion || passport.release?.versionLabel || "",
|
|
278
|
+
repository,
|
|
279
|
+
},
|
|
280
|
+
formula: {
|
|
281
|
+
path: selectedFormulaPath,
|
|
282
|
+
artifacts,
|
|
283
|
+
},
|
|
284
|
+
kfd,
|
|
285
|
+
manifestPath,
|
|
286
|
+
manifestProjection,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function renderHomebrewFormula(facts) {
|
|
291
|
+
if (!facts || facts.contract !== HOMEBREW_TAP_FACTS_CONTRACT) {
|
|
292
|
+
throw new Error("facts must be collected with collectHomebrewTapFacts");
|
|
293
|
+
}
|
|
294
|
+
const artifacts = facts.formula?.artifacts || [];
|
|
295
|
+
const darwinArm64 = artifacts.find((artifact) => artifact.platform === "darwin-arm64");
|
|
296
|
+
const linuxX64 = artifacts.find((artifact) => artifact.platform === "linux-x64");
|
|
297
|
+
if (!darwinArm64 || !linuxX64) {
|
|
298
|
+
throw new Error("Homebrew formula requires darwin-arm64 and linux-x64 tar.gz artifacts");
|
|
299
|
+
}
|
|
300
|
+
return `class ${facts.package.formulaClass} < Formula
|
|
301
|
+
desc "${rubyString(facts.package.desc)}"
|
|
302
|
+
homepage "${rubyString(facts.package.homepage)}"
|
|
303
|
+
version "${rubyString(facts.release.version)}"
|
|
304
|
+
license "${rubyString(facts.package.license)}"
|
|
305
|
+
|
|
306
|
+
if OS.mac? && Hardware::CPU.arm?
|
|
307
|
+
url "${rubyString(darwinArm64.url)}"
|
|
308
|
+
sha256 "${rubyString(darwinArm64.sha256)}"
|
|
309
|
+
elsif OS.linux? && Hardware::CPU.intel?
|
|
310
|
+
url "${rubyString(linuxX64.url)}"
|
|
311
|
+
sha256 "${rubyString(linuxX64.sha256)}"
|
|
312
|
+
else
|
|
313
|
+
odie "${facts.package.formulaClass} Homebrew formula currently supports macOS arm64 and Linux x86_64 binary archives."
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def install
|
|
317
|
+
bin.install "${rubyString(facts.package.name)}"
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
test do
|
|
321
|
+
assert_match version.to_s, shell_output("#{bin}/${rubyString(facts.package.name)} version")
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
`;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function checkStatus(ok, id, message, details = {}) {
|
|
328
|
+
return { id, status: ok ? "pass" : "fail", message, details };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function sameJson(left, right) {
|
|
332
|
+
return stableJson(left) === stableJson(right);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export async function checkHomebrewTap({
|
|
336
|
+
cwd = process.cwd(),
|
|
337
|
+
packageName = "buildchain",
|
|
338
|
+
releasePassport = "",
|
|
339
|
+
manifestPath = DEFAULT_MANIFEST_PATH,
|
|
340
|
+
formulaPath = "",
|
|
341
|
+
} = {}) {
|
|
342
|
+
const facts = await collectHomebrewTapFacts({ cwd, packageName, releasePassport, manifestPath, formulaPath });
|
|
343
|
+
const expectedFormula = renderHomebrewFormula(facts);
|
|
344
|
+
const expectedManifest = facts.manifestProjection;
|
|
345
|
+
const formulaFilePath = path.join(facts.cwd, facts.formula.path);
|
|
346
|
+
const manifestFilePath = path.join(facts.cwd, facts.manifestPath);
|
|
347
|
+
const currentFormula = fs.existsSync(formulaFilePath) ? fs.readFileSync(formulaFilePath, "utf8") : "";
|
|
348
|
+
const currentManifest = readJsonFile(manifestFilePath, undefined);
|
|
349
|
+
const formulaCurrent = currentFormula === expectedFormula;
|
|
350
|
+
const manifestCurrent = currentManifest !== undefined && sameJson(currentManifest, expectedManifest);
|
|
351
|
+
const checks = [
|
|
352
|
+
checkStatus(facts.releasePassport.verified, "upstream-passport.verified", "upstream release passport verifies", {
|
|
353
|
+
error: facts.releasePassport.verificationError,
|
|
354
|
+
}),
|
|
355
|
+
checkStatus((facts.formula.artifacts || []).length >= 2, "formula.artifacts", "formula has required platform artifacts", {
|
|
356
|
+
platforms: (facts.formula.artifacts || []).map((artifact) => artifact.platform),
|
|
357
|
+
}),
|
|
358
|
+
checkStatus(formulaCurrent, "formula.current", "Formula is current with upstream passport projection", {
|
|
359
|
+
path: facts.formula.path,
|
|
360
|
+
expectedSha256: sha256Text(expectedFormula),
|
|
361
|
+
actualSha256: currentFormula ? sha256Text(currentFormula) : "",
|
|
362
|
+
}),
|
|
363
|
+
checkStatus(manifestCurrent, "tap-manifest.current", "tap manifest is current with upstream passport projection", {
|
|
364
|
+
path: facts.manifestPath,
|
|
365
|
+
expectedSha256: sha256Text(`${JSON.stringify(expectedManifest, null, 2)}\n`),
|
|
366
|
+
actualSha256: currentManifest ? sha256Text(`${JSON.stringify(currentManifest, null, 2)}\n`) : "",
|
|
367
|
+
}),
|
|
368
|
+
...KFD_KEYS.map((key) => checkStatus(
|
|
369
|
+
facts.kfd[key] === "passed",
|
|
370
|
+
`kfd.${key}`,
|
|
371
|
+
`${key} passed is backed by verified upstream release passport`,
|
|
372
|
+
{ status: facts.kfd[key] },
|
|
373
|
+
)),
|
|
374
|
+
];
|
|
375
|
+
return {
|
|
376
|
+
schemaVersion: 1,
|
|
377
|
+
contract: HOMEBREW_TAP_CHECK_CONTRACT,
|
|
378
|
+
cwd: facts.cwd,
|
|
379
|
+
ok: checks.every((check) => check.status === "pass"),
|
|
380
|
+
package: packageName,
|
|
381
|
+
checks,
|
|
382
|
+
facts,
|
|
383
|
+
expected: {
|
|
384
|
+
formula: expectedFormula,
|
|
385
|
+
manifest: expectedManifest,
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export async function updateHomebrewTap({
|
|
391
|
+
cwd = process.cwd(),
|
|
392
|
+
packageName = "buildchain",
|
|
393
|
+
releasePassport = "",
|
|
394
|
+
manifestPath = DEFAULT_MANIFEST_PATH,
|
|
395
|
+
formulaPath = "",
|
|
396
|
+
write = true,
|
|
397
|
+
} = {}) {
|
|
398
|
+
const facts = await collectHomebrewTapFacts({ cwd, packageName, releasePassport, manifestPath, formulaPath });
|
|
399
|
+
const formula = renderHomebrewFormula(facts);
|
|
400
|
+
const manifest = facts.manifestProjection;
|
|
401
|
+
if (write) {
|
|
402
|
+
writeTextFile(path.join(facts.cwd, facts.formula.path), formula);
|
|
403
|
+
writeJsonFile(path.join(facts.cwd, facts.manifestPath), manifest);
|
|
404
|
+
}
|
|
405
|
+
return {
|
|
406
|
+
schemaVersion: 1,
|
|
407
|
+
contract: "kungfu-buildchain-homebrew-tap-update",
|
|
408
|
+
ok: true,
|
|
409
|
+
written: write ? [facts.formula.path, facts.manifestPath] : [],
|
|
410
|
+
facts,
|
|
411
|
+
formula,
|
|
412
|
+
manifest,
|
|
413
|
+
};
|
|
414
|
+
}
|
package/packages/core/index.js
CHANGED
|
@@ -192,6 +192,27 @@ export {
|
|
|
192
192
|
workflowFrictionMarker,
|
|
193
193
|
} from "./issue-reporting.js";
|
|
194
194
|
|
|
195
|
+
export {
|
|
196
|
+
README_BADGE_BLOCK_END,
|
|
197
|
+
README_BADGE_BLOCK_START,
|
|
198
|
+
README_BADGE_FACTS_CONTRACT,
|
|
199
|
+
checkReadmeBadgeBlock,
|
|
200
|
+
collectReadmeBadgeFacts,
|
|
201
|
+
readReadme,
|
|
202
|
+
renderReadmeBadgeBlock,
|
|
203
|
+
updateReadmeBadgeBlock,
|
|
204
|
+
} from "./readme-badges.js";
|
|
205
|
+
|
|
206
|
+
export {
|
|
207
|
+
HOMEBREW_TAP_CHECK_CONTRACT,
|
|
208
|
+
HOMEBREW_TAP_FACTS_CONTRACT,
|
|
209
|
+
HOMEBREW_TAP_MANIFEST_CONTRACT,
|
|
210
|
+
checkHomebrewTap,
|
|
211
|
+
collectHomebrewTapFacts,
|
|
212
|
+
renderHomebrewFormula,
|
|
213
|
+
updateHomebrewTap,
|
|
214
|
+
} from "./homebrew.js";
|
|
215
|
+
|
|
195
216
|
export {
|
|
196
217
|
RELEASE_PROPAGATION_GRAPH_CONTRACT,
|
|
197
218
|
RELEASE_PROPAGATION_LOCK_CONTRACT,
|