Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 64 additions & 49 deletions generateReleasePlan.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,69 +2,84 @@

// 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)) {
let hasOutputFutureScheduleYet = false;
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, '', '</details>').join('\n'));
outputFutureReleaseSchedule(majorVersion, lastReleaseDate);
hasOutputFutureScheduleYet = true;
console.log('\n\n<details><summary>Current</summary>\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'));
if (!hasOutputFutureScheduleYet) {
const majorVersion = existingReleases[0].slice(1, existingReleases[0].indexOf('.'));
outputFutureReleaseSchedule(majorVersion, lastReleaseDate);
}
} else {
outputFutureReleaseSchedule(majorVersion);
}