Initial upload with CI/CD workflow
Some checks failed
CI / test (push) Has been cancelled

This commit is contained in:
2026-01-31 12:25:56 +00:00
parent b23be2ca46
commit 485d1e327b

168
src/cli.ts Normal file
View 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();
}