import { relative, resolve } from 'node:path'; import { readFileSync, writeFileSync } from 'node:fs'; import { format } from 'prettier'; import consola from 'consola'; import { execaCommand } from 'execa'; let hasGenerated = false; const cliDir = resolve('packages/wxt/src/cli/commands'); const cliDirGlob = resolve(cliDir, '**'); const cliTemplatePath = resolve('docs/.vitepress/plugins/cli.tpl.md'); const cliPath = resolve('docs/api/cli.md'); const PREFACE = ``; export function generateCliDocs() { writeFileSync(cliPath, ''); const generateDocs = async () => { consola.info(`Generating ${relative(process.cwd(), cliPath)}`); try { const res = await execaCommand('pnpm -s wxt --help', { cwd: 'packages/wxt', }); const dev = splitInfo(res.stdout); const lines: Array = [ `## \`wxt\``, '```sh', dev.rest, '```', ]; const commands = await Promise.allSettled( extractCommands(dev.info).map(async (command) => { const res = await execaCommand(`pnpm -s wxt ${command} --help`, { cwd: 'packages/wxt', }); const { rest: docs } = splitInfo(res.stdout); return [`## \`wxt ${command}\``, '```sh', docs, '```']; }), ); lines.push( ...commands .filter((res) => res.status === 'fulfilled') .map( (res) => (res as unknown as PromiseFulfilledResult).value, ), ); const text = await format( PREFACE + '\n\n' + readFileSync(cliTemplatePath, 'utf-8').replace( '{{ DOCS }}', lines.flat().join('\n'), ), { parser: 'markdown' }, ); writeFileSync(cliPath, text); consola.success(`Generated ${relative(process.cwd(), cliPath)}`); } catch (err) { consola.fail(`Failed to generate ${relative(process.cwd(), cliPath)}`); consola.error(err.message); } }; return { name: 'docs:generate-cli-docs', async config() { if (!hasGenerated) { hasGenerated = true; await generateDocs(); } }, configureServer(server: any) { server.watcher.add(cliDirGlob); }, async handleHotUpdate(ctx: { file: string }) { if (ctx.file === cliTemplatePath || ctx.file.includes(cliDir)) { await generateDocs(); } }, }; } function splitInfo(text: string): { info: string; rest: string } { const infoStart = text.indexOf('For more info,'); const infoEnd = text.indexOf('\n\nOptions:'); if (infoStart === -1 || infoEnd === -1) { return { info: '', rest: text }; } const info = text.substring(infoStart, infoEnd).trim(); const rest = text.replace(info, '').trim().replace('\n\n\n\n', '\n\n'); return { info, rest }; } function extractCommands(info: string): string[] { const commands: string[] = []; // Split the info by line and iterate through each line info.split('\n').forEach((line) => { // Use regex to capture the command after "$ wxt " and before "--help" const match = line.match(/\$ wxt (\w+) --help/); if (match && match[1]) { commands.push(match[1]); } }); return commands; }