Files
paseo/scripts/github-release.mjs
T
Mohamed Boudra d67a6f5f6c fix(release): keep releases draft until manifests are ready (#4201)
Create tag-triggered GitHub releases as drafts and publish them only after the desktop updater manifests are complete, stamped, and uploaded. Preserve beta prerelease semantics and safe retry behavior.\n\nCloses #4183
2026-09-02 11:29:41 +02:00

109 lines
3.0 KiB
JavaScript

import { execFileSync as nodeExecFileSync } from "node:child_process";
function parseJson(output) {
return JSON.parse(output);
}
function isNotFoundError(error) {
return String(error?.stderr ?? "").includes("HTTP 404");
}
export function getGitHubRelease(repo, tag, execFileSync = nodeExecFileSync) {
try {
return parseJson(
execFileSync("gh", ["api", `repos/${repo}/releases/tags/${tag}`], {
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
}),
);
} catch (error) {
if (!isNotFoundError(error)) {
throw error;
}
const expectedName = `Paseo ${tag}`;
const output = execFileSync(
"gh",
[
"api",
`repos/${repo}/releases?per_page=100`,
"--jq",
`[.[] | select(.draft == true and .name == ${JSON.stringify(expectedName)})] | sort_by(.id)`,
],
{
encoding: "utf8",
},
);
return parseJson(output)[0] ?? null;
}
}
export function getReleaseLookupTag(release) {
if (release.draft === true && release.html_url) {
return new URL(release.html_url).pathname.split("/").at(-1);
}
return release.tag_name;
}
function parseArgs(argv) {
let repo = "";
let tag = "";
let cleanupDuplicates = false;
for (let index = 0; index < argv.length; index += 1) {
if (argv[index] === "--repo") {
repo = argv[index + 1] ?? "";
index += 1;
} else if (argv[index] === "--tag") {
tag = argv[index + 1] ?? "";
index += 1;
} else if (argv[index] === "--cleanup-duplicates") {
cleanupDuplicates = true;
} else {
throw new Error(`Unknown argument: ${argv[index]}`);
}
}
if (!repo || !tag) {
throw new Error("Usage: node scripts/github-release.mjs --repo <owner/repo> --tag <tag>");
}
return { cleanupDuplicates, repo, tag };
}
if (import.meta.url === `file://${process.argv[1]}`) {
const { cleanupDuplicates, repo, tag } = parseArgs(process.argv.slice(2));
let release = null;
for (let attempt = 0; attempt < 5 && !release; attempt += 1) {
release = getGitHubRelease(repo, tag);
if (!release && attempt < 4) {
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1_000);
}
}
if (!release) {
process.exitCode = 1;
} else {
if (cleanupDuplicates && release.draft === true) {
const expectedName = `Paseo ${tag}`;
const duplicateIds = nodeExecFileSync(
"gh",
[
"api",
`repos/${repo}/releases?per_page=100`,
"--jq",
`.[] | select(.draft == true and .name == ${JSON.stringify(expectedName)} and .id != ${release.id}) | .id`,
],
{ encoding: "utf8" },
)
.trim()
.split("\n")
.filter(Boolean);
for (const duplicateId of duplicateIds) {
nodeExecFileSync("gh", [
"api",
"--method",
"DELETE",
`repos/${repo}/releases/${duplicateId}`,
]);
}
}
process.stdout.write(`${getReleaseLookupTag(release)}\n`);
}
}