first commit

This commit is contained in:
Blas Santome Ocampo
2026-03-23 09:35:15 +01:00
commit 5ff88d8cf6
17 changed files with 1292 additions and 0 deletions

311
generate.js Executable file
View File

@@ -0,0 +1,311 @@
#!/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);
});