From 399f610cfda92228e787552e9d9c8babf8f9b879 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Fri, 30 Jan 2026 07:09:15 +0000 Subject: [PATCH] Add utility files: spec-parser, file-writer, helpers, wizard --- src/utils/wizard.ts | 401 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 401 insertions(+) create mode 100644 src/utils/wizard.ts diff --git a/src/utils/wizard.ts b/src/utils/wizard.ts new file mode 100644 index 0000000..35596e2 --- /dev/null +++ b/src/utils/wizard.ts @@ -0,0 +1,401 @@ +import inquirer from 'inquirer'; +import * as fs from 'fs'; +import type { CLISpec, Option, Command, Argument, Example } from '../types/spec.js'; +import { validateSpec } from '../validators/schema.js'; + +async function promptBasicInfo(): Promise> { + const answers = await inquirer.prompt([ + { + type: 'input', + name: 'name', + message: 'CLI name (kebab-case):', + validate: (input: string) => input.length > 0 || 'Name is required', + }, + { + type: 'input', + name: 'version', + message: 'Version (semantic, e.g., 1.0.0):', + default: '1.0.0', + validate: (input: string) => /^\d+\.\d+\.\d+/.test(input) || 'Version must be semantic (e.g., 1.0.0)', + }, + { + type: 'input', + name: 'description', + message: 'Description:', + validate: (input: string) => input.length > 0 || 'Description is required', + }, + { + type: 'input', + name: 'author', + message: 'Author (optional):', + }, + { + type: 'input', + name: 'license', + message: 'License (optional):', + }, + { + type: 'input', + name: 'bin', + message: 'Binary name (optional, defaults to CLI name):', + }, + ]); + return answers; +} + +async function promptGlobalOptions(): Promise { + const options: Option[] = []; + let adding = true; + + while (adding) { + const { shouldAdd } = await inquirer.prompt([ + { + type: 'confirm', + name: 'shouldAdd', + message: 'Add a global option?', + default: false, + }, + ]); + + if (!shouldAdd) break; + + const optionAnswers = await inquirer.prompt([ + { + type: 'input', + name: 'name', + message: 'Option name (--name):', + validate: (input: string) => input.length > 0 || 'Option name is required', + }, + { + type: 'input', + name: 'short', + message: 'Short flag (-s, optional):', + }, + { + type: 'input', + name: 'description', + message: 'Description:', + validate: (input: string) => input.length > 0 || 'Description is required', + }, + { + type: 'list', + name: 'type', + message: 'Type:', + choices: ['string', 'number', 'boolean', 'array'], + }, + { + type: 'confirm', + name: 'required', + message: 'Required?', + default: false, + }, + ]); + + options.push({ + name: optionAnswers.name, + short: optionAnswers.short || undefined, + description: optionAnswers.description, + type: optionAnswers.type, + required: optionAnswers.required, + }); + } + + return options; +} + +async function promptArguments(): Promise { + const args: Argument[] = []; + let adding = true; + + while (adding) { + const { shouldAdd } = await inquirer.prompt([ + { + type: 'confirm', + name: 'shouldAdd', + message: 'Add an argument?', + default: false, + }, + ]); + + if (!shouldAdd) break; + + const argAnswers = await inquirer.prompt([ + { + type: 'input', + name: 'name', + message: 'Argument name:', + validate: (input: string) => input.length > 0 || 'Argument name is required', + }, + { + type: 'input', + name: 'description', + message: 'Description:', + validate: (input: string) => input.length > 0 || 'Description is required', + }, + { + type: 'confirm', + name: 'required', + message: 'Required?', + default: true, + }, + { + type: 'confirm', + name: 'variadic', + message: 'Variadic (multiple values)?', + default: false, + }, + ]); + + args.push({ + name: argAnswers.name, + description: argAnswers.description, + required: argAnswers.required, + variadic: argAnswers.variadic, + }); + } + + return args; +} + +async function promptOptions(): Promise { + const options: Option[] = []; + let adding = true; + + while (adding) { + const { shouldAdd } = await inquirer.prompt([ + { + type: 'confirm', + name: 'shouldAdd', + message: 'Add an option to this command?', + default: false, + }, + ]); + + if (!shouldAdd) break; + + const optionAnswers = await inquirer.prompt([ + { + type: 'input', + name: 'name', + message: 'Option name (--name):', + validate: (input: string) => input.length > 0 || 'Option name is required', + }, + { + type: 'input', + name: 'short', + message: 'Short flag (-s, optional):', + }, + { + type: 'input', + name: 'description', + message: 'Description:', + validate: (input: string) => input.length > 0 || 'Description is required', + }, + { + type: 'list', + name: 'type', + message: 'Type:', + choices: ['string', 'number', 'boolean', 'array'], + }, + ]); + + options.push({ + name: optionAnswers.name, + short: optionAnswers.short || undefined, + description: optionAnswers.description, + type: optionAnswers.type, + }); + } + + return options; +} + +async function promptSubcommands(): Promise { + const subcommands: Command[] = []; + let adding = true; + + while (adding) { + const { shouldAdd } = await inquirer.prompt([ + { + type: 'confirm', + name: 'shouldAdd', + message: 'Add a subcommand?', + default: false, + }, + ]); + + if (!shouldAdd) break; + + const subcmdAnswers = await inquirer.prompt([ + { + type: 'input', + name: 'name', + message: 'Subcommand name:', + validate: (input: string) => input.length > 0 || 'Subcommand name is required', + }, + { + type: 'input', + name: 'description', + message: 'Description:', + validate: (input: string) => input.length > 0 || 'Description is required', + }, + ]); + + const args = await promptArguments(); + const opts = await promptOptions(); + + subcommands.push({ + name: subcmdAnswers.name, + description: subcmdAnswers.description, + arguments: args, + options: opts, + }); + } + + return subcommands; +} + +async function promptCommands(): Promise { + const commands: Command[] = []; + let adding = true; + + while (adding) { + const { shouldAdd } = await inquirer.prompt([ + { + type: 'confirm', + name: 'shouldAdd', + message: 'Add a command?', + default: true, + }, + ]); + + if (!shouldAdd) break; + + const cmdAnswers = await inquirer.prompt([ + { + type: 'input', + name: 'name', + message: 'Command name:', + validate: (input: string) => input.length > 0 || 'Command name is required', + }, + { + type: 'input', + name: 'description', + message: 'Description:', + validate: (input: string) => input.length > 0 || 'Description is required', + }, + { + type: 'input', + name: 'aliases', + message: 'Aliases (comma-separated, optional):', + }, + ]); + + const args = await promptArguments(); + const opts = await promptOptions(); + const subcommands = await promptSubcommands(); + + commands.push({ + name: cmdAnswers.name, + description: cmdAnswers.description, + aliases: cmdAnswers.aliases ? cmdAnswers.aliases.split(',').map((a: string) => a.trim()) : undefined, + arguments: args, + options: opts, + subcommands: subcommands.length > 0 ? subcommands : undefined, + }); + } + + return commands; +} + +async function promptExamples(): Promise { + const examples: Example[] = []; + let adding = true; + + while (adding) { + const { shouldAdd } = await inquirer.prompt([ + { + type: 'confirm', + name: 'shouldAdd', + message: 'Add an example?', + default: false, + }, + ]); + + if (!shouldAdd) break; + + const exampleAnswers = await inquirer.prompt([ + { + type: 'input', + name: 'description', + message: 'Example description:', + validate: (input: string) => input.length > 0 || 'Description is required', + }, + { + type: 'input', + name: 'command', + message: 'Command:', + validate: (input: string) => input.length > 0 || 'Command is required', + }, + { + type: 'input', + name: 'output', + message: 'Expected output (optional):', + }, + ]); + + examples.push({ + description: exampleAnswers.description, + command: exampleAnswers.command, + output: exampleAnswers.output || undefined, + }); + } + + return examples; +} + +export async function createSpecWizard(): Promise { + console.log('\n=== CLI Spec Generator Wizard ===\n'); + + const basicInfo = await promptBasicInfo(); + const globalOptions = await promptGlobalOptions(); + const commands = await promptCommands(); + const examples = await promptExamples(); + + const spec: CLISpec = { + name: basicInfo.name!, + version: basicInfo.version!, + description: basicInfo.description!, + author: basicInfo.author, + license: basicInfo.license, + bin: basicInfo.bin || undefined, + globalOptions: globalOptions.length > 0 ? globalOptions : undefined, + commands, + examples: examples.length > 0 ? examples : undefined, + }; + + const validation = validateSpec(spec); + if (!validation.success) { + console.warn('\n⚠️ Spec validation warnings:'); + validation.errors.forEach(err => console.warn(` - ${err}`)); + } + + console.log('\n✅ Spec created successfully!\n'); + + return spec; +} + +export async function saveSpec(spec: CLISpec, outputPath?: string): Promise { + const fileName = outputPath || `${spec.name}.yaml`; + const yaml = await import('js-yaml'); + const content = yaml.default.dump(spec, { indent: 2, lineWidth: -1 }); + + fs.writeFileSync(fileName, content, 'utf-8'); + return fileName; +} + +export async function editSpecWizard(existingSpec: CLISpec): Promise { + console.log('\n=== Spec Editor ===\n'); + console.log('Note: Full editing not yet implemented. Please manually edit the file.\n'); + return existingSpec; +}