Add utility files: spec-parser, file-writer, helpers, wizard

This commit is contained in:
2026-01-30 07:09:13 +00:00
parent 8813834c40
commit de47cc44ae

122
src/utils/spec-parser.ts Normal file
View File

@@ -0,0 +1,122 @@
import * as fs from 'fs';
import * as yaml from 'js-yaml';
import { CLISpec } from '../types/spec.js';
import { validateSpec } from '../validators/schema.js';
export interface ParseOptions {
filePath?: string;
content?: string;
format?: 'auto' | 'json' | 'yaml';
}
export interface ParseResult {
success: boolean;
spec?: CLISpec;
errors?: string[];
source?: string;
}
function detectFormat(content: string): 'json' | 'yaml' {
const trimmed = content.trim();
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
return 'json';
}
return 'yaml';
}
function parseJSON(content: string): unknown {
return JSON.parse(content);
}
function parseYAML(content: string): unknown {
return yaml.load(content);
}
export function parseSpec(options: ParseOptions): ParseResult {
let content: string;
let source: string;
if (options.content) {
content = options.content;
source = 'input';
} else if (options.filePath) {
if (!fs.existsSync(options.filePath)) {
return {
success: false,
errors: [`File not found: ${options.filePath}`],
source: options.filePath,
};
}
content = fs.readFileSync(options.filePath, 'utf-8');
source = options.filePath;
} else {
return {
success: false,
errors: ['Either filePath or content must be provided'],
source: undefined,
};
}
let parsed: unknown;
try {
const format = options.format === 'auto' || !options.format ? detectFormat(content) : options.format;
if (format === 'json') {
parsed = parseJSON(content);
} else {
parsed = parseYAML(content);
}
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown parsing error';
return {
success: false,
errors: [`Failed to parse ${source}: ${message}`],
source,
};
}
if (!parsed || typeof parsed !== 'object') {
return {
success: false,
errors: [`Invalid spec format in ${source}: expected object`],
source,
};
}
const validation = validateSpec(parsed);
if (!validation.success) {
return {
success: false,
errors: validation.errors,
source,
};
}
return {
success: true,
spec: validation.data,
source,
};
}
export function loadSpec(filePath: string): ParseResult {
return parseSpec({ filePath });
}
export function loadSpecFromStdin(): Promise<ParseResult> {
return new Promise((resolve) => {
let content = '';
process.stdin.setEncoding('utf-8');
process.stdin.on('readable', () => {
let chunk;
while ((chunk = process.stdin.read()) !== null) {
content += chunk;
}
});
process.stdin.on('end', () => {
resolve(parseSpec({ content }));
});
});
}