From 7079006781e07116c723cdc54ee2d3988b007d01 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sun, 21 Jun 2026 12:09:27 +0200 Subject: [PATCH 1/2] fix LTS transition handling of `generateReleasePlan` --- generateReleasePlan.mjs | 107 ++++++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 49 deletions(-) diff --git a/generateReleasePlan.mjs b/generateReleasePlan.mjs index 2bd1b4c..56daa4b 100755 --- a/generateReleasePlan.mjs +++ b/generateReleasePlan.mjs @@ -2,69 +2,78 @@ // Generate Release Plan table for a given release line. // Usage: -// node --harmony-temporal generateReleasePlan.mjs xx +// node generateReleasePlan.mjs xx // or -// node --harmony-temporal generateReleasePlan.mjs path/to/CHANGELOG_Vxx.md +// node generateReleasePlan.mjs path/to/CHANGELOG_Vxx.md -import { open, readFile } from 'node:fs/promises' +import { open, readFile } from 'node:fs/promises'; const schedule = JSON.parse(await readFile(new URL('./schedule.json', import.meta.url), 'utf-8')); -let [,, version] = process.argv; - -console.log(`_Draft schedule - all dates subject to change_ +let [, , majorVersion] = process.argv; +const tableHeader = ` Version | Release Date | Releaser(s) ---------|--------------| -------------`); +------- | ------------ | -----------`; +console.log(`_Draft schedule - all dates subject to change_\n${tableHeader}`); -let lastReleaseDate; -if (isNaN(version)) { - const existingReleases = []; - const releaseTitle = /^## (\d{4}-\d{2}-\d{2}), Version (\d+\.\d+\.\d+) ([^,]+), (.+)$/; - const securityRelease = /^This is a security release\.$/; - const fd = await open(version, 'r'); - let releaseType - for await (const line of fd.readLines()) { - const releaseTitleMatch = releaseTitle.exec(line); - if (releaseTitleMatch) { - const [, date, version, type, releasers] = releaseTitleMatch; - lastReleaseDate ??= Temporal.PlainDate.from(date); - const versionStr = type === '(Current)' && type !== releaseType ? `${version} (LTS transition)` : version; - existingReleases.unshift(` ${versionStr} | ${date} | ${releasers}`) - releaseType = type; - } else if (securityRelease.test(line)) { - existingReleases[0] = existingReleases[0].replace(/^([^|]+) \|/, "$1 (Security) |"); - } - } - console.log(existingReleases.join('\n')) - - version = existingReleases[0].slice(1, existingReleases[0].indexOf('.')); -} -const versionKey = `v${version}`; +function outputFutureReleaseSchedule(majorVersion, lastReleaseDate) { + const versionKey = `v${majorVersion}`; -if (!Object.hasOwn(schedule, versionKey)) { - throw new Error(`Unknown version ${version}, accepted values are ${Object.keys(schedule)}`); -} + if (!Object.hasOwn(schedule, versionKey)) { + throw new Error(`Unknown version ${majorVersion}, accepted values are ${Object.keys(schedule)}`); + } -const { start, maintenance, lts } = schedule[versionKey]; + const { start, maintenance, lts } = schedule[versionKey]; -const ltsDateTime = lts && Temporal.PlainDate.from(lts); -const maintenanceDateTime = Temporal.PlainDate.from(maintenance); + const ltsDateTime = lts && Temporal.PlainDate.from(lts); + const maintenanceDateTime = Temporal.PlainDate.from(maintenance); -const isNowDuringLTS = lts && Temporal.PlainDate.compare(Temporal.Now.plainDateTimeISO(), ltsDateTime) > 0; -const weeks = isNowDuringLTS ? 4 : 2; + const isNowDuringLTS = lts && Temporal.PlainDate.compare(Temporal.Now.plainDateTimeISO(), ltsDateTime) > 0; + const weeks = isNowDuringLTS ? 4 : 2; -const startOfCycle = lastReleaseDate?.add({ weeks }) ?? (isNowDuringLTS ? ltsDateTime : Temporal.PlainDate.from(start)); -const endOfCycle = isNowDuringLTS ? maintenanceDateTime : ltsDateTime || maintenanceDateTime; + const startOfCycle = + lastReleaseDate?.add({ weeks }) ?? (isNowDuringLTS ? ltsDateTime : Temporal.PlainDate.from(start)); + const endOfCycle = isNowDuringLTS ? maintenanceDateTime : ltsDateTime || maintenanceDateTime; + for (let i = startOfCycle; Temporal.PlainDate.compare(i, endOfCycle) === -1; i = i.add({ weeks })) { + console.log(` ${majorVersion}.x.x | ${i} | `); + } -for (let i = startOfCycle; Temporal.PlainDate.compare(i, endOfCycle) === -1; i = i.add({ weeks })) { - console.log(` ${version}.x.x | ${i} | `); + if (lts && (!lastReleaseDate || Temporal.PlainDate.compare(maintenance, lastReleaseDate) > 0)) { + console.log( + `${majorVersion}.x.x (${isNowDuringLTS ? 'Maintenance' : 'LTS'} transition) | ${ + isNowDuringLTS ? maintenance : lts + } | ${isNowDuringLTS ? '_No release_' : ''}`, + ); + } } -if (lts && (!lastReleaseDate || Temporal.PlainDate.compare(maintenance, lastReleaseDate) > 0)) { - console.log( - `${version}.x.x (${isNowDuringLTS ? 'Maintenance' : 'LTS'} transition) | ${ - isNowDuringLTS ? maintenance : lts - } | ${isNowDuringLTS ? '_No release_' : ''}`, - ); +if (isNaN(majorVersion)) { + const existingReleases = []; + const releaseTitle = /^## (\d{4}-\d{2}-\d{2}), Version (\d+\.\d+\.\d+) ([^,]+), (.+)$/; + const securityRelease = /^This is a security release\.$/; + const fd = await open(majorVersion, 'r'); + let releaseType, lastReleaseDate; + for await (const line of fd.readLines()) { + const releaseTitleMatch = releaseTitle.exec(line); + if (releaseTitleMatch) { + const [, date, version, type, releasers] = releaseTitleMatch; + lastReleaseDate ??= Temporal.PlainDate.from(date); + const wasLTSTransition = releaseType && type === '(Current)' && type !== releaseType; + if (wasLTSTransition) { + const majorVersion = existingReleases[0].slice(1, existingReleases[0].indexOf('.')); + existingReleases[0] = existingReleases[0].replace('|', '(LTS transition) |'); + console.log(existingReleases.splice(0, Infinity, '', '').join('\n')); + outputFutureReleaseSchedule(majorVersion, lastReleaseDate); + console.log('\n\n
Current\n\n' + tableHeader); + } + existingReleases.unshift(` ${version} | ${date} | ${releasers}`); + releaseType = type; + } else if (securityRelease.test(line)) { + existingReleases[0] = existingReleases[0].replace('|', '(Security) |'); + } + } + console.log(existingReleases.join('\n')); +} else { + outputFutureReleaseSchedule(majorVersion); } From f8dfe26e05e2be233b86715bfe62e6ea61092044 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Thu, 25 Jun 2026 16:50:43 +0200 Subject: [PATCH 2/2] fixup! fix LTS transition handling of `generateReleasePlan` fix regression with Current --- generateReleasePlan.mjs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/generateReleasePlan.mjs b/generateReleasePlan.mjs index 56daa4b..c11c116 100755 --- a/generateReleasePlan.mjs +++ b/generateReleasePlan.mjs @@ -49,6 +49,7 @@ function outputFutureReleaseSchedule(majorVersion, lastReleaseDate) { } if (isNaN(majorVersion)) { + let hasOutputFutureScheduleYet = false; const existingReleases = []; const releaseTitle = /^## (\d{4}-\d{2}-\d{2}), Version (\d+\.\d+\.\d+) ([^,]+), (.+)$/; const securityRelease = /^This is a security release\.$/; @@ -65,6 +66,7 @@ if (isNaN(majorVersion)) { existingReleases[0] = existingReleases[0].replace('|', '(LTS transition) |'); console.log(existingReleases.splice(0, Infinity, '', '
').join('\n')); outputFutureReleaseSchedule(majorVersion, lastReleaseDate); + hasOutputFutureScheduleYet = true; console.log('\n\n
Current\n\n' + tableHeader); } existingReleases.unshift(` ${version} | ${date} | ${releasers}`); @@ -74,6 +76,10 @@ if (isNaN(majorVersion)) { } } console.log(existingReleases.join('\n')); + if (!hasOutputFutureScheduleYet) { + const majorVersion = existingReleases[0].slice(1, existingReleases[0].indexOf('.')); + outputFutureReleaseSchedule(majorVersion, lastReleaseDate); + } } else { outputFutureReleaseSchedule(majorVersion); }