UNPKG

@vuedoc/parser

Version:

Generate a JSON documentation for a Vue file

269 lines (208 loc) 6.99 kB
#!/usr/bin/env node import { readFileSync, createWriteStream } from 'node:fs'; import { dirname, isAbsolute, join, parse } from 'node:path'; import { fileURLToPath } from 'node:url'; import { VuedocParser, parseComponent } from '../main.js'; import { Parser } from '../../types/Parser.js'; import merge from 'deepmerge'; import JsonSchemav from 'jsonschemav'; import ValidationError from 'jsonschemav/lib/error.js'; import ConfigSchema from '../schema/config.js'; export type CliOptions = { join: boolean; stream: any; filenames: string[]; output?: string; parsing: Partial<Parser.Options>; }; const jsv = new JsonSchemav(); const validator = jsv.compile(ConfigSchema); const ARG_IGNORE_PREFIX = '--ignore-'; const usage = 'Usage: vuedoc-json [*.{js,vue} files]...'; export const MISSING_FILENAME_MESSAGE = `Missing filenames. ${usage}\n`; export async function parseArgs(argv: string[], requireFiles = false): Promise<CliOptions> { const parsing: Partial<Parser.Options> = { features: VuedocParser.SUPPORTED_FEATURES as Parser.Feature[], }; const options: CliOptions = { join: false, stream: true as any, filenames: [], parsing, }; const promises = []; for (let i = 0; i < argv.length; i++) { const arg = argv[i]; switch (arg) { /* istanbul ignore next */ case '-v': /* istanbul ignore next */ case '--version': { const __dirname = dirname(fileURLToPath(import.meta.url)); const packageFilename = join(__dirname, '../../package.json'); const { name, version } = JSON.parse(readFileSync(packageFilename, 'utf-8')); const output = `${name} v${version}\n`; process.stdout.write(output); return null; } case '-c': case '--config': { let configFile = argv[i + 1] || ''; if (configFile.endsWith('.js')) { i++; } else { configFile = 'vuedoc.config.js'; } const configPath = isAbsolute(configFile) ? configFile : join(process.cwd(), configFile); promises.push((async () => { const config = await import(configPath); if (config.default) { Object.assign(options, config.default); if (config.default.parsing) { options.parsing = { ...parsing, ...config.default.parsing }; } } })()); break; } case '-o': case '--output': if (!argv[i + 1]) { throw new Error('Missing output value. Usage: --output [file or directory]\n'); } options.output = argv[i + 1]; i++; break; case '-j': case '--join': options.join = true; break; case '-': break; default: { if (arg.startsWith(ARG_IGNORE_PREFIX)) { const feature = arg.substring(ARG_IGNORE_PREFIX.length); options.parsing.features = options.parsing.features.filter((item) => item !== feature); } else { options.filenames.push(arg); } break; } } } await Promise.all(promises); if (requireFiles && options.filenames.length === 0) { throw new Error(MISSING_FILENAME_MESSAGE); } return options; } function renderFile(filename: string, { ...options }: CliOptions) { return ({ warnings = [], errors = [], ...component }) => new Promise((resolve, reject) => { warnings.forEach((message) => process.stderr.write(`Warn: ${message}\n`)); if (errors.length) { errors.forEach((message) => process.stderr.write(`Err: ${message}\n`)); reject(new Error(component.errors[0])); return; } const output = JSON.stringify(component, null, 2) + '\n'; const stream = typeof options.stream === 'function' && filename ? options.stream(filename) : options.stream; stream.write(output); resolve(output); }); } export async function parseComponentOptions({ stream, filename, ...options }) { if (!options.parsing) { options.parsing = {}; } // compatibility with previous versions if (filename && !options.filenames) { options.filenames = [filename]; } try { const instance = await validator; const { parsing: parsingOptions, join, filenames, ...restOptions } = await instance.validate(options); const promises: Promise<any>[] = await new Promise((resolve, reject) => { const renderOptions = { stream, ...restOptions }; if (filenames.length) { const parsers = filenames.map((filename) => parseComponent({ ...parsingOptions, filename })); let promises = []; if (join) { promises = [ Promise.all(parsers).then(merge.all).then(renderFile(null, renderOptions)), ]; } else { promises = parsers.map((promise, index) => { return promise.then(renderFile(filenames[index], renderOptions)); }); } resolve(promises); } else if (parsingOptions.filecontent) { resolve([ parseComponent(parsingOptions).then(renderFile(null, renderOptions)), ]); } else { reject(new Error('Invalid options. Missing options.filenames')); } }); const docs = await Promise.all(promises); if (filenames.length === 1 && docs.length === 1) { return docs[0]; } return docs; } catch (err) { if (err instanceof ValidationError) { err.message = 'Invalid options'; } throw err; } } export async function processRawContent(argv: string[], componentRawContent: string) { const options = await parseArgs(argv, false); if (options) { options.stream = process.stdout; (options.parsing as Parser.FilecontentOptions).filecontent = componentRawContent; return parseComponentOptions(options as any); } return ''; } export async function processContentOutput(options) { if (options.output.endsWith('.json')) { options.stream = createWriteStream(options.output); } else { options.stream = (filename: string) => { const info = parse(filename); const jsoname = `${info.name}.json`; const dest = join(options.output, jsoname); return createWriteStream(dest); }; } return parseComponentOptions(options); } export async function processContent(options) { if (options.output) { return processContentOutput(options); } options.stream = process.stdout; return parseComponentOptions(options); } export async function exec(argv: string[], componentRawContent = '') { if (componentRawContent) { await processRawContent(argv, componentRawContent); } else { const options = await parseArgs(argv, true); if (options) { await processContent(options); } } } export async function silenceExec(argv: string[], componentRawContent = '') { try { await exec(argv, componentRawContent); } catch (err) { process.stderr.write(`${err.message}\n`); } }