-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlaunch.py
More file actions
executable file
·114 lines (95 loc) · 3.43 KB
/
launch.py
File metadata and controls
executable file
·114 lines (95 loc) · 3.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#!/usr/bin/env python3
"""Launch BioLab backend + frontend in one shot. Ctrl-C kills both."""
import os
import platform
import shutil
import signal
import subprocess
import sys
import time
ROOT = os.path.dirname(os.path.abspath(__file__))
FRONTEND_DIR = os.path.join(ROOT, "frontend")
IS_WINDOWS = platform.system() == "Windows"
# Resolve venv python — Scripts/python.exe on Windows, bin/python elsewhere
if IS_WINDOWS:
VENV_PYTHON = os.path.join(ROOT, ".venv", "Scripts", "python.exe")
else:
VENV_PYTHON = os.path.join(ROOT, ".venv", "bin", "python")
BACKEND_PORT = int(os.environ.get("BIOLAB_BACKEND_PORT", "8000"))
FRONTEND_PORT = int(os.environ.get("BIOLAB_FRONTEND_PORT", "5173"))
procs: list[subprocess.Popen] = []
def kill_port(port: int) -> None:
"""Kill any process currently holding a port."""
try:
if IS_WINDOWS:
out = subprocess.check_output(
["netstat", "-ano"], text=True, stderr=subprocess.DEVNULL,
)
for line in out.splitlines():
if f":{port}" in line and "LISTENING" in line:
pid = line.strip().split()[-1]
subprocess.run(
["taskkill", "/F", "/PID", pid],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
)
time.sleep(0.5)
else:
out = subprocess.check_output(["lsof", "-ti", f":{port}"], text=True).strip()
for pid in out.splitlines():
os.kill(int(pid), signal.SIGKILL)
time.sleep(0.5)
except (subprocess.CalledProcessError, ValueError, OSError):
pass
def shutdown(*_: object) -> None:
for p in procs:
try:
p.terminate()
except OSError:
pass
deadline = time.time() + 3
for p in procs:
remaining = max(0, deadline - time.time())
try:
p.wait(timeout=remaining)
except subprocess.TimeoutExpired:
p.kill()
sys.exit(0)
def main() -> None:
signal.signal(signal.SIGINT, shutdown)
if hasattr(signal, "SIGTERM"):
signal.signal(signal.SIGTERM, shutdown)
# Free ports
kill_port(BACKEND_PORT)
kill_port(FRONTEND_PORT)
# Backend — uvicorn with app factory
python = VENV_PYTHON if os.path.exists(VENV_PYTHON) else sys.executable
print(f"\033[36m[BioLab]\033[0m Starting backend on :{BACKEND_PORT}")
backend = subprocess.Popen(
[
python, "-m", "uvicorn",
"biolab.api.app:create_app", "--factory",
"--reload", "--port", str(BACKEND_PORT),
],
cwd=ROOT,
)
procs.append(backend)
# Frontend — resolve npm executable (npm.cmd on Windows)
npm = shutil.which("npm") or ("npm.cmd" if IS_WINDOWS else "npm")
print(f"\033[32m[BioLab]\033[0m Starting frontend on :{FRONTEND_PORT}")
frontend = subprocess.Popen(
[npm, "run", "dev", "--", "--port", str(FRONTEND_PORT)],
cwd=FRONTEND_DIR,
)
procs.append(frontend)
print(f"\033[33m[BioLab]\033[0m Ctrl-C to stop both\n")
# Wait — if either dies, kill the other
while True:
for p in procs:
ret = p.poll()
if ret is not None:
name = "Backend" if p is backend else "Frontend"
print(f"\033[31m[BioLab]\033[0m {name} exited with code {ret}")
shutdown()
time.sleep(0.5)
if __name__ == "__main__":
main()