19
//
20
// 2. Read each directory specified in the `releases.json`. The data in
21
// `docs/content` will be read. Each file will be translated in order
22
-// to add `redirects` (as `redirect_from` frontmatter). Other
23
-// `translations` may be specified to make the data suitable for the
24
-// main documentation from the CLI documentation. Finally, metadata
25
-// will be added so that the gatsby theme knows the GitHub repository
26
-// information for the content.
22
+// to add `redirects` (as `redirect_from` frontmatter). Index pages
23
+// will be created for each section of the CLI documentation. Finally,
24
+// metadata will be added so that the gatsby theme knows the GitHub
25
+// repository information for the content.
26
//
27
// 3. The CLI's navigation (in `docs/nav.yml`) will be added to the
28
// main site's base navigation (in `../src/nav-base.yml`) to produce
31
32
const fs = require('fs');
33
const path = require('path');
35
-const config = require('./releases.json');
34
+const releases = require('./releases.json');
35
const yaml = require('yaml');
36
const mkdirp = require('mkdirp');
37
38
const githubRepo = 'npm/cli';
40
-const githubUrl = 'https://github.com/npm/cli';
39
40
const docsPath = path.dirname(__dirname);
41
const inputPath = path.join(docsPath, 'cli');
50
const cliNavFile = path.join('docs', 'nav.yml');
51
const cliContentPath = path.join('docs', 'content');
52
55
-const indexMarkdown = `<Index depth="1" />`;
56
-
57
-const translations = {
58
- 'index.mdx': {
59
- 'frontmatter': { 'title': 'CLI documentation' },
60
- 'mdx': indexMarkdown,
61
- },
62
- 'commands/index.mdx': {
63
- 'frontmatter': { 'title': 'CLI commands' },
64
- 'mdx': indexMarkdown,
65
- },
66
- 'configuring-npm/index.mdx': {
67
- 'frontmatter': { 'title': 'Configuring npm' },
68
- 'mdx': indexMarkdown,
69
- },
70
- 'using-npm/index.mdx': {
71
- 'frontmatter': { 'title': 'Using npm' },
72
- 'mdx': indexMarkdown,
73
- },
74
-};
75
-
53
const redirects = {
54
'index.mdx': [
55
'/cli-documentation',
96
'/files/shrinkwrap.json.html',
97
],
98
'using-npm/index.mdx': [
122
- 'cli-documentation/misc',
123
- 'cli-documentation/using-npm',
99
+ '/cli-documentation/misc',
100
+ '/cli-documentation/using-npm',
101
'/misc/index.html',
102
],
103
'using-npm/removal.md': [
109
],
110
};
111
135
-const pagesForVersion = { }
136
-
137
-config.forEach((version) => {
112
+const pagesForVersion = { };
113
+const navForVersion = { };
114
+
115
+releases.forEach(buildCliVersion);
116
+updateNav();
117
+ensurePagesLinked();
118
+
119
+function buildCliVersion(version) {
120
+ const navInputFile = fs.readFileSync(path.join(inputPath, version.id, cliNavFile), 'utf8');
121
+ const children = rewriteUrls(version, yaml.parse(navInputFile));
122
+ navForVersion[version.id] = {
123
+ title: version.title,
124
+ shortName: version.id,
125
+ url: `${cliUrl}/${version.id}`,
126
+ default: version.default ? true : false,
127
+ children,
128
+ };
129
pagesForVersion[version.id] = copyDocs(version);
139
-});
130
+}
131
+
132
+function rewriteUrls(version, nodes) {
133
+ nodes.forEach((n) => {
134
+ const path = n.url.startsWith('/') ? n.url.substring(1) : n.url;
135
+ const data = translate(version, { path: path });
136
141
-updateNav(config);
142
-ensurePagesLinked(config);
137
+ n.url = `${cliUrl}/${version.id}/${data.path}`;
138
144
-function updateNav(config) {
139
+ if (n.children) {
140
+ rewriteUrls(version, n.children);
141
+ }
142
+ });
143
+ return nodes
144
+}
145
+
146
+function updateNav() {
147
const nav = yaml.parse(fs.readFileSync(baseNavFile, 'utf8'));
148
const variants = new Array();
149
148
- config.forEach((version) => {
149
- variants.push(readNavForVersion(version));
150
+ releases.forEach((version) => {
151
+ variants.push(navForVersion[version.id]);
152
});
153
154
nav.push({
160
161
const output = '# This file is automatically generated. Do not edit.\n' +
162
'# For registry content, edit `src/nav-base.yml in this repository.\n' +
161
- '# For CLI content, edit `docs/nav.yml` in https://github.com/npm/cli.\n' +
163
+ `# For CLI content, edit \`${cliNavFile}\` in https://github.com/${githubRepo}.\n` +
164
'\n' +
165
yaml.stringify(nav);
166
167
fs.writeFileSync(outputNavFile, output);
168
}
169
168
-function readNavForVersion(config) {
169
- const navInputFile = path.join(inputPath, config.id, cliNavFile);
170
- const children = yaml.parse(fs.readFileSync(navInputFile, 'utf8'));
171
-
172
- rewriteUrls(config, children);
173
-
174
- return {
175
- "title": config.title,
176
- "shortName": config.id,
177
- "url": `${cliUrl}/${config.id}`,
178
- "default": config.default ? true : false,
179
- "children": children
180
- };
181
-}
182
-
183
-function rewriteUrls(config, nodes) {
184
- nodes.forEach((n) => {
185
- const path = n.url.startsWith('/') ? n.url.substring(1) : n.url;
186
- const data = translate(config, { path: path });
187
-
188
- n.url = `${cliUrl}/${config.id}/${data.path}`;
189
-
190
- if (n.children) {
191
- rewriteUrls(config, n.children);
192
- }
193
- });
194
-}
195
-
196
-function translate(config, data) {
197
- const translation = translations[data.path] ? translations[data.path] : { };
198
- let matches;
199
-
170
+function translate(version, data) {
171
if (!data.frontmatter) {
172
data.frontmatter = { };
173
}
174
204
- if (data.path.match(/^index(?:\.md(x)?)?/)) {
205
- if (config.default && data.frontmatter) {
206
- data.frontmatter['redirect_from'] = [
207
- `/cli`,
208
- ];
209
- }
175
+ if (!data.frontmatter.redirect_from) {
176
+ data.frontmatter.redirect_from = [ ];
177
}
178
212
- else if ((matches = data.path.match(/(?:(.*)\/)index(?:\.md(x)?)?$/))) {
213
- if (config.default && data.frontmatter) {
214
- const section = matches[1];
179
+ let matches;
180
+
181
+ if ((matches = data.path.match(/(?:(^|.*?)\/?)index(?:\.md(?:x)?)$/))) {
182
+ if (version.default) {
183
+ const [, section] = matches;
184
216
- data.frontmatter['redirect_from'] = [
185
+ data.frontmatter.redirect_from = section ? [
186
`${section}`,
187
`/cli/${section}`,
219
- ];
188
+ ] : [
189
+ `/cli`,
190
+ ]
191
}
192
}
193
223
- else if (data.path.match(/^commands\/npm(\.md(x)?)?$/)) {
224
- if (config.default && data.frontmatter) {
225
- data.frontmatter['redirect_from'] = [
194
+ else if (data.path.match(/^commands\/npm(?:\.md(?:x)?)?$/)) {
195
+ if (version.default) {
196
+ data.frontmatter.redirect_from = [
197
`/cli/npm`,
198
`/cli/npm.html`,
199
`/cli/commands/npm`,
203
}
204
}
205
235
- else if ((matches = data.path.match(/^commands\/npm-(.*?)(\.md(?:x)?)?$/)) != null) {
236
- const command = matches[1];
237
- const extension = matches[2] ? matches[2] : '';
206
+ else if ((matches = data.path.match(/^commands\/npm-(.*?)(?:\.md(?:x)?)?$/)) != null) {
207
+ const [, command] = matches;
208
239
- if (config.default && data.frontmatter) {
240
- data.frontmatter['redirect_from'] = [
209
+ if (version.default) {
210
+ data.frontmatter.redirect_from = [
211
`/cli/${command}`,
212
`/cli/${command}.html`,
213
`/cli/commands/${command}`,
218
}
219
}
220
251
- else if ((matches = data.path.match(/^(configuring-npm)\/(.*?)(\.md(?:x)?)?$/)) != null) {
252
- const path = matches[1];
253
- const page = matches[2];
254
- const extension = matches[3] ? matches[3] : '';
221
+ else if ((matches = data.path.match(/^(configuring-npm)\/(.*?)(?:\.md(?:x)?)?$/)) != null) {
222
+ const [, path, page] = matches;
223
256
- if (config.default && data.frontmatter) {
257
- data.frontmatter['redirect_from'] = [
224
+ if (version.default) {
225
+ data.frontmatter.redirect_from = [
226
`/${path}/${page}`,
227
`/${path}/${page}.html`,
228
];
229
}
230
}
231
264
- else if ((matches = data.path.match(/^(using-npm)\/(.*?)(\.md(?:x)?)?$/)) != null) {
265
- const path = matches[1];
266
- const page = matches[2];
267
- const extension = matches[3] ? matches[3] : '';
232
+ else if ((matches = data.path.match(/^(using-npm)\/(.*?)(?:\.md(?:x)?)?$/)) != null) {
233
+ const [, path, page] = matches;
234
269
- if (config.default && data.frontmatter) {
270
- data.frontmatter['redirect_from'] = [
235
+ if (version.default) {
236
+ data.frontmatter.redirect_from = [
237
`/${path}/${page}`,
238
`/${path}/${page}.html`,
239
`/misc/${page}`,
242
}
243
}
244
279
- if (redirects[data.path] && config.default) {
280
- if (!data.frontmatter['redirect_from']) {
281
- data.frontmatter['redirect_from'] = { }
282
- }
245
+ data.frontmatter.github_repo = githubRepo;
246
+ data.frontmatter.github_branch = version.branch;
247
+ data.frontmatter.github_path = `${cliContentPath}/${data.path}`;
248
284
- Array.prototype.push.apply(data.frontmatter['redirect_from'], redirects[data.path]);
249
+ if (redirects[data.path] && version.default) {
250
+ data.frontmatter.redirect_from.push(...redirects[data.path]);
251
}
286
-
287
- if (data.frontmatter) {
288
- data.frontmatter['github_repo'] = `${githubRepo}`;
289
- data.frontmatter['github_branch'] = `${config.branch}`;
290
- data.frontmatter['github_path'] = `${cliContentPath}/${data.path}`;
252
+
253
+ if ((matches = data.path.match(/(?:(^|.*?)\/?)index(?:\.md(?:x)?)$/)) != null && !data.mdx) {
254
+ // For virtual index pages (meaning they dont come from the cli
255
+ // repo), we get the title from the nav section with a matching url.
256
+ // Also point the edit link to the nav file, in case there are
257
+ // typos or something to fix there.
258
+ const [, section] = matches;
259
+
260
+ data.frontmatter.title = section
261
+ ? navForVersion[version.id].children.find((c) => path.basename(c.url) === section).title
262
+ : cliTitle;
263
+ data.frontmatter.github_path = cliNavFile;
264
+ data.mdx = '\n<Index depth="1" />\n';
265
}
266
293
- if (data.frontmatter && translation.frontmatter) {
294
- for (let name of Object.keys(translation.frontmatter)) {
295
- data.frontmatter[name] = translation.frontmatter[name];
296
- }
297
- }
267
+ if (data.mdx) {
268
+ const replacer = (_, p1, p2) => `[${p1}](/cli/${version.id}/${p2})`;
269
299
- if ((data.mdx || !data.contents) && translation.mdx) {
300
- data.mdx = translation.mdx;
301
- }
302
- else if (data.mdx) {
303
- function replacer(matches, p1, p2) {
304
- return `[${p1}](/cli/${config.id}/${p2})`;
305
- }
306
-
307
- data.mdx = data.mdx.replace(/@VERSION@/g, config.version)
270
+ data.mdx = data.mdx.replace(/@VERSION@/g, version.version)
271
.replace(/\[([^\]]+)\]\(\/(commands\/[^)]+)\)/g, replacer)
272
.replace(/\[([^\]]+)\]\(\/(configuring-npm\/[^)]+)\)/g, replacer)
273
.replace(/\[([^\]]+)\]\(\/(using-npm\/[^)]+)\)/g, replacer);
274
}
275
276
+ if (data?.frontmatter?.redirect_from?.length === 0) {
277
+ delete data.frontmatter.redirect_from
278
+ }
279
+
280
return data;
281
}
282
316
-function ensurePagesLinked(config) {
283
+function ensurePagesLinked() {
284
const nav = yaml.parse(fs.readFileSync(outputNavFile, 'utf8'));
285
let pages = { }
286
let success = true
303
// identify pages that aren't listed in the nav
304
walkNavigation(nav, (n) => { delete pages[n.url] });
305
339
- Object.keys(pages).filter(p => !p.match("^(.*\/)?index\.md(x)?$")).forEach((page) => {
306
+ Object.keys(pages).filter(p => !p.match("^(.*\/)?index\.md(?:x)?$")).forEach((page) => {
307
console.log(`warning: ${page} is not included in navigation`);
308
success = false;
309
});
327
})
328
}
329
363
-function copyDocs(config, relativedir) {
364
- const contentRoot = path.join(inputPath, config.id, cliContentPath);
330
+function copyDocs(version, relativedir) {
331
+ const contentRoot = path.join(inputPath, version.id, cliContentPath);
332
const dirPath = relativedir ? path.join(contentRoot, relativedir) : contentRoot;
333
334
let paths = [ ]
345
const exists = fs.existsSync(childpath);
346
347
if (exists && fs.lstatSync(childpath).isDirectory()) {
381
- const childpaths = copyDocs(config, relativechild);
348
+ const childpaths = copyDocs(version, relativechild);
349
paths = paths.concat(childpaths);
350
}
351
else {
354
let output;
355
356
let filedata = {
390
- 'path': relativechild,
391
- 'contents': contents,
392
- 'frontmatter': components ? yaml.parse(components[1]) : null,
393
- 'mdx': components ? components[2] : null
357
+ path: relativechild,
358
+ contents: contents,
359
+ frontmatter: components ? yaml.parse(components[1]) : null,
360
+ mdx: components ? components[2] : null
361
};
362
396
- filedata = translate(config, filedata);
363
+ filedata = translate(version, filedata);
364
365
if (filedata) {
366
output = "---\n" + yaml.stringify(filedata.frontmatter) + "---\n" + filedata.mdx;
368
output = contents;
369
}
370
404
- const filePath = path.join('cli', config.id, filedata.path);
371
+ const filePath = path.join('cli', version.id, filedata.path);
372
const outputFilePath = path.join(outputPath, filePath);
373
mkdirp.sync(path.dirname(outputFilePath));
374
fs.writeFileSync(outputFilePath, output);