Files
openapi-clean-arch-gen/generate.js
Blas Santome Ocampo 5ff88d8cf6 first commit
2026-03-23 09:35:15 +01:00

312 lines
9.3 KiB
JavaScript
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 <file>', 'Archivo OpenAPI/Swagger (yaml o json)', 'swagger.yaml')
.option('-o, --output <dir>', 'Directorio de salida', './src/app')
.option('-t, --templates <dir>', '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);
});