UNPKG

backend-mcp

Version:

Generador automático de backends con Node.js, Express, Prisma y módulos configurables. Servidor MCP compatible con npx para agentes IA. Soporta PostgreSQL, MySQL, MongoDB y SQLite.

774 lines (665 loc) 19.9 kB
#!/usr/bin/env node /** * 🚀 Módulo CI/CD Avanzado * * Configuración avanzada de CI/CD con soporte para múltiples proveedores * y estrategias de despliegue complejas. */ const fs = require('fs-extra'); const path = require('path'); const yaml = require('js-yaml'); const chalk = require('chalk'); class CICDModule { constructor(config = {}) { this.config = { provider: 'github', strategy: 'rolling', environments: ['staging', 'production'], notifications: {}, security: { scan_vulnerabilities: true, scan_secrets: true, compliance_checks: false }, ...config }; this.projectPath = process.cwd(); this.templatesPath = path.join(__dirname, 'templates'); } async init() { try { console.log(chalk.blue.bold('🚀 Configurando CI/CD Avanzado...')); // Validar configuración await this.validateConfig(); // Crear estructura de directorios await this.createDirectories(); // Generar archivos según el proveedor await this.generateProviderFiles(); // Configurar Docker para CI await this.setupDockerCI(); // Generar scripts de despliegue await this.generateDeploymentScripts(); // Configurar monitoreo await this.setupMonitoring(); // Configurar notificaciones await this.setupNotifications(); console.log(chalk.green.bold('✅ CI/CD configurado exitosamente!')); this.showNextSteps(); } catch (error) { console.error(chalk.red('❌ Error configurando CI/CD:'), error.message); throw error; } } async validateConfig() { const validProviders = ['github', 'gitlab', 'jenkins', 'azure', 'circleci']; const validStrategies = ['rolling', 'blue-green', 'canary']; if (!validProviders.includes(this.config.provider)) { throw new Error(`Proveedor no válido: ${this.config.provider}. Opciones: ${validProviders.join(', ')}`); } if (!validStrategies.includes(this.config.strategy)) { throw new Error(`Estrategia no válida: ${this.config.strategy}. Opciones: ${validStrategies.join(', ')}`); } if (!Array.isArray(this.config.environments) || this.config.environments.length === 0) { throw new Error('Debe especificar al menos un entorno de despliegue'); } } async createDirectories() { const dirs = [ 'scripts', '.github/workflows', 'docker', 'k8s', 'terraform', 'monitoring' ]; for (const dir of dirs) { await fs.ensureDir(path.join(this.projectPath, dir)); } } async generateProviderFiles() { switch (this.config.provider) { case 'github': await this.generateGitHubActions(); break; case 'gitlab': await this.generateGitLabCI(); break; case 'jenkins': await this.generateJenkinsfile(); break; case 'azure': await this.generateAzurePipelines(); break; case 'circleci': await this.generateCircleCI(); break; } } async generateGitHubActions() { const ciWorkflow = { name: 'CI/CD Pipeline', on: { push: { branches: ['main', 'develop'] }, pull_request: { branches: ['main'] } }, jobs: { test: { 'runs-on': 'ubuntu-latest', strategy: { matrix: { 'node-version': ['16.x', '18.x', '20.x'] } }, steps: [ { uses: 'actions/checkout@v4' }, { name: 'Setup Node.js', uses: 'actions/setup-node@v3', with: { 'node-version': '${{ matrix.node-version }}', cache: 'npm' } }, { name: 'Install dependencies', run: 'npm ci' }, { name: 'Run tests', run: 'npm test' }, { name: 'Run linting', run: 'npm run lint' } ] } } }; // Añadir job de seguridad si está habilitado if (this.config.security.scan_vulnerabilities) { ciWorkflow.jobs.security = { 'runs-on': 'ubuntu-latest', steps: [ { uses: 'actions/checkout@v4' }, { name: 'Run security audit', run: 'npm audit --audit-level moderate' }, { name: 'Scan for secrets', uses: 'trufflesecurity/trufflehog@main', with: { path: './' } } ] }; } // Añadir jobs de despliegue para cada entorno for (const env of this.config.environments) { ciWorkflow.jobs[`deploy-${env}`] = { needs: ['test'], 'runs-on': 'ubuntu-latest', if: env === 'production' ? "github.ref == 'refs/heads/main'" : "github.ref == 'refs/heads/develop'", environment: env, steps: [ { uses: 'actions/checkout@v4' }, { name: `Deploy to ${env}`, run: `./scripts/deploy.sh ${env}`, env: { DEPLOY_ENV: env } } ] }; } const workflowPath = path.join(this.projectPath, '.github/workflows/ci.yml'); await fs.writeFile(workflowPath, yaml.dump(ciWorkflow, { indent: 2 })); console.log(chalk.green('✅ GitHub Actions workflow generado')); } async generateGitLabCI() { const gitlabCI = { stages: ['test', 'security', 'deploy'], variables: { NODE_VERSION: '18' }, test: { stage: 'test', image: 'node:$NODE_VERSION', script: [ 'npm ci', 'npm test', 'npm run lint' ], coverage: '/coverage/', artifacts: { reports: { coverage_report: { coverage_format: 'cobertura', path: 'coverage/cobertura-coverage.xml' } } } } }; if (this.config.security.scan_vulnerabilities) { gitlabCI.security = { stage: 'security', image: 'node:$NODE_VERSION', script: [ 'npm audit --audit-level moderate' ], allow_failure: true }; } // Añadir jobs de despliegue for (const env of this.config.environments) { gitlabCI[`deploy-${env}`] = { stage: 'deploy', script: [ `./scripts/deploy.sh ${env}` ], environment: { name: env, url: `https://${env}.example.com` }, only: env === 'production' ? ['main'] : ['develop'] }; } const ciPath = path.join(this.projectPath, '.gitlab-ci.yml'); await fs.writeFile(ciPath, yaml.dump(gitlabCI, { indent: 2 })); console.log(chalk.green('✅ GitLab CI configurado')); } async generateJenkinsfile() { const jenkinsfile = ` pipeline { agent any tools { nodejs '${this.config.nodeVersion || '18'}' } stages { stage('Install') { steps { sh 'npm ci' } } stage('Test') { parallel { stage('Unit Tests') { steps { sh 'npm test' } post { always { publishTestResults testResultsPattern: 'test-results.xml' } } } stage('Lint') { steps { sh 'npm run lint' } } } } ${this.config.security.scan_vulnerabilities ? ` stage('Security') { steps { sh 'npm audit --audit-level moderate' } }` : ''} ${this.config.environments.map(env => ` stage('Deploy ${env}') { when { branch '${env === 'production' ? 'main' : 'develop'}' } steps { sh './scripts/deploy.sh ${env}' } }`).join('')} } post { always { cleanWs() } failure { ${this.config.notifications.slack ? ` slackSend( channel: '#deployments', color: 'danger', message: "Build Failed: \${env.JOB_NAME} - \${env.BUILD_NUMBER}" )` : ''} } success { ${this.config.notifications.slack ? ` slackSend( channel: '#deployments', color: 'good', message: "Build Successful: \${env.JOB_NAME} - \${env.BUILD_NUMBER}" )` : ''} } } } `; const jenkinsfilePath = path.join(this.projectPath, 'Jenkinsfile'); await fs.writeFile(jenkinsfilePath, jenkinsfile); console.log(chalk.green('✅ Jenkinsfile generado')); } async generateAzurePipelines() { const azurePipeline = { trigger: { branches: { include: ['main', 'develop'] } }, pool: { vmImage: 'ubuntu-latest' }, variables: { nodeVersion: '18.x' }, stages: [ { stage: 'Test', jobs: [ { job: 'TestJob', steps: [ { task: 'NodeTool@0', inputs: { versionSpec: '$(nodeVersion)' }, displayName: 'Install Node.js' }, { script: 'npm ci', displayName: 'Install dependencies' }, { script: 'npm test', displayName: 'Run tests' }, { script: 'npm run lint', displayName: 'Run linting' } ] } ] } ] }; // Añadir stage de despliegue const deployStage = { stage: 'Deploy', dependsOn: 'Test', jobs: [] }; for (const env of this.config.environments) { deployStage.jobs.push({ deployment: `Deploy${env.charAt(0).toUpperCase() + env.slice(1)}`, environment: env, strategy: { runOnce: { deploy: { steps: [ { script: `./scripts/deploy.sh ${env}`, displayName: `Deploy to ${env}` } ] } } } }); } azurePipeline.stages.push(deployStage); const pipelinePath = path.join(this.projectPath, 'azure-pipelines.yml'); await fs.writeFile(pipelinePath, yaml.dump(azurePipeline, { indent: 2 })); console.log(chalk.green('✅ Azure Pipelines configurado')); } async generateCircleCI() { const circleConfig = { version: 2.1, orbs: { node: 'circleci/node@5.0.0' }, workflows: { 'test-and-deploy': { jobs: [ 'test', ...this.config.environments.map(env => ({ [`deploy-${env}`]: { requires: ['test'], filters: { branches: { only: env === 'production' ? 'main' : 'develop' } } } })).reduce((acc, job) => ({ ...acc, ...job }), {}) ] } }, jobs: { test: { executor: 'node/default', steps: [ 'checkout', 'node/install-packages', { run: { name: 'Run tests', command: 'npm test' } }, { run: { name: 'Run linting', command: 'npm run lint' } } ] } } }; // Añadir jobs de despliegue for (const env of this.config.environments) { circleConfig.jobs[`deploy-${env}`] = { executor: 'node/default', steps: [ 'checkout', { run: { name: `Deploy to ${env}`, command: `./scripts/deploy.sh ${env}` } } ] }; } const configPath = path.join(this.projectPath, '.circleci/config.yml'); await fs.ensureDir(path.dirname(configPath)); await fs.writeFile(configPath, yaml.dump(circleConfig, { indent: 2 })); console.log(chalk.green('✅ CircleCI configurado')); } async setupDockerCI() { const dockerCompose = { version: '3.8', services: { app: { build: { context: '.', dockerfile: 'Dockerfile' }, environment: [ 'NODE_ENV=test' ], volumes: [ '.:/app', '/app/node_modules' ], command: 'npm test' }, db: { image: 'postgres:15', environment: { POSTGRES_DB: 'test_db', POSTGRES_USER: 'test_user', POSTGRES_PASSWORD: 'test_pass' }, ports: ['5432:5432'] } } }; const composePath = path.join(this.projectPath, 'docker-compose.ci.yml'); await fs.writeFile(composePath, yaml.dump(dockerCompose, { indent: 2 })); console.log(chalk.green('✅ Docker CI configurado')); } async generateDeploymentScripts() { const deployScript = `#!/bin/bash # Script de despliegue automatizado # Uso: ./deploy.sh [environment] set -e ENVIRONMENT=\${1:-staging} TIMESTAMP=$(date +%Y%m%d_%H%M%S) BACKUP_DIR="backups/\${ENVIRONMENT}_\${TIMESTAMP}" echo "🚀 Iniciando despliegue a \$ENVIRONMENT..." # Validar entorno if [[ ! "\$ENVIRONMENT" =~ ^(staging|production)$ ]]; then echo "❌ Entorno no válido: \$ENVIRONMENT" echo "Entornos válidos: staging, production" exit 1 fi # Crear backup echo "📦 Creando backup..." mkdir -p \$BACKUP_DIR # Función de rollback rollback() { echo "🔄 Ejecutando rollback..." # Lógica de rollback aquí exit 1 } # Trap para rollback en caso de error trap rollback ERR # Instalar dependencias echo "📦 Instalando dependencias..." npm ci --production # Ejecutar migraciones de base de datos if [ -f "package.json" ] && grep -q "migrate" package.json; then echo "🗄️ Ejecutando migraciones..." npm run migrate fi # Build de la aplicación echo "🔨 Construyendo aplicación..." npm run build # Ejecutar tests de smoke echo "🧪 Ejecutando tests de smoke..." npm run test:smoke # Reiniciar servicios echo "🔄 Reiniciando servicios..." if command -v pm2 &> /dev/null; then pm2 reload ecosystem.config.js --env \$ENVIRONMENT elif command -v systemctl &> /dev/null; then sudo systemctl restart myapp else echo "⚠️ No se encontró PM2 ni systemctl. Reinicio manual requerido." fi # Verificar salud de la aplicación echo "🏥 Verificando salud de la aplicación..." sleep 10 if [ "\$ENVIRONMENT" = "staging" ]; then HEALTH_URL="https://staging.example.com/health" else HEALTH_URL="https://example.com/health" fi if curl -f \$HEALTH_URL > /dev/null 2>&1; then echo "✅ Despliegue exitoso a \$ENVIRONMENT" else echo "❌ Verificación de salud falló" rollback fi # Limpiar archivos temporales echo "🧹 Limpiando archivos temporales..." rm -rf tmp/* echo "🎉 Despliegue completado exitosamente!" `; const deployPath = path.join(this.projectPath, 'scripts/deploy.sh'); await fs.writeFile(deployPath, deployScript); await fs.chmod(deployPath, '755'); // Script de rollback const rollbackScript = `#!/bin/bash # Script de rollback # Uso: ./rollback.sh [environment] [version] set -e ENVIRONMENT=\${1:-staging} VERSION=\${2:-previous} echo "🔄 Iniciando rollback en \$ENVIRONMENT a versión \$VERSION..." # Lógica de rollback aquí echo "✅ Rollback completado" `; const rollbackPath = path.join(this.projectPath, 'scripts/rollback.sh'); await fs.writeFile(rollbackPath, rollbackScript); await fs.chmod(rollbackPath, '755'); console.log(chalk.green('✅ Scripts de despliegue generados')); } async setupMonitoring() { // Configuración básica de monitoreo const monitoringConfig = { healthCheck: { endpoint: '/health', interval: '30s', timeout: '10s' }, metrics: { enabled: true, endpoint: '/metrics', format: 'prometheus' }, logging: { level: 'info', format: 'json', outputs: ['console', 'file'] } }; const monitoringPath = path.join(this.projectPath, 'monitoring/config.json'); await fs.writeJson(monitoringPath, monitoringConfig, { spaces: 2 }); console.log(chalk.green('✅ Monitoreo configurado')); } async setupNotifications() { if (Object.keys(this.config.notifications).length === 0) { return; } const notificationScript = `#!/bin/bash # Script de notificaciones # Uso: ./notify.sh [status] [message] STATUS=\${1:-info} MESSAGE=\${2:-"Notificación del CI/CD"} ${this.config.notifications.slack ? ` # Notificación a Slack curl -X POST -H 'Content-type: application/json' \\ --data '{"text":"[\$STATUS] \$MESSAGE"}' \\ ${this.config.notifications.slack} ` : ''} ${this.config.notifications.teams ? ` # Notificación a Teams curl -X POST -H 'Content-type: application/json' \\ --data '{"text":"[\$STATUS] \$MESSAGE"}' \\ ${this.config.notifications.teams} ` : ''} echo "📢 Notificación enviada: [\$STATUS] \$MESSAGE" `; const notifyPath = path.join(this.projectPath, 'scripts/notify.sh'); await fs.writeFile(notifyPath, notificationScript); await fs.chmod(notifyPath, '755'); console.log(chalk.green('✅ Notificaciones configuradas')); } showNextSteps() { console.log(chalk.blue.bold('\n📋 Próximos pasos:')); console.log(chalk.gray('1. Revisar y personalizar los archivos generados')); console.log(chalk.gray('2. Configurar variables de entorno en tu proveedor CI/CD')); console.log(chalk.gray('3. Configurar secretos de despliegue')); console.log(chalk.gray('4. Hacer commit y push de los cambios')); console.log(chalk.gray('5. Verificar que el pipeline se ejecute correctamente')); console.log(chalk.blue.bold('\n🔧 Archivos generados:')); console.log(chalk.gray(`- Pipeline para ${this.config.provider}`)); console.log(chalk.gray('- Scripts de despliegue y rollback')); console.log(chalk.gray('- Configuración de Docker para CI')); console.log(chalk.gray('- Configuración de monitoreo')); if (Object.keys(this.config.notifications).length > 0) { console.log(chalk.gray('- Scripts de notificaciones')); } } } // Función principal de inicialización async function initCICD(config = {}) { const cicd = new CICDModule(config); await cicd.init(); return cicd; } module.exports = { initCICD, CICDModule }; // Ejecutar si es llamado directamente if (require.main === module) { const config = { provider: process.env.CI_PROVIDER || 'github', strategy: process.env.DEPLOY_STRATEGY || 'rolling', environments: (process.env.DEPLOY_ENVIRONMENTS || 'staging,production').split(','), notifications: { slack: process.env.SLACK_WEBHOOK, teams: process.env.TEAMS_WEBHOOK } }; initCICD(config).catch(console.error); }