Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: source of truth for version support #18

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
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
23 changes: 19 additions & 4 deletions src/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const { Octokit } = require('@octokit/rest');
const ExpiryMap = require('expiry-map');
const pMemoize = require('p-memoize');
const semver = require('semver');
const fs = require('fs');
const path = require('path');

let octokit = null;
const getOctokit = async () => {
Expand All @@ -30,15 +32,28 @@ const getOctokit = async () => {
return octokit;
};

const getReleasesOrUpdate = pMemoize(
const getVersionsOrUpdate = pMemoize(
async () => {
const response = await fetch.default('https://electronjs.org/headers/index.json');
const releases = await response.json();
return releases.sort((a, b) => semver.compare(b.version, a.version));
const support = JSON.parse(fs.readFileSync(path.join(__dirname, 'versions-support.json')));
return {
support: support.map((version) => {
const stableRelease =
releases.find((release) => release.version === `${version.major}.0.0`) !== undefined;
const endOfLife = version.endOfLife;
return {
...version,
isSupported: !endOfLife || new Date() <= new Date(endOfLife),
isStable: stableRelease,
};
}),
releases: releases.sort((a, b) => semver.compare(b.version, a.version)),
};
},
{
cache: new ExpiryMap(60 * 1000),
cacheKey: () => 'releases',
cacheKey: () => 'versions',
},
);

Expand Down Expand Up @@ -164,7 +179,7 @@ const getTSDefs = pMemoize(

module.exports = {
getGitHubRelease,
getReleasesOrUpdate,
getVersionsOrUpdate,
getActiveReleasesOrUpdate,
getAllSudowoodoReleasesOrUpdate,
getPR,
Expand Down
20 changes: 18 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const exphbs = require('express-handlebars');
const path = require('path');

const a = require('./utils/a');
const { getReleasesOrUpdate, getActiveReleasesOrUpdate } = require('./data');
const { getVersionsOrUpdate, getActiveReleasesOrUpdate } = require('./data');

const app = express();

Expand All @@ -25,7 +25,23 @@ app.get(
'/releases.json',
a(async (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.json(await getReleasesOrUpdate());
res.json((await getVersionsOrUpdate()).releases);
}),
);

app.get(
'/versions.json',
a(async (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.json(await getVersionsOrUpdate());
}),
);

app.get(
'/support.json',
a(async (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.json((await getVersionsOrUpdate()).support);
}),
);

Expand Down
4 changes: 2 additions & 2 deletions src/routes/history.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { Router } = require('express');

const a = require('../utils/a');
const { getReleasesOrUpdate } = require('../data');
const { getVersionsOrUpdate } = require('../data');

const router = new Router();

Expand All @@ -14,7 +14,7 @@ router.get('/', (req, res) =>
router.get(
'/:date',
a(async (req, res) => {
const releases = await getReleasesOrUpdate();
const { releases } = await getVersionsOrUpdate();
const onDate = releases.filter((r) => r.date === req.params.date);
if (onDate.length === 0) return res.redirect('/history');
res.render('date', {
Expand Down
11 changes: 7 additions & 4 deletions src/routes/home.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const Handlebars = require('handlebars');
const semver = require('semver');

const a = require('../utils/a');
const { getReleasesOrUpdate, getActiveReleasesOrUpdate } = require('../data');
const { getVersionsOrUpdate, getActiveReleasesOrUpdate } = require('../data');
const { timeSince, minutesSince } = require('../utils/format-time');

const router = new Router();
Expand Down Expand Up @@ -64,8 +64,8 @@ Handlebars.registerPartial('releaseSquare', function (release) {
router.get(
'/',
a(async (req, res) => {
const [releases, activeReleases] = await Promise.all([
getReleasesOrUpdate(),
const [{ releases, support }, activeReleases] = await Promise.all([
getVersionsOrUpdate(),
getActiveReleasesOrUpdate(),
]);
const lastNightly = releases.find((r) => semver.parse(r.version).prerelease[0] === 'nightly');
Expand All @@ -76,7 +76,10 @@ router.get(
);
const lastStable = releases.find((r) => semver.parse(r.version).prerelease.length === 0);
const stableMajor = semver.parse(lastStable.version).major;
const latestSupported = [stableMajor, stableMajor - 1, stableMajor - 2].map((major) =>
const supportedMajors = support
.filter((version) => version.isSupported && version.isStable)
.map((version) => version.major);
const latestSupported = supportedMajors.map((major) =>
releases.find((r) => semver.parse(r.version).major === major),
);
if (semver.parse(lastPreRelease.version).major <= stableMajor) {
Expand Down
4 changes: 2 additions & 2 deletions src/routes/pr.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ const { Router } = require('express');
const semver = require('semver');

const a = require('../utils/a');
const { compareTagToCommit, getReleasesOrUpdate, getPR, getPRComments } = require('../data');
const { compareTagToCommit, getVersionsOrUpdate, getPR, getPRComments } = require('../data');

const router = new Router();

async function getPRReleaseStatus(prNumber) {
const releases = [...(await getReleasesOrUpdate())].reverse();
const releases = [...(await getVersionsOrUpdate()).releases].reverse();
const [prInfo, comments] = await Promise.all([getPR(prNumber), getPRComments(prNumber)]);
if (!prInfo) return null;

Expand Down
8 changes: 4 additions & 4 deletions src/routes/release.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const Prism = require('prismjs');
const semver = require('semver');

const a = require('../utils/a');
const { getGitHubRelease, getReleasesOrUpdate, getTSDefs } = require('../data');
const { getGitHubRelease, getVersionsOrUpdate, getTSDefs } = require('../data');

const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);
Expand Down Expand Up @@ -59,7 +59,7 @@ Handlebars.registerHelper('markdownMergeHeaders', function (contentArr) {
});

async function getValidVersionRange(startVersion, endVersion, res) {
const allReleases = await getReleasesOrUpdate();
const { releases: allReleases } = await getVersionsOrUpdate();

const parsedStart = semver.parse(startVersion);
const parsedEnd = semver.parse(endVersion);
Expand Down Expand Up @@ -182,9 +182,9 @@ router.get(
'/:version',
a(async (req, res) => {
const version = req.params.version;
const [release, allReleases] = await Promise.all([
const [release, { releases: allReleases }] = await Promise.all([
getGitHubRelease(version),
getReleasesOrUpdate(),
getVersionsOrUpdate(),
]);
if (!release) {
return res.redirect('/');
Expand Down
4 changes: 2 additions & 2 deletions src/routes/releases.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const { Router } = require('express');
const paginate = require('express-paginate');
const Handlebars = require('handlebars');
const a = require('../utils/a');
const { getGitHubRelease, getReleasesOrUpdate } = require('../data');
const { getGitHubRelease, getVersionsOrUpdate } = require('../data');
const semver = require('semver');

const router = new Router();
Expand Down Expand Up @@ -75,7 +75,7 @@ router.get(
filter = ({ version }) => version.includes('nightly');
}

const releases = await getReleasesOrUpdate();
const { releases } = await getVersionsOrUpdate();
const { page, limit, version } = req.query;
const releasesFromChannel = releases.filter(filter);
const major = Number(version);
Expand Down
177 changes: 177 additions & 0 deletions src/versions-support.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
[
{
"major": 26,
"alpha": "2023-06-01",
"beta": "2023-06-27",
"stable": "2023-08-08",
Comment on lines +4 to +6
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These dates shouldn't need to be hardcoded, they can be auto-generated from historical release data? Unless the intention is that these are date predictions, in which case I'm not a huge fan of this just living in a json file in this repo 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These dates shouldn't need to be hardcoded, they can be auto-generated from historical release data?

Fair point, I could look at making that change and generating them from the releases data.

Unless the intention is that these are date predictions, in which case I'm not a huge fan of this just living in a json file in this repo 🤔

For future versions (like 26 in this case) the alpha/beta/stable dates are added to e/e as date "predictions" (AKA the schedule) via PRs like electron/electron#38378.

The point of them "just living in a json file in this repo" is they need to live somewhere (currently in that table in e/e docs), and if they live here they can be used to generate that table in the docs instead, in addition to being generally available in machine-readable form for usage in automation.

One of the current main motivators of this PR is for the dates for the next release branch to be available in machine-readable form for the new release board automation on e/e so it can fill in dates for when specific items need to be done by.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idea which came up in the @~electron/wg-releases meeting today - what if alpha/beta/stable is auto-generated from historical data if those releases for that major have already happened (ignoring any hardcoded values) and uses the hardcoded "predictions" otherwise? Then as part of updating the scheduled dates for the next major version the PR would just cull the old dates for released versions (except for EOL).

in which case I'm not a huge fan of this just living in a json file in this repo 🤔

If this is the biggest concern, is there a spot you'd rather have it? I agree I'm not a huge fan of it being in this repo, but since this repo serves /releases.json it seemed like the spot with the fewest moving pieces. If we'd rather we could put it somewhere in e/e and have it grabbed (and cached) via octokit?

"endOfLife": null
},
{
"major": 25,
"alpha": "2023-04-10",
"beta": "2023-05-02",
"stable": "2023-05-30",
"endOfLife": "2023-12-05"
},
{
"major": 24,
"alpha": "2023-02-09",
"beta": "2023-03-07",
"stable": "2023-04-04",
"endOfLife": "2023-10-03"
},
{
"major": 23,
"alpha": "2022-12-01",
"beta": "2023-01-10",
"stable": "2023-02-07",
"endOfLife": "2023-08-08"
},
{
"major": 22,
"alpha": "2022-09-29",
"beta": "2022-10-25",
"stable": "2022-11-29",
"endOfLife": "2023-10-10"
},
{
"major": 21,
"alpha": "2022-08-04",
"beta": "2022-08-30",
"stable": "2022-09-27",
"endOfLife": "2023-04-04"
},
{
"major": 20,
"alpha": "2022-05-26",
"beta": "2022-06-21",
"stable": "2022-08-02",
"endOfLife": "2023-02-07"
},
{
"major": 19,
"alpha": "2022-03-31",
"beta": "2022-04-26",
"stable": "2022-05-24",
"endOfLife": "2022-11-29"
},
{
"major": 18,
"alpha": "2022-02-03",
"beta": "2022-03-03",
"stable": "2022-03-29",
"endOfLife": "2022-09-27"
},
{
"major": 17,
"alpha": "2021-11-18",
"beta": "2022-01-06",
"stable": "2022-02-01",
"endOfLife": "2022-08-02"
},
{
"major": 16,
"alpha": "2021-09-23",
"beta": "2021-10-20",
"stable": "2021-11-16",
"endOfLife": "2022-05-24"
},
{
"major": 15,
"alpha": "2021-07-20",
"beta": "2021-09-01",
"stable": "2021-09-21",
"endOfLife": "2022-05-24"
},
{
"major": 14,
"alpha": null,
"beta": "2021-05-27",
"stable": "2021-08-31",
"endOfLife": "2022-03-29"
},
{
"major": 13,
"alpha": null,
"beta": "2021-03-04",
"stable": "2021-05-25",
"endOfLife": "2022-02-01"
},
{
"major": 12,
"alpha": null,
"beta": "2020-11-19",
"stable": "2021-03-02",
"endOfLife": "2021-11-16"
},
{
"major": 11,
"alpha": null,
"beta": "2020-08-27",
"stable": "2020-11-17",
"endOfLife": "2021-08-31"
},
{
"major": 10,
"alpha": null,
"beta": "2020-05-21",
"stable": "2020-08-25",
"endOfLife": "2021-05-25"
},
{
"major": 9,
"alpha": null,
"beta": "2020-02-06",
"stable": "2020-05-19",
"endOfLife": "2021-03-02"
},
{
"major": 8,
"alpha": null,
"beta": "2019-10-24",
"stable": "2020-02-04",
"endOfLife": "2020-11-17"
},
{
"major": 7,
"alpha": null,
"beta": "2019-08-01",
"stable": "2019-10-22",
"endOfLife": "2020-08-25"
},
{
"major": 6,
"alpha": null,
"beta": "2019-04-25",
"stable": "2019-07-30",
"endOfLife": "2020-05-19"
},
{
"major": 5,
"alpha": null,
"beta": "2019-01-22",
"stable": "2019-04-23",
"endOfLife": "2020-02-04"
},
{
"major": 4,
"alpha": null,
"beta": "2018-10-11",
"stable": "2018-12-20",
"endOfLife": "2019-10-22"
},
{
"major": 3,
"alpha": null,
"beta": "2018-06-21",
"stable": "2018-09-18",
"endOfLife": "2019-07-30"
},
{
"major": 2,
"alpha": null,
"beta": "2018-02-21",
"stable": "2018-05-01",
"endOfLife": "2019-04-23"
}
]