Skip to content

Commit 747c861

Browse files
committed
fixup!
1 parent ae3de90 commit 747c861

File tree

2 files changed

+184
-0
lines changed

2 files changed

+184
-0
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import '../common/index.mjs';
2+
import assert from 'node:assert';
3+
import { createReadStream } from 'node:fs';
4+
import { createInterface } from 'node:readline';
5+
import { resolve, join } from 'node:path';
6+
7+
// This test checks that all the environment variables defined in the public CLI documentation (doc/api/cli.md)
8+
// are also documented in the manpage file (doc/node.1) and vice-versa (that all the environment variables
9+
// in the manpage are present in the CLI documentation)
10+
11+
const rootDir = resolve(import.meta.dirname, '..', '..');
12+
13+
const cliMdEnvVarNames = await collectCliMdEnvVarNames();
14+
const manpageEnvVarNames = await collectManPageEnvVarNames();
15+
16+
assert(cliMdEnvVarNames.size > 0,
17+
'Unexpectedly not even a single env variable was detected when scanning the `doc/api/cli.md` file'
18+
);
19+
20+
assert(manpageEnvVarNames.size > 0,
21+
'Unexpectedly not even a single env variable was detected when scanning the `doc/node.1` file'
22+
);
23+
24+
for (const envVarName of cliMdEnvVarNames) {
25+
if (!manpageEnvVarNames.has(envVarName)) {
26+
assert.fail(`The "${envVarName}" environment variable (present in \`doc/api/cli.md\`) is missing from the \`doc/node.1\` file`);
27+
}
28+
manpageEnvVarNames.delete(envVarName);
29+
}
30+
31+
if (manpageEnvVarNames.size > 0) {
32+
assert.fail(`The following env variables are present in the \`doc/node.1\` file but not in the \`doc/api/cli.md\` file: ${
33+
[...manpageEnvVarNames].map((name) => `"${name}"`).join(', ')
34+
}`);
35+
}
36+
37+
async function collectManPageEnvVarNames() {
38+
const manPagePath = join(rootDir, 'out', 'doc', 'node.1');
39+
const fileStream = createReadStream(manPagePath);
40+
41+
const rl = createInterface({
42+
input: fileStream,
43+
});
44+
45+
const envVarNames = new Set();
46+
47+
for await (const line of rl) {
48+
const match = line.match(/^\.It Ev (?<envName>[^ ]*)/);
49+
if (match) {
50+
envVarNames.add(match.groups.envName);
51+
}
52+
}
53+
54+
return envVarNames;
55+
}
56+
57+
async function collectCliMdEnvVarNames() {
58+
const cliMdPath = join(rootDir, 'doc', 'api', 'cli.md');
59+
const fileStream = createReadStream(cliMdPath);
60+
61+
let insideEnvVariablesSection = false;
62+
63+
const rl = createInterface({
64+
input: fileStream,
65+
});
66+
67+
const envVariableRE = /^### `(?<varName>[^`]*?)(?:=[^`]+)?`$/;
68+
69+
const envVarNames = new Set();
70+
71+
for await (const line of rl) {
72+
if (line.startsWith('## ')) {
73+
if (insideEnvVariablesSection) {
74+
// We were in the environment variables section and we're now exiting it,
75+
// so there is no need to keep checking the remaining lines,
76+
// we might as well close the stream and return
77+
fileStream.close();
78+
return envVarNames;
79+
}
80+
81+
// We've just entered the options section
82+
insideEnvVariablesSection = line === '## Environment variables';
83+
continue;
84+
}
85+
86+
if (insideEnvVariablesSection) {
87+
const match = line.match(envVariableRE);
88+
if (match) {
89+
const { varName } = match.groups;
90+
envVarNames.add(varName);
91+
}
92+
}
93+
}
94+
95+
return envVarNames;
96+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import '../common/index.mjs';
2+
import assert from 'node:assert';
3+
import { createReadStream, readFileSync } from 'node:fs';
4+
import { createInterface } from 'node:readline';
5+
import { resolve, join } from 'node:path';
6+
7+
// This test checks that all the CLI flags defined in the public CLI documentation (doc/api/cli.md)
8+
// are also documented in the manpage file (doc/node.1)
9+
// Note: the opposite (that all variables in doc/node.1 are documented in the CLI documentation)
10+
// is covered in the test-cli-node-options-docs.js file
11+
12+
const rootDir = resolve(import.meta.dirname, '..', '..');
13+
14+
const cliMdPath = join(rootDir, 'doc', 'api', 'cli.md');
15+
const cliMdContentsStream = createReadStream(cliMdPath);
16+
17+
const manPagePath = join(rootDir, 'out', 'doc', 'node.1');
18+
const manPageContents = readFileSync(manPagePath, { encoding: 'utf8' });
19+
20+
let insideOptionsSection = false;
21+
22+
const rl = createInterface({
23+
input: cliMdContentsStream,
24+
});
25+
26+
const isOptionLineRegex = /^###(?: `[^`]*`,?)*$/;
27+
28+
for await (const line of rl) {
29+
if (line.startsWith('## ')) {
30+
if (insideOptionsSection) {
31+
// We were in the options section and we're now exiting it,
32+
// so there is no need to keep checking the remaining lines,
33+
// we might as well close the stream and exit the loop
34+
cliMdContentsStream.close();
35+
break;
36+
}
37+
38+
// We've just entered the options section
39+
insideOptionsSection = line === '## Options';
40+
continue;
41+
}
42+
43+
if (insideOptionsSection && isOptionLineRegex.test(line)) {
44+
const flagNames = extractFlagNames(line);
45+
const flagMatcher = new RegExp(`^\\.It ${flagNames.map((f) => `Fl ${f}.*`).join(', ')}$`, 'm');
46+
47+
if (!manPageContents.match(flagMatcher)) {
48+
assert.fail(
49+
`The following flag${
50+
flagNames.length === 1 ? '' : 's'
51+
} (present in \`doc/api/cli.md\`) ${flagNames.length === 1 ? 'is' : 'are'} missing in the \`doc/node.1\` file: ${
52+
flagNames.map((flag) => `"-${flag}"`).join(', ')
53+
}`
54+
);
55+
}
56+
}
57+
}
58+
59+
/**
60+
* Function that given a string containing backtick enclosed cli flags
61+
* separated by `, ` returns the name of flags present in the string
62+
* e.g. `extractFlagNames('`-x`, `--print "script"`')` === `['x', 'print']`
63+
* @param {string} str target string
64+
* @returns {string[]} the name of the detected flags
65+
*/
66+
function extractFlagNames(str) {
67+
const match = str.match(/`[^`]*?`/g);
68+
if (!match) {
69+
return [];
70+
}
71+
return match.map((flag) => {
72+
// Remove the backticks, and leading dash from the flag
73+
flag = flag.slice(2, -1);
74+
75+
// If the flag contains parameters make sure to remove those
76+
const nameDelimiters = ['=', ' ', '['];
77+
const nameCutOffIdx = Math.min(...nameDelimiters.map((d) => {
78+
const idx = flag.indexOf(d);
79+
if (idx > 0) {
80+
return idx;
81+
}
82+
return flag.length;
83+
}));
84+
flag = flag.slice(0, nameCutOffIdx);
85+
86+
return flag;
87+
});
88+
}

0 commit comments

Comments
 (0)