fix(release): promote CNB releases after package publishing
This commit is contained in:
1 parent
1a6d4df68f
commit
522e0dddf3
3 files changed
+126
-14
No files matched your search
@@ -17,6 +17,12 @@ async function main() {
|
|||||||
|
|
||||||
const client = new CnbClient(args);
|
const client = new CnbClient(args);
|
||||||
const release = await client.ensureRelease(tag, githubRelease);
|
const release = await client.ensureRelease(tag, githubRelease);
|
||||||
|
|
||||||
|
if (args.metadataOnly) {
|
||||||
|
console.log(`Updated CNB release metadata for ${tag}.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const existingAssets = new Set((release.assets || []).map((asset) => asset.name));
|
const existingAssets = new Set((release.assets || []).map((asset) => asset.name));
|
||||||
const assets = localAssets(args.assetsDir).filter((assetPath) => {
|
const assets = localAssets(args.assetsDir).filter((assetPath) => {
|
||||||
const name = basename(assetPath);
|
const name = basename(assetPath);
|
||||||
@@ -42,17 +48,21 @@ function parseArgs(argv) {
|
|||||||
overwriteExisting: false,
|
overwriteExisting: false,
|
||||||
githubReleasePath: "",
|
githubReleasePath: "",
|
||||||
assetsDir: "",
|
assetsDir: "",
|
||||||
|
metadataOnly: false,
|
||||||
};
|
};
|
||||||
for (let index = 0; index < argv.length; index++) {
|
for (let index = 0; index < argv.length; index++) {
|
||||||
const arg = argv[index];
|
const arg = argv[index];
|
||||||
if (arg === "--github-release") args.githubReleasePath = argv[++index];
|
if (arg === "--github-release") args.githubReleasePath = argv[++index];
|
||||||
else if (arg === "--assets-dir") args.assetsDir = argv[++index];
|
else if (arg === "--assets-dir") args.assetsDir = argv[++index];
|
||||||
|
else if (arg === "--metadata-only") args.metadataOnly = true;
|
||||||
else if (arg === "--overwrite-existing") args.overwriteExisting = true;
|
else if (arg === "--overwrite-existing") args.overwriteExisting = true;
|
||||||
else throw new Error(`Unknown argument: ${arg}`);
|
else throw new Error(`Unknown argument: ${arg}`);
|
||||||
}
|
}
|
||||||
if (!args.token) throw new Error("CNB_TOKEN is required.");
|
if (!args.token) throw new Error("CNB_TOKEN is required.");
|
||||||
if (!args.githubReleasePath || !args.assetsDir) {
|
if (!args.githubReleasePath || (!args.metadataOnly && !args.assetsDir)) {
|
||||||
throw new Error("Usage: sync-cnb-release.mjs --github-release <release.json> --assets-dir <dir>");
|
throw new Error(
|
||||||
|
"Usage: sync-cnb-release.mjs --github-release <release.json> (--assets-dir <dir> | --metadata-only)",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (!Number.isInteger(args.concurrency) || args.concurrency < 1) {
|
if (!Number.isInteger(args.concurrency) || args.concurrency < 1) {
|
||||||
throw new Error("CNB_UPLOAD_CONCURRENCY must be a positive integer.");
|
throw new Error("CNB_UPLOAD_CONCURRENCY must be a positive integer.");
|
||||||
@@ -99,6 +109,7 @@ export class CnbClient {
|
|||||||
await this.request("PATCH", `/${this.repository}/-/releases/${existing.id}`, {
|
await this.request("PATCH", `/${this.repository}/-/releases/${existing.id}`, {
|
||||||
name: payload.name,
|
name: payload.name,
|
||||||
body: payload.body,
|
body: payload.body,
|
||||||
|
prerelease: payload.prerelease,
|
||||||
});
|
});
|
||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,78 @@
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
|
import { spawn } from "node:child_process";
|
||||||
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||||
import { createServer } from "node:http";
|
import { createServer } from "node:http";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
import test from "node:test";
|
import test from "node:test";
|
||||||
|
|
||||||
import { CnbClient } from "./sync-cnb-release.mjs";
|
import { CnbClient } from "./sync-cnb-release.mjs";
|
||||||
|
|
||||||
|
const scriptPath = fileURLToPath(new URL("./sync-cnb-release.mjs", import.meta.url));
|
||||||
|
|
||||||
test("ensureRelease accepts an empty successful PATCH response", async (t) => {
|
test("ensureRelease accepts an empty successful PATCH response", async (t) => {
|
||||||
const release = { id: "release-1", tag_name: "agents-latest", assets: [] };
|
const release = { id: "release-1", tag_name: "agents-latest", assets: [] };
|
||||||
const requests = [];
|
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;
|
||||||
|
}
|
||||||
|
if (request.method === "PATCH") {
|
||||||
|
response.statusCode = 200;
|
||||||
|
response.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
response.statusCode = 500;
|
||||||
|
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",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await client.ensureRelease("agents-latest", {
|
||||||
|
name: "Latest agents",
|
||||||
|
body: "Latest stable agent release",
|
||||||
|
isPrerelease: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(result, release);
|
||||||
|
assert.deepEqual(requests, [
|
||||||
|
{ method: "GET", url: "/dbxio.com/dbx/-/releases/tags/agents-latest", body: null },
|
||||||
|
{
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/dbxio.com/dbx/-/releases/release-1",
|
||||||
|
body: {
|
||||||
|
name: "Latest agents",
|
||||||
|
body: "Latest stable agent release",
|
||||||
|
prerelease: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("metadata-only sync updates release 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) => {
|
const server = createServer((request, response) => {
|
||||||
requests.push({ method: request.method, url: request.url });
|
requests.push({ method: request.method, url: request.url });
|
||||||
if (request.method === "GET") {
|
if (request.method === "GET") {
|
||||||
@@ -26,22 +92,36 @@ test("ensureRelease accepts an empty successful PATCH response", async (t) => {
|
|||||||
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
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()))));
|
t.after(() => new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve()))));
|
||||||
|
|
||||||
|
const tempDir = await mkdtemp(join(tmpdir(), "sync-cnb-release-"));
|
||||||
|
t.after(() => rm(tempDir, { recursive: true, force: true }));
|
||||||
|
const releasePath = join(tempDir, "release.json");
|
||||||
|
await writeFile(
|
||||||
|
releasePath,
|
||||||
|
JSON.stringify({ tagName: "v1.2.3", name: "DBX v1.2.3", body: "Stable", isPrerelease: false }),
|
||||||
|
);
|
||||||
|
|
||||||
const address = server.address();
|
const address = server.address();
|
||||||
assert.ok(address && typeof address !== "string");
|
assert.ok(address && typeof address !== "string");
|
||||||
const client = new CnbClient({
|
const result = await new Promise((resolve) => {
|
||||||
apiBase: `http://127.0.0.1:${address.port}`,
|
const child = spawn(process.execPath, [scriptPath, "--github-release", releasePath, "--metadata-only"], {
|
||||||
repository: "dbxio.com/dbx",
|
env: {
|
||||||
token: "test-token",
|
...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));
|
||||||
|
child.stderr.on("data", (chunk) => (stderr += chunk));
|
||||||
|
child.on("close", (code) => resolve({ code, stdout, stderr }));
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await client.ensureRelease("agents-latest", {
|
assert.equal(result.code, 0, result.stderr);
|
||||||
name: "Latest agents",
|
assert.match(result.stdout, /Updated CNB release metadata for v1\.2\.3/);
|
||||||
body: "Latest stable agent release",
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.deepEqual(result, release);
|
|
||||||
assert.deepEqual(requests, [
|
assert.deepEqual(requests, [
|
||||||
{ method: "GET", url: "/dbxio.com/dbx/-/releases/tags/agents-latest" },
|
{ method: "GET", url: "/dbxio.com/dbx/-/releases/tags/v1.2.3" },
|
||||||
{ method: "PATCH", url: "/dbxio.com/dbx/-/releases/release-1" },
|
{ method: "PATCH", url: "/dbxio.com/dbx/-/releases/release-2" },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -365,6 +365,9 @@ jobs:
|
|||||||
needs: [publish, upload-r2]
|
needs: [publish, upload-r2]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Promote prerelease to release
|
- name: Promote prerelease to release
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -376,6 +379,24 @@ jobs:
|
|||||||
--latest
|
--latest
|
||||||
echo "::notice::Release ${TAG} promoted from prerelease to latest release"
|
echo "::notice::Release ${TAG} promoted from prerelease to latest release"
|
||||||
|
|
||||||
|
- name: Promote CNB prerelease to release
|
||||||
|
env:
|
||||||
|
CNB_TOKEN: ${{ secrets.CNB_TOKEN }}
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
TAG: ${{ github.event.inputs.tag }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir -p "$RUNNER_TEMP/github-release"
|
||||||
|
gh release view "$TAG" \
|
||||||
|
--repo "${{ github.repository }}" \
|
||||||
|
--json tagName,name,body,targetCommitish,isPrerelease,isDraft \
|
||||||
|
> "$RUNNER_TEMP/github-release/release.json"
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
notify-qq:
|
notify-qq:
|
||||||
name: QQ Notify
|
name: QQ Notify
|
||||||
needs: [promote-release]
|
needs: [promote-release]
|
||||||
|
|||||||
Reference in new issue
Block a user