UNPKG

weaver-frontend-cli

Version:

🕷️ Weaver CLI - Generador completo de arquitectura Clean Architecture con parser OpenAPI avanzado para entidades CRUD y flujos de negocio complejos

1,082 lines (1,081 loc) 70 kB
#!/usr/bin/env node "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.showMainMenu = showMainMenu; const inquirer_1 = __importDefault(require("inquirer")); const chalk_1 = __importDefault(require("chalk")); const correct_entity_flow_generator_1 = require("./generators/correct-entity-flow-generator"); const business_flow_generator_1 = require("./generators/business-flow-generator"); const redux_flow_generator_1 = require("./generators/redux-flow-generator"); const cleanup_generator_1 = require("./generators/cleanup-generator"); const swagger_parser_1 = require("./parsers/swagger-parser"); const swagger_redux_parser_1 = require("./parsers/swagger-redux-parser"); const project_validator_1 = require("./validators/project-validator"); const auth_manager_1 = require("./auth/auth-manager"); const directory_detector_1 = require("./utils/directory-detector"); const path = __importStar(require("path")); const fs = __importStar(require("fs-extra")); const menuChoices = [ { name: '🏗️ Crear flujo entity', value: 'create-entity-flow' }, { name: '💼 Crear flujo de negocio', value: 'create-business-flow' }, { name: '🔴 Crear flujo Redux', value: 'create-redux-flow' }, { name: '🧹 Limpiar/Eliminar código generado', value: 'cleanup' }, { name: '📊 Ver información de sesión', value: 'session-info' }, { name: '🚪 Cerrar sesión y salir', value: 'logout' }, { name: '🚪 Salir', value: 'exit' } ]; async function showMainMenu(isLocalMode = false) { console.log(chalk_1.default.blue.bold('\n🕷️ WEAVER CLI')); console.log(chalk_1.default.gray('Teje la estructura perfecta de tu código frontend\n')); const { action } = await inquirer_1.default.prompt([ { type: 'list', name: 'action', message: '¿Qué deseas generar?', choices: menuChoices, pageSize: 10 } ]); switch (action) { case 'create-entity-flow': await handleCreateEntityFlow(isLocalMode); break; case 'create-business-flow': await handleCreateBusinessFlow(isLocalMode); break; case 'create-redux-flow': await handleCreateReduxFlow(isLocalMode); break; case 'cleanup': await handleCleanup(isLocalMode); break; case 'session-info': await auth_manager_1.AuthManager.showSessionInfo(); await showMainMenu(isLocalMode); break; case 'logout': await auth_manager_1.AuthManager.logout(); console.log(chalk_1.default.green('\n👋 ¡Hasta luego!')); process.exit(0); break; case 'exit': console.log(chalk_1.default.green('\n👋 ¡Hasta luego!')); process.exit(0); break; default: console.log(chalk_1.default.red('Opción no válida')); await showMainMenu(isLocalMode); } } async function handleCreateEntityFlow(isLocalMode = false) { try { console.log(chalk_1.default.yellow('\n📋 Configurando flujo entity...')); // 🔍 DETECTAR DIRECTORIO ACTUAL Y APIs DISPONIBLES console.log(chalk_1.default.blue('🔍 Analizando estructura del directorio...')); const directoryInfo = await directory_detector_1.DirectoryDetector.detectCurrentApi(); if (directoryInfo.currentApiName) { console.log(chalk_1.default.green(`✅ API detectada en directorio actual: ${directoryInfo.currentApiName}`)); } else { console.log(chalk_1.default.yellow('⚠️ No se detectó estructura de API en el directorio actual')); } if (directoryInfo.possibleApiNames.length > 0) { console.log(chalk_1.default.gray(`📁 APIs disponibles: ${directoryInfo.possibleApiNames.join(', ')}`)); } // 1. Solicitar URL del OpenAPI/Swagger const { swaggerUrl } = await inquirer_1.default.prompt([ { type: 'input', name: 'swaggerUrl', message: 'URL del OpenAPI/Swagger JSON:', default: 'http://backend-platform-prod-env.eba-dddmvypu.us-east-1.elasticbeanstalk.com/openapi.json', validate: (input) => { if (!input.trim()) { return 'La URL del swagger es requerida'; } try { new URL(input.trim()); return true; } catch { return 'Por favor ingresa una URL válida'; } } } ]); // Cargar y analizar el swagger console.log(chalk_1.default.blue('\n🔍 Analizando OpenAPI...')); const swaggerAnalyzer = new swagger_parser_1.SwaggerAnalyzer(); try { await swaggerAnalyzer.loadFromUrl(swaggerUrl.trim()); } catch (error) { console.error(chalk_1.default.red('\n❌ Error cargando el swagger:'), error); return await handleCreateEntityFlow(isLocalMode); } const availableEntities = swaggerAnalyzer.getAvailableEntities(); if (availableEntities.length === 0) { console.log(chalk_1.default.yellow('\n⚠️ No se encontraron entidades en el swagger')); return await showMainMenu(isLocalMode); } console.log(chalk_1.default.green(`\n✅ Se encontraron ${availableEntities.length} entidades disponibles`)); // 🔗 CONFIGURAR NOMBRE DE LA API const detectedApiName = swaggerAnalyzer.getDetectedApiName(); const suggestedNames = swaggerAnalyzer.suggestApiNames(); let apiName; if (detectedApiName) { const { useDetectedApi } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'useDetectedApi', message: `¿Usar la API detectada "${detectedApiName}"?`, default: true } ]); if (useDetectedApi) { apiName = detectedApiName; } else { const { selectedApiName } = await inquirer_1.default.prompt([ { type: 'list', name: 'selectedApiName', message: 'Selecciona el nombre de la API:', choices: [ ...suggestedNames.map(name => ({ name, value: name })), { name: '📝 Ingresar nombre personalizado', value: 'custom' } ] } ]); if (selectedApiName === 'custom') { const { customApiName } = await inquirer_1.default.prompt([ { type: 'input', name: 'customApiName', message: 'Nombre de la API:', validate: (input) => { if (!input.trim()) { return 'El nombre de la API es requerido'; } if (!/^[a-z][a-z0-9-]*$/.test(input.trim())) { return 'El nombre debe empezar con minúscula y solo contener letras, números y guiones'; } return true; } } ]); apiName = customApiName.trim(); } else { apiName = selectedApiName; } } } else { const { selectedApiName } = await inquirer_1.default.prompt([ { type: 'list', name: 'selectedApiName', message: 'No se pudo detectar automáticamente. Selecciona el nombre de la API:', choices: [ ...suggestedNames.map(name => ({ name, value: name })), { name: '📝 Ingresar nombre personalizado', value: 'custom' } ] } ]); if (selectedApiName === 'custom') { const { customApiName } = await inquirer_1.default.prompt([ { type: 'input', name: 'customApiName', message: 'Nombre de la API:', validate: (input) => { if (!input.trim()) { return 'El nombre de la API es requerido'; } if (!/^[a-z][a-z0-9-]*$/.test(input.trim())) { return 'El nombre debe empezar con minúscula y solo contener letras, números y guiones'; } return true; } } ]); apiName = customApiName.trim(); } else { apiName = selectedApiName; } } console.log(chalk_1.default.blue(`🔗 API configurada: ${apiName}`)); // 📁 SELECCIONAR DIRECTORIO DE DESTINO (dónde crear físicamente) console.log(chalk_1.default.yellow('\n📁 Configurando directorio de destino...')); let targetBasePath; if (isLocalMode) { // En modo local, permitir seleccionar carpeta existente o crear nueva const testOutputPath = path.resolve('./test-output'); await fs.ensureDir(testOutputPath); // Buscar carpetas existentes en test-output const existingDirs = []; if (await fs.pathExists(testOutputPath)) { const contents = await fs.readdir(testOutputPath); for (const item of contents) { const itemPath = path.join(testOutputPath, item); const stat = await fs.stat(itemPath); if (stat.isDirectory()) { existingDirs.push(item); } } } const directoryChoices = []; // Agregar carpetas existentes for (const dir of existingDirs) { directoryChoices.push({ name: `${dir} (existente)`, value: path.join(testOutputPath, dir), short: dir }); } // Agregar opción para crear nueva carpeta directoryChoices.push({ name: `Crear nueva carpeta: ${apiName}`, value: 'create_new', short: `nuevo: ${apiName}` }); const { selectedDirectory } = await inquirer_1.default.prompt([ { type: 'list', name: 'selectedDirectory', message: '¿En qué directorio crear la entidad?', choices: directoryChoices, pageSize: 10 } ]); if (selectedDirectory === 'create_new') { targetBasePath = path.resolve(`./test-output/${apiName}`); await fs.ensureDir(targetBasePath); } else { targetBasePath = selectedDirectory; } console.log(chalk_1.default.green(`✅ Directorio target válido: ${targetBasePath}`)); } else { // En proyecto real, crear opciones de directorio const directoryChoices = []; // Opción 1: Directorio actual (si tiene estructura de API) if (directoryInfo.currentApiName) { directoryChoices.push({ name: `${directoryInfo.currentApiName} (directorio actual)`, value: directoryInfo.baseDirectory, short: directoryInfo.currentApiName }); } // Opción 2: APIs hermanas disponibles for (const siblingApi of directoryInfo.possibleApiNames) { if (siblingApi !== directoryInfo.currentApiName) { const siblingPath = path.join(path.dirname(directoryInfo.baseDirectory), siblingApi); directoryChoices.push({ name: `${siblingApi} (API hermana)`, value: siblingPath, short: siblingApi }); } } const { selectedDirectory } = await inquirer_1.default.prompt([ { type: 'list', name: 'selectedDirectory', message: '¿En qué directorio crear la entidad?', choices: directoryChoices, pageSize: 10 } ]); targetBasePath = selectedDirectory; // Verificar que el directorio target sea válido const validation = await directory_detector_1.DirectoryDetector.validateTargetPath(targetBasePath); if (!validation.isValid) { console.log(chalk_1.default.red(`\n❌ ${validation.message}`)); return await showMainMenu(isLocalMode); } console.log(chalk_1.default.green(`✅ ${validation.message}`)); } console.log(chalk_1.default.blue(`🎯 Generando entidad en API: ${apiName}`)); console.log(chalk_1.default.gray(`📁 Estructura: ${targetBasePath}/domain/models/apis/${apiName}/entities/...`)); // Mostrar entidades disponibles para selección const entityChoices = availableEntities.map(entity => ({ name: entity, value: entity })); entityChoices.push({ name: chalk_1.default.gray('📝 Ingresar nombre personalizado'), value: 'custom' }); const { selectedEntity } = await inquirer_1.default.prompt([ { type: 'list', name: 'selectedEntity', message: 'Selecciona la entidad a generar:', choices: entityChoices, pageSize: 15 } ]); let entityName = selectedEntity; // Si seleccionó custom, pedir el nombre if (selectedEntity === 'custom') { const { customEntityName } = await inquirer_1.default.prompt([ { type: 'input', name: 'customEntityName', message: 'Nombre de la entity personalizada:', validate: (input) => { if (!input.trim()) { return 'El nombre de la entity es requerido'; } if (!/^[A-Z][a-zA-Z0-9]*$/.test(input.trim())) { return 'El nombre debe empezar con mayúscula y solo contener letras y números'; } return true; } } ]); entityName = customEntityName.trim(); } // Mostrar información de la entidad seleccionada if (selectedEntity !== 'custom') { swaggerAnalyzer.printEntityInfo(entityName); } const { confirmGeneration } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'confirmGeneration', message: `¿Generar flujo completo para la entity "${entityName}"?`, default: true } ]); if (confirmGeneration) { const entitySchema = selectedEntity !== 'custom' ? swaggerAnalyzer.getEntitySchema(entityName) : null; // 🔍 VALIDACIONES PRE-GENERACIÓN (usar variables target) const validation = await project_validator_1.ProjectValidator.validateBeforeGeneration(entityName, targetBasePath, apiName); if (!validation.canProceed) { console.log(chalk_1.default.red('\n❌ No se puede continuar debido a problemas de validación')); if (validation.recommendations.length > 0) { console.log(chalk_1.default.yellow('\n💡 Recomendaciones:')); validation.recommendations.forEach(rec => console.log(chalk_1.default.gray(` • ${rec}`))); } return await showMainMenu(isLocalMode); } // Mostrar advertencias si existen if (validation.recommendations.length > 0) { console.log(chalk_1.default.yellow('\n⚠️ Advertencias:')); validation.recommendations.forEach(rec => console.log(chalk_1.default.gray(` • ${rec}`))); } // Mostrar resumen de lo que se va a generar project_validator_1.ProjectValidator.showGenerationSummary(entityName, targetBasePath, validation.entityResult.exists); // Confirmación final si hay conflictos if (validation.entityResult.exists) { const { proceedWithOverwrite } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'proceedWithOverwrite', message: chalk_1.default.yellow('⚠️ ¿Continuar y sobrescribir los archivos existentes?'), default: false } ]); if (!proceedWithOverwrite) { console.log(chalk_1.default.yellow('\n❌ Generación cancelada por el usuario')); return await showMainMenu(isLocalMode); } } console.log(chalk_1.default.blue(`\n🔧 Generando flujo para ${entityName}...`)); await (0, correct_entity_flow_generator_1.createCorrectEntityFlow)(entityName, targetBasePath, entitySchema, apiName); console.log(chalk_1.default.green(`\n✅ Flujo ${entityName} generado exitosamente!`)); if (isLocalMode) { console.log(chalk_1.default.blue(`📁 Archivos generados en: ${targetBasePath}`)); console.log(chalk_1.default.gray('💡 Puedes revisar los archivos en la carpeta test-output/')); } else { console.log(chalk_1.default.blue(`📁 Archivos generados en el proyecto real: ${targetBasePath}`)); } } else { console.log(chalk_1.default.yellow('\n❌ Generación cancelada')); } // Volver al menú principal const { backToMenu } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'backToMenu', message: '¿Volver al menú principal?', default: true } ]); if (backToMenu) { await showMainMenu(isLocalMode); } else { console.log(chalk_1.default.green('\n👋 ¡Hasta luego!')); process.exit(0); } } catch (error) { console.error(chalk_1.default.red('\n❌ Error al generar el flujo:'), error); await showMainMenu(isLocalMode); } } /** * Maneja la creación de un flujo de negocio completo basado en swagger */ async function handleCreateBusinessFlow(isLocalMode = false) { try { console.log(chalk_1.default.yellow('\n📋 Configurando flujo de negocio...')); // 🔍 DETECTAR DIRECTORIO ACTUAL Y APIs DISPONIBLES console.log(chalk_1.default.blue('🔍 Analizando estructura del directorio...')); const directoryInfo = await directory_detector_1.DirectoryDetector.detectCurrentApi(); if (directoryInfo.currentApiName) { console.log(chalk_1.default.green(`✅ API detectada en directorio actual: ${directoryInfo.currentApiName}`)); } else { console.log(chalk_1.default.yellow('⚠️ No se detectó estructura de API en el directorio actual')); } if (directoryInfo.possibleApiNames.length > 0) { console.log(chalk_1.default.gray(`📁 APIs disponibles: ${directoryInfo.possibleApiNames.join(', ')}`)); } // 1. Solicitar URL del OpenAPI/Swagger const { swaggerUrl } = await inquirer_1.default.prompt([ { type: 'input', name: 'swaggerUrl', message: 'URL del OpenAPI/Swagger JSON:', default: 'http://backend-platform-prod-env.eba-dddmvypu.us-east-1.elasticbeanstalk.com/openapi.json', validate: (input) => { if (!input.trim()) { return 'La URL del swagger es requerida'; } try { new URL(input.trim()); return true; } catch { return 'Por favor ingresa una URL válida'; } } } ]); // Cargar y analizar el swagger console.log(chalk_1.default.blue('\n🔍 Analizando OpenAPI...')); const swaggerAnalyzer = new swagger_parser_1.SwaggerAnalyzer(); try { await swaggerAnalyzer.loadFromUrl(swaggerUrl.trim()); } catch (error) { console.error(chalk_1.default.red('\n❌ Error cargando el swagger:'), error); return await handleCreateBusinessFlow(isLocalMode); } const availableBusinessServices = swaggerAnalyzer.getAvailableBusinessServices(); if (availableBusinessServices.length === 0) { console.log(chalk_1.default.yellow('\n⚠️ No se encontraron servicios de negocio en el swagger')); return await showMainMenu(isLocalMode); } console.log(chalk_1.default.green(`\n✅ Se encontraron ${availableBusinessServices.length} servicios de negocio disponibles`)); // 🔗 CONFIGURAR NOMBRE DE LA API const detectedApiName = swaggerAnalyzer.getDetectedApiName(); const suggestedNames = swaggerAnalyzer.suggestApiNames(); let apiName; if (detectedApiName) { const { useDetectedApi } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'useDetectedApi', message: `¿Usar la API detectada "${detectedApiName}"?`, default: true } ]); if (useDetectedApi) { apiName = detectedApiName; } else { const { selectedApiName } = await inquirer_1.default.prompt([ { type: 'list', name: 'selectedApiName', message: 'Selecciona el nombre de la API:', choices: [ ...suggestedNames.map(name => ({ name, value: name })), { name: '📝 Ingresar nombre personalizado', value: 'custom' } ] } ]); if (selectedApiName === 'custom') { const { customApiName } = await inquirer_1.default.prompt([ { type: 'input', name: 'customApiName', message: 'Nombre de la API:', validate: (input) => { if (!input.trim()) { return 'El nombre de la API es requerido'; } return true; } } ]); apiName = customApiName.trim(); } else { apiName = selectedApiName; } } } else { // No se detectó API, solicitar nombre const choices = [ ...suggestedNames.map(name => ({ name, value: name })), { name: '📝 Ingresar nombre personalizado', value: 'custom' } ]; const { selectedApiName } = await inquirer_1.default.prompt([ { type: 'list', name: 'selectedApiName', message: 'Selecciona el nombre de la API:', choices } ]); if (selectedApiName === 'custom') { const { customApiName } = await inquirer_1.default.prompt([ { type: 'input', name: 'customApiName', message: 'Nombre de la API:', validate: (input) => { if (!input.trim()) { return 'El nombre de la API es requerido'; } return true; } } ]); apiName = customApiName.trim(); } else { apiName = selectedApiName; } } console.log(chalk_1.default.green(`🔗 API configurada: ${apiName}`)); // 📁 CONFIGURAR DIRECTORIO DE DESTINO console.log(chalk_1.default.blue('\n📁 Configurando directorio de destino...')); let targetDirectory; if (isLocalMode) { // En modo local, usar test-output con opciones más flexibles const localTestPath = './test-output'; // Crear test-output si no existe await fs.ensureDir(localTestPath); // Obtener carpetas existentes en test-output const existingFolders = (await fs.readdir(localTestPath, { withFileTypes: true })) .filter(dirent => dirent.isDirectory()) .map(dirent => dirent.name); const folderChoices = [ ...existingFolders.map(folder => ({ name: `📁 ${folder} (existente)`, value: folder })), { name: '✨ Crear nueva carpeta', value: 'new-folder' } ]; if (folderChoices.length === 1) { // Solo la opción de crear nueva carpeta const { newFolderName } = await inquirer_1.default.prompt([ { type: 'input', name: 'newFolderName', message: '¿En qué directorio crear la entidad?', default: apiName, validate: (input) => { if (!input.trim()) { return 'El nombre del directorio es requerido'; } return true; } } ]); targetDirectory = path.join(localTestPath, newFolderName.trim()); } else { const { selectedFolder } = await inquirer_1.default.prompt([ { type: 'list', name: 'selectedFolder', message: '¿En qué directorio crear la entidad?', choices: folderChoices } ]); if (selectedFolder === 'new-folder') { const { newFolderName } = await inquirer_1.default.prompt([ { type: 'input', name: 'newFolderName', message: 'Nombre del nuevo directorio:', default: apiName, validate: (input) => { if (!input.trim()) { return 'El nombre del directorio es requerido'; } return true; } } ]); targetDirectory = path.join(localTestPath, newFolderName.trim()); } else { targetDirectory = path.join(localTestPath, selectedFolder); } } } else { // Modo producción: detectar APIs hermanas const currentDir = process.cwd(); // Buscar directorios hermanos que puedan ser APIs const parentDir = path.dirname(currentDir); const siblingDirs = (await fs.readdir(parentDir, { withFileTypes: true })) .filter(dirent => dirent.isDirectory()) .map(dirent => dirent.name) .filter(name => !name.startsWith('.')); if (siblingDirs.length > 0) { const dirChoices = siblingDirs.map(dir => ({ name: `📁 ${dir}`, value: path.join(parentDir, dir) })); const { selectedDir } = await inquirer_1.default.prompt([ { type: 'list', name: 'selectedDir', message: 'Selecciona el directorio de destino:', choices: dirChoices } ]); targetDirectory = selectedDir; } else { targetDirectory = currentDir; } } // Validar que el directorio target es válido try { await fs.ensureDir(targetDirectory); console.log(chalk_1.default.green(`✅ Directorio target válido: ${targetDirectory}`)); } catch (error) { console.error(chalk_1.default.red(`❌ Error accediendo al directorio target: ${targetDirectory}`)); return await showMainMenu(isLocalMode); } console.log(chalk_1.default.cyan(`🎯 Generando servicio de negocio en API: ${apiName}`)); console.log(chalk_1.default.gray(`📁 Estructura: ${targetDirectory}/domain/models/apis/${apiName}/business/...`)); // 4. Seleccionar servicio de negocio a generar const serviceChoices = availableBusinessServices.map(service => ({ name: service, value: service })); const { selectedService } = await inquirer_1.default.prompt([ { type: 'list', name: 'selectedService', message: 'Selecciona el servicio de negocio a generar:', choices: serviceChoices, pageSize: 15 } ]); // 5. Obtener schema del servicio de negocio seleccionado const serviceSchema = swaggerAnalyzer.getBusinessServiceSchema(selectedService); if (!serviceSchema) { console.log(chalk_1.default.red(`❌ No se pudo obtener el schema para el servicio: ${selectedService}`)); return await showMainMenu(isLocalMode); } // 6. Mostrar información del servicio de negocio console.log(chalk_1.default.cyan(`\n📋 Información del servicio: ${selectedService}`)); let selectedOperations = []; if (serviceSchema.businessOperations && serviceSchema.businessOperations.length > 0) { console.log(chalk_1.default.blue('\n🔧 Operaciones de negocio disponibles:')); serviceSchema.businessOperations.forEach(op => { const operationName = op.path.split('/').pop() || op.operationId; console.log(` • ${operationName} - ${op.method.toUpperCase()} ${op.path}`); if (op.summary) { console.log(chalk_1.default.gray(` ${op.summary}`)); } }); // 7. Seleccionar operaciones a generar const operationChoices = [ { name: '✅ TODOS los flujos de negocio', value: 'all' }, new inquirer_1.default.Separator('--- Operaciones individuales ---'), ...serviceSchema.businessOperations.map((op) => { const operationName = op.path.split('/').pop() || op.operationId; return { name: `${operationName} (${op.method.toUpperCase()} ${op.path})`, value: op, short: operationName }; }) ]; const { selectedOperation } = await inquirer_1.default.prompt([ { type: 'list', name: 'selectedOperation', message: 'Selecciona qué flujos generar:', choices: operationChoices, pageSize: 15 } ]); if (selectedOperation === 'all') { selectedOperations = serviceSchema.businessOperations; console.log(chalk_1.default.green(`\n✅ Se generarán TODAS las operaciones (${selectedOperations.length})`)); } else { selectedOperations = [selectedOperation]; const operationName = selectedOperation.path.split('/').pop() || selectedOperation.operationId; console.log(chalk_1.default.green(`\n✅ Se generará solo: ${operationName}`)); } // Mostrar detalles de las operaciones seleccionadas console.log(chalk_1.default.blue('\n📋 Detalles de operaciones a generar:')); selectedOperations.forEach(op => { const operationName = op.path.split('/').pop() || op.operationId; console.log(chalk_1.default.cyan(`\n ${operationName}:`)); console.log(` • ${op.method.toUpperCase()} ${op.path}`); if (op.requestSchema) { console.log(` 📥 Request: ${op.requestSchema}`); } if (op.responseSchema) { console.log(` 📤 Response: ${op.responseSchema}`); } if (op.fields.length > 0) { console.log(chalk_1.default.gray(` 📊 Campos (${op.fields.length}):`)); op.fields.slice(0, 5).forEach((field) => { const required = field.required ? '🔴' : '🔵'; console.log(chalk_1.default.gray(` ${required} ${field.name}: ${field.type}`)); }); if (op.fields.length > 5) { console.log(chalk_1.default.gray(` ... y ${op.fields.length - 5} campos más`)); } } }); } else { // Flujo legacy (sin businessOperations) console.log(chalk_1.default.blue('\n🔧 Operaciones disponibles:')); console.log(` • Crear: ${serviceSchema.operations.create ? '✅' : '❌'}`); console.log(` • Leer: ${serviceSchema.operations.read ? '✅' : '❌'}`); console.log(` • Actualizar: ${serviceSchema.operations.update ? '✅' : '❌'}`); console.log(` • Eliminar: ${serviceSchema.operations.delete ? '✅' : '❌'}`); console.log(` • Listar: ${serviceSchema.operations.list ? '✅' : '❌'}`); console.log(chalk_1.default.blue(`\n📊 Campos (${serviceSchema.fields.length}):`)); serviceSchema.fields.forEach(field => { const required = field.required ? '🔴' : '🔵'; console.log(` ${required} ${field.name}: ${field.type}`); }); selectedOperations = []; // Generará todo el servicio } // 8. Confirmar generación const { shouldGenerate } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'shouldGenerate', message: selectedOperations.length === 1 && serviceSchema.businessOperations ? `¿Generar flujo de negocio para la operación seleccionada?` : `¿Generar flujo de negocio completo para el servicio "${selectedService}"?`, default: true } ]); if (!shouldGenerate) { return await showMainMenu(isLocalMode); } // Crear una copia del schema con solo las operaciones seleccionadas let finalSchema = serviceSchema; if (selectedOperations.length > 0 && serviceSchema.businessOperations) { finalSchema = { ...serviceSchema, businessOperations: selectedOperations }; } // 8. Ejecutar validaciones pre-generación console.log(chalk_1.default.blue('\n🔍 Ejecutando validaciones pre-generación...')); const validation = await project_validator_1.ProjectValidator.validateProjectStructure(targetDirectory); console.log(chalk_1.default.blue('\n🔍 Validando estructura del proyecto...')); console.log(chalk_1.default.blue(`🔍 Verificando si el servicio "${selectedService}" ya existe...`)); // Validar que el servicio no existe en business (similar a entities) const businessEntityExists = await project_validator_1.ProjectValidator.checkBusinessEntityExists(selectedService, targetDirectory, apiName); if (validation.isValid) { console.log(chalk_1.default.green('✅ Estructura del proyecto válida')); } // Mostrar warnings si los hay if (validation.warnings && validation.warnings.length > 0) { validation.warnings.forEach((warning) => { console.log(chalk_1.default.yellow(`⚠️ ${warning}`)); }); } if (businessEntityExists.exists) { console.log(chalk_1.default.yellow(`⚠️ El servicio "${selectedService}" ya existe parcial o completamente en business`)); if (businessEntityExists.conflictingFiles.length > 0) { console.log(chalk_1.default.yellow('\nArchivos/directorios existentes:')); businessEntityExists.conflictingFiles.forEach((detail) => { console.log(chalk_1.default.gray(` ${detail}`)); }); } } // Mostrar warnings de validación if ((validation.warnings && validation.warnings.length > 0) || businessEntityExists.exists) { console.log(chalk_1.default.yellow('\n⚠️ Advertencias:')); if (businessEntityExists.exists) { console.log(chalk_1.default.yellow(' • La entidad ya existe. Si continúas, se sobrescribirán los archivos existentes')); console.log(chalk_1.default.yellow(' • Considera hacer un backup antes de continuar')); } if (validation.warnings) { validation.warnings.forEach((warning) => { console.log(chalk_1.default.yellow(` • ${warning}`)); }); } } // 9. Mostrar resumen antes de generar console.log(chalk_1.default.blue('\n📋 Resumen de generación:')); console.log(chalk_1.default.white(`Servicio: ${selectedService}`)); console.log(chalk_1.default.white(`Ubicación: ${targetDirectory}`)); console.log(chalk_1.default.white(`Archivos a generar: ~29 archivos TypeScript`)); if (businessEntityExists.exists) { console.log(chalk_1.default.yellow('⚠️ Se sobrescribirán archivos existentes')); } console.log(chalk_1.default.gray('\n📁 Directorios que se crearán/utilizarán:')); const serviceNameLower = selectedService.toLowerCase(); console.log(chalk_1.default.gray(` 📂 domain/models/apis/${apiName}/business/${serviceNameLower}/`)); console.log(chalk_1.default.gray(` 📂 domain/services/use_cases/apis/${apiName}/business/${serviceNameLower}/`)); console.log(chalk_1.default.gray(` 📂 infrastructure/entities/apis/${apiName}/business/${serviceNameLower}/`)); console.log(chalk_1.default.gray(` 📂 infrastructure/mappers/apis/${apiName}/business/${serviceNameLower}/`)); console.log(chalk_1.default.gray(` 📂 infrastructure/repositories/.../business/${serviceNameLower}/`)); console.log(chalk_1.default.gray(` 📂 facade/apis/${apiName}/business/`)); console.log(chalk_1.default.gray(` 📂 injection folders...`)); // Confirmación final si hay advertencias if ((validation.warnings && validation.warnings.length > 0) || businessEntityExists.exists) { const { shouldContinue } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'shouldContinue', message: '⚠️ ¿Continuar y sobrescribir los archivos existentes?', default: false } ]); if (!shouldContinue) { console.log(chalk_1.default.yellow('Operación cancelada por el usuario')); return await showMainMenu(isLocalMode); } } // 10. Generar el flujo de negocio if (selectedOperations.length > 0 && serviceSchema.businessOperations) { console.log(chalk_1.default.green(`\n🔧 Generando ${selectedOperations.length === serviceSchema.businessOperations.length ? 'TODAS las operaciones' : 'operación seleccionada'} para ${selectedService}...`)); } else { console.log(chalk_1.default.green(`\n🔧 Generando flujo de negocio para ${selectedService}...`)); } await (0, business_flow_generator_1.createBusinessFlow)(selectedService, targetDirectory, finalSchema, apiName); console.log(chalk_1.default.green(`\n✅ Flujo de negocio ${selectedService} generado exitosamente!`)); console.log(chalk_1.default.cyan(`📁 Archivos generados en: ${targetDirectory}`)); console.log(chalk_1.default.gray('💡 Puedes revisar los archivos en la carpeta especificada')); // Preguntar si quiere continuar const { continueWorking } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'continueWorking', message: '¿Volver al menú principal?', default: true } ]); if (continueWorking) { await showMainMenu(isLocalMode); } else { console.log(chalk_1.default.blue('\n👋 ¡Hasta luego!')); } } catch (error) { console.error(chalk_1.default.red('\n❌ Error en el flujo de negocio:'), error); const { retry } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'retry', message: '¿Intentar nuevamente?', default: true } ]); if (retry) { await handleCreateBusinessFlow(isLocalMode); } else { await showMainMenu(isLocalMode); } } } /** * Maneja la creación de un flujo Redux completo (simplificado - solo Custom Flows) */ async function handleCreateReduxFlow(isLocalMode = false) { try { console.log(chalk_1.default.yellow('\n📋 Configurando flujo Redux...')); // 🔍 DETECTAR DIRECTORIO ACTUAL Y APIs DISPONIBLES console.log(chalk_1.default.blue('🔍 Analizando estructura del directorio...')); const directoryInfo = await directory_detector_1.DirectoryDetector.detectCurrentApi(); if (directoryInfo.currentApiName) { console.log(chalk_1.default.green(`✅ API detectada en directorio actual: ${directoryInfo.currentApiName}`)); } else { console.log(chalk_1.default.yellow('⚠️ No se detectó estructura de API en el directorio actual')); } if (directoryInfo.possibleApiNames.length > 0) { console.log(chalk_1.default.gray(`📁 APIs disponibles: ${directoryInfo.possibleApiNames.join(', ')}`)); } // 1. Solicitar ruta del archivo OpenAPI/Swagger const { swaggerFile } = await inquirer_1.default.prompt([ { type: 'input', name: 'swaggerFile', message: 'Ruta del archivo OpenAPI/Swagger (JSON/YAML):', validate: (input) => { if (!input.trim()) { return 'La ruta del archivo es requerida'; } if (!fs.existsSync(input.trim())) { return 'El archivo no existe'; } if (!/\.(json|yaml|yml)$/i.test(input.trim())) { return 'El archivo debe ser .json, .yaml o .yml'; } return true; } } ]); // Cargar y analizar el swagger (usando parser especializado para Redux) console.log(chalk_1.default.blue('\n🔍 Analizando OpenAPI...')); const swaggerAnalyzer = new swagger_redux_parser_1.SwaggerReduxAnalyzer(); try { await swaggerAnalyzer.loadFromFile(swaggerFile.trim()); } catch (error) { console.error(chalk_1.default.red('\n❌ Error cargando el swagger:'), error); return await handleCreateReduxFlow(isLocalMode); } // 3. Obtener el nombre de la API const detectedApiName = swaggerAnalyzer.getDetectedApiName(); const suggestedNames = swaggerAnalyzer.suggestApiNames(); let apiName; if (detectedApiName) { const { useDetectedApi } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'useDetectedApi', message: `¿Usar la API detectada "${detectedApiName}"?`, default: true } ]); if (useDetectedApi) { apiName = detectedApiName; } else { const { selectedApiName } = await inquirer_1.default.prompt([ { type: 'list', name: 'selectedApiName', message: 'Selecciona el nombre de la API:', choices: [ ...suggestedNames.map(name => ({ name, value: name })), { name: '📝 Ingresar nombre personalizado', value: 'custom' } ] } ]); if (selectedApiName === 'custom') { const { customApiName } = await inquirer_1.default.prompt([ { type: 'input', name: 'customApiName', message: 'Nombre de la API:', validate: (input) => { if (!input.trim()) return 'El nombre de la API es requerido'; if (!/^[a-z][a-z0-9-]*$/.test(input.trim())) { return 'El nombre debe empezar con minúscula y solo contener letras, números y guiones'; } return true; } } ]); apiName = customApiName.trim(); } else { apiName = selectedApiName; } } } else { const { selectedApiName } = await inquirer_1.default.prompt([ { type: 'list', name: 'selectedApiName', message: 'No se pudo detectar automáticamente. Selecciona el nombre de la API:', choices: [ ...suggestedNames.map(name => ({ name, value: name })),