#!/usr/bin/env node const { spawn, execSync } = require('child_process'); const path = require('path'); const colors = { reset: '\x1b[0m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m', white: '\x1b[37m', bold: '\x1b[1m' }; function log(message, color = colors.white) { console.log(`${color}${message}${colors.reset}`); } function logStep(step, message) { log(`[${step}] ${message}`, colors.cyan); } function logSuccess(message) { log(`✅ ${message}`, colors.green); } function logError(message) { log(`❌ ${message}`, colors.red); } function logWarning(message) { log(`⚠️ ${message}`, colors.yellow); } async function checkDockerRunning() { try { execSync('docker info', { stdio: 'ignore' }); return true; } catch (error) { return false; } } async function checkServiceRunning(containerName) { try { const result = execSync(`docker ps --filter "name=${containerName}" --filter "status=running" --format "{{.Names}}"`, { encoding: 'utf8' }); return result.trim() === containerName; } catch (error) { return false; } } async function checkPortInUse(port) { try { execSync(`lsof -i:${port}`, { stdio: 'ignore' }); return true; } catch (error) { return false; } } async function waitForService(serviceName, checkCommand, maxAttempts = 30) { logStep('WAIT', `Waiting for ${serviceName} to be ready...`); for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { execSync(checkCommand, { stdio: 'ignore' }); logSuccess(`${serviceName} is ready!`); return true; } catch (error) { if (attempt === maxAttempts) { logError(`${serviceName} failed to start after ${maxAttempts} attempts`); return false; } process.stdout.write(`⏳ Waiting for ${serviceName}... (${attempt}/${maxAttempts})\r`); await new Promise(resolve => setTimeout(resolve, 1000)); } } return false; } async function startServices() { logStep('DOCKER', 'Checking if Docker is running...'); if (!await checkDockerRunning()) { logError('Docker is not running. Please start Docker and try again.'); process.exit(1); } logSuccess('Docker is running'); // Check if services are already running const pgRunning = await checkServiceRunning('portal-postgres'); const redisRunning = await checkServiceRunning('portal-redis'); if (pgRunning && redisRunning) { logSuccess('PostgreSQL and Redis are already running'); } else { logStep('SERVICES', 'Starting PostgreSQL and Redis...'); try { execSync('docker-compose -f tools/deployment/docker-compose.yml up -d', { stdio: 'inherit' }); logSuccess('Services started'); } catch (error) { logError(`Failed to start services: ${error.message}`); process.exit(1); } } // Wait for PostgreSQL const pgReady = await waitForService( 'PostgreSQL', 'docker exec portal-postgres pg_isready -U app -d portal' ); if (!pgReady) { process.exit(1); } // Wait for Redis const redisReady = await waitForService( 'Redis', 'docker exec portal-redis redis-cli ping' ); if (!redisReady) { process.exit(1); } } async function startPrismaStudio() { logStep('DB', 'Starting Prisma Studio...'); // Check if Prisma Studio port (5555) is already in use const studioPortInUse = await checkPortInUse(5555); if (studioPortInUse) { logSuccess('Prisma Studio is already running on port 5555'); return null; } const studioProcess = spawn('pnpm', ['--filter', '@customer-portal/bff', 'run', 'db:studio'], { stdio: 'pipe', cwd: process.cwd(), detached: true }); // Wait a moment for Prisma Studio to start setTimeout(() => { logSuccess('Prisma Studio started on http://localhost:5555'); }, 3000); return studioProcess; } async function startDevelopment() { logStep('DEV', 'Starting frontend and backend in development mode...'); // Start Prisma Studio first const studioProcess = await startPrismaStudio(); const devProcess = spawn('pnpm', ['--parallel', '--recursive', 'run', 'dev'], { stdio: 'inherit', cwd: process.cwd() }); // Show ready message after services start setTimeout(() => { logSuccess('Development environment is fully ready!'); log(` 🎉 ${colors.bold}${colors.green}All services are running:${colors.reset} 🖥️ Frontend: ${colors.cyan}http://localhost:3000${colors.reset} 🔌 Backend: ${colors.cyan}http://localhost:4000${colors.reset} 🗄️ Database: ${colors.cyan}http://localhost:5555${colors.reset} `, colors.white); }, 8000); // Wait 8 seconds for Next.js to be ready // Handle graceful shutdown process.on('SIGINT', () => { log('\n🛑 Shutting down development servers...', colors.yellow); if (studioProcess) { studioProcess.kill('SIGTERM'); } devProcess.kill('SIGINT'); setTimeout(() => { log('👋 Development servers stopped', colors.green); process.exit(0); }, 1000); }); devProcess.on('close', (code) => { if (studioProcess) { studioProcess.kill('SIGTERM'); } if (code !== 0) { logError(`Development servers exited with code ${code}`); } process.exit(code); }); } async function main() { log(` ${colors.bold}${colors.blue}🚀 Customer Portal Development Environment${colors.reset} ${colors.cyan}==============================================${colors.reset} `, colors.bold); try { await startServices(); await startDevelopment(); } catch (error) { logError(`Failed to start development environment: ${error.message}`); process.exit(1); } } if (require.main === module) { main(); }