Add source files: main entry, types, and configuration
This commit is contained in:
245
src/index.ts
Normal file
245
src/index.ts
Normal 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();
|
||||
Reference in New Issue
Block a user