312 lines
9.3 KiB
JavaScript
Executable File
312 lines
9.3 KiB
JavaScript
Executable File
#!/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);
|
||
});
|