fix(release): mark promoted CNB release as latest

This commit is contained in:
t8y2 2026-07-30 09:45:29 +08:00
parent beaf52582f
commit cd10acb310
No known key found for this signature in database
3 changed files with 106 additions and 26 deletions

View File

@ -16,7 +16,7 @@ async function main() {
if (!tag) throw new Error("GitHub release JSON is missing tagName.");
const client = new CnbClient(args);
const release = await client.ensureRelease(tag, githubRelease);
const release = await client.ensureRelease(tag, githubRelease, { makeLatest: args.makeLatest });
if (args.metadataOnly) {
console.log(`Updated CNB release metadata for ${tag}.`);
@ -56,6 +56,7 @@ function parseArgs(argv) {
assetsDir: "",
metadataOnly: false,
pruneAssets: false,
makeLatest: false,
};
for (let index = 0; index < argv.length; index++) {
const arg = argv[index];
@ -64,12 +65,13 @@ function parseArgs(argv) {
else if (arg === "--metadata-only") args.metadataOnly = true;
else if (arg === "--overwrite-existing") args.overwriteExisting = true;
else if (arg === "--prune-assets") args.pruneAssets = true;
else if (arg === "--make-latest") args.makeLatest = true;
else throw new Error(`Unknown argument: ${arg}`);
}
if (!args.token) throw new Error("CNB_TOKEN is required.");
if (!args.githubReleasePath || (!args.metadataOnly && !args.assetsDir)) {
throw new Error(
"Usage: sync-cnb-release.mjs --github-release <release.json> (--assets-dir <dir> | --metadata-only) [--prune-assets]",
"Usage: sync-cnb-release.mjs --github-release <release.json> (--assets-dir <dir> | --metadata-only) [--prune-assets] [--make-latest]",
);
}
if (!Number.isInteger(args.concurrency) || args.concurrency < 1) {
@ -112,13 +114,14 @@ export class CnbClient {
this.token = token;
}
async ensureRelease(tag, githubRelease) {
async ensureRelease(tag, githubRelease, { makeLatest = false } = {}) {
const payload = {
tag_name: tag,
name: githubRelease.name || tag,
body: githubRelease.body || "",
prerelease: Boolean(githubRelease.isPrerelease || githubRelease.prerelease),
target_commitish: githubRelease.targetCommitish || githubRelease.target_commitish || "",
...(makeLatest ? { make_latest: "true" } : {}),
};
const existing = await this.request("GET", `/${this.repository}/-/releases/tags/${encodeURIComponent(tag)}`, null, true);
if (!existing) return this.request("POST", `/${this.repository}/-/releases`, payload);
@ -128,6 +131,7 @@ export class CnbClient {
name: payload.name,
body: payload.body,
prerelease: payload.prerelease,
...(makeLatest ? { make_latest: "true" } : {}),
});
return existing;
}

View File

@ -70,23 +70,85 @@ test("ensureRelease accepts an empty successful PATCH response", async (t) => {
]);
});
test("metadata-only sync updates release without reading an assets directory", async (t) => {
test("ensureRelease explicitly marks a promoted release as latest", async (t) => {
const release = { id: "release-latest", tag_name: "v1.2.3", assets: [] };
const requests = [];
const server = createServer((request, response) => {
const bodyChunks = [];
request.on("data", (chunk) => bodyChunks.push(chunk));
request.on("end", () => {
requests.push({
method: request.method,
url: request.url,
body: bodyChunks.length ? JSON.parse(Buffer.concat(bodyChunks).toString("utf8")) : null,
});
if (request.method === "GET") {
response.setHeader("Content-Type", "application/json");
response.end(JSON.stringify(release));
return;
}
response.statusCode = 200;
response.end();
});
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
t.after(() => new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve()))));
const address = server.address();
assert.ok(address && typeof address !== "string");
const client = new CnbClient({
apiBase: `http://127.0.0.1:${address.port}`,
repository: "dbxio.com/dbx",
token: "test-token",
});
await client.ensureRelease(
"v1.2.3",
{ name: "DBX v1.2.3", body: "Stable", isPrerelease: false },
{ makeLatest: true },
);
assert.deepEqual(requests, [
{ method: "GET", url: "/dbxio.com/dbx/-/releases/tags/v1.2.3", body: null },
{
method: "PATCH",
url: "/dbxio.com/dbx/-/releases/release-latest",
body: {
name: "DBX v1.2.3",
body: "Stable",
prerelease: false,
make_latest: "true",
},
},
]);
});
test("metadata-only sync can mark the release as latest without reading an assets directory", async (t) => {
const release = { id: "release-2", tag_name: "v1.2.3", assets: [{ name: "large.dmg" }] };
const requests = [];
const server = createServer((request, response) => {
requests.push({ method: request.method, url: request.url });
if (request.method === "GET") {
response.setHeader("Content-Type", "application/json");
response.end(JSON.stringify(release));
return;
}
if (request.method === "PATCH") {
response.statusCode = 200;
const bodyChunks = [];
request.on("data", (chunk) => bodyChunks.push(chunk));
request.on("end", () => {
requests.push({
method: request.method,
url: request.url,
body: bodyChunks.length ? JSON.parse(Buffer.concat(bodyChunks).toString("utf8")) : null,
});
if (request.method === "GET") {
response.setHeader("Content-Type", "application/json");
response.end(JSON.stringify(release));
return;
}
if (request.method === "PATCH") {
response.statusCode = 200;
response.end();
return;
}
response.statusCode = 500;
response.end();
return;
}
response.statusCode = 500;
response.end();
});
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
@ -103,14 +165,18 @@ test("metadata-only sync updates release without reading an assets directory", a
const address = server.address();
assert.ok(address && typeof address !== "string");
const result = await new Promise((resolve) => {
const child = spawn(process.execPath, [scriptPath, "--github-release", releasePath, "--metadata-only"], {
env: {
...process.env,
CNB_API_BASE: `http://127.0.0.1:${address.port}`,
CNB_REPOSITORY: "dbxio.com/dbx",
CNB_TOKEN: "test-token",
const child = spawn(
process.execPath,
[scriptPath, "--github-release", releasePath, "--metadata-only", "--make-latest"],
{
env: {
...process.env,
CNB_API_BASE: `http://127.0.0.1:${address.port}`,
CNB_REPOSITORY: "dbxio.com/dbx",
CNB_TOKEN: "test-token",
},
},
});
);
let stdout = "";
let stderr = "";
child.stdout.on("data", (chunk) => (stdout += chunk));
@ -121,8 +187,17 @@ test("metadata-only sync updates release without reading an assets directory", a
assert.equal(result.code, 0, result.stderr);
assert.match(result.stdout, /Updated CNB release metadata for v1\.2\.3/);
assert.deepEqual(requests, [
{ method: "GET", url: "/dbxio.com/dbx/-/releases/tags/v1.2.3" },
{ method: "PATCH", url: "/dbxio.com/dbx/-/releases/release-2" },
{ method: "GET", url: "/dbxio.com/dbx/-/releases/tags/v1.2.3", body: null },
{
method: "PATCH",
url: "/dbxio.com/dbx/-/releases/release-2",
body: {
name: "DBX v1.2.3",
body: "Stable",
prerelease: false,
make_latest: "true",
},
},
]);
});

View File

@ -400,7 +400,8 @@ jobs:
# Sync metadata only so promoting a release never re-uploads large CNB assets.
node .github/scripts/sync-cnb-release.mjs \
--github-release "$RUNNER_TEMP/github-release/release.json" \
--metadata-only
--metadata-only \
--make-latest
notify-qq:
name: QQ Notify