#!/usr/bin/env node import fs from 'fs-extra'; import mustache from 'mustache'; import path from 'path'; import { program } from 'commander'; import { log, logSuccess, logInfo, logWarning, logError, colors } from './src/utils/logger'; import { checkOpenApiGenerator, installOpenApiGenerator } from './src/utils/openapi-generator'; import { createDirectoryStructure, cleanup } from './src/utils/filesystem'; import { analyzeSwagger } from './src/swagger/analyzer'; import { generateCode, organizeFiles, addDtoImports } from './src/generators/dto.generator'; import { generateCleanArchitecture, extractTagsWithOperations } from './src/generators/clean-arch.generator'; import { generateReport } from './src/generators/report.generator'; import { findEnvironmentFile, parseApiKeys } from './src/utils/environment-finder'; import { askApiKeysForTags, askSelectionFilter } from './src/utils/prompt'; import type { SelectionFilter } from './src/types'; import type { CliOptions } from './src/types'; import packageJson from './package.json'; // Disable HTML escaping so that < and > produce valid TypeScript generic types. (mustache as { escape: (text: string) => string }).escape = function (text: string): string { return text; }; // ── CLI CONFIGURATION ──────────────────────────────────────────────────────── program .name('generate-clean-arch') .description('Generador de código Angular con Clean Architecture desde OpenAPI/Swagger') .version(packageJson.version) .option('-i, --input ', 'Archivo OpenAPI/Swagger (yaml o json)', 'swagger.yaml') .option('-o, --output ', 'Directorio de salida', './src/app') .option( '-t, --templates ', 'Directorio de templates personalizados', path.join(__dirname, 'templates') ) .option('--skip-install', 'No instalar dependencias') .option('--dry-run', 'Simular sin generar archivos') .option('-s, --select-endpoints', 'Seleccionar interactivamente qué tags y endpoints generar') .parse(process.argv); const options = program.opts(); // ── MAIN ORCHESTRATOR ──────────────────────────────────────────────────────── async function main(): Promise { console.log('\n' + '='.repeat(60)); log(' OpenAPI Clean Architecture Generator', 'bright'); log(' Angular + Clean Architecture Code Generator', 'cyan'); console.log('='.repeat(60) + '\n'); if (!fs.existsSync(options.input)) { logError(`Archivo no encontrado: ${options.input}`); process.exit(1); } logInfo(`Archivo de entrada: ${options.input}`); logInfo(`Directorio de salida: ${options.output}`); logInfo(`Templates: ${options.templates}`); if (options.dryRun) { logWarning('Modo DRY RUN - No se generarán archivos'); } if (!checkOpenApiGenerator()) { logWarning('OpenAPI Generator CLI no encontrado'); if (!options.skipInstall) { installOpenApiGenerator(); } else { logError( 'Instala openapi-generator-cli con: npm install -g @openapitools/openapi-generator-cli' ); process.exit(1); } } else { logSuccess('OpenAPI Generator CLI encontrado'); } const analysis = analyzeSwagger(options.input); if (options.dryRun) { logInfo('Finalizando en modo DRY RUN'); return; } createDirectoryStructure(options.output); // ── SELECTION: tags and endpoints ───────────────────────────────────────── const tagSummaries = extractTagsWithOperations(analysis); let selectionFilter: SelectionFilter = {}; if (options.selectEndpoints) { selectionFilter = await askSelectionFilter(tagSummaries); } const selectedTags = options.selectEndpoints ? Object.keys(selectionFilter) : tagSummaries.map((t) => t.tag); // ── ENVIRONMENT API KEY SELECTION ────────────────────────────────────────── const envFile = findEnvironmentFile(process.cwd()); let apiKeys: ReturnType = []; if (envFile) { const envContent = fs.readFileSync(envFile, 'utf8'); apiKeys = parseApiKeys(envContent); logSuccess( `environment.ts encontrado: ${colors.cyan}${path.relative(process.cwd(), envFile)}${colors.reset}` ); if (apiKeys.length > 0) { logInfo(`Claves de API detectadas: ${apiKeys.map((k) => k.key).join(', ')}`); } else { logWarning( 'No se encontraron claves con "api" en environment.ts. Se solicitará manualmente.' ); } } else { logWarning( 'No se encontró environment.ts. Se solicitará la clave manualmente por repositorio.' ); } const tagApiKeyMap = await askApiKeysForTags(selectedTags, apiKeys); // ────────────────────────────────────────────────────────────────────────── const tempDir = generateCode(options.input, options.templates); organizeFiles(tempDir, options.output); addDtoImports(options.output); generateCleanArchitecture( analysis, options.output, options.templates, tagApiKeyMap, selectionFilter ); cleanup(tempDir); const report = generateReport(options.output, analysis); console.log('\n' + '='.repeat(60)); log(' ✨ Generación completada con éxito', 'green'); console.log('='.repeat(60)); console.log(`\n📊 Resumen:`); console.log(` - DTOs generados: ${report.structure.dtos}`); console.log(` - Repositories: ${report.structure.repositories}`); console.log(` - Mappers: ${report.structure.mappers}`); console.log(` - Use Cases: ${report.structure.useCases}`); console.log(` - Providers: ${report.structure.providers}`); console.log(`\n📁 Archivos generados en: ${colors.cyan}${options.output}${colors.reset}\n`); } main().catch((error: unknown) => { const err = error as Error; logError(`Error fatal: ${err.message}`); console.error(error); process.exit(1); });