From e0abeb8e6897e4fac549be5e635808cfcb416d24 Mon Sep 17 00:00:00 2001 From: Richard Lau Date: Sun, 12 May 2019 13:08:37 -0400 Subject: [PATCH] doc,tools: get altDocs versions from CHANGELOG.md Parse `CHANGELOG.md` for versions of Node.js used by the documentation feature `View another version` so that we don't have to manually update the list when we cut a new version or transition a release to LTS. --- test/internet/test-doctool-versions.js | 59 ++++++++++++++++++++++++++ tools/doc/html.js | 21 +++------ tools/doc/versions.js | 45 ++++++++++++++++++++ 3 files changed, 109 insertions(+), 16 deletions(-) create mode 100644 test/internet/test-doctool-versions.js create mode 100644 tools/doc/versions.js diff --git a/test/internet/test-doctool-versions.js b/test/internet/test-doctool-versions.js new file mode 100644 index 00000000000000..8bb4f81c795d95 --- /dev/null +++ b/test/internet/test-doctool-versions.js @@ -0,0 +1,59 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const util = require('util'); +const { versions } = require('../../tools/doc/versions.js'); + +// At the time of writing these are the minimum expected versions. +// New versions of Node.js do not have to be explicitly added here. +const expected = [ + '12.x', + '11.x', + '10.x', + '9.x', + '8.x', + '7.x', + '6.x', + '5.x', + '4.x', + '0.12.x', + '0.10.x', +]; + +async function test() { + const vers = await versions(); + // Coherence checks for each returned version. + for (const version of vers) { + const tested = util.inspect(version); + const parts = version.num.split('.'); + const expectedLength = parts[0] === '0' ? 3 : 2; + assert.strictEqual(parts.length, expectedLength, + `'num' from ${tested} should be '.x'.`); + assert.strictEqual(parts[parts.length - 1], 'x', + `'num' from ${tested} doesn't end in '.x'.`); + const isEvenRelease = Number.parseInt(parts[expectedLength - 2]) % 2 === 0; + const hasLtsProperty = version.hasOwnProperty('lts'); + if (hasLtsProperty) { + // Odd-numbered versions of Node.js are never LTS. + assert.ok(isEvenRelease, `${tested} should not be an 'lts' release.`); + assert.ok(version.lts, `'lts' from ${tested} should 'true'.`); + } + } + + // Check that the minimum number of versions were returned. + // Later versions are allowed, but not checked for here (they were checked + // above). + // Also check for the previous semver major -- From master this will be the + // most recent major release. + const thisMajor = Number.parseInt(process.versions.node.split('.')[0]); + const prevMajorString = `${thisMajor - 1}.x`; + if (!expected.includes(prevMajorString)) { + expected.unshift(prevMajorString); + } + for (const version of expected) { + assert.ok(vers.find((x) => x.num === version), + `Did not find entry for '${version}' in ${util.inspect(vers)}`); + } +} +test(); diff --git a/tools/doc/html.js b/tools/doc/html.js index efdc8b0d475b0f..318feefe3461a1 100644 --- a/tools/doc/html.js +++ b/tools/doc/html.js @@ -23,6 +23,7 @@ const common = require('./common.js'); const fs = require('fs'); +const getVersions = require('./versions.js'); const unified = require('unified'); const find = require('unist-util-find'); const visit = require('unist-util-visit'); @@ -62,7 +63,7 @@ const gtocHTML = unified() const templatePath = path.join(docPath, 'template.html'); const template = fs.readFileSync(templatePath, 'utf8'); -function toHTML({ input, content, filename, nodeVersion }, cb) { +async function toHTML({ input, content, filename, nodeVersion }, cb) { filename = path.basename(filename, '.md'); const id = filename.replace(/\W+/g, '-'); @@ -80,7 +81,7 @@ function toHTML({ input, content, filename, nodeVersion }, cb) { const docCreated = input.match( //); if (docCreated) { - HTML = HTML.replace('__ALTDOCS__', altDocs(filename, docCreated)); + HTML = HTML.replace('__ALTDOCS__', await altDocs(filename, docCreated)); } else { console.error(`Failed to add alternative version links to ${filename}`); HTML = HTML.replace('__ALTDOCS__', ''); @@ -390,22 +391,10 @@ function getId(text, idCounters) { return text; } -function altDocs(filename, docCreated) { +async function altDocs(filename, docCreated) { const [, docCreatedMajor, docCreatedMinor] = docCreated.map(Number); const host = 'https://nodejs.org'; - const versions = [ - { num: '12.x' }, - { num: '11.x' }, - { num: '10.x', lts: true }, - { num: '9.x' }, - { num: '8.x', lts: true }, - { num: '7.x' }, - { num: '6.x' }, - { num: '5.x' }, - { num: '4.x' }, - { num: '0.12.x' }, - { num: '0.10.x' }, - ]; + const versions = await getVersions.versions(); const getHref = (versionNum) => `${host}/docs/latest-v${versionNum}/api/${filename}.html`; diff --git a/tools/doc/versions.js b/tools/doc/versions.js new file mode 100644 index 00000000000000..854329bd9a8a02 --- /dev/null +++ b/tools/doc/versions.js @@ -0,0 +1,45 @@ +'use strict'; + +let _versions; + +const getUrl = (url) => { + return new Promise((resolve, reject) => { + const https = require('https'); + const request = https.get(url, (response) => { + if (response.statusCode !== 200) { + reject(new Error( + `Failed to get ${url}, status code ${response.statusCode}`)); + } + response.setEncoding('utf8'); + let body = ''; + response.on('data', (data) => body += data); + response.on('end', () => resolve(body)); + }); + request.on('error', (err) => reject(err)); + }); +}; + +module.exports = { + async versions() { + if (_versions) { + return _versions; + } + + // The CHANGELOG.md on release branches may not reference newer semver + // majors of Node.js so fetch and parse the version from the master branch. + const githubContentUrl = 'https://raw.githubusercontent.com/nodejs/node/'; + const changelog = await getUrl(`${githubContentUrl}/master/CHANGELOG.md`); + const ltsRE = /Long Term Support/i; + const versionRE = /\* \[Node\.js ([0-9.]+)\][^-—]+[-—]\s*(.*)\n/g; + _versions = []; + let match; + while ((match = versionRE.exec(changelog)) != null) { + const entry = { num: `${match[1]}.x` }; + if (ltsRE.test(match[2])) { + entry.lts = true; + } + _versions.push(entry); + } + return _versions; + } +};