diff --git a/scripts/release.mjs b/scripts/release.mjs index 1962f049..be8f5d6c 100644 --- a/scripts/release.mjs +++ b/scripts/release.mjs @@ -313,7 +313,17 @@ export const resolveReleaseVersion = ({ } if (draftCandidate.targetCommitish === headSha) { - if (packageVersion !== expectedNextVersion) { + const expectedParts = expectedNextVersion.split(".").map(Number); + const packageParts = packageVersion.split(".").map(Number); + const bumpIndex = { major: 0, minor: 1, patch: 2 }[bump]; + // Replacement Drafts reserve prior failed versions. They remain resumable + // when repeated bumps at the explicitly requested level reach this version. + const compatibleVersion = packageParts.length === 3 && packageParts.every((part, index) => + Number.isInteger(part) && (index === bumpIndex + ? part >= expectedParts[index] + : part === expectedParts[index]) + ); + if (!compatibleVersion) { throw new Error( `${draftCandidate.tagName} cannot resume because --bump ${bump} expects v${expectedNextVersion}.`, ); diff --git a/scripts/release.test.mjs b/scripts/release.test.mjs index 3acf9ad1..2e695375 100644 --- a/scripts/release.test.mjs +++ b/scripts/release.test.mjs @@ -406,6 +406,24 @@ describe("release automation", () => { }); }); + test("resumes replacement Drafts after earlier version numbers were reserved", () => { + for (const [bump, packageVersion] of [["patch", "1.17.2"], ["minor", "1.19.0"], ["major", "3.0.0"]]) { + const draftCandidate = { + tagName: `v${packageVersion}`, isDraft: true, isPrerelease: false, + targetCommitish: "current-head", + }; + expect(resolveReleaseVersion({ + previousVersion: "1.17.0", packageVersion, bump, + headSha: "current-head", draftCandidate, + })).toMatchObject({ releaseVersion: packageVersion, resumedDraft: draftCandidate, releaseBaseTag: "v1.17.0" }); + } + expect(() => resolveReleaseVersion({ + previousVersion: "1.17.0", packageVersion: "1.19.0", bump: "patch", + headSha: "current-head", + draftCandidate: { tagName: "v1.19.0", isDraft: true, isPrerelease: false, targetCommitish: "current-head" }, + })).toThrow("cannot resume"); + }); + test("reserves a withdrawn Draft version and keeps the published audit baseline", () => { const draftCandidate = { tagName: "v1.17.1",