#!/usr/bin/env node const { execSync } = require('child_process'); const fs = require('fs-extra'); const path = require('path'); const yaml = require('js-yaml'); const { program } = require('commander'); // Colores para console (sin dependencias externas) const colors = { reset: '\x1b[0m', bright: '\x1b[1m', green: '\x1b[32m', blue: '\x1b[34m', yellow: '\x1b[33m', red: '\x1b[31m', cyan: '\x1b[36m' }; function log(message, color = 'reset') { console.log(`${colors[color]}${message}${colors.reset}`); } function logSuccess(message) { log(`✅ ${message}`, 'green'); } function logInfo(message) { log(`ℹ️ ${message}`, 'blue'); } function logWarning(message) { log(`⚠️ ${message}`, 'yellow'); } function logError(message) { log(`❌ ${message}`, 'red'); } function logStep(message) { log(`\n🚀 ${message}`, 'cyan'); } // Configuración del CLI program .name('generate-clean-arch') .description('Generador de código Angular con Clean Architecture desde OpenAPI/Swagger') .version('1.0.0') .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', './templates') .option('--skip-install', 'No instalar dependencias') .option('--dry-run', 'Simular sin generar archivos') .parse(process.argv); const options = program.opts(); // Validar que existe openapi-generator-cli function checkOpenApiGenerator() { try { execSync('openapi-generator-cli version', { stdio: 'ignore' }); return true; } catch (error) { return false; } } // Instalar openapi-generator-cli function installOpenApiGenerator() { logStep('Instalando @openapitools/openapi-generator-cli...'); try { execSync('npm install -g @openapitools/openapi-generator-cli', { stdio: 'inherit' }); logSuccess('OpenAPI Generator CLI instalado correctamente'); } catch (error) { logError('Error al instalar OpenAPI Generator CLI'); process.exit(1); } } // Crear estructura de directorios function createDirectoryStructure(baseDir) { const dirs = [ path.join(baseDir, 'data/dtos'), path.join(baseDir, 'data/repositories'), path.join(baseDir, 'data/mappers'), path.join(baseDir, 'domain/repositories'), path.join(baseDir, 'domain/use-cases'), path.join(baseDir, 'di/repositories'), path.join(baseDir, 'di/use-cases'), path.join(baseDir, 'entities/models') ]; dirs.forEach(dir => { fs.ensureDirSync(dir); }); logSuccess('Estructura de directorios creada'); } // Analizar el swagger para extraer tags y dominios function analyzeSwagger(swaggerFile) { logStep('Analizando archivo OpenAPI...'); try { const fileContent = fs.readFileSync(swaggerFile, 'utf8'); const swagger = yaml.load(fileContent); const tags = swagger.tags || []; const paths = swagger.paths || {}; logInfo(`Encontrados ${tags.length} tags en el API`); logInfo(`Encontrados ${Object.keys(paths).length} endpoints`); tags.forEach(tag => { logInfo(` - ${tag.name}: ${tag.description || 'Sin descripción'}`); }); return { tags, paths, swagger }; } catch (error) { logError(`Error al leer el archivo Swagger: ${error.message}`); process.exit(1); } } // Generar código con OpenAPI Generator function generateCode(swaggerFile, templatesDir) { logStep('Generando código desde OpenAPI...'); const tempDir = path.join(process.cwd(), '.temp-generated'); // Limpiar directorio temporal if (fs.existsSync(tempDir)) { fs.removeSync(tempDir); } try { const command = `openapi-generator-cli generate \ -i "${swaggerFile}" \ -g typescript-angular \ -t "${templatesDir}" \ -o "${tempDir}" \ --additional-properties=ngVersion=17.0.0,withInterfaces=true,providedInRoot=false,supportsES6=true,modelPropertyNaming=camelCase`; execSync(command, { stdio: 'inherit' }); logSuccess('Código generado correctamente'); return tempDir; } catch (error) { logError('Error al generar código'); if (fs.existsSync(tempDir)) { fs.removeSync(tempDir); } process.exit(1); } } // Organizar archivos según Clean Architecture function organizeFiles(tempDir, outputDir) { logStep('Organizando archivos en estructura Clean Architecture...'); const moves = [ { from: 'model', to: path.join(outputDir, 'data/dtos'), pattern: '*.dto.ts' }, { from: 'model', to: path.join(outputDir, 'entities/models'), pattern: '*.model.ts' }, { from: 'api', to: path.join(outputDir, 'domain/repositories'), pattern: '*.repository.contract.ts' }, { from: 'api', to: path.join(outputDir, 'data/repositories'), pattern: '*.repository.impl.ts' }, { from: 'api', to: path.join(outputDir, 'data/repositories'), pattern: '*.repository.mock.ts' }, { from: 'api', to: path.join(outputDir, 'data/mappers'), pattern: '*.mapper.ts' }, { from: 'api', to: path.join(outputDir, 'domain/use-cases'), pattern: '*.use-cases.contract.ts' }, { from: 'api', to: path.join(outputDir, 'domain/use-cases'), pattern: '*.use-cases.impl.ts' }, { from: 'providers', to: path.join(outputDir, 'di/repositories'), pattern: '*.repository.provider.ts' }, { from: 'providers', to: path.join(outputDir, 'di/use-cases'), pattern: '*.use-cases.provider.ts' } ]; let filesMoved = 0; moves.forEach(({ from, to, pattern }) => { const sourceDir = path.join(tempDir, from); if (fs.existsSync(sourceDir)) { fs.ensureDirSync(to); const files = fs.readdirSync(sourceDir).filter(file => { if (pattern.includes('*')) { const regex = new RegExp(pattern.replace('*', '.*')); return regex.test(file); } return file.endsWith(pattern); }); files.forEach(file => { const sourcePath = path.join(sourceDir, file); const destPath = path.join(to, file); fs.copySync(sourcePath, destPath); filesMoved++; logInfo(` ${file} → ${path.relative(process.cwd(), destPath)}`); }); } }); logSuccess(`${filesMoved} archivos organizados correctamente`); } // Limpiar directorio temporal function cleanup(tempDir) { if (fs.existsSync(tempDir)) { fs.removeSync(tempDir); logInfo('Archivos temporales eliminados'); } } // Generar reporte function generateReport(outputDir, analysis) { logStep('Generando reporte de generación...'); const report = { timestamp: new Date().toISOString(), tags: analysis.tags.length, endpoints: Object.keys(analysis.paths).length, outputDirectory: outputDir, structure: { dtos: fs.readdirSync(path.join(outputDir, 'data/dtos')).length, repositories: fs.readdirSync(path.join(outputDir, 'data/repositories')).length, mappers: fs.readdirSync(path.join(outputDir, 'data/mappers')).length, useCases: fs.readdirSync(path.join(outputDir, 'domain/use-cases')).length } }; const reportPath = path.join(process.cwd(), 'generation-report.json'); fs.writeJsonSync(reportPath, report, { spaces: 2 }); logSuccess(`Reporte guardado en: ${reportPath}`); return report; } // Función principal async function main() { console.log('\n' + '='.repeat(60)); log(' OpenAPI Clean Architecture Generator', 'bright'); log(' Angular + Clean Architecture Code Generator', 'cyan'); console.log('='.repeat(60) + '\n'); // Validar archivo de entrada 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'); } // Verificar/Instalar OpenAPI Generator 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'); } // Analizar Swagger const analysis = analyzeSwagger(options.input); if (options.dryRun) { logInfo('Finalizando en modo DRY RUN'); return; } // Crear estructura de directorios createDirectoryStructure(options.output); // Generar código const tempDir = generateCode(options.input, options.templates); // Organizar archivos organizeFiles(tempDir, options.output); // Limpiar cleanup(tempDir); // Generar reporte const report = generateReport(options.output, analysis); // Resumen final 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(`\n📁 Archivos generados en: ${colors.cyan}${options.output}${colors.reset}\n`); } // Ejecutar main().catch(error => { logError(`Error fatal: ${error.message}`); console.error(error); process.exit(1); });