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:
2026-04-22 18:44:18 +03:00
parent 5f9c6aee5a
commit bd39f6f1b7

View File

@@ -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)