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
JavaScript
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;