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