import * as plugins from './plugins.js'; import * as paths from './paths.js'; import { logger } from './logging.js'; import { commitinfo } from './00_commitinfo_data.js'; import { TypeDoc } from './classes.typedoc.js'; import { AiDoc } from './classes.aidoc.js'; import { NoChangesError } from './aidocs_classes/commit.js'; const createAiDoc = async (argvArg: plugins.smartconsole.ICliArgv): Promise => { const aidocInstance = new AiDoc(argvArg); await aidocInstance.start(); return aidocInstance; }; const handleAuthCommand = async (subcommand = 'status'): Promise => { if (subcommand === 'login') { await plugins.tsagent.loginChatGptAuth(); return; } const inspections = await plugins.tsagent.inspectChatGptAuthSources(); console.log('OpenAI ChatGPT auth sources:'); for (const inspection of inspections) { const status = inspection.usable ? 'usable' : inspection.exists ? inspection.expired ? 'expired' : 'not usable' : 'missing'; const account = inspection.email ? ` (${inspection.email})` : ''; console.log(` ${inspection.source}: ${status}${account} - ${inspection.filePath}`); } }; /** * The AI documentation commands forward every option to the AI agent, which reads options such as --model and --provider. */ const aidocCommand = { allowUnknownOptions: true } as const; export const run = async () => { const out = new plugins.smartconsole.SmartConsole(); out.cli.configure({ name: 'tsdoc', version: commitinfo.version }); out.cli.default({ description: 'Detect the documentation format and build it', options: { publicSubdir: { type: 'string', description: 'Subdirectory of public/ for the TypeDoc output' } }, allowUnknownOptions: true, }, async ({ argv }) => { logger.log('warn', `Auto detecting environment!`); switch (true) { case await TypeDoc.isTypeDocDir(paths.cwd): logger.log('ok', `Detected TypeDoc compliant directory at ${paths.cwd}`); await out.cli.dispatch('typedoc', argv); break; default: logger.log('error', `Cannot determine docs format at ${paths.cwd}`); } }); out.cli.command({ name: 'typedoc', description: 'Build the TypeDoc API documentation', options: { publicSubdir: { type: 'string', description: 'Subdirectory of public/ for the TypeDoc output' } }, allowUnknownOptions: true, }, async ({ options }) => { const typeDocInstance = new TypeDoc(paths.cwd); await typeDocInstance.compile({ publicSubdir: options.publicSubdir, }); }); out.cli.command({ name: 'website', description: 'Serve a documentation website', options: { dir: { type: 'string', description: 'Documentation directory' }, port: { type: 'number', default: 3000, description: 'Port to listen on' }, host: { type: 'string', description: 'Address to bind' }, }, allowUnknownOptions: true, }, async ({ options }) => { if (options.dir === '') { throw new Error('The --dir option must be a directory path.'); } const docsDir = plugins.path.resolve(paths.cwd, options.dir ?? '.'); const port = options.port; if (!Number.isInteger(port) || port < 1 || port > 65535) { throw new Error(`Invalid --port value: ${String(port)}. Expected an integer from 1 to 65535.`); } const { DocsWebsiteServer } = await import('./website/index.js'); const websiteServer = new DocsWebsiteServer({ docsDir, port, listenHostname: options.host }); const buildResult = await websiteServer.start(); const displayHost = websiteServer.listenHostname.includes(':') ? `[${websiteServer.listenHostname}]` : websiteServer.listenHostname; logger.log( 'ok', `Docs website ready at http://${displayHost}:${port} (${buildResult.manifest.pages.length} pages)`, ); let stopping = false; const stop = async (): Promise => { if (stopping) return; stopping = true; process.removeListener('SIGINT', stop); process.removeListener('SIGTERM', stop); try { await websiteServer.stop(); process.exitCode = 0; } catch (error) { stopping = false; process.exitCode = 1; logger.log('error', `Could not stop docs website: ${(error as Error).message}`); process.once('SIGINT', stop); process.once('SIGTERM', stop); } }; process.once('SIGINT', stop); process.once('SIGTERM', stop); }); out.cli.command({ name: 'aidoc', aliases: ['aidocs'], description: 'Generate the readme, description and keywords', ...aidocCommand }, async ({ argv }) => { const aidocInstance = await createAiDoc(argv); logger.log('info', `Generating new readme...`); logger.log('info', `This may take some time...`); await aidocInstance.buildReadme(paths.cwd); logger.log('info', `Generating new keywords...`); logger.log('info', `This may take some time...`); await aidocInstance.buildDescription(paths.cwd); }); out.cli.command({ name: 'readme', description: 'Generate the readme', ...aidocCommand }, async ({ argv }) => { const aidocInstance = await createAiDoc(argv); logger.log('info', `Generating new readme...`); logger.log('info', `This may take some time...`); await aidocInstance.buildReadme(paths.cwd); }); out.cli.command({ name: 'description', description: 'Generate the description and keywords', ...aidocCommand }, async ({ argv }) => { const aidocInstance = await createAiDoc(argv); logger.log('info', `Generating new description and keywords...`); logger.log('info', `This may take some time...`); await aidocInstance.buildDescription(paths.cwd); }); out.cli.command({ name: 'commit', description: 'Generate a commit message for the uncommitted changes', ...aidocCommand }, async ({ argv }) => { const aidocInstance = await createAiDoc(argv); logger.log('info', `Generating commit message...`); logger.log('info', `This may take some time...`); let commitObject: Awaited>; try { commitObject = await aidocInstance.buildNextCommitObject(paths.cwd); } catch (error) { if (error instanceof NoChangesError || (error as Error).name === 'NoChangesError') { logger.log('info', 'No uncommitted changes found.'); console.log(JSON.stringify({ ok: true, noChanges: true }, null, 2)); return; } throw error; } logger.log('ok', `Commit message generated:`); console.log(JSON.stringify(commitObject, null, 2)); }); out.cli.command({ name: 'auth', description: 'Show the OpenAI ChatGPT auth sources (status) or log in (login)', allowUnknownOptions: true }, async ({ args }) => { await handleAuthCommand(args[0]); }); out.cli.command({ name: 'test', description: 'Build the TypeDoc documentation and remove it when the process exits', options: { publicSubdir: { type: 'string', description: 'Subdirectory of public/ for the TypeDoc output' } }, allowUnknownOptions: true, }, async ({ argv }) => { process.on('exit', async () => { await plugins.fsInstance.directory(paths.publicDir).recursive().delete(); }); await out.cli.dispatch('typedoc', argv); }); try { await out.cli.run(plugins.smartconsole.runtimeArguments()); } catch (error) { if (!(error instanceof plugins.smartconsole.CliUsageError)) { throw error; } logger.log('error', error.message); process.exitCode = 1; } finally { await out.dispose(); } };