Added Windows-optimized launcher: - start-dev.bat: Simple batch file using npm run dev - Automatically kills processes on ports 3001 and 5173 - Uses concurrently to run both services - Single window, simpler than Python launcher - Recommended for Windows users Improved app.py: - Better error output capture and display - Increased wait time to 3 seconds - Shows last 500 characters of error output Windows users now have 3 options: 1. start-dev.bat (simplest - recommended) 2. python app.py (advanced with port management) 3. npm run dev (manual) 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
379 lines
14 KiB
Python
379 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
5 Why Root Cause Analyzer - Application Launcher
|
|
Version: 1.0.0
|
|
Description: Python script to start both backend and frontend services
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import time
|
|
import signal
|
|
import subprocess
|
|
import platform
|
|
from pathlib import Path
|
|
|
|
class Color:
|
|
"""ANSI color codes for terminal output"""
|
|
HEADER = '\033[95m'
|
|
OKBLUE = '\033[94m'
|
|
OKCYAN = '\033[96m'
|
|
OKGREEN = '\033[92m'
|
|
WARNING = '\033[93m'
|
|
FAIL = '\033[91m'
|
|
ENDC = '\033[0m'
|
|
BOLD = '\033[1m'
|
|
UNDERLINE = '\033[4m'
|
|
|
|
class AppLauncher:
|
|
"""Main application launcher class"""
|
|
|
|
def __init__(self):
|
|
self.project_root = Path(__file__).parent
|
|
self.backend_process = None
|
|
self.frontend_process = None
|
|
self.is_windows = platform.system() == 'Windows'
|
|
|
|
def print_banner(self):
|
|
"""Print application banner"""
|
|
print(f"\n{Color.HEADER}{Color.BOLD}{'='*70}{Color.ENDC}")
|
|
print(f"{Color.HEADER}{Color.BOLD} 5 Why Root Cause Analyzer - v1.0.0{Color.ENDC}")
|
|
print(f"{Color.HEADER}{Color.BOLD} Enterprise-grade Root Cause Analysis Tool{Color.ENDC}")
|
|
print(f"{Color.HEADER}{Color.BOLD}{'='*70}{Color.ENDC}\n")
|
|
|
|
def print_info(self, message):
|
|
"""Print info message"""
|
|
print(f"{Color.OKBLUE}[INFO]{Color.ENDC} {message}")
|
|
|
|
def print_success(self, message):
|
|
"""Print success message"""
|
|
print(f"{Color.OKGREEN}[SUCCESS]{Color.ENDC} {message}")
|
|
|
|
def print_warning(self, message):
|
|
"""Print warning message"""
|
|
print(f"{Color.WARNING}[WARNING]{Color.ENDC} {message}")
|
|
|
|
def print_error(self, message):
|
|
"""Print error message"""
|
|
print(f"{Color.FAIL}[ERROR]{Color.ENDC} {message}")
|
|
|
|
def check_node_installed(self):
|
|
"""Check if Node.js is installed"""
|
|
try:
|
|
result = subprocess.run(
|
|
['node', '--version'],
|
|
capture_output=True,
|
|
text=True,
|
|
check=True
|
|
)
|
|
version = result.stdout.strip()
|
|
self.print_success(f"Node.js detected: {version}")
|
|
return True
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
self.print_error("Node.js is not installed or not in PATH")
|
|
self.print_info("Please install Node.js 18+ from https://nodejs.org/")
|
|
return False
|
|
|
|
def check_npm_installed(self):
|
|
"""Check if npm is installed"""
|
|
try:
|
|
# Use npm.cmd on Windows, npm on Unix
|
|
npm_cmd = 'npm.cmd' if self.is_windows else 'npm'
|
|
result = subprocess.run(
|
|
[npm_cmd, '--version'],
|
|
capture_output=True,
|
|
text=True,
|
|
check=True
|
|
)
|
|
version = result.stdout.strip()
|
|
self.print_success(f"npm detected: {version}")
|
|
return True
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
self.print_error("npm is not installed or not in PATH")
|
|
return False
|
|
|
|
def check_dependencies_installed(self):
|
|
"""Check if node_modules exists"""
|
|
node_modules = self.project_root / 'node_modules'
|
|
if node_modules.exists():
|
|
self.print_success("Dependencies installed (node_modules found)")
|
|
return True
|
|
else:
|
|
self.print_warning("Dependencies not installed (node_modules not found)")
|
|
self.print_info("Run 'npm install' to install dependencies")
|
|
return False
|
|
|
|
def check_env_file(self):
|
|
"""Check if .env file exists"""
|
|
env_file = self.project_root / '.env'
|
|
if env_file.exists():
|
|
self.print_success("Environment file (.env) found")
|
|
return True
|
|
else:
|
|
self.print_warning(".env file not found")
|
|
self.print_info("Copy .env.example to .env and configure it")
|
|
return False
|
|
|
|
def check_port_available(self, port):
|
|
"""Check if a port is available"""
|
|
import socket
|
|
try:
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
s.bind(('', port))
|
|
return True
|
|
except OSError:
|
|
return False
|
|
|
|
def kill_process_on_port(self, port):
|
|
"""Kill process using the specified port"""
|
|
try:
|
|
if self.is_windows:
|
|
# Find PID using netstat
|
|
result = subprocess.run(
|
|
['netstat', '-ano'],
|
|
capture_output=True,
|
|
text=True,
|
|
check=True
|
|
)
|
|
|
|
for line in result.stdout.split('\n'):
|
|
if f':{port}' in line and 'LISTENING' in line:
|
|
parts = line.split()
|
|
if len(parts) >= 5:
|
|
pid = parts[-1]
|
|
subprocess.run(['taskkill', '/F', '/PID', pid], capture_output=True)
|
|
self.print_success(f"Killed existing process on port {port}")
|
|
time.sleep(1)
|
|
return True
|
|
else:
|
|
# Unix/Linux
|
|
subprocess.run(
|
|
['lsof', '-ti', f':{port}', '|', 'xargs', 'kill', '-9'],
|
|
shell=True,
|
|
capture_output=True
|
|
)
|
|
self.print_success(f"Killed existing process on port {port}")
|
|
time.sleep(1)
|
|
return True
|
|
except Exception as e:
|
|
self.print_warning(f"Could not kill process on port {port}: {str(e)}")
|
|
return False
|
|
|
|
def start_backend(self):
|
|
"""Start the backend server"""
|
|
self.print_info("Starting backend server...")
|
|
|
|
try:
|
|
# Use 'npm.cmd' on Windows, 'npm' on Unix
|
|
npm_cmd = 'npm.cmd' if self.is_windows else 'npm'
|
|
|
|
self.backend_process = subprocess.Popen(
|
|
[npm_cmd, 'run', 'server'],
|
|
cwd=str(self.project_root),
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
text=True,
|
|
bufsize=1,
|
|
universal_newlines=True,
|
|
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if self.is_windows else 0
|
|
)
|
|
|
|
# Wait a bit and check if process started successfully
|
|
time.sleep(3)
|
|
|
|
if self.backend_process.poll() is None:
|
|
self.print_success("Backend server started successfully")
|
|
self.print_info("Backend running at: http://localhost:3001")
|
|
return True
|
|
else:
|
|
self.print_error("Backend server failed to start")
|
|
# Try to read error output
|
|
if self.backend_process.stdout:
|
|
try:
|
|
output = self.backend_process.stdout.read()
|
|
if output:
|
|
print(f"\n{Color.FAIL}Error output:{Color.ENDC}")
|
|
print(output[-500:] if len(output) > 500 else output) # Last 500 chars
|
|
except:
|
|
pass
|
|
return False
|
|
|
|
except Exception as e:
|
|
self.print_error(f"Failed to start backend: {str(e)}")
|
|
return False
|
|
|
|
def start_frontend(self):
|
|
"""Start the frontend development server"""
|
|
self.print_info("Starting frontend development server...")
|
|
|
|
try:
|
|
# Use 'npm.cmd' on Windows, 'npm' on Unix
|
|
npm_cmd = 'npm.cmd' if self.is_windows else 'npm'
|
|
|
|
self.frontend_process = subprocess.Popen(
|
|
[npm_cmd, 'run', 'client'],
|
|
cwd=str(self.project_root),
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
text=True,
|
|
bufsize=1,
|
|
universal_newlines=True,
|
|
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if self.is_windows else 0
|
|
)
|
|
|
|
# Wait a bit and check if process started successfully
|
|
time.sleep(3)
|
|
|
|
if self.frontend_process.poll() is None:
|
|
self.print_success("Frontend server started successfully")
|
|
self.print_info("Frontend running at: http://localhost:5173")
|
|
return True
|
|
else:
|
|
self.print_error("Frontend server failed to start")
|
|
return False
|
|
|
|
except Exception as e:
|
|
self.print_error(f"Failed to start frontend: {str(e)}")
|
|
return False
|
|
|
|
def monitor_processes(self):
|
|
"""Monitor both processes and print their output"""
|
|
self.print_info("\n" + "="*70)
|
|
self.print_success("Both services are running!")
|
|
self.print_info("="*70)
|
|
print(f"\n{Color.OKGREEN}{Color.BOLD}Access the application:{Color.ENDC}")
|
|
print(f" {Color.OKCYAN}Frontend:{Color.ENDC} http://localhost:5173")
|
|
print(f" {Color.OKCYAN}Backend API:{Color.ENDC} http://localhost:3001")
|
|
print(f" {Color.OKCYAN}API Health:{Color.ENDC} http://localhost:3001/health")
|
|
print(f"\n{Color.OKGREEN}{Color.BOLD}Test Accounts:{Color.ENDC}")
|
|
print(f" {Color.OKCYAN}Admin:{Color.ENDC} admin@example.com / Admin@123456")
|
|
print(f" {Color.OKCYAN}User1:{Color.ENDC} user001@example.com / User@123456")
|
|
print(f" {Color.OKCYAN}User2:{Color.ENDC} user002@example.com / User@123456")
|
|
print(f"\n{Color.WARNING}Press Ctrl+C to stop all services{Color.ENDC}\n")
|
|
|
|
try:
|
|
# Keep the script running and monitor processes
|
|
while True:
|
|
# Check if backend is still running
|
|
if self.backend_process.poll() is not None:
|
|
self.print_error("Backend process terminated unexpectedly")
|
|
break
|
|
|
|
# Check if frontend is still running
|
|
if self.frontend_process.poll() is not None:
|
|
self.print_error("Frontend process terminated unexpectedly")
|
|
break
|
|
|
|
time.sleep(1)
|
|
|
|
except KeyboardInterrupt:
|
|
self.print_info("\nReceived shutdown signal...")
|
|
|
|
def cleanup(self):
|
|
"""Clean up and terminate all processes"""
|
|
self.print_info("Shutting down services...")
|
|
|
|
# Terminate frontend
|
|
if self.frontend_process and self.frontend_process.poll() is None:
|
|
try:
|
|
if self.is_windows:
|
|
# On Windows, use taskkill to terminate process tree
|
|
subprocess.run(
|
|
['taskkill', '/F', '/T', '/PID', str(self.frontend_process.pid)],
|
|
capture_output=True
|
|
)
|
|
else:
|
|
self.frontend_process.terminate()
|
|
self.frontend_process.wait(timeout=5)
|
|
self.print_success("Frontend server stopped")
|
|
except Exception as e:
|
|
self.print_warning(f"Error stopping frontend: {str(e)}")
|
|
|
|
# Terminate backend
|
|
if self.backend_process and self.backend_process.poll() is None:
|
|
try:
|
|
if self.is_windows:
|
|
# On Windows, use taskkill to terminate process tree
|
|
subprocess.run(
|
|
['taskkill', '/F', '/T', '/PID', str(self.backend_process.pid)],
|
|
capture_output=True
|
|
)
|
|
else:
|
|
self.backend_process.terminate()
|
|
self.backend_process.wait(timeout=5)
|
|
self.print_success("Backend server stopped")
|
|
except Exception as e:
|
|
self.print_warning(f"Error stopping backend: {str(e)}")
|
|
|
|
self.print_success("All services stopped successfully")
|
|
|
|
def run(self):
|
|
"""Main run method"""
|
|
self.print_banner()
|
|
|
|
# Pre-flight checks
|
|
self.print_info("Running pre-flight checks...")
|
|
|
|
if not self.check_node_installed():
|
|
return 1
|
|
|
|
if not self.check_npm_installed():
|
|
return 1
|
|
|
|
if not self.check_dependencies_installed():
|
|
self.print_warning("Please run 'npm install' first")
|
|
return 1
|
|
|
|
if not self.check_env_file():
|
|
self.print_warning("Please configure .env file first")
|
|
return 1
|
|
|
|
self.print_success("All pre-flight checks passed!\n")
|
|
|
|
# Check and free ports if needed
|
|
backend_port = 3001
|
|
frontend_port = 5173
|
|
|
|
if not self.check_port_available(backend_port):
|
|
self.print_warning(f"Port {backend_port} is already in use")
|
|
self.print_info("Attempting to free the port...")
|
|
self.kill_process_on_port(backend_port)
|
|
|
|
if not self.check_port_available(frontend_port):
|
|
self.print_warning(f"Port {frontend_port} is already in use")
|
|
self.print_info("Attempting to free the port...")
|
|
self.kill_process_on_port(frontend_port)
|
|
|
|
# Start services
|
|
if not self.start_backend():
|
|
self.cleanup()
|
|
return 1
|
|
|
|
time.sleep(2) # Give backend time to start
|
|
|
|
if not self.start_frontend():
|
|
self.cleanup()
|
|
return 1
|
|
|
|
# Monitor processes
|
|
try:
|
|
self.monitor_processes()
|
|
finally:
|
|
self.cleanup()
|
|
|
|
return 0
|
|
|
|
def main():
|
|
"""Main entry point"""
|
|
launcher = AppLauncher()
|
|
|
|
try:
|
|
sys.exit(launcher.run())
|
|
except Exception as e:
|
|
print(f"\n{Color.FAIL}[FATAL ERROR]{Color.ENDC} {str(e)}")
|
|
launcher.cleanup()
|
|
sys.exit(1)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|