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
JavaScript
#!/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 })),