From 3bb2e87d343d3b7992e3891322c1eb931407b714 Mon Sep 17 00:00:00 2001 From: olegberman Date: Sun, 6 Sep 2026 16:46:53 -0400 Subject: [PATCH] Find unpublished Nightly drafts through the GitHub CLI --- scripts/release/channels.mjs | 23 ++++++++++- test/unit/scripts/candidate-release.test.mjs | 40 ++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 test/unit/scripts/candidate-release.test.mjs diff --git a/scripts/release/channels.mjs b/scripts/release/channels.mjs index e577d47..26a0b29 100644 --- a/scripts/release/channels.mjs +++ b/scripts/release/channels.mjs @@ -70,10 +70,31 @@ export async function verifiedCandidate(version) { return { manifest, nightlyEnvelope, versionEnvelope }; } +export function candidateRelease(version, runGh = gh) { + const release = JSON.parse( + runGh([ + "release", + "view", + `v${version}`, + "--repo", + repository, + "--json", + "tagName,isDraft,isPrerelease" + ]) + ); + if ( + release.tagName !== `v${version}` || + typeof release.isDraft !== "boolean" || + typeof release.isPrerelease !== "boolean" + ) + throw new Error("Candidate release identity is invalid."); + return { draft: release.isDraft, prerelease: release.isPrerelease }; +} + async function publish(version) { const before = api("releases/latest"); const deployBefore = api("git/ref/heads/deploy").object.sha; - const release = api(`releases/tags/v${version}`); + const release = candidateRelease(version); if (!release.draft && !release.prerelease) throw new Error("This version is already Stable."); gh([ "release", diff --git a/test/unit/scripts/candidate-release.test.mjs b/test/unit/scripts/candidate-release.test.mjs new file mode 100644 index 0000000..336a7b0 --- /dev/null +++ b/test/unit/scripts/candidate-release.test.mjs @@ -0,0 +1,40 @@ +import { describe, expect, it, vi } from "vitest"; +import { candidateRelease } from "../../../scripts/release/channels.mjs"; + +describe("candidate release lookup", () => { + it.each([ + [true, false], + [false, true], + [false, false] + ])("reads draft=%s and prerelease=%s through the draft-aware CLI", (isDraft, isPrerelease) => { + const runGh = vi.fn(() => JSON.stringify({ tagName: "v1.4.0", isDraft, isPrerelease })); + expect(candidateRelease("1.4.0", runGh)).toEqual({ draft: isDraft, prerelease: isPrerelease }); + expect(runGh).toHaveBeenCalledWith([ + "release", + "view", + "v1.4.0", + "--repo", + "HQBase/hqbase", + "--json", + "tagName,isDraft,isPrerelease" + ]); + }); + + it.each([ + { tagName: "v1.4.1", isDraft: true, isPrerelease: false }, + { tagName: "v1.4.0", isPrerelease: false }, + { tagName: "v1.4.0", isDraft: true } + ])("rejects a changed or incomplete release identity", (release) => { + expect(() => candidateRelease("1.4.0", () => JSON.stringify(release))).toThrow( + "identity is invalid" + ); + }); + + it("propagates a missing release without assuming it is a draft", () => { + expect(() => + candidateRelease("1.4.0", () => { + throw new Error("release not found"); + }) + ).toThrow("release not found"); + }); +});