Skip to content

Commit ffdd82b

Browse files
richardlauMylesBorins
authored andcommitted
tools: only fetch previous versions when necessary
Refactor the logic for working out the previous versions of Node.js for the API documentation so that the parsing (including the potential https get) happens at most once per build (as opposed to the current once per generated API doc). Signed-off-by: Richard Lau <[email protected]> PR-URL: #32518 Fixes: #32512 Reviewed-By: Joyee Cheung <[email protected]> Reviewed-By: Myles Borins <[email protected]>
1 parent 8bcbb8d commit ffdd82b

File tree

6 files changed

+132
-88
lines changed

6 files changed

+132
-88
lines changed

Makefile

+9-2
Original file line numberDiff line numberDiff line change
@@ -780,15 +780,22 @@ out/doc/api/assets/%: doc/api_assets/% out/doc/api/assets
780780
run-npm-ci = $(PWD)/$(NPM) ci
781781

782782
LINK_DATA = out/doc/apilinks.json
783+
VERSIONS_DATA = out/doc/previous-versions.json
783784
gen-api = tools/doc/generate.js --node-version=$(FULLVERSION) \
784-
--apilinks=$(LINK_DATA) $< --output-directory=out/doc/api
785+
--apilinks=$(LINK_DATA) $< --output-directory=out/doc/api \
786+
--versions-file=$(VERSIONS_DATA)
785787
gen-apilink = tools/doc/apilinks.js $(LINK_DATA) $(wildcard lib/*.js)
786788

787789
$(LINK_DATA): $(wildcard lib/*.js) tools/doc/apilinks.js
788790
$(call available-node, $(gen-apilink))
789791

792+
# Regenerate previous versions data if the current version changes
793+
$(VERSIONS_DATA): CHANGELOG.md src/node_version.h tools/doc/versions.js
794+
$(call available-node, tools/doc/versions.js $@)
795+
790796
out/doc/api/%.json out/doc/api/%.html: doc/api/%.md tools/doc/generate.js \
791-
tools/doc/markdown.js tools/doc/html.js tools/doc/json.js tools/doc/apilinks.js | $(LINK_DATA)
797+
tools/doc/markdown.js tools/doc/html.js tools/doc/json.js \
798+
tools/doc/apilinks.js $(VERSIONS_DATA) | $(LINK_DATA)
792799
$(call available-node, $(gen-api))
793800

794801
out/doc/api/all.html: $(apidocs_html) tools/doc/allhtml.js \

test/doctool/test-doctool-html.js

+16-5
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const testLinksMapper = {
3636
}
3737
};
3838

39-
async function toHTML({ input, filename, nodeVersion }) {
39+
function toHTML({ input, filename, nodeVersion, versions }) {
4040
const content = unified()
4141
.use(replaceLinks, { filename, linksMapper: testLinksMapper })
4242
.use(markdown)
@@ -49,7 +49,7 @@ async function toHTML({ input, filename, nodeVersion }) {
4949
.use(htmlStringify)
5050
.processSync(input);
5151

52-
return html.toHTML({ input, content, filename, nodeVersion });
52+
return html.toHTML({ input, content, filename, nodeVersion, versions });
5353
}
5454

5555
// Test data is a list of objects with two properties.
@@ -129,16 +129,27 @@ const testData = [
129129
];
130130

131131
const spaces = /\s/g;
132+
const versions = [
133+
{ num: '10.x', lts: true },
134+
{ num: '9.x' },
135+
{ num: '8.x' },
136+
{ num: '7.x' },
137+
{ num: '6.x' },
138+
{ num: '5.x' },
139+
{ num: '4.x' },
140+
{ num: '0.12.x' },
141+
{ num: '0.10.x' }];
132142

133143
testData.forEach(({ file, html }) => {
134144
// Normalize expected data by stripping whitespace.
135145
const expected = html.replace(spaces, '');
136146

137147
readFile(file, 'utf8', common.mustCall(async (err, input) => {
138148
assert.ifError(err);
139-
const output = await toHTML({ input: input,
140-
filename: 'foo',
141-
nodeVersion: process.version });
149+
const output = toHTML({ input: input,
150+
filename: 'foo',
151+
nodeVersion: process.version,
152+
versions: versions });
142153

143154
const actual = output.replace(spaces, '');
144155
// Assert that the input stripped of all whitespace contains the

test/doctool/test-doctool-versions.js

+49-34
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22

33
require('../common');
44
const assert = require('assert');
5+
const { spawnSync } = require('child_process');
6+
const fs = require('fs');
7+
const path = require('path');
8+
const tmpdir = require('../common/tmpdir');
59
const util = require('util');
6-
const { versions } = require('../../tools/doc/versions.js');
10+
11+
const debuglog = util.debuglog('test');
12+
const versionsTool = path.join('../../tools/doc/versions.js');
713

814
// At the time of writing these are the minimum expected versions.
915
// New versions of Node.js do not have to be explicitly added here.
@@ -21,39 +27,48 @@ const expected = [
2127
'0.10.x',
2228
];
2329

24-
async function test() {
25-
const vers = await versions();
26-
// Coherence checks for each returned version.
27-
for (const version of vers) {
28-
const tested = util.inspect(version);
29-
const parts = version.num.split('.');
30-
const expectedLength = parts[0] === '0' ? 3 : 2;
31-
assert.strictEqual(parts.length, expectedLength,
32-
`'num' from ${tested} should be '<major>.x'.`);
33-
assert.strictEqual(parts[parts.length - 1], 'x',
34-
`'num' from ${tested} doesn't end in '.x'.`);
35-
const isEvenRelease = Number.parseInt(parts[expectedLength - 2]) % 2 === 0;
36-
const hasLtsProperty = version.hasOwnProperty('lts');
37-
if (hasLtsProperty) {
38-
// Odd-numbered versions of Node.js are never LTS.
39-
assert.ok(isEvenRelease, `${tested} should not be an 'lts' release.`);
40-
assert.ok(version.lts, `'lts' from ${tested} should 'true'.`);
41-
}
42-
}
30+
tmpdir.refresh();
31+
const versionsFile = path.join(tmpdir.path, 'versions.json');
32+
debuglog(versionsFile);
33+
const opts = { cwd: tmpdir.path, encoding: 'utf8' };
34+
const cp = spawnSync(process.execPath, [ versionsTool, versionsFile ], opts);
35+
debuglog(cp.stderr);
36+
debuglog(cp.stdout);
37+
assert.strictEqual(cp.stdout, '');
38+
assert.strictEqual(cp.signal, null);
39+
assert.strictEqual(cp.status, 0);
40+
const versions = JSON.parse(fs.readFileSync(versionsFile));
41+
debuglog(versions);
4342

44-
// Check that the minimum number of versions were returned.
45-
// Later versions are allowed, but not checked for here (they were checked
46-
// above).
47-
// Also check for the previous semver major -- From master this will be the
48-
// most recent major release.
49-
const thisMajor = Number.parseInt(process.versions.node.split('.')[0]);
50-
const prevMajorString = `${thisMajor - 1}.x`;
51-
if (!expected.includes(prevMajorString)) {
52-
expected.unshift(prevMajorString);
53-
}
54-
for (const version of expected) {
55-
assert.ok(vers.find((x) => x.num === version),
56-
`Did not find entry for '${version}' in ${util.inspect(vers)}`);
43+
// Coherence checks for each returned version.
44+
for (const version of versions) {
45+
const tested = util.inspect(version);
46+
const parts = version.num.split('.');
47+
const expectedLength = parts[0] === '0' ? 3 : 2;
48+
assert.strictEqual(parts.length, expectedLength,
49+
`'num' from ${tested} should be '<major>.x'.`);
50+
assert.strictEqual(parts[parts.length - 1], 'x',
51+
`'num' from ${tested} doesn't end in '.x'.`);
52+
const isEvenRelease = Number.parseInt(parts[expectedLength - 2]) % 2 === 0;
53+
const hasLtsProperty = version.hasOwnProperty('lts');
54+
if (hasLtsProperty) {
55+
// Odd-numbered versions of Node.js are never LTS.
56+
assert.ok(isEvenRelease, `${tested} should not be an 'lts' release.`);
57+
assert.ok(version.lts, `'lts' from ${tested} should 'true'.`);
5758
}
5859
}
59-
test();
60+
61+
// Check that the minimum number of versions were returned.
62+
// Later versions are allowed, but not checked for here (they were checked
63+
// above).
64+
// Also check for the previous semver major -- From master this will be the
65+
// most recent major release.
66+
const thisMajor = Number.parseInt(process.versions.node.split('.')[0]);
67+
const prevMajorString = `${thisMajor - 1}.x`;
68+
if (!expected.includes(prevMajorString)) {
69+
expected.unshift(prevMajorString);
70+
}
71+
for (const version of expected) {
72+
assert.ok(versions.find((x) => x.num === version),
73+
`Did not find entry for '${version}' in ${util.inspect(versions)}`);
74+
}

tools/doc/generate.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ let filename = null;
4242
let nodeVersion = null;
4343
let outputDir = null;
4444
let apilinks = {};
45+
let versions = {};
4546

4647
async function main() {
4748
for (const arg of args) {
@@ -58,6 +59,13 @@ async function main() {
5859
throw new Error(`${linkFile} is empty`);
5960
}
6061
apilinks = JSON.parse(data);
62+
} else if (arg.startsWith('--versions-file=')) {
63+
const versionsFile = arg.replace(/^--versions-file=/, '');
64+
const data = await fs.readFile(versionsFile, 'utf8');
65+
if (!data.trim()) {
66+
throw new Error(`${versionsFile} is empty`);
67+
}
68+
versions = JSON.parse(data);
6169
}
6270
}
6371

@@ -84,7 +92,8 @@ async function main() {
8492
.use(htmlStringify)
8593
.process(input);
8694

87-
const myHtml = await html.toHTML({ input, content, filename, nodeVersion });
95+
const myHtml = await html.toHTML({ input, content, filename, nodeVersion,
96+
versions });
8897
const basename = path.basename(filename, '.md');
8998
const htmlTarget = path.join(outputDir, `${basename}.html`);
9099
const jsonTarget = path.join(outputDir, `${basename}.json`);

tools/doc/html.js

+3-5
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323

2424
const common = require('./common.js');
2525
const fs = require('fs');
26-
const getVersions = require('./versions.js');
2726
const unified = require('unified');
2827
const find = require('unist-util-find');
2928
const visit = require('unist-util-visit');
@@ -63,7 +62,7 @@ const gtocHTML = unified()
6362
const templatePath = path.join(docPath, 'template.html');
6463
const template = fs.readFileSync(templatePath, 'utf8');
6564

66-
async function toHTML({ input, content, filename, nodeVersion }) {
65+
function toHTML({ input, content, filename, nodeVersion, versions }) {
6766
filename = path.basename(filename, '.md');
6867

6968
const id = filename.replace(/\W+/g, '-');
@@ -81,7 +80,7 @@ async function toHTML({ input, content, filename, nodeVersion }) {
8180
const docCreated = input.match(
8281
/<!--\s*introduced_in\s*=\s*v([0-9]+)\.([0-9]+)\.[0-9]+\s*-->/);
8382
if (docCreated) {
84-
HTML = HTML.replace('__ALTDOCS__', await altDocs(filename, docCreated));
83+
HTML = HTML.replace('__ALTDOCS__', altDocs(filename, docCreated, versions));
8584
} else {
8685
console.error(`Failed to add alternative version links to ${filename}`);
8786
HTML = HTML.replace('__ALTDOCS__', '');
@@ -391,10 +390,9 @@ function getId(text, idCounters) {
391390
return text;
392391
}
393392

394-
async function altDocs(filename, docCreated) {
393+
function altDocs(filename, docCreated, versions) {
395394
const [, docCreatedMajor, docCreatedMinor] = docCreated.map(Number);
396395
const host = 'https://nodejs.org';
397-
const versions = await getVersions.versions();
398396

399397
const getHref = (versionNum) =>
400398
`${host}/docs/latest-v${versionNum}/api/${filename}.html`;

tools/doc/versions.js

+45-41
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
'use strict';
22

3-
const { readFileSync } = require('fs');
3+
const { readFileSync, writeFileSync } = require('fs');
44
const path = require('path');
55
const srcRoot = path.join(__dirname, '..', '..');
66

7-
let _versions;
8-
97
const isRelease = () => {
108
const re = /#define NODE_VERSION_IS_RELEASE 0/;
119
const file = path.join(srcRoot, 'src', 'node_version.h');
@@ -15,7 +13,7 @@ const isRelease = () => {
1513
const getUrl = (url) => {
1614
return new Promise((resolve, reject) => {
1715
const https = require('https');
18-
const request = https.get(url, { timeout: 5000 }, (response) => {
16+
const request = https.get(url, { timeout: 30000 }, (response) => {
1917
if (response.statusCode !== 200) {
2018
reject(new Error(
2119
`Failed to get ${url}, status code ${response.statusCode}`));
@@ -32,45 +30,51 @@ const getUrl = (url) => {
3230
};
3331

3432
const kNoInternet = !!process.env.NODE_TEST_NO_INTERNET;
33+
const outFile = (process.argv.length > 2 ? process.argv[2] : undefined);
3534

36-
module.exports = {
37-
async versions() {
38-
if (_versions) {
39-
return _versions;
40-
}
41-
42-
// The CHANGELOG.md on release branches may not reference newer semver
43-
// majors of Node.js so fetch and parse the version from the master branch.
44-
const url =
45-
'https://raw.githubusercontent.com/nodejs/node/master/CHANGELOG.md';
46-
let changelog;
47-
const file = path.join(srcRoot, 'CHANGELOG.md');
48-
if (kNoInternet) {
49-
changelog = readFileSync(file, { encoding: 'utf8' });
50-
} else {
51-
try {
52-
changelog = await getUrl(url);
53-
} catch (e) {
54-
// Fail if this is a release build, otherwise fallback to local files.
55-
if (isRelease()) {
56-
throw e;
57-
} else {
58-
console.warn(`Unable to retrieve ${url}. Falling back to ${file}.`);
59-
changelog = readFileSync(file, { encoding: 'utf8' });
60-
}
35+
async function versions() {
36+
// The CHANGELOG.md on release branches may not reference newer semver
37+
// majors of Node.js so fetch and parse the version from the master branch.
38+
const url =
39+
'https://raw.githubusercontent.com/nodejs/node/master/CHANGELOG.md';
40+
let changelog;
41+
const file = path.join(srcRoot, 'CHANGELOG.md');
42+
if (kNoInternet) {
43+
changelog = readFileSync(file, { encoding: 'utf8' });
44+
} else {
45+
try {
46+
changelog = await getUrl(url);
47+
} catch (e) {
48+
// Fail if this is a release build, otherwise fallback to local files.
49+
if (isRelease()) {
50+
throw e;
51+
} else {
52+
console.warn(`Unable to retrieve ${url}. Falling back to ${file}.`);
53+
changelog = readFileSync(file, { encoding: 'utf8' });
6154
}
6255
}
63-
const ltsRE = /Long Term Support/i;
64-
const versionRE = /\* \[Node\.js ([0-9.]+)\]\S+ (.*)\r?\n/g;
65-
_versions = [];
66-
let match;
67-
while ((match = versionRE.exec(changelog)) != null) {
68-
const entry = { num: `${match[1]}.x` };
69-
if (ltsRE.test(match[2])) {
70-
entry.lts = true;
71-
}
72-
_versions.push(entry);
56+
}
57+
const ltsRE = /Long Term Support/i;
58+
const versionRE = /\* \[Node\.js ([0-9.]+)\]\S+ (.*)\r?\n/g;
59+
const _versions = [];
60+
let match;
61+
while ((match = versionRE.exec(changelog)) != null) {
62+
const entry = { num: `${match[1]}.x` };
63+
if (ltsRE.test(match[2])) {
64+
entry.lts = true;
7365
}
74-
return _versions;
66+
_versions.push(entry);
7567
}
76-
};
68+
return _versions;
69+
}
70+
71+
versions().then((v) => {
72+
if (outFile) {
73+
writeFileSync(outFile, JSON.stringify(v));
74+
} else {
75+
console.log(JSON.stringify(v));
76+
}
77+
}).catch((err) => {
78+
console.error(err);
79+
process.exit(1);
80+
});

0 commit comments

Comments
 (0)