Add source files: main entry, types, and configuration

This commit is contained in:
2026-01-30 07:07:58 +00:00
parent e9fef1b009
commit 81a4905d85

245
src/index.ts Normal file
View File

@@ -0,0 +1,245 @@
#!/usr/bin/env node
import { Command } from 'commander';
import * as fs from 'fs';
import * as path from 'path';
import figlet from 'figlet';
import { parseSpec } from './utils/spec-parser.js';
import { writeFile, ensureDirectory } from './utils/file-writer.js';
import {
generateCode,
generateCompletion,
generateManPage,
generateReadmeSection,
getOutputFileName,
getCompletionFileName,
getManPageFileName,
getReadmeFileName,
} from './generators/index.js';
import { createSpecWizard, saveSpec } from './utils/wizard.js';
import { LanguageTarget, ShellType } from './types/spec.js';
const program = new Command();
program
.name('cli-spec-gen')
.description('Generate CLI parsers, completions, and docs from YAML/JSON specs')
.version('1.0.0');
program
.command('generate <spec-file>')
.alias('gen')
.description('Generate code from a spec file')
.option('-l, --language <python|go|rust|node-commander|node-yargs>', 'Target language', 'python')
.option('-o, --output <dir>', 'Output directory', '.')
.option('--all', 'Generate for all languages')
.action(async (specFile, options) => {
const result = parseSpec({ filePath: specFile });
if (!result.success) {
console.error('❌ Error parsing spec:');
result.errors?.forEach(err => console.error(` - ${err}`));
process.exit(1);
}
const spec = result.spec!;
const languages: LanguageTarget[] = options.all
? ['python', 'go', 'rust', 'node-commander', 'node-yargs']
: [options.language as LanguageTarget];
console.log(`\n📦 Generating code for ${spec.name} v${spec.version}\n`);
for (const lang of languages) {
const genResult = generateCode(spec, lang);
if (!genResult.success) {
console.error(`❌ Error generating ${lang}: ${genResult.error}`);
continue;
}
const fileName = getOutputFileName(spec, lang);
const writeResult = writeFile({
outputDir: options.output,
fileName,
content: genResult.code!,
overwrite: true,
});
if (writeResult.success) {
console.log(`✅ Generated ${fileName}`);
} else {
console.error(`❌ Failed to write ${fileName}: ${writeResult.error}`);
}
}
});
program
.command('completion <spec-file>')
.alias('comp')
.description('Generate shell completion scripts')
.option('-s, --shell <bash|zsh|fish>', 'Target shell', 'bash')
.option('-o, --output <dir>', 'Output directory', '.')
.option('--all', 'Generate for all shells')
.action(async (specFile, options) => {
const result = parseSpec({ filePath: specFile });
if (!result.success) {
console.error('❌ Error parsing spec:');
result.errors?.forEach(err => console.error(` - ${err}`));
process.exit(1);
}
const spec = result.spec!;
const shells: ShellType[] = options.all
? ['bash', 'zsh', 'fish']
: [options.shell as ShellType];
console.log(`\n🐚 Generating completions for ${spec.name}\n`);
for (const shell of shells) {
const genResult = generateCompletion(spec, shell);
if (!genResult.success) {
console.error(`❌ Error generating ${shell} completion: ${genResult.error}`);
continue;
}
const fileName = getCompletionFileName(spec, shell);
const writeResult = writeFile({
outputDir: options.output,
fileName,
content: genResult.script!,
overwrite: true,
});
if (writeResult.success) {
console.log(`✅ Generated ${fileName}`);
} else {
console.error(`❌ Failed to write ${fileName}: ${writeResult.error}`);
}
}
});
program
.command('docs <spec-file>')
.description('Generate documentation (man page, README)')
.option('-o, --output <dir>', 'Output directory', '.')
.action(async (specFile, options) => {
const result = parseSpec({ filePath: specFile });
if (!result.success) {
console.error('❌ Error parsing spec:');
result.errors?.forEach(err => console.error(` - ${err}`));
process.exit(1);
}
const spec = result.spec!;
ensureDirectory(options.output);
console.log(`\n📚 Generating docs for ${spec.name}\n`);
const manResult = generateManPage(spec);
if (manResult.success) {
const manFileName = getManPageFileName(spec);
const manPath = path.join(options.output, manFileName);
fs.writeFileSync(manPath, manResult.content!, 'utf-8');
console.log(`✅ Generated ${manFileName}`);
}
const readmeResult = generateReadmeSection(spec);
if (readmeResult.success) {
const readmeFileName = getReadmeFileName();
const readmePath = path.join(options.output, readmeFileName);
fs.writeFileSync(readmePath, readmeResult.content!, 'utf-8');
console.log(`✅ Generated ${readmeFileName}`);
}
});
program
.command('init')
.alias('new')
.description('Interactive wizard to create a new spec')
.option('-o, --output <file>', 'Output file', 'cli-spec.yaml')
.action(async (options) => {
const spec = await createSpecWizard();
const fileName = await saveSpec(spec, options.output);
console.log(`\n📄 Spec saved to: ${fileName}`);
});
program
.command('validate <spec-file>')
.alias('check')
.description('Validate a spec file')
.action((specFile) => {
const result = parseSpec({ filePath: specFile });
if (result.success) {
console.log(`✅ Valid spec: ${result.spec!.name} v${result.spec!.version}`);
console.log(` Commands: ${result.spec!.commands.length}`);
console.log(` Global options: ${result.spec!.globalOptions?.length || 0}`);
} else {
console.error('❌ Invalid spec:');
result.errors?.forEach(err => console.error(` - ${err}`));
process.exit(1);
}
});
program
.command('all <spec-file>')
.description('Generate everything (code, completions, docs)')
.option('-o, --output <dir>', 'Output directory', 'generated')
.action(async (specFile, options) => {
const result = parseSpec({ filePath: specFile });
if (!result.success) {
console.error('❌ Error parsing spec:');
result.errors?.forEach(err => console.error(` - ${err}`));
process.exit(1);
}
const spec = result.spec!;
ensureDirectory(options.output);
console.log(`\n🚀 Generating everything for ${spec.name}\n`);
const languages: LanguageTarget[] = ['python', 'go', 'rust', 'node-commander', 'node-yargs'];
for (const lang of languages) {
const genResult = generateCode(spec, lang);
if (genResult.success) {
const fileName = getOutputFileName(spec, lang);
const filePath = path.join(options.output, fileName);
fs.writeFileSync(filePath, genResult.code!, 'utf-8');
console.log(`✅ Generated ${fileName}`);
}
}
const shells: ShellType[] = ['bash', 'zsh', 'fish'];
for (const shell of shells) {
const compResult = generateCompletion(spec, shell);
if (compResult.success) {
const fileName = getCompletionFileName(spec, shell);
const filePath = path.join(options.output, fileName);
fs.writeFileSync(filePath, compResult.script!, 'utf-8');
console.log(`✅ Generated ${fileName}`);
}
}
const manResult = generateManPage(spec);
if (manResult.success) {
const manFileName = getManPageFileName(spec);
const manPath = path.join(options.output, manFileName);
fs.writeFileSync(manPath, manResult.content!, 'utf-8');
console.log(`✅ Generated ${manFileName}`);
}
const readmeResult = generateReadmeSection(spec);
if (readmeResult.success) {
const readmeFileName = getReadmeFileName();
const readmePath = path.join(options.output, readmeFileName);
fs.writeFileSync(readmePath, readmeResult.content!, 'utf-8');
console.log(`✅ Generated ${readmeFileName}`);
}
console.log('\n✨ All files generated successfully!\n');
});
function main() {
console.log(figlet.textSync('CLI Spec', { font: 'Standard' }));
console.log('Generator v1.0.0\n');
program.parse(process.argv);
}
main();