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
JavaScript
/**
* 🚀 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);
}