This commit is contained in:
168
src/cli.ts
Normal file
168
src/cli.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import { Command } from 'commander';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import chalk from 'chalk';
|
||||
import { TypeInferrer } from './type-inferrer.js';
|
||||
import { DeclarationGenerator } from './declaration-generator.js';
|
||||
import { readJsonFile, writeDeclarationFile, getDeclarationPath, getInterfaceName, extractJsonSamplesFromFile } from './file-watcher.js';
|
||||
|
||||
function processFile(inputPath: string, outputPath?: string, rootName?: string, verbose: boolean = false): Promise<void> {
|
||||
return (async () => {
|
||||
const jsonPath = path.resolve(inputPath);
|
||||
const outputFilePath = outputPath || getDeclarationPath(jsonPath);
|
||||
const interfaceName = rootName || getInterfaceName(jsonPath);
|
||||
|
||||
if (!fs.existsSync(jsonPath)) {
|
||||
console.error(chalk.red(`Error: File not found: ${jsonPath}`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
console.log(chalk.gray(`Processing: ${jsonPath}`));
|
||||
}
|
||||
|
||||
try {
|
||||
await readJsonFile(jsonPath);
|
||||
const samples = extractJsonSamplesFromFile(jsonPath);
|
||||
|
||||
const inferrer = new TypeInferrer({ rootName: interfaceName });
|
||||
const result = inferrer.inferFromMultiple(samples);
|
||||
|
||||
const generator = new DeclarationGenerator();
|
||||
const declaration = generator.generate(result.types);
|
||||
|
||||
await writeDeclarationFile(outputFilePath, declaration);
|
||||
|
||||
if (verbose) {
|
||||
console.log(chalk.green(`Generated: ${outputFilePath}`));
|
||||
console.log(chalk.gray('---'));
|
||||
console.log(declaration);
|
||||
} else {
|
||||
console.log(chalk.green(`Generated: ${outputFilePath}`));
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof SyntaxError) {
|
||||
console.error(chalk.red(`Error: Invalid JSON in ${jsonPath}`));
|
||||
} else {
|
||||
console.error(chalk.red(`Error processing ${jsonPath}: ${error}`));
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
async function processGlobPattern(pattern: string, outputDir?: string, rootName?: string, verbose: boolean = false): Promise<void> {
|
||||
const { glob } = await import('glob');
|
||||
const files = await glob(pattern);
|
||||
|
||||
if (files.length === 0) {
|
||||
console.error(chalk.red(`No files found matching: ${pattern}`));
|
||||
return;
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
await processFile(file, outputDir, rootName, verbose);
|
||||
}
|
||||
}
|
||||
|
||||
async function watchFiles(patterns: string[], outputDir?: string, rootName?: string, verbose: boolean = false): Promise<void> {
|
||||
console.log(chalk.blue('Starting watch mode...'));
|
||||
console.log(chalk.gray(`Patterns: ${patterns.join(', ')}`));
|
||||
console.log(chalk.gray('Press Ctrl+C to stop\n'));
|
||||
|
||||
const { FileWatcher } = await import('./file-watcher.js');
|
||||
const watcher = new FileWatcher({ patterns });
|
||||
|
||||
await watcher.initialize();
|
||||
|
||||
let isProcessing = false;
|
||||
const pendingFiles: string[] = [];
|
||||
|
||||
watcher.start(async (event, filePath) => {
|
||||
if (event === 'unlink') {
|
||||
const dtsPath = getDeclarationPath(filePath, outputDir);
|
||||
if (fs.existsSync(dtsPath)) {
|
||||
fs.unlinkSync(dtsPath);
|
||||
console.log(chalk.yellow(`Removed: ${dtsPath}`));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isProcessing) {
|
||||
if (!pendingFiles.includes(filePath)) {
|
||||
pendingFiles.push(filePath);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
isProcessing = true;
|
||||
|
||||
try {
|
||||
await processFile(filePath, outputDir, rootName, verbose);
|
||||
|
||||
while (pendingFiles.length > 0) {
|
||||
const nextFile = pendingFiles.shift()!;
|
||||
await processFile(nextFile, outputDir, rootName, verbose);
|
||||
}
|
||||
} finally {
|
||||
isProcessing = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function createCli(): Command {
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name('type-from-json')
|
||||
.description('Generate TypeScript type definitions from JSON files')
|
||||
.version('1.0.0');
|
||||
|
||||
program
|
||||
.command('infer')
|
||||
.description('Generate type definitions from JSON files')
|
||||
.argument('<pattern>', 'File pattern (e.g., "*.json", "src/**/*.json")')
|
||||
.option('-o, --output <directory>', 'Output directory for .d.ts files')
|
||||
.option('-n, --name <name>', 'Root interface/type name')
|
||||
.option('-v, --verbose', 'Show generated type definitions')
|
||||
.action(async (pattern, options) => {
|
||||
await processGlobPattern(pattern, options.output, options.name, options.verbose);
|
||||
});
|
||||
|
||||
program
|
||||
.command('watch')
|
||||
.description('Watch files and generate types on changes')
|
||||
.argument('<pattern>', 'File pattern to watch')
|
||||
.option('-o, --output <directory>', 'Output directory for .d.ts files')
|
||||
.option('-n, --name <name>', 'Root interface/type name')
|
||||
.option('-v, --verbose', 'Show generated type definitions')
|
||||
.action(async (pattern, options) => {
|
||||
await watchFiles([pattern], options.output, options.name, options.verbose);
|
||||
});
|
||||
|
||||
program
|
||||
.option('-i, --input <pattern>', 'Input file pattern')
|
||||
.option('-o, --output <directory>', 'Output directory')
|
||||
.option('-n, --name <name>', 'Root interface/type name')
|
||||
.option('-w, --watch', 'Watch for changes')
|
||||
.option('-v, --verbose', 'Show generated type definitions')
|
||||
.action(async (options) => {
|
||||
if (!options.input) {
|
||||
program.help();
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.watch) {
|
||||
await watchFiles([options.input], options.output, options.name, options.verbose);
|
||||
} else {
|
||||
await processGlobPattern(options.input, options.output, options.name, options.verbose);
|
||||
}
|
||||
});
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
export async function run(): Promise<void> {
|
||||
const program = createCli();
|
||||
program.parse();
|
||||
}
|
||||
Reference in New Issue
Block a user