diff --git a/.gitignore b/.gitignore
index 5e99da5..9096800 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,3 +42,4 @@ jspm_packages
*.html
*.png
*.svg
+!release-schedule-proposal/*.svg
diff --git a/bin/lts.js b/bin/lts.js
index 3752e8c..e5fca0c 100644
--- a/bin/lts.js
+++ b/bin/lts.js
@@ -4,9 +4,9 @@ const Path = require('path');
const Bossy = require('bossy');
const Lib = require('../lib');
const now = new Date();
-const oneYearFromNow = new Date();
+const oneYearFromNow = new Date('2035-01-01');
-oneYearFromNow.setFullYear(now.getFullYear() + 1);
+// oneYearFromNow.setFullYear(now.getFullYear() + 1);
const cliArgs = {
'd': {
@@ -103,7 +103,7 @@ const options = {
queryStart: new Date(args.start),
queryEnd: new Date(args.end),
html: args.html ? Path.resolve(args.html) : null,
- svg: args.svg ? Path.resolve(args.svg) : null,
+ svg: args.svg ? Path.resolve(args.svg) : Path.join(__dirname, '..', 'release-schedule-proposal', 'rolling.svg'),
png: args.png ? Path.resolve(args.png) : null,
animate: args.animate,
excludeMain: args.excludeMain,
@@ -112,3 +112,10 @@ const options = {
};
Lib.create(options);
+
+Lib.create({
+ ...options,
+ queryStart: new Date('2029-09-01'),
+ queryEnd: new Date('2031-05-01'),
+ svg: options.svg.replace('.svg', '2030.svg'),
+});
diff --git a/lib/index.js b/lib/index.js
index ff99db3..6996c54 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -2,8 +2,12 @@
const Fs = require('fs');
const D3 = require('d3');
const D3Node = require('d3-node');
+const { Temporal } = require("@js-temporal/polyfill");
const styles = `
+.beta {
+ fill:rgb(240, 240, 75);
+}
.current {
fill: #5fa04e;
}
@@ -13,7 +17,7 @@ const styles = `
.maintenance {
fill: #b1bcc2;
}
-.unstable {
+.unstable,.alpha {
fill: #e99c40;
}
.bar-join {
@@ -29,20 +33,80 @@ const styles = `
.axis--y .tick text {
text-anchor: end;
}
-.label {
+.label,.label-beta {
fill: #fff;
font: 20px sans-serif;
font-weight: 100;
text-anchor: start;
dominant-baseline: middle;
text-transform: uppercase;
+}
+.label-beta{
+ fill: #000;
}`;
+const schedule = {
+ title: 'Rolling semver-major',
+ currentDuration: { weeks: 6 },
+ maintenanceDuration: { weeks: 8 },
+ activeDuration: false,
+};
+
+console.log(`\n## ${schedule.title}`);
+{
+ const ___ = duration_s => Array.isArray(duration_s) ?
+ `\n - for even-numbered releases:${___(duration_s[0])}\n - for odd-numbered releases:${___(duration_s[1])}` :
+ ` ${duration_s ? Temporal.Duration.from(duration_s).weeks : 0} weeks`;
+ console.log(Object.entries(schedule).map(([key, duration], i) => key === 'title' ? '' :
+ ` ${i}. ${key.replace('D', ' d')}:${___(duration)}`
+ ).join('\n'));
+}
+{
+ const durations = Object.entries(schedule).filter(c => c[0] !== 'title' && c[0] !== 'alphaDuration' && c[1]).map(c => c[1]);
+ const hasArray = durations.some(Array.isArray);
+ const __ = arr => arr.reduce((acc, dur) => acc+Temporal.Duration.from(dur).weeks, 0)
+ const ___ = () => hasArray ?
+ `\n - for even-numbered releases: ${__(durations.map(c => (Array.isArray(c) ? c[0] : c) || 'P0M'))} weeks\n - for odd-numbered releases: ${__(durations.map(c => (Array.isArray(c) ? c[1] : c) || 'P0M'))} weeks` :
+ ` ${__(durations)} weeks`;
+ console.log(`\n Total life span (from start of beta until end of maintenance):${___(schedule)}`)
+}
+
+const __ = (duration_s, i) => Array.isArray(duration_s) ? duration_s[i % duration_s.length] : duration_s;
+
+function createDataEntry(start, i) {
+ const ret = {};
+
+ const isLTS = (start.month === 10 && start.year !== 2026) || (start.month === 9 && start.day > 17);
+
+ const alphaDuration = __(schedule.alphaDuration, i);
+ const betaDuration = __(schedule.betaDuration, i);
+ const currentDuration = __(schedule.currentDuration, i);
+ const activeDuration = __(isLTS ? { months: 12} : schedule.activeDuration, i);
+ const maintenanceDuration = __(isLTS ? {months: 18} : schedule.maintenanceDuration, i);
+
+ if (alphaDuration) {
+ ret.alpha = start.toString();
+ start = start.add(alphaDuration);
+ }
+ if (betaDuration) {
+ ret.beta = start.toString();
+ start = start.add(betaDuration);
+ }
+ ret.start = start.toString();
+ start = start.add(currentDuration);
+ if (activeDuration) {
+ ret.lts = start.toString();
+ start = start.add(activeDuration);
+ }
+ ret.maintenance = start.toString();
+ ret.end = start.add(maintenanceDuration).toString();
+ return ret;
+}
+
function parseInput (data, queryStart, queryEnd, excludeMain, projectName) {
const output = [];
- Object.keys(data).forEach((v) => {
- const version = data[v];
+ const addData = (v, version) => {
const name = `${projectName} ${v.replace('v', '')}`;
const current = version.start ? new Date(version.start) : null;
const active = version.lts ? new Date(version.lts) : null;
@@ -76,7 +140,20 @@ function parseInput (data, queryStart, queryEnd, excludeMain, projectName) {
if (current < queryEnd && end > queryStart) {
output.push({ name, type: 'current', start: current, end });
}
- });
+ if (version.beta) {
+ const start = new Date(version.beta);
+ output.push(({ name, type: 'beta', start, end: current }));
+ output.push(({ name, type: 'alpha', start: new Date(version.alpha), end: start }));
+ }
+ }
+ Object.entries(data).forEach(v => addData(...v, false));
+ const queryEndPlainDate = Temporal.PlainDate.from(queryEnd.toISOString().slice(0, 10));
+ for (let start = Temporal.PlainDate.from(data.v26.lts), i = 27; Temporal.PlainDate.compare(queryEndPlainDate, start) === 1; i++) {
+ const dataEntry = createDataEntry(start, i);
+ addData(`v${i}`, dataEntry);
+ const nextStart = Temporal.PlainDate.from(dataEntry.lts || dataEntry.maintenance);
+ start = nextStart;
+ }
if (!excludeMain) {
output.unshift({
@@ -92,7 +169,7 @@ function parseInput (data, queryStart, queryEnd, excludeMain, projectName) {
function create (options) {
- const { queryStart, queryEnd, html, svg: svgFile, png, animate, excludeMain, projectName, margin: marginInput, currentDateMarker } = options;
+ const { queryStart, queryEnd, html, svg: svgFile, png, animate, excludeMain, projectName, margin: marginInput } = options;
const data = parseInput(options.data, queryStart, queryEnd, excludeMain, projectName);
const d3n = new D3Node({ svgStyles: styles, d3Module: D3 });
const margin = marginInput || { top: 30, right: 30, bottom: 30, left: 110 };
@@ -108,7 +185,7 @@ function create (options) {
.padding(0.3);
const xAxis = D3.axisBottom(xScale)
.tickSize(height)
- .tickFormat(D3.timeFormat('%b %Y'));
+ .tickFormat(D3.timeFormat('%b%y'));
const yAxis = D3.axisRight(yScale).tickSize(width);
const svg = d3n.createSVG()
.attr('width', width + margin.left + margin.right)
@@ -148,21 +225,6 @@ function create (options) {
.attr('stroke', '#89a19d');
}
- if (currentDateMarker) {
- const currentDate = new Date();
-
- // Add a vertical red line for the current date
- const currentX = xScale(currentDate);
- svg.append('line')
- .attr('x1', currentX)
- .attr('x2', currentX)
- .attr('stroke-width', 2)
- .attr('y1', 0)
- .attr('y2', height)
- .attr('stroke', currentDateMarker)
- .attr('opacity', 1);
- }
-
svg.append('g')
.attr('class', 'axis axis--x')
.call(customXAxis);
@@ -196,7 +258,7 @@ function create (options) {
.attr('height', calculateHeight)
.style('opacity', (data) => {
// Hack to hide on current and unstable
- if ((data.type === 'unstable' || data.type === 'current') ||
+ if ((data.type === 'unstable' || data.type === 'current' || data.type === 'alpha') ||
xScale(data.start) <= 0) {
return 0;
}
@@ -205,7 +267,7 @@ function create (options) {
});
bar.append('text')
- .attr('class', 'label')
+ .attr('class', data => data.type=== 'beta' ? 'label-beta' : 'label')
.attr('x', (data) => {
return xScale(data.start) + 15;
})
@@ -226,6 +288,8 @@ function create (options) {
if (typeof svgFile === 'string') {
Fs.writeFileSync(svgFile, d3n.svgString());
+
+ console.log(`\n )})`)
}
if (typeof png === 'string') {
diff --git a/lts.json b/lts.json
index 69a967b..f59db08 100644
--- a/lts.json
+++ b/lts.json
@@ -1,53 +1,21 @@
{
- "v0.10": {
- "start": "2013-03-11",
- "end": "2016-10-31"
- },
- "v0.12": {
- "start": "2015-02-06",
- "end": "2016-12-31"
- },
- "v4": {
- "start": "2015-09-08",
- "lts": "2015-10-12",
- "maintenance": "2017-04-01",
- "end": "2018-04-01",
- "codename": "Argon"
- },
- "v5": {
- "start": "2015-10-29",
- "maintenance": "2016-04-30",
- "end": "2016-06-30"
- },
- "v6": {
- "start": "2016-04-26",
- "lts": "2016-10-18",
- "maintenance": "2018-04-01",
- "end": "2019-04-01",
- "codename": "Boron"
- },
- "v7": {
- "start": "2016-10-25",
- "maintenance": "2017-04-30",
- "end": "2017-06-30"
- },
- "v8": {
- "start": "2017-04-30",
- "lts": "2017-10-31",
- "maintenance": "2019-04-01",
- "end": "2019-12-31",
+ "v24": {
+ "start": "2025-05-06",
+ "lts": "2025-10-28",
+ "maintenance": "2026-10-20",
+ "end": "2028-04-30",
"codename": ""
},
- "v9": {
- "start": "2017-10-01",
- "maintenance": "2018-04-01",
- "end": "2018-06-30"
+ "v25": {
+ "start": "2025-10-15",
+ "maintenance": "2026-04-01",
+ "end": "2026-06-01"
},
- "v10": {
- "start": "2018-04-30",
- "lts": "2018-10-01",
- "maintenance": "2020-04-01",
- "end": "2021-04-01",
+ "v26": {
+ "start": "2026-04-22",
+ "lts": "2026-10-20",
+ "maintenance": "2028-04-20",
+ "end": "2029-04-30",
"codename": ""
}
}
diff --git a/package.json b/package.json
index 7829b90..6facaf3 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
"node": ">=4.0.0"
},
"dependencies": {
+ "@js-temporal/polyfill": "0.4.4",
"bossy": "3.0.4",
"d3": "4.7.4",
"d3-node": "1.0.1",
diff --git a/release-schedule-proposal/README.md b/release-schedule-proposal/README.md
new file mode 100644
index 0000000..f33846a
--- /dev/null
+++ b/release-schedule-proposal/README.md
@@ -0,0 +1,167 @@
+# Release schedule proposals
+
+To add a new proposal to this list, update the `schedule` variable in `lib/index.js`,
+the SVG name in `bin/lts.js`, and use the following command:
+
+```sh
+node ./bin/lts.js >> release-schedule-proposal/README.md
+```
+
+Here are the release schedule proposals:
+
+## Status quo
+
+ 1. current duration: 6 months
+ 2. active duration:
+ - for even-numbered releases: 12 months
+ - for odd-numbered releases: 0 months
+ 3. maintenance duration:
+ - for even-numbered releases: 24 months
+ - for odd-numbered releases: 3 months
+
+ Total life span (from start of beta until end of maintenance):
+ - for even-numbered releases: 42 months
+ - for odd-numbered releases: 9 months
+
+ 
+
+ 
+
+## LTS every year
+
+ 1. Alpha duration: 5 months
+ 2. Beta duration: 1 months
+ 3. Current duration: 6 months
+ 4. Active duration: 12 months
+ 5. Maintenance duration: 18 months
+
+ Total life span (from start of beta until end of maintenance): 37 months
+
+ 
+
+ 
+
+### Alpha and Beta definition
+
+#### Stability expectations
+
+Alpha releases may contain semver-major changes (assuming such change does not "break the ecosystem"),
+but no ABI-breaking changes (so native addons can be tested on those versions).
+
+Once the release has reached the beta period, semver-major changes will no
+longer be included, except:
+
+- security fixes
+- V8 update
+- one-time exception granted by the TSC
+
+Once the beta period is ended, only security fixes and one-time exception
+granted by the TSC may contain semver-major changes.
+
+Updating V8 during the Beta period is considered the optimal tradeoff, despite
+the ABI and API potential breakage, as otherwise the included V8 version would
+be 1 year old by the time the release line transitions to LTS.
+
+#### Release frequency
+
+Version | Tentative Date | Type of changes
+--------|--------------| -------------
+v27.0.0-alpha.0 | 2026-10-20 | Branch-off
+v27.0.0-alpha.1 | 2026-11-03 | most changes[^1]
+v27.0.0-alpha.2 | 2026-11-17 | most changes[^1]
+v27.0.0-alpha.3 | 2026-12-01 | most changes[^1]
+v27.0.0-alpha.4 | 2026-12-15 | most changes[^1]
+v27.0.0-alpha.5 | 2026-12-29 | most changes[^1]
+v27.0.0-alpha.6 | 2027-01-12 | most changes[^1]
+v27.0.0-alpha.7 | 2027-01-26 | most changes[^1]
+v27.0.0-alpha.8 | 2027-02-09 | most changes[^1]
+v27.0.0-alpha.9 | 2027-02-23 | most changes[^1]
+v27.0.0-alpha.10 | 2027-03-09 | most changes[^1]
+v27.0.0-beta.0 | 2027-03-23 | most changes[^1] + V8 update
+v27.0.0-beta.1 | 2027-04-06 | semver-minor only + V8 update
+v27.0.0 | 2027-04-20 | semver-minor only
+v27.x.x | 2027-05-04 | …
+v27.x.x | 2027-10-05 | …
+v27.x.x (LTS transition) | 2027-10-20 | no changes
+(Maintenance transition) | 2028-10-20 | _No release_
+(End-of-Life) | 2030-10-20 | _No release_
+
+[^1]: Most changes from `main`, except those marked as `dont-land-on-v27.x` because e.g. they contain ABI-breaking changes, or are considered too breaking for the ecosystem, or are moving a deprecation to EOL if 27.x already contains the change moving it to Runtime.
+
+#### Security support
+
+TL;DR: TBD
+
+It has not been decided whether security fixes would
+trigger a security release, or if those would simply be included in the next
+regular release (potentially ahead of schedule). The difference on a maintenance
+perspective is that a security release requires locking the CI until all the
+queued security releases are ready.
+
+## Longer life cycle
+
+ 1. current duration:
+ - for even-numbered releases: 6 months
+ - for odd-numbered releases: 12 months
+ 2. active duration:
+ - for even-numbered releases: 18 months
+ - for odd-numbered releases: 0 months
+ 3. maintenance duration:
+ - for even-numbered releases: 24 months
+ - for odd-numbered releases: 2 months
+
+ Total life span (from start of beta until end of maintenance):
+ - for even-numbered releases: 48 months
+ - for odd-numbered releases: 14 months
+
+ 
+
+ 
+
+## Less frequent LTS
+
+ 1. current duration: 6 months
+ 2. active duration:
+ - for modulo-4-numbered releases: 24 months
+ - for other releases: 0 months
+ 3. maintenance duration:
+ - for modulo-4-numbered releases: 18 months
+ - for other releases: 2 months
+
+ Total life span (from start of beta until end of maintenance):
+ - for module-4-numbered releases: 48 months
+ - for releases: 8 months
+
+ 
+
+ 
+
+## No Active LTS anymore
+
+ 1. current duration: 12 months
+ 2. active duration: 0 months
+ 3. maintenance duration: 24 months
+
+ Total life span (from start of beta until end of maintenance): 36 months
+
+ 
+
+ 
+
+## Rolling semver-major
+
+ 1. current duration: 6 weeks
+ 3. active duration:
+ - 0 weeks
+ - or 12 months for the October release
+ 2. maintenance duration:
+ - 8 weeks
+ - or 18 months for the October release
+
+ Total life span (from start of beta until end of maintenance):
+ - 14 weeks
+ - or 31 months for the October release
+
+ 
+
+ 
diff --git a/release-schedule-proposal/less-frequent-lts.svg b/release-schedule-proposal/less-frequent-lts.svg
new file mode 100644
index 0000000..fffdc9f
--- /dev/null
+++ b/release-schedule-proposal/less-frequent-lts.svg
@@ -0,0 +1,40 @@
+
\ No newline at end of file
diff --git a/release-schedule-proposal/less-frequent-lts2030.svg b/release-schedule-proposal/less-frequent-lts2030.svg
new file mode 100644
index 0000000..e1664b7
--- /dev/null
+++ b/release-schedule-proposal/less-frequent-lts2030.svg
@@ -0,0 +1,40 @@
+
\ No newline at end of file
diff --git a/release-schedule-proposal/longer-life-cycle.svg b/release-schedule-proposal/longer-life-cycle.svg
new file mode 100644
index 0000000..a98ed11
--- /dev/null
+++ b/release-schedule-proposal/longer-life-cycle.svg
@@ -0,0 +1,40 @@
+
\ No newline at end of file
diff --git a/release-schedule-proposal/longer-life-cycle2030.svg b/release-schedule-proposal/longer-life-cycle2030.svg
new file mode 100644
index 0000000..73622ef
--- /dev/null
+++ b/release-schedule-proposal/longer-life-cycle2030.svg
@@ -0,0 +1,40 @@
+
\ No newline at end of file
diff --git a/release-schedule-proposal/lts-every-year.svg b/release-schedule-proposal/lts-every-year.svg
new file mode 100644
index 0000000..cf4c553
--- /dev/null
+++ b/release-schedule-proposal/lts-every-year.svg
@@ -0,0 +1,40 @@
+
\ No newline at end of file
diff --git a/release-schedule-proposal/lts-every-year2030.svg b/release-schedule-proposal/lts-every-year2030.svg
new file mode 100644
index 0000000..fe84cae
--- /dev/null
+++ b/release-schedule-proposal/lts-every-year2030.svg
@@ -0,0 +1,40 @@
+
\ No newline at end of file
diff --git a/release-schedule-proposal/no-active-lts.svg b/release-schedule-proposal/no-active-lts.svg
new file mode 100644
index 0000000..4594175
--- /dev/null
+++ b/release-schedule-proposal/no-active-lts.svg
@@ -0,0 +1,40 @@
+
\ No newline at end of file
diff --git a/release-schedule-proposal/no-active-lts2030.svg b/release-schedule-proposal/no-active-lts2030.svg
new file mode 100644
index 0000000..f7ce0ca
--- /dev/null
+++ b/release-schedule-proposal/no-active-lts2030.svg
@@ -0,0 +1,40 @@
+
\ No newline at end of file
diff --git a/release-schedule-proposal/rolling.svg b/release-schedule-proposal/rolling.svg
new file mode 100644
index 0000000..28d8c7f
--- /dev/null
+++ b/release-schedule-proposal/rolling.svg
@@ -0,0 +1,40 @@
+
\ No newline at end of file
diff --git a/release-schedule-proposal/rolling2030.svg b/release-schedule-proposal/rolling2030.svg
new file mode 100644
index 0000000..8e32392
--- /dev/null
+++ b/release-schedule-proposal/rolling2030.svg
@@ -0,0 +1,40 @@
+
\ No newline at end of file
diff --git a/release-schedule-proposal/status-quo.svg b/release-schedule-proposal/status-quo.svg
new file mode 100644
index 0000000..8270d09
--- /dev/null
+++ b/release-schedule-proposal/status-quo.svg
@@ -0,0 +1,40 @@
+
\ No newline at end of file
diff --git a/release-schedule-proposal/status-quo2030.svg b/release-schedule-proposal/status-quo2030.svg
new file mode 100644
index 0000000..6a93dbd
--- /dev/null
+++ b/release-schedule-proposal/status-quo2030.svg
@@ -0,0 +1,40 @@
+
\ No newline at end of file