From 485d1e327bf725b49d61556d46f06ebd6d62f55f Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Sat, 31 Jan 2026 12:25:56 +0000 Subject: [PATCH] Initial upload with CI/CD workflow --- src/cli.ts | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 src/cli.ts diff --git a/src/cli.ts b/src/cli.ts new file mode 100644 index 0000000..7654d83 --- /dev/null +++ b/src/cli.ts @@ -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 { + 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 { + 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 { + 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('', 'File pattern (e.g., "*.json", "src/**/*.json")') + .option('-o, --output ', 'Output directory for .d.ts files') + .option('-n, --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('', 'File pattern to watch') + .option('-o, --output ', 'Output directory for .d.ts files') + .option('-n, --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 ', 'Input file pattern') + .option('-o, --output ', 'Output directory') + .option('-n, --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 { + const program = createCli(); + program.parse(); +}