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.

776 lines (647 loc) 18.4 kB
const fs = require('fs'); const path = require('path'); class DockerModuleInitializer { constructor(projectPath, config = {}) { this.projectPath = projectPath; this.config = { nodeVersion: '18', baseImage: 'node:18-alpine', enableNginx: true, enableSSL: false, enableDatabase: false, enableCache: false, enableHealthChecks: true, enableMultiStage: true, enableSecurity: true, port: 3000, nginxPort: 80, sslPort: 443, environment: 'development', ...config }; } async initialize() { console.log('🐳 Inicializando módulo Docker...'); try { // 1. Crear estructura de directorios this.createDirectoryStructure(); // 2. Generar Dockerfile this.generateDockerfile(); // 3. Generar docker-compose para desarrollo this.generateDockerComposeDev(); // 4. Generar docker-compose para producción this.generateDockerComposeProd(); // 5. Generar .dockerignore this.generateDockerignore(); // 6. Generar configuración de Nginx if (this.config.enableNginx) { this.generateNginxConfig(); } // 7. Generar scripts de automatización this.generateScripts(); // 8. Actualizar package.json await this.updatePackageJson(); console.log('✅ Módulo Docker inicializado correctamente'); return { success: true, message: 'Docker module initialized successfully', files: this.getGeneratedFiles() }; } catch (error) { console.error('❌ Error inicializando módulo Docker:', error); throw error; } } createDirectoryStructure() { const dirs = [ 'nginx', 'nginx/ssl', 'scripts', 'docker' ]; dirs.forEach(dir => { const dirPath = path.join(this.projectPath, dir); if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } }); } generateDockerfile() { const dockerfileContent = `# Multi-stage Dockerfile for Node.js application # Stage 1: Dependencies FROM ${this.config.baseImage} AS dependencies # Set working directory WORKDIR /app # Create non-root user for security RUN addgroup -g 1001 -S nodejs RUN adduser -S nextjs -u 1001 # Copy package files COPY package*.json ./ COPY prisma ./prisma/ # Install dependencies RUN npm ci --only=production && npm cache clean --force # Stage 2: Build FROM ${this.config.baseImage} AS builder WORKDIR /app # Copy package files COPY package*.json ./ COPY prisma ./prisma/ # Install all dependencies (including dev) RUN npm ci # Copy source code COPY . . # Generate Prisma client RUN npx prisma generate # Build application RUN npm run build # Stage 3: Runtime FROM ${this.config.baseImage} AS runtime WORKDIR /app # Create non-root user RUN addgroup -g 1001 -S nodejs RUN adduser -S nextjs -u 1001 # Copy built application COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist COPY --from=builder --chown=nextjs:nodejs /app/prisma ./prisma COPY --from=dependencies --chown=nextjs:nodejs /app/node_modules ./node_modules COPY --from=builder --chown=nextjs:nodejs /app/package*.json ./ # Set environment variables ENV NODE_ENV=production ENV PORT=${this.config.port} # Expose port EXPOSE ${this.config.port} # Switch to non-root user USER nextjs # Health check ${this.config.enableHealthChecks ? `HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:${this.config.port}/health || exit 1` : ''} # Start application CMD ["npm", "start"] `; this.writeFile('Dockerfile', dockerfileContent); } generateDockerComposeDev() { const services = { app: { build: { context: '.', target: 'runtime' }, ports: [`${this.config.port}:${this.config.port}`], environment: { NODE_ENV: 'development', DATABASE_URL: '${DATABASE_URL}', JWT_SECRET: '${JWT_SECRET}' }, volumes: [ '.:/app', '/app/node_modules' ], depends_on: [], networks: ['app-network'], restart: 'unless-stopped' } }; // Agregar base de datos si está habilitada if (this.config.enableDatabase) { services.postgres = { image: 'postgres:15-alpine', environment: { POSTGRES_DB: '${DB_NAME:-myapp}', POSTGRES_USER: '${DB_USER:-postgres}', POSTGRES_PASSWORD: '${DB_PASSWORD:-password}' }, ports: ['5432:5432'], volumes: [ 'postgres_data:/var/lib/postgresql/data', './docker/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql' ], networks: ['app-network'], restart: 'unless-stopped' }; services.app.depends_on.push('postgres'); } // Agregar Redis si está habilitado if (this.config.enableCache) { services.redis = { image: 'redis:7-alpine', ports: ['6379:6379'], volumes: ['redis_data:/data'], networks: ['app-network'], restart: 'unless-stopped', command: 'redis-server --appendonly yes' }; services.app.depends_on.push('redis'); } // Agregar Nginx si está habilitado if (this.config.enableNginx) { services.nginx = { image: 'nginx:alpine', ports: [`${this.config.nginxPort}:80`], volumes: [ './nginx/nginx.conf:/etc/nginx/nginx.conf', './nginx/ssl:/etc/nginx/ssl' ], depends_on: ['app'], networks: ['app-network'], restart: 'unless-stopped' }; } const volumes = {}; if (this.config.enableDatabase) { volumes.postgres_data = {}; } if (this.config.enableCache) { volumes.redis_data = {}; } const compose = { version: '3.8', services, volumes, networks: { 'app-network': { driver: 'bridge' } } }; this.writeFile('docker-compose.yml', this.yamlStringify(compose)); } generateDockerComposeProd() { const services = { app: { build: { context: '.', target: 'runtime' }, environment: { NODE_ENV: 'production', DATABASE_URL: '${DATABASE_URL}', JWT_SECRET: '${JWT_SECRET}' }, networks: ['app-network'], restart: 'unless-stopped', deploy: { replicas: 2, resources: { limits: { memory: '512M', cpus: '0.5' }, reservations: { memory: '256M', cpus: '0.25' } } } } }; // Configuración de producción para base de datos if (this.config.enableDatabase) { services.postgres = { image: 'postgres:15-alpine', environment: { POSTGRES_DB: '${DB_NAME}', POSTGRES_USER: '${DB_USER}', POSTGRES_PASSWORD: '${DB_PASSWORD}' }, volumes: [ 'postgres_data:/var/lib/postgresql/data' ], networks: ['db-network'], restart: 'unless-stopped', deploy: { resources: { limits: { memory: '1G', cpus: '1.0' } } } }; } // Nginx con SSL para producción if (this.config.enableNginx) { const nginxPorts = [this.config.nginxPort + ':80']; if (this.config.enableSSL) { nginxPorts.push(this.config.sslPort + ':443'); } services.nginx = { image: 'nginx:alpine', ports: nginxPorts, volumes: [ './nginx/nginx.conf:/etc/nginx/nginx.conf', './nginx/ssl:/etc/nginx/ssl' ], depends_on: ['app'], networks: ['app-network'], restart: 'unless-stopped', deploy: { resources: { limits: { memory: '128M', cpus: '0.25' } } } }; } const volumes = {}; if (this.config.enableDatabase) { volumes.postgres_data = { driver: 'local' }; } const networks = { 'app-network': { driver: 'bridge' } }; if (this.config.enableDatabase) { networks['db-network'] = { driver: 'bridge', internal: true }; } const compose = { version: '3.8', services, volumes, networks }; this.writeFile('docker-compose.prod.yml', this.yamlStringify(compose)); } generateDockerignore() { const dockerignoreContent = `# Dependencies node_modules npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage .grunt # Bower dependency directory bower_components # node-waf configuration .lock-wscript # Compiled binary addons build/Release # Dependency directories node_modules/ jspm_packages/ # Optional npm cache directory .npm # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env .env.local .env.development.local .env.test.local .env.production.local # IDE files .vscode .idea *.swp *.swo *~ # OS generated files .DS_Store .DS_Store? ._* .Spotlight-V100 .Trashes ehthumbs.db Thumbs.db # Logs logs *.log # Git .git .gitignore # Docker Dockerfile* docker-compose* .dockerignore # Documentation README.md *.md docs/ # Tests __tests__ *.test.js *.spec.js test/ # Build artifacts dist/ build/ # Temporary files tmp/ temp/ `; this.writeFile('.dockerignore', dockerignoreContent); } generateNginxConfig() { const nginxConfig = `events { worker_connections 1024; } http { upstream app { server app:${this.config.port}; } # Rate limiting limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; # Gzip compression gzip on; gzip_vary on; gzip_min_length 1024; gzip_proxied any; gzip_comp_level 6; gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/atom+xml image/svg+xml; # Security headers add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; server { listen 80; server_name _; # Rate limiting limit_req zone=api burst=20 nodelay; # Health check endpoint location /health { access_log off; return 200 "healthy\\n"; add_header Content-Type text/plain; } # API routes location / { proxy_pass http://app; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; # Timeouts proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; } # Static files (if any) location /static/ { alias /app/public/; expires 1y; add_header Cache-Control "public, immutable"; } } ${this.config.enableSSL ? ` server { listen 443 ssl http2; server_name _; ssl_certificate /etc/nginx/ssl/cert.pem; ssl_certificate_key /etc/nginx/ssl/key.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; # Rate limiting limit_req zone=api burst=20 nodelay; location / { proxy_pass http://app; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; } }` : ''} } `; this.writeFile('nginx/nginx.conf', nginxConfig); // Crear certificados SSL de ejemplo (para desarrollo) if (this.config.enableSSL) { const sslReadme = `# SSL Certificates For development, you can generate self-signed certificates: \`\`\`bash openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout nginx/ssl/key.pem \ -out nginx/ssl/cert.pem \ -subj "/C=US/ST=State/L=City/O=Organization/CN=localhost" \`\`\` For production, use certificates from a trusted CA like Let's Encrypt. `; this.writeFile('nginx/ssl/README.md', sslReadme); } } generateScripts() { // Script de build const buildScript = `#!/bin/bash set -e echo "🐳 Building Docker images..." # Build main application docker build -t myapp:latest . echo "✅ Docker images built successfully!" `; this.writeFile('scripts/docker-build.sh', buildScript); // Script de desarrollo const devScript = `#!/bin/bash set -e echo "🚀 Starting development environment..." # Start development services docker-compose up -d echo "✅ Development environment started!" echo "📱 App: http://localhost:${this.config.port}" ${this.config.enableNginx ? `echo "🌐 Nginx: http://localhost:${this.config.nginxPort}"` : ''} echo "📊 View logs: docker-compose logs -f" echo "🛑 Stop: docker-compose down" `; this.writeFile('scripts/docker-dev.sh', devScript); // Script de producción const deployScript = `#!/bin/bash set -e echo "🚀 Deploying to production..." # Build and deploy docker-compose -f docker-compose.prod.yml up -d --build echo "✅ Production deployment completed!" `; this.writeFile('scripts/docker-deploy.sh', deployScript); // Script de logs const logsScript = `#!/bin/bash # Show logs for all services if [ $# -eq 0 ]; then docker-compose logs -f else docker-compose logs -f $1 fi `; this.writeFile('scripts/docker-logs.sh', logsScript); // Hacer scripts ejecutables const scripts = ['docker-build.sh', 'docker-dev.sh', 'docker-deploy.sh', 'docker-logs.sh']; scripts.forEach(script => { try { fs.chmodSync(path.join(this.projectPath, 'scripts', script), '755'); } catch (error) { // Ignorar errores de permisos en Windows } }); } yamlStringify(obj) { // Función simple para convertir objeto a YAML const stringify = (obj, indent = 0) => { const spaces = ' '.repeat(indent); let result = ''; for (const [key, value] of Object.entries(obj)) { if (value === null || value === undefined) { result += `${spaces}${key}: null\n`; } else if (typeof value === 'object' && !Array.isArray(value)) { result += `${spaces}${key}:\n${stringify(value, indent + 1)}`; } else if (Array.isArray(value)) { result += `${spaces}${key}:\n`; value.forEach(item => { if (typeof item === 'object') { result += `${spaces} -\n${stringify(item, indent + 2)}`; } else { result += `${spaces} - ${item}\n`; } }); } else { result += `${spaces}${key}: ${value}\n`; } } return result; }; return stringify(obj); } writeFile(relativePath, content) { const outputPath = path.join(this.projectPath, relativePath); const dir = path.dirname(outputPath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(outputPath, content); } async updatePackageJson() { const packageJsonPath = path.join(this.projectPath, 'package.json'); if (!fs.existsSync(packageJsonPath)) { console.log('⚠️ package.json not found, skipping script updates'); return; } try { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); // Agregar scripts de Docker if (!packageJson.scripts) { packageJson.scripts = {}; } const dockerScripts = { 'docker:build': 'bash scripts/docker-build.sh', 'docker:dev': 'bash scripts/docker-dev.sh', 'docker:deploy': 'bash scripts/docker-deploy.sh', 'docker:logs': 'bash scripts/docker-logs.sh', 'docker:up': 'docker-compose up -d', 'docker:down': 'docker-compose down', 'docker:restart': 'docker-compose restart', 'docker:clean': 'docker system prune -f' }; let hasChanges = false; for (const [script, command] of Object.entries(dockerScripts)) { if (!packageJson.scripts[script]) { packageJson.scripts[script] = command; hasChanges = true; } } if (hasChanges) { fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); console.log('✅ package.json actualizado con scripts de Docker'); } else { console.log('ℹ️ package.json ya contiene los scripts de Docker'); } } catch (error) { console.error('❌ Error actualizando package.json:', error); } } getGeneratedFiles() { const files = [ 'Dockerfile', 'docker-compose.yml', 'docker-compose.prod.yml', '.dockerignore', 'scripts/docker-build.sh', 'scripts/docker-dev.sh', 'scripts/docker-deploy.sh', 'scripts/docker-logs.sh' ]; if (this.config.enableNginx) { files.push( 'nginx/nginx.conf', 'nginx/ssl/README.md' ); } return files; } } module.exports = DockerModuleInitializer;