fix(6): standalone launcher improvements
- Use uvicorn CLI directly (not python -c workaround) - Add process group handling for cleaner shutdown - Silent npm output during setup - Better error messages and logging - Support graceful SIGTERM/SIGINT handling
This commit is contained in:
@@ -10,7 +10,6 @@ import sys
|
||||
import subprocess
|
||||
import signal
|
||||
import time
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@@ -106,32 +105,36 @@ class StandaloneServerManager:
|
||||
subprocess.run(
|
||||
["npm", "ci", "--silent"],
|
||||
cwd=str(frontend_dir),
|
||||
check=True
|
||||
check=True,
|
||||
capture_output=True
|
||||
)
|
||||
|
||||
# Build if needed
|
||||
if not (frontend_dir / ".next").exists():
|
||||
if not (frontend_dir / ".next" / "standalone").exists():
|
||||
self.log("Building Next.js application...")
|
||||
subprocess.run(
|
||||
["npm", "run", "build"],
|
||||
cwd=str(frontend_dir),
|
||||
check=True
|
||||
check=True,
|
||||
capture_output=True
|
||||
)
|
||||
|
||||
def start_backend(self):
|
||||
"""Start FastAPI backend server"""
|
||||
"""Start FastAPI backend server using uvicorn directly from project root"""
|
||||
self.log(f"Starting backend on port {self.backend_port}...")
|
||||
backend_log = self.logs_dir / "backend.log"
|
||||
|
||||
backend_dir = self.root_dir / "backend"
|
||||
|
||||
env = os.environ.copy()
|
||||
env['PATH'] = f"{self.python_exe.parent}:{env['PATH']}"
|
||||
|
||||
# Use uvicorn CLI from venv to run the app
|
||||
with open(backend_log, 'w') as log_f:
|
||||
self.backend_process = subprocess.Popen(
|
||||
[
|
||||
str(self.python_exe),
|
||||
"-m", "uvicorn",
|
||||
"-m",
|
||||
"uvicorn",
|
||||
"main:app",
|
||||
"--host", "0.0.0.0",
|
||||
"--port", str(self.backend_port),
|
||||
@@ -140,7 +143,8 @@ class StandaloneServerManager:
|
||||
cwd=str(backend_dir),
|
||||
stdout=log_f,
|
||||
stderr=subprocess.STDOUT,
|
||||
env=env
|
||||
env=env,
|
||||
preexec_fn=os.setsid # Create new process group to avoid signal issues
|
||||
)
|
||||
|
||||
self.log(f"✓ Backend started (PID: {self.backend_process.pid})")
|
||||
@@ -153,6 +157,7 @@ class StandaloneServerManager:
|
||||
|
||||
env = os.environ.copy()
|
||||
env['PORT'] = str(self.frontend_port)
|
||||
env['NODE_ENV'] = 'production'
|
||||
|
||||
with open(frontend_log, 'w') as log_f:
|
||||
self.frontend_process = subprocess.Popen(
|
||||
@@ -160,7 +165,8 @@ class StandaloneServerManager:
|
||||
cwd=str(frontend_dir),
|
||||
stdout=log_f,
|
||||
stderr=subprocess.STDOUT,
|
||||
env=env
|
||||
env=env,
|
||||
preexec_fn=os.setsid # Create new process group
|
||||
)
|
||||
|
||||
self.log(f"✓ Frontend started (PID: {self.frontend_process.pid})")
|
||||
@@ -205,8 +211,7 @@ class StandaloneServerManager:
|
||||
print(f" Frontend: tail -f {self.logs_dir / 'frontend.log'}")
|
||||
print(f" All logs: tail -f {self.logs_dir}/*.log")
|
||||
print("\nTo stop servers:")
|
||||
print(f" kill {self.backend_process.pid} {self.frontend_process.pid}")
|
||||
print("\nPress Ctrl+C to stop both servers")
|
||||
print(f" Press Ctrl+C in this terminal")
|
||||
print("="*60 + "\n")
|
||||
|
||||
def run(self):
|
||||
@@ -225,14 +230,16 @@ class StandaloneServerManager:
|
||||
self.print_summary()
|
||||
|
||||
# Monitor processes
|
||||
self.log("✓ Servers are running!")
|
||||
self.log("✓ Servers are running! Press Ctrl+C to stop.")
|
||||
while True:
|
||||
# Check if processes are alive
|
||||
if self.backend_process.poll() is not None:
|
||||
self.log(f"⚠ Backend process exited with code {self.backend_process.returncode}")
|
||||
self.log(" Check logs/backend.log for details")
|
||||
|
||||
if self.frontend_process.poll() is not None:
|
||||
self.log(f"⚠ Frontend process exited with code {self.frontend_process.returncode}")
|
||||
self.log(" Check logs/frontend.log for details")
|
||||
|
||||
time.sleep(5)
|
||||
|
||||
@@ -244,19 +251,27 @@ class StandaloneServerManager:
|
||||
|
||||
def cleanup(self):
|
||||
"""Gracefully shutdown servers"""
|
||||
import signal
|
||||
|
||||
if self.backend_process:
|
||||
try:
|
||||
self.backend_process.terminate()
|
||||
os.killpg(os.getpgid(self.backend_process.pid), signal.SIGTERM)
|
||||
self.backend_process.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
self.backend_process.kill()
|
||||
except Exception:
|
||||
try:
|
||||
self.backend_process.kill()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if self.frontend_process:
|
||||
try:
|
||||
self.frontend_process.terminate()
|
||||
os.killpg(os.getpgid(self.frontend_process.pid), signal.SIGTERM)
|
||||
self.frontend_process.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
self.frontend_process.kill()
|
||||
except Exception:
|
||||
try:
|
||||
self.frontend_process.kill()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self.log("✓ Servers stopped")
|
||||
sys.exit(0)
|
||||
|
||||
Reference in New Issue
Block a user