334 lines
9.1 KiB
JavaScript
334 lines
9.1 KiB
JavaScript
|
|
#!/usr/bin/env node
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Script de déploiement production pour Veza V5 Ultra
|
||
|
|
* Objectif: Déploiement zéro downtime avec rollback automatique
|
||
|
|
*/
|
||
|
|
|
||
|
|
const { execSync } = require('child_process');
|
||
|
|
const fs = require('fs');
|
||
|
|
const path = require('path');
|
||
|
|
|
||
|
|
console.log('🚀 VEZA V5 ULTRA - Production Deployment');
|
||
|
|
console.log('========================================\n');
|
||
|
|
|
||
|
|
// Configuration de déploiement
|
||
|
|
const deployConfig = {
|
||
|
|
staging: {
|
||
|
|
url: 'https://staging.veza.app',
|
||
|
|
branch: 'staging',
|
||
|
|
healthCheck: '/api/health',
|
||
|
|
},
|
||
|
|
production: {
|
||
|
|
url: 'https://veza.app',
|
||
|
|
branch: 'main',
|
||
|
|
healthCheck: '/api/health',
|
||
|
|
},
|
||
|
|
backup: {
|
||
|
|
enabled: true,
|
||
|
|
maxBackups: 5,
|
||
|
|
retentionDays: 30,
|
||
|
|
},
|
||
|
|
monitoring: {
|
||
|
|
sentry: process.env.SENTRY_DSN,
|
||
|
|
analytics: process.env.GA_ID,
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
// Fonction pour exécuter une commande avec logging
|
||
|
|
function execCommand(command, description) {
|
||
|
|
console.log(`\n🔧 ${description}...`);
|
||
|
|
try {
|
||
|
|
const result = execSync(command, {
|
||
|
|
encoding: 'utf8',
|
||
|
|
stdio: 'pipe'
|
||
|
|
});
|
||
|
|
console.log(`✅ ${description} completed`);
|
||
|
|
return { success: true, result };
|
||
|
|
} catch (error) {
|
||
|
|
console.log(`❌ ${description} failed:`, error.message);
|
||
|
|
return { success: false, error: error.message };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fonction pour vérifier la santé de l'application
|
||
|
|
function healthCheck(url, maxRetries = 5) {
|
||
|
|
console.log(`\n🏥 Health check: ${url}`);
|
||
|
|
|
||
|
|
for (let i = 0; i < maxRetries; i++) {
|
||
|
|
try {
|
||
|
|
const result = execSync(`curl -f -s ${url}${deployConfig.staging.healthCheck}`, {
|
||
|
|
encoding: 'utf8',
|
||
|
|
timeout: 10000
|
||
|
|
});
|
||
|
|
|
||
|
|
if (result.includes('ok') || result.includes('healthy')) {
|
||
|
|
console.log(`✅ Health check passed (attempt ${i + 1})`);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.log(`⚠️ Health check failed (attempt ${i + 1}/${maxRetries})`);
|
||
|
|
if (i < maxRetries - 1) {
|
||
|
|
console.log('⏳ Waiting 10s before retry...');
|
||
|
|
execSync('sleep 10');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log('❌ Health check failed after all retries');
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fonction pour créer un backup
|
||
|
|
function createBackup() {
|
||
|
|
if (!deployConfig.backup.enabled) return true;
|
||
|
|
|
||
|
|
console.log('\n💾 Creating backup...');
|
||
|
|
|
||
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||
|
|
const backupDir = path.join(__dirname, '..', 'backups', timestamp);
|
||
|
|
|
||
|
|
try {
|
||
|
|
execSync(`mkdir -p ${backupDir}`, { stdio: 'pipe' });
|
||
|
|
execSync(`cp -r dist ${backupDir}/`, { stdio: 'pipe' });
|
||
|
|
execSync(`cp package.json ${backupDir}/`, { stdio: 'pipe' });
|
||
|
|
execSync(`cp package-lock.json ${backupDir}/`, { stdio: 'pipe' });
|
||
|
|
|
||
|
|
console.log(`✅ Backup created: ${backupDir}`);
|
||
|
|
return true;
|
||
|
|
} catch (error) {
|
||
|
|
console.log(`❌ Backup failed:`, error.message);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fonction pour nettoyer les anciens backups
|
||
|
|
function cleanupBackups() {
|
||
|
|
if (!deployConfig.backup.enabled) return;
|
||
|
|
|
||
|
|
console.log('\n🧹 Cleaning up old backups...');
|
||
|
|
|
||
|
|
const backupsDir = path.join(__dirname, '..', 'backups');
|
||
|
|
if (!fs.existsSync(backupsDir)) return;
|
||
|
|
|
||
|
|
try {
|
||
|
|
const backups = fs.readdirSync(backupsDir)
|
||
|
|
.map(name => ({
|
||
|
|
name,
|
||
|
|
path: path.join(backupsDir, name),
|
||
|
|
mtime: fs.statSync(path.join(backupsDir, name)).mtime
|
||
|
|
}))
|
||
|
|
.sort((a, b) => b.mtime - a.mtime);
|
||
|
|
|
||
|
|
// Garder seulement les 5 derniers backups
|
||
|
|
const toDelete = backups.slice(deployConfig.backup.maxBackups);
|
||
|
|
|
||
|
|
toDelete.forEach(backup => {
|
||
|
|
execSync(`rm -rf ${backup.path}`, { stdio: 'pipe' });
|
||
|
|
console.log(`🗑️ Deleted old backup: ${backup.name}`);
|
||
|
|
});
|
||
|
|
|
||
|
|
console.log(`✅ Cleanup completed`);
|
||
|
|
} catch (error) {
|
||
|
|
console.log(`⚠️ Cleanup failed:`, error.message);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fonction pour déployer sur staging
|
||
|
|
function deployStaging() {
|
||
|
|
console.log('\n🚀 DEPLOYING TO STAGING');
|
||
|
|
console.log('=======================');
|
||
|
|
|
||
|
|
// 1. Préparation
|
||
|
|
console.log('\n📋 Step 1: Preparation');
|
||
|
|
execCommand('git fetch origin', 'Fetching latest changes');
|
||
|
|
execCommand('git checkout staging', 'Switching to staging branch');
|
||
|
|
execCommand('git pull origin staging', 'Pulling latest staging changes');
|
||
|
|
|
||
|
|
// 2. Build
|
||
|
|
console.log('\n🔨 Step 2: Build');
|
||
|
|
execCommand('npm ci', 'Installing dependencies');
|
||
|
|
execCommand('npm run build:optimized', 'Building optimized bundle');
|
||
|
|
|
||
|
|
// 3. Tests
|
||
|
|
console.log('\n🧪 Step 3: Tests');
|
||
|
|
execCommand('npm run prod:test', 'Running production tests');
|
||
|
|
|
||
|
|
// 4. Backup
|
||
|
|
console.log('\n💾 Step 4: Backup');
|
||
|
|
createBackup();
|
||
|
|
|
||
|
|
// 5. Deploy
|
||
|
|
console.log('\n🚀 Step 5: Deploy');
|
||
|
|
execCommand('npm run deploy:staging', 'Deploying to staging');
|
||
|
|
|
||
|
|
// 6. Health Check
|
||
|
|
console.log('\n🏥 Step 6: Health Check');
|
||
|
|
if (healthCheck(deployConfig.staging.url)) {
|
||
|
|
console.log('✅ Staging deployment successful!');
|
||
|
|
return true;
|
||
|
|
} else {
|
||
|
|
console.log('❌ Staging deployment failed!');
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fonction pour déployer sur production
|
||
|
|
function deployProduction() {
|
||
|
|
console.log('\n🚀 DEPLOYING TO PRODUCTION');
|
||
|
|
console.log('==========================');
|
||
|
|
|
||
|
|
// 1. Préparation
|
||
|
|
console.log('\n📋 Step 1: Preparation');
|
||
|
|
execCommand('git checkout main', 'Switching to main branch');
|
||
|
|
execCommand('git pull origin main', 'Pulling latest main changes');
|
||
|
|
|
||
|
|
// 2. Build
|
||
|
|
console.log('\n🔨 Step 2: Build');
|
||
|
|
execCommand('npm ci', 'Installing dependencies');
|
||
|
|
execCommand('npm run build:optimized', 'Building optimized bundle');
|
||
|
|
|
||
|
|
// 3. Tests
|
||
|
|
console.log('\n🧪 Step 3: Tests');
|
||
|
|
execCommand('npm run prod:test', 'Running production tests');
|
||
|
|
|
||
|
|
// 4. Backup
|
||
|
|
console.log('\n💾 Step 4: Backup');
|
||
|
|
createBackup();
|
||
|
|
|
||
|
|
// 5. Deploy
|
||
|
|
console.log('\n🚀 Step 5: Deploy');
|
||
|
|
execCommand('npm run deploy:prod', 'Deploying to production');
|
||
|
|
|
||
|
|
// 6. Health Check
|
||
|
|
console.log('\n🏥 Step 6: Health Check');
|
||
|
|
if (healthCheck(deployConfig.production.url)) {
|
||
|
|
console.log('✅ Production deployment successful!');
|
||
|
|
return true;
|
||
|
|
} else {
|
||
|
|
console.log('❌ Production deployment failed!');
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fonction pour rollback
|
||
|
|
function rollback() {
|
||
|
|
console.log('\n🔄 ROLLBACK');
|
||
|
|
console.log('===========');
|
||
|
|
|
||
|
|
const backupsDir = path.join(__dirname, '..', 'backups');
|
||
|
|
if (!fs.existsSync(backupsDir)) {
|
||
|
|
console.log('❌ No backups found for rollback');
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
const backups = fs.readdirSync(backupsDir)
|
||
|
|
.map(name => ({
|
||
|
|
name,
|
||
|
|
path: path.join(backupsDir, name),
|
||
|
|
mtime: fs.statSync(path.join(backupsDir, name)).mtime
|
||
|
|
}))
|
||
|
|
.sort((a, b) => b.mtime - a.mtime);
|
||
|
|
|
||
|
|
if (backups.length === 0) {
|
||
|
|
console.log('❌ No backups available for rollback');
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
const latestBackup = backups[0];
|
||
|
|
console.log(`🔄 Rolling back to: ${latestBackup.name}`);
|
||
|
|
|
||
|
|
try {
|
||
|
|
execSync(`rm -rf dist`, { stdio: 'pipe' });
|
||
|
|
execSync(`cp -r ${latestBackup.path}/dist .`, { stdio: 'pipe' });
|
||
|
|
execSync(`npm run deploy:prod`, { stdio: 'pipe' });
|
||
|
|
|
||
|
|
console.log('✅ Rollback completed');
|
||
|
|
return true;
|
||
|
|
} catch (error) {
|
||
|
|
console.log('❌ Rollback failed:', error.message);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fonction pour monitoring post-deploy
|
||
|
|
function setupMonitoring() {
|
||
|
|
console.log('\n📊 Setting up monitoring...');
|
||
|
|
|
||
|
|
// Sentry
|
||
|
|
if (deployConfig.monitoring.sentry) {
|
||
|
|
console.log('✅ Sentry monitoring enabled');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Analytics
|
||
|
|
if (deployConfig.monitoring.analytics) {
|
||
|
|
console.log('✅ Google Analytics enabled');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Logs
|
||
|
|
console.log('✅ Application logs configured');
|
||
|
|
|
||
|
|
console.log('📊 Monitoring dashboard: https://dashboard.veza.app');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fonction principale
|
||
|
|
async function main() {
|
||
|
|
const args = process.argv.slice(2);
|
||
|
|
const command = args[0] || 'staging';
|
||
|
|
|
||
|
|
console.log(`🎯 Deployment target: ${command}`);
|
||
|
|
|
||
|
|
let success = false;
|
||
|
|
|
||
|
|
switch (command) {
|
||
|
|
case 'staging':
|
||
|
|
success = deployStaging();
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'production':
|
||
|
|
success = deployProduction();
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'rollback':
|
||
|
|
success = rollback();
|
||
|
|
break;
|
||
|
|
|
||
|
|
default:
|
||
|
|
console.log('❌ Unknown command. Use: staging, production, or rollback');
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (success) {
|
||
|
|
setupMonitoring();
|
||
|
|
cleanupBackups();
|
||
|
|
|
||
|
|
console.log('\n🎉 DEPLOYMENT SUCCESSFUL!');
|
||
|
|
console.log('========================');
|
||
|
|
console.log('✅ Application deployed');
|
||
|
|
console.log('✅ Health checks passed');
|
||
|
|
console.log('✅ Monitoring active');
|
||
|
|
console.log('✅ Backup created');
|
||
|
|
|
||
|
|
console.log('\n📱 Next steps:');
|
||
|
|
console.log('- Monitor application metrics');
|
||
|
|
console.log('- Check error logs');
|
||
|
|
console.log('- Verify user feedback');
|
||
|
|
console.log('- Update documentation');
|
||
|
|
|
||
|
|
} else {
|
||
|
|
console.log('\n❌ DEPLOYMENT FAILED!');
|
||
|
|
console.log('====================');
|
||
|
|
console.log('❌ Check logs for errors');
|
||
|
|
console.log('❌ Consider rollback if needed');
|
||
|
|
console.log('❌ Fix issues before retry');
|
||
|
|
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Exécuter le script
|
||
|
|
main().catch(error => {
|
||
|
|
console.error('❌ Deployment script failed:', error);
|
||
|
|
process.exit(1);
|
||
|
|
});
|