From eefe2e8837a74c7443a9915b1d593de7e806bafa Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 10 Jul 2026 01:58:24 -0700 Subject: [PATCH] fix(release): let alpha tags publish from their exact changelog heading Shipped alpha tags carry a dedicated ## X-alpha.N heading with no base section (see v2026.6.20-alpha.1's tagged CHANGELOG.md); the renderer and candidate provenance now prefer that dedicated heading for alpha and correction tags while beta/stable remain pinned to the stable base section per #103222. --- scripts/release-candidate-checklist.mjs | 17 ++++++------ scripts/render-github-release-notes.mjs | 25 ++++++++--------- .../render-github-release-notes.test.ts | 27 +++++++++++++++++++ 3 files changed, 49 insertions(+), 20 deletions(-) diff --git a/scripts/release-candidate-checklist.mjs b/scripts/release-candidate-checklist.mjs index 6953f092815a..821900b715e4 100644 --- a/scripts/release-candidate-checklist.mjs +++ b/scripts/release-candidate-checklist.mjs @@ -8,7 +8,7 @@ import { fileURLToPath } from "node:url"; import { stripLeadingPackageManagerSeparator } from "./lib/arg-utils.mjs"; import { readBoundedResponseText } from "./lib/bounded-response.mjs"; import { - correctionVersionForTag, + dedicatedSectionVersionForTag, extractChangelogReleaseSections, extractChangelogSection, formatShippedBaselineExclusions, @@ -489,18 +489,19 @@ export function validateCandidateChangelogProvenance({ isAncestor = gitIsAncestor, loadShippedBaseline = loadCandidateShippedBaseline, }) { - // Validate the same section the renderer publishes: correction tags may - // carry their own heading, and alpha tags may fall back to Unreleased. + // Validate the same section the renderer publishes: alpha and correction + // tags may carry their own heading, and alpha tags may fall back to + // Unreleased. let section; let sectionVersion = version; let usesAlphaUnreleasedFallback = false; - const correctionVersion = correctionVersionForTag(tag); - if (correctionVersion && correctionVersion !== version) { + const dedicatedVersion = dedicatedSectionVersionForTag(tag); + if (dedicatedVersion && dedicatedVersion !== version) { try { - section = extractChangelogSection(changelog, correctionVersion); - sectionVersion = correctionVersion; + section = extractChangelogSection(changelog, dedicatedVersion); + sectionVersion = dedicatedVersion; } catch { - // The correction has no dedicated section; validate the base section. + // No dedicated section; validate the base section. } } if (section === undefined) { diff --git a/scripts/render-github-release-notes.mjs b/scripts/render-github-release-notes.mjs index bc8911e45fec..71736f035063 100644 --- a/scripts/render-github-release-notes.mjs +++ b/scripts/render-github-release-notes.mjs @@ -230,24 +230,25 @@ function compactReleaseNotes(section, repository, tag) { ].join("\n"); } -export function correctionVersionForTag(tag) { - // Numeric-correction tags (vX-N) may carry their own changelog heading; - // alpha/beta prerelease tags never do. +export function dedicatedSectionVersionForTag(tag) { + // Correction (vX-N) and alpha tags may carry their own exact changelog + // heading; beta and stable bodies must come from the stable base section. const taggedVersion = tag.replace(/^v/u, ""); - const isCorrection = - /-[1-9][0-9]*$/u.test(taggedVersion) && !/-(?:alpha|beta)\.[1-9][0-9]*$/u.test(taggedVersion); - return isCorrection ? taggedVersion : undefined; + if (/-beta\.[1-9][0-9]*$/u.test(taggedVersion)) { + return undefined; + } + return /-(?:alpha\.)?[1-9][0-9]*$/u.test(taggedVersion) ? taggedVersion : undefined; } export function releaseNotesSectionForTag(changelog, version, tag) { - // Correction tags prefer their own exact heading when the changelog carries - // one; otherwise they fall back to the base version. - const correctionVersion = correctionVersionForTag(tag); - if (correctionVersion && correctionVersion !== version) { + // Alpha and correction tags prefer their own exact heading when the + // changelog carries one; otherwise they fall back to the base version. + const dedicatedVersion = dedicatedSectionVersionForTag(tag); + if (dedicatedVersion && dedicatedVersion !== version) { try { - return extractChangelogSection(changelog, correctionVersion); + return extractChangelogSection(changelog, dedicatedVersion); } catch { - // The correction has no dedicated section; use the base version below. + // No dedicated section; use the base version below. } } try { diff --git a/test/scripts/render-github-release-notes.test.ts b/test/scripts/render-github-release-notes.test.ts index 4c24632e5d28..f1575e2beb37 100644 --- a/test/scripts/render-github-release-notes.test.ts +++ b/test/scripts/render-github-release-notes.test.ts @@ -239,6 +239,33 @@ describe("GitHub release-note rendering", () => { ).toThrow("cannot be compacted without a complete contribution record"); }); + it("prefers an alpha tag's exact changelog heading over the Unreleased fallback", () => { + // Shipped alpha tags carry their own heading with no base section, + // matching the tagged CHANGELOG.md shape of v2026.6.20-alpha.1. + const changelog = [ + "# Changelog", + "", + "## 2026.7.1-alpha.2", + "", + "- Alpha-only fix.", + "", + "## 2026.6.11", + "", + "- Previous release.", + "", + ].join("\n"); + const rendered = renderGithubReleaseNotes({ + changelog, + version, + tag: "v2026.7.1-alpha.2", + repository, + }); + + expect(rendered.body).toContain("## 2026.7.1-alpha.2"); + expect(rendered.body).toContain("Alpha-only fix."); + expect(rendered.body).not.toContain("Previous release."); + }); + it("permits the Unreleased fallback only for alpha tags", () => { const changelog = changelogFor("- **PR #123** fix: example.").replace( "## 2026.7.1",