• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright Joyent, Inc. and other Node contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to permit
8// persons to whom the Software is furnished to do so, subject to the
9// following conditions:
10//
11// The above copyright notice and this permission notice shall be included
12// in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20// USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22import { readFileSync, promises as fs } from 'fs';
23import path from 'path';
24
25import raw from 'rehype-raw';
26import htmlStringify from 'rehype-stringify';
27import gfm from 'remark-gfm';
28import markdown from 'remark-parse';
29import remark2rehype from 'remark-rehype';
30import frontmatter from 'remark-frontmatter';
31import { unified } from 'unified';
32
33import * as html from './html.mjs';
34import * as json from './json.mjs';
35import { replaceLinks } from './markdown.mjs';
36
37const linksMapperFile = new URL('links-mapper.json', import.meta.url);
38const linksMapper = JSON.parse(readFileSync(linksMapperFile, 'utf8'));
39
40// Parse the args.
41// Don't use nopt or whatever for this. It's simple enough.
42
43const args = process.argv.slice(2);
44let filename = null;
45let nodeVersion = null;
46let outputDir = null;
47let apilinks = {};
48let versions = [];
49
50async function main() {
51  for (const arg of args) {
52    if (!arg.startsWith('--')) {
53      filename = arg;
54    } else if (arg.startsWith('--node-version=')) {
55      nodeVersion = arg.replace(/^--node-version=/, '');
56    } else if (arg.startsWith('--output-directory=')) {
57      outputDir = arg.replace(/^--output-directory=/, '');
58    } else if (arg.startsWith('--apilinks=')) {
59      const linkFile = arg.replace(/^--apilinks=/, '');
60      const data = await fs.readFile(linkFile, 'utf8');
61      if (!data.trim()) {
62        throw new Error(`${linkFile} is empty`);
63      }
64      apilinks = JSON.parse(data);
65    } else if (arg.startsWith('--versions-file=')) {
66      const versionsFile = arg.replace(/^--versions-file=/, '');
67      const data = await fs.readFile(versionsFile, 'utf8');
68      if (!data.trim()) {
69        throw new Error(`${versionsFile} is empty`);
70      }
71      versions = JSON.parse(data);
72    }
73  }
74
75  nodeVersion = nodeVersion || process.version;
76
77  if (!filename) {
78    throw new Error('No input file specified');
79  } else if (!outputDir) {
80    throw new Error('No output directory specified');
81  }
82
83  const input = await fs.readFile(filename, 'utf8');
84
85  const content = await unified()
86    .use(frontmatter)
87    .use(replaceLinks, { filename, linksMapper })
88    .use(markdown)
89    .use(gfm)
90    .use(html.preprocessText, { nodeVersion })
91    .use(json.jsonAPI, { filename })
92    .use(html.firstHeader)
93    .use(html.preprocessElements, { filename })
94    .use(html.buildToc, { filename, apilinks })
95    .use(remark2rehype, { allowDangerousHtml: true })
96    .use(raw)
97    .use(htmlStringify)
98    .process(input);
99
100  const myHtml = await html.toHTML({ input, content, filename, nodeVersion,
101                                     versions });
102  const basename = path.basename(filename, '.md');
103  const htmlTarget = path.join(outputDir, `${basename}.html`);
104  const jsonTarget = path.join(outputDir, `${basename}.json`);
105
106  return Promise.allSettled([
107    fs.writeFile(htmlTarget, myHtml),
108    fs.writeFile(jsonTarget, JSON.stringify(content.json, null, 2)),
109  ]);
110}
111
112main()
113  .then((tasks) => {
114    // Filter rejected tasks
115    const errors = tasks.filter(({ status }) => status === 'rejected')
116      .map(({ reason }) => reason);
117
118    // Log errors
119    for (const error of errors) {
120      console.error(error);
121    }
122
123    // Exit process with code 1 if some errors
124    if (errors.length > 0) {
125      return process.exit(1);
126    }
127
128    // Else with code 0
129    process.exit(0);
130  })
131  .catch((error) => {
132    console.error(error);
133
134    process.exit(1);
135  });
136