veza/veza-desktop/scripts/deploy-production.js

333 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);
});