From 2860f7f4c1fa1be2aa880d01d93a046b10a6984f Mon Sep 17 00:00:00 2001 From: SandObserver <260779319+SandObserver@users.noreply.github.com> Date: Sun, 16 Aug 2026 22:10:40 -0400 Subject: [PATCH] Resolve release notes for a prerelease tag A prerelease is tagged before its changelog section is dated, so the lookup found nothing and the release page step exited after the image had published. Fall back to the base version's section, then to [Unreleased]. --- api/test/changelog.test.js | 10 ++++++++++ scripts/release-notes.js | 22 +++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/api/test/changelog.test.js b/api/test/changelog.test.js index a640f9ac..2612d758 100644 --- a/api/test/changelog.test.js +++ b/api/test/changelog.test.js @@ -173,3 +173,13 @@ test('release notes are the section body without the link definitions', () => { assert.equal(notesFor(sample, 'v1.1.0'), notes); assert.equal(notesFor(sample, '9.9.9'), null); }); + +/* A beta is tagged before its section is dated, so it has none of its own. With + no fallback the release page step fails after the image has published. */ +test('a prerelease falls back to its base version, then to Unreleased', () => { + assert.match(notesFor(sample, 'v1.2.0-beta.1'), /Widget for the doorbell\./); + assert.equal(notesFor(sample, 'v1.1.0-beta.1'), notesFor(sample, '1.1.0')); + + const nothingPending = sample.replace('### Added\n\n- Widget for the doorbell.\n\n', ''); + assert.equal(notesFor(nothingPending, 'v1.2.0-beta.1'), null); +}); diff --git a/scripts/release-notes.js b/scripts/release-notes.js index e3e792b4..d7961680 100644 --- a/scripts/release-notes.js +++ b/scripts/release-notes.js @@ -11,10 +11,30 @@ const cl = require('./changelog.js'); const ROOT = path.join(__dirname, '..'); +/** The section a tag describes. + + A prerelease has no section of its own: the changelog is dated when the + stable version is cut. `v1.8.0-beta.1` reads the 1.8.0 section if it already + exists, and [Unreleased] otherwise, which is what it is a candidate for. + + @param {string} markdown @param {string} version */ +function sectionFor(doc, version) { + const name = version.replace(/^v/, ''); + const exact = doc.versions.find(v => v.name === name); + if (exact) return exact; + const parsed = cl.parseTagName(name); + if (!parsed || !parsed.prerelease) return null; + const base = `${parsed.major}.${parsed.minor}.${parsed.patch}`; + const stable = doc.versions.find(v => v.name === base); + if (stable) return stable; + const unreleased = cl.unreleased(doc); + return unreleased && unreleased.sections.length ? unreleased : null; +} + /** @param {string} markdown @param {string} version */ function notesFor(markdown, version) { const doc = cl.parse(markdown); - const wanted = doc.versions.find(v => v.name === version.replace(/^v/, '')); + const wanted = sectionFor(doc, version); if (!wanted) return null; const next = doc.versions[doc.versions.indexOf(wanted) + 1]; const end = next ? next.line - 1 : doc.lines.length;