diff --git a/scripts/publish-bk-desktop-dmg.test.ts b/scripts/publish-bk-desktop-dmg.test.ts index 05c9622d7d52..d12bebb835a9 100644 --- a/scripts/publish-bk-desktop-dmg.test.ts +++ b/scripts/publish-bk-desktop-dmg.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "vite-plus/test"; import { fingerprintDesignatedRequirement, + findPreviousChannelTag, summarizeCommitSubjects, } from "./publish-bk-desktop-dmg.ts"; @@ -79,3 +80,35 @@ describe("summarizeCommitSubjects", () => { expect(summarizeCommitSubjects(["Merge branch 'bkmain' into expbkmain", " ", ""])).toEqual([]); }); }); + +/** + * These tags are the real shapes, because the first version of this lookup keyed + * on the brand's `updateChannel` — already `staging-nightly` — and searched for + * `-staging-nightly-nightly.`. It matched nothing, so a release shipped with an + * empty change list and nothing failed to say so. + */ +describe("findPreviousChannelTag", () => { + const TAGS = [ + "v0.0.34-staging-nightly.20260819.1", + "v0.0.34-production-nightly.20260818.2", + "v0.0.34-staging-nightly.20260818.6", + "v0.0.34-production-nightly.20260817.1", + ]; + + it("finds the newest tag of the requested channel", () => { + expect(findPreviousChannelTag(TAGS, "staging")).toBe("v0.0.34-staging-nightly.20260819.1"); + expect(findPreviousChannelTag(TAGS, "production")).toBe( + "v0.0.34-production-nightly.20260818.2", + ); + }); + + it("never returns the other channel's build", () => { + expect(findPreviousChannelTag(["v0.0.34-production-nightly.20260818.2"], "staging")).toBe( + undefined, + ); + }); + + it("returns undefined for the very first build of a channel", () => { + expect(findPreviousChannelTag([], "production")).toBe(undefined); + }); +}); diff --git a/scripts/publish-bk-desktop-dmg.ts b/scripts/publish-bk-desktop-dmg.ts index 6b4356d87ffa..17b39c84de44 100644 --- a/scripts/publish-bk-desktop-dmg.ts +++ b/scripts/publish-bk-desktop-dmg.ts @@ -386,6 +386,23 @@ export function summarizeCommitSubjects( return [...unique.slice(0, limit), `…and ${unique.length - limit} more commits.`]; } +/** + * The newest published tag belonging to one channel. + * + * Keyed on the channel — `staging` / `production` — and not on the brand's + * `updateChannel`, which is already `staging-nightly`; matching on that produced + * `-staging-nightly-nightly.`, found nothing, and silently shipped a release with + * no change list. Same predicate the workflow's already-published check uses. + * + * `gh release list` returns newest first, so the first hit is the previous build. + */ +export function findPreviousChannelTag( + publishedTags: ReadonlyArray, + variant: BkManagedChannel, +): string | undefined { + return publishedTags.find((candidate) => candidate.includes(`-${variant}-nightly.`)); +} + const GhCompare = Schema.Struct({ commits: Schema.Array(Schema.Struct({ commit: Schema.Struct({ message: Schema.String }) })), }); @@ -799,9 +816,7 @@ const command = Command.make( const newest = resolveNewestNightlyVersion(publishedTags, variant); // The most recent published build of *this* channel is what the reader is // upgrading from, so it is the right base for "what changed". - const previousTag = publishedTags.find((candidate) => - candidate.includes(`-${brand.updateChannel}-nightly.`), - ); + const previousTag = findPreviousChannelTag(publishedTags, variant); if (newest && compareNightlyVersions(parsed, newest) <= 0) { return yield* new ReleaseVersionNotNewerError({ version,