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.
This commit is contained in:
Peter Steinberger
2026-07-10 02:06:53 -07:00
parent c2b4ac02da
commit eefe2e8837
3 changed files with 49 additions and 20 deletions
+9 -8
View File
@@ -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) {
+13 -12
View File
@@ -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 {
@@ -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",