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