UNPKG

markdown-code-example-inserter

Version:
157 lines (156 loc) 5.68 kB
import { extractRelevantArgs, replaceWithWindowsPathIfNeeded } from '@augment-vir/node'; import { glob } from 'glob'; import { existsSync } from 'node:fs'; import { relative, resolve } from 'node:path'; import { createOrderedLogging } from '../augments/console.js'; import { MarkdownCodeExampleInserterError } from '../errors/markdown-code-example-inserter.error.js'; import { OutOfDateInsertedCodeError } from '../errors/out-of-date-inserted-code.error.js'; import { isCodeUpdated, writeAllExamples } from '../example-inserter/example-inserter.js'; /** * Flag for setting a specific index file. * * @category Internals */ export const forceIndexTrigger = '--index'; const ignoreTrigger = '--ignore'; const silentTrigger = '--silent'; const checkOnlyTrigger = '--check'; /** * Parse all CLI args out of a raw CLI arg string. * * @category Internals */ export async function parseArgs(rawArgs, filePath) { const args = extractRelevantArgs({ rawArgs, binName: 'md-code', fileName: filePath || 'cli.js', }); let forceIndex = undefined; let silent = false; const inputFiles = []; const globs = []; const ignoreList = []; let checkOnly = false; let lastArgWasForceIndexTrigger = false; let lastArgWasIgnoreTrigger = false; args.forEach((arg) => { if (arg === forceIndexTrigger && forceIndex != undefined) { throw new MarkdownCodeExampleInserterError('Cannot have multiple index paths'); } else if (arg === forceIndexTrigger) { lastArgWasForceIndexTrigger = true; } else if (lastArgWasForceIndexTrigger) { forceIndex = replaceWithWindowsPathIfNeeded(arg); lastArgWasForceIndexTrigger = false; } else if (arg === ignoreTrigger) { lastArgWasIgnoreTrigger = true; } else if (arg === checkOnlyTrigger && checkOnly) { throw new MarkdownCodeExampleInserterError(`${checkOnlyTrigger} accidentally duplicated in your inputs`); } else if (arg === checkOnlyTrigger) { checkOnly = true; } else if (lastArgWasIgnoreTrigger) { ignoreList.push(arg); lastArgWasIgnoreTrigger = false; } else if (arg === silentTrigger) { silent = true; } else if (existsSync(arg)) { inputFiles.push(relative(process.cwd(), arg)); } else { globs.push(arg); } }); await Promise.all(globs.map(async (globString) => { const paths = await glob(globString, { ignore: [ ...ignoreList, '**/node_modules/**', ], nodir: true, follow: true, nocase: true, }); if (paths.length) { inputFiles.push(...paths.map((path) => { return relative(process.cwd(), path); })); } })); const uniqueFiles = Array.from(new Set(inputFiles)).sort(); return { forceIndex, silent, checkOnly, files: uniqueFiles, }; } /** * Run the `md-code` CLI. * * @category Main */ export async function runCli({ cwd = process.cwd(), rawArgs, cliFilePath = 'cli.js', }) { const args = await parseArgs(rawArgs, cliFilePath); if (!args.files.length) { throw new MarkdownCodeExampleInserterError('No markdown files given to insert code into.'); } if (!args.silent) { if (args.checkOnly) { console.info(`Checking that code in markdown is up to date:`); } else { console.info(`Inserting code into markdown:`); } } const errors = []; const orderedLog = createOrderedLogging(); await Promise.all(args.files.map(async (relativeFilePath, index) => { try { if (args.checkOnly) { const upToDate = await isCodeUpdated(resolve(relativeFilePath), cwd, args.forceIndex); if (upToDate) { if (!args.silent) { orderedLog(index, console.info, ` ${relativeFilePath}: up to date`); } } else { if (!args.silent) { orderedLog(index, console.error, ` ${relativeFilePath}: NOT up to date`); } errors.push(new OutOfDateInsertedCodeError(`${relativeFilePath} is not update to date.`)); } } else { if (!args.silent) { orderedLog(index, console.info, ` ${relativeFilePath}`); } await writeAllExamples(resolve(relativeFilePath), cwd, args.forceIndex); } } catch (error) { const errorWrapper = new MarkdownCodeExampleInserterError(`Errored on ${relativeFilePath}: ${String(error)}`); console.error(errorWrapper.message); errors.push(errorWrapper); } })); if (errors.length) { if ( /** Weird necessary as cast to prevent TypeScript's over-exuberant type guarding. */ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion errors.every((error) => error instanceof OutOfDateInsertedCodeError)) { throw new OutOfDateInsertedCodeError('Code in Markdown file(s) is out of date. Run without --check to update.'); } else { errors.forEach((error) => console.error(error)); throw new MarkdownCodeExampleInserterError(`Code insertion into Markdown failed.`); } } }