@kungfu-tech/buildchain 2.6.2-alpha.0 → 2.6.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.
|
@@ -22,6 +22,13 @@ Release asset upload is gated before the matrix starts. Manual
|
|
|
22
22
|
`v*` tag-triggered run so an invalid manual upload request cannot spend the
|
|
23
23
|
three-platform build matrix and then fail at `gh release upload`.
|
|
24
24
|
|
|
25
|
+
GitHub Release metadata is deterministic and tag-derived. Exact alpha tags such
|
|
26
|
+
as `v2.6.2-alpha.0` are created or updated with `prerelease=true` and
|
|
27
|
+
`make_latest=false`; exact stable tags such as `v2.6.1` are created or updated
|
|
28
|
+
with `prerelease=false` and `make_latest=true`. The workflow uses
|
|
29
|
+
`scripts/ensure-github-release.mjs` before asset upload instead of relying on
|
|
30
|
+
GitHub's default latest-release heuristic.
|
|
31
|
+
|
|
25
32
|
Each archive is accompanied by:
|
|
26
33
|
|
|
27
34
|
- a platform manifest from the standalone binary builder;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungfu-tech/buildchain",
|
|
3
|
-
"version": "2.6.2
|
|
3
|
+
"version": "2.6.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Buildchain Release Passport, release governance, CLI toolkit, and site facts.",
|
|
6
6
|
"repository": "https://github.com/kungfu-systems/buildchain",
|
|
@@ -27,6 +27,7 @@ const requiredPaths = [
|
|
|
27
27
|
"scripts/release-line-dry-run.mjs",
|
|
28
28
|
"scripts/build-standalone-binary.mjs",
|
|
29
29
|
"scripts/create-release-bundle.mjs",
|
|
30
|
+
"scripts/ensure-github-release.mjs",
|
|
30
31
|
"scripts/generate-site-bundle.mjs",
|
|
31
32
|
"scripts/generate-release-candidate-passport.mjs",
|
|
32
33
|
"scripts/artifact-relay-s3.mjs",
|
|
@@ -276,12 +277,20 @@ for (const requiredSnippet of [
|
|
|
276
277
|
"verify artifact",
|
|
277
278
|
"scripts/create-release-bundle.mjs",
|
|
278
279
|
"buildchain-release-bundle",
|
|
280
|
+
"scripts/ensure-github-release.mjs",
|
|
279
281
|
"gh release upload",
|
|
280
282
|
]) {
|
|
281
283
|
if (!binaryDistributionWorkflow.includes(requiredSnippet)) {
|
|
282
284
|
throw new Error(`binary distribution workflow missing required snippet: ${requiredSnippet}`);
|
|
283
285
|
}
|
|
284
286
|
}
|
|
287
|
+
for (const forbiddenSnippet of [
|
|
288
|
+
"gh release create",
|
|
289
|
+
]) {
|
|
290
|
+
if (binaryDistributionWorkflow.includes(forbiddenSnippet)) {
|
|
291
|
+
throw new Error(`binary distribution workflow must not use unmanaged release metadata snippet: ${forbiddenSnippet}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
285
294
|
if (/runs-on:\s*self-hosted/.test(binaryDistributionWorkflow)) {
|
|
286
295
|
throw new Error("binary distribution production workflow must not require self-hosted runners");
|
|
287
296
|
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
|
|
4
|
+
export function classifyReleaseTag(tag) {
|
|
5
|
+
const normalized = String(tag || "").trim();
|
|
6
|
+
const match = normalized.match(/^v\d+\.\d+\.\d+(?:-alpha\.\d+)?$/);
|
|
7
|
+
if (!match) {
|
|
8
|
+
throw new Error(`Unsupported Buildchain release tag: ${tag}`);
|
|
9
|
+
}
|
|
10
|
+
const alpha = normalized.includes("-alpha.");
|
|
11
|
+
return {
|
|
12
|
+
tag: normalized,
|
|
13
|
+
prerelease: alpha,
|
|
14
|
+
makeLatest: alpha ? "false" : "true",
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseArgs(argv) {
|
|
19
|
+
const args = {
|
|
20
|
+
apiUrl: process.env.GITHUB_API_URL || "https://api.github.com",
|
|
21
|
+
repository: process.env.GITHUB_REPOSITORY || "",
|
|
22
|
+
tag: "",
|
|
23
|
+
title: "",
|
|
24
|
+
notes: "",
|
|
25
|
+
target: "",
|
|
26
|
+
};
|
|
27
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
28
|
+
const arg = argv[index];
|
|
29
|
+
const readValue = () => {
|
|
30
|
+
index += 1;
|
|
31
|
+
if (index >= argv.length) {
|
|
32
|
+
throw new Error(`${arg} requires a value`);
|
|
33
|
+
}
|
|
34
|
+
return argv[index];
|
|
35
|
+
};
|
|
36
|
+
if (arg === "--repository") args.repository = readValue();
|
|
37
|
+
else if (arg === "--tag") args.tag = readValue();
|
|
38
|
+
else if (arg === "--title") args.title = readValue();
|
|
39
|
+
else if (arg === "--notes") args.notes = readValue();
|
|
40
|
+
else if (arg === "--target") args.target = readValue();
|
|
41
|
+
else if (arg === "--api-url") args.apiUrl = readValue();
|
|
42
|
+
else if (arg === "--help" || arg === "-h") {
|
|
43
|
+
args.help = true;
|
|
44
|
+
} else {
|
|
45
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return args;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function usage() {
|
|
52
|
+
return [
|
|
53
|
+
"usage: node scripts/ensure-github-release.mjs --repository <owner/repo> --tag <tag>",
|
|
54
|
+
"",
|
|
55
|
+
"Ensures Buildchain GitHub Release metadata is deterministic:",
|
|
56
|
+
"- vX.Y.Z-alpha.N => prerelease=true, make_latest=false",
|
|
57
|
+
"- vX.Y.Z => prerelease=false, make_latest=true",
|
|
58
|
+
].join("\n");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function requireToken() {
|
|
62
|
+
const token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
|
|
63
|
+
if (!token) {
|
|
64
|
+
throw new Error("GH_TOKEN or GITHUB_TOKEN is required");
|
|
65
|
+
}
|
|
66
|
+
return token;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function splitRepository(repository) {
|
|
70
|
+
const match = String(repository || "").trim().match(/^([^/\s]+)\/([^/\s]+)$/);
|
|
71
|
+
if (!match) {
|
|
72
|
+
throw new Error("--repository must be in owner/repo form");
|
|
73
|
+
}
|
|
74
|
+
return { owner: match[1], repo: match[2] };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function githubRequest({ apiUrl, token, method = "GET", path, body }) {
|
|
78
|
+
const response = await fetch(`${apiUrl.replace(/\/$/, "")}${path}`, {
|
|
79
|
+
method,
|
|
80
|
+
headers: {
|
|
81
|
+
accept: "application/vnd.github+json",
|
|
82
|
+
authorization: `Bearer ${token}`,
|
|
83
|
+
"content-type": "application/json",
|
|
84
|
+
"x-github-api-version": "2022-11-28",
|
|
85
|
+
},
|
|
86
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
87
|
+
});
|
|
88
|
+
if (response.status === 404) {
|
|
89
|
+
return { status: 404, data: undefined };
|
|
90
|
+
}
|
|
91
|
+
const text = await response.text();
|
|
92
|
+
const data = text ? JSON.parse(text) : undefined;
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`GitHub API ${method} ${path} failed with ${response.status}: ${data?.message || text}`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return { status: response.status, data };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function ensureGitHubRelease({
|
|
102
|
+
apiUrl = "https://api.github.com",
|
|
103
|
+
token,
|
|
104
|
+
repository,
|
|
105
|
+
tag,
|
|
106
|
+
title = "",
|
|
107
|
+
notes = "",
|
|
108
|
+
target = "",
|
|
109
|
+
} = {}) {
|
|
110
|
+
const { owner, repo } = splitRepository(repository);
|
|
111
|
+
const metadata = classifyReleaseTag(tag);
|
|
112
|
+
const encodedTag = encodeURIComponent(metadata.tag);
|
|
113
|
+
const releasePath = `/repos/${owner}/${repo}/releases/tags/${encodedTag}`;
|
|
114
|
+
const refPath = `/repos/${owner}/${repo}/git/ref/tags/${encodedTag}`;
|
|
115
|
+
const existing = await githubRequest({ apiUrl, token, path: releasePath });
|
|
116
|
+
if (existing.status === 404) {
|
|
117
|
+
const tagRef = await githubRequest({ apiUrl, token, path: refPath });
|
|
118
|
+
if (tagRef.status === 404) {
|
|
119
|
+
throw new Error(`Git tag ${metadata.tag} does not exist in ${repository}`);
|
|
120
|
+
}
|
|
121
|
+
const created = await githubRequest({
|
|
122
|
+
apiUrl,
|
|
123
|
+
token,
|
|
124
|
+
method: "POST",
|
|
125
|
+
path: `/repos/${owner}/${repo}/releases`,
|
|
126
|
+
body: {
|
|
127
|
+
tag_name: metadata.tag,
|
|
128
|
+
name: title || metadata.tag,
|
|
129
|
+
body: notes || `Buildchain release passport assets for ${metadata.tag}.`,
|
|
130
|
+
prerelease: metadata.prerelease,
|
|
131
|
+
make_latest: metadata.makeLatest,
|
|
132
|
+
...(target ? { target_commitish: target } : {}),
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
return { action: "created", release: created.data, metadata };
|
|
136
|
+
}
|
|
137
|
+
const patched = await githubRequest({
|
|
138
|
+
apiUrl,
|
|
139
|
+
token,
|
|
140
|
+
method: "PATCH",
|
|
141
|
+
path: `/repos/${owner}/${repo}/releases/${existing.data.id}`,
|
|
142
|
+
body: {
|
|
143
|
+
name: title || existing.data.name || metadata.tag,
|
|
144
|
+
prerelease: metadata.prerelease,
|
|
145
|
+
make_latest: metadata.makeLatest,
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
return { action: "updated", release: patched.data, metadata };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function main() {
|
|
152
|
+
const args = parseArgs(process.argv.slice(2));
|
|
153
|
+
if (args.help) {
|
|
154
|
+
console.log(usage());
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const result = await ensureGitHubRelease({
|
|
158
|
+
apiUrl: args.apiUrl,
|
|
159
|
+
token: requireToken(),
|
|
160
|
+
repository: args.repository,
|
|
161
|
+
tag: args.tag,
|
|
162
|
+
title: args.title,
|
|
163
|
+
notes: args.notes,
|
|
164
|
+
target: args.target,
|
|
165
|
+
});
|
|
166
|
+
console.log(`github-release-${result.action}=${result.metadata.tag}`);
|
|
167
|
+
console.log(`github-release-prerelease=${result.metadata.prerelease}`);
|
|
168
|
+
console.log(`github-release-make-latest=${result.metadata.makeLatest}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
172
|
+
main().catch((error) => {
|
|
173
|
+
console.error(error.message);
|
|
174
|
+
process.exit(1);
|
|
175
|
+
});
|
|
176
|
+
}
|