"""Own all development child processes, including npm and uvicorn descendants.""" import argparse import os import signal import subprocess import sys import time import urllib.request import webbrowser from pathlib import Path root = Path(__file__).resolve().parents[1] parser = argparse.ArgumentParser() parser.add_argument("--port", type=int, default=8010) parser.add_argument("--no-open", action="store_true") args = parser.parse_args() url = f"http://127.0.0.1:{args.port}" stop = False def request_stop(*_): global stop stop = True signal.signal(signal.SIGINT, request_stop) signal.signal(signal.SIGTERM, request_stop) processes = [] logs = [] try: for cmd, cwd, logname in [ (["npm", "run", "watch"], root / "frontend", "frontend.log"), ( [ sys.executable, "-m", "uvicorn", "app.main:app", "--app-dir", "backend", "--host", "127.0.0.1", "--port", str(args.port), "--reload", "--reload-dir", "backend/app", ], root, "backend.log", ), ]: log = (root / ".runtime" / logname).open("w") logs.append(log) processes.append( subprocess.Popen( cmd, cwd=cwd, stdout=log, stderr=subprocess.STDOUT, start_new_session=True, ) ) ready = False for _ in range(60): if stop: break if any(p.poll() is not None for p in processes): raise RuntimeError( "Um processo encerrou. Consulte .runtime/backend.log e frontend.log." ) try: with urllib.request.urlopen(url + "/api/health", timeout=1) as r: ready = r.status == 200 except Exception: pass if ready: break time.sleep(1) if not ready and not stop: raise RuntimeError( "A aplicação não respondeu em 60 segundos. Consulte .runtime/backend.log." ) if ready: from dotenv import dotenv_values settings = dotenv_values(root / ".env") identity = ( "Login: mesmos usuários e senhas da MESA (matrícula sem dígito)." if os.getenv("AUTH_PROVIDER", settings.get("AUTH_PROVIDER", "mesa")) == "mesa" else f'Acesso inicial: {root / ".runtime/dev-access.txt"}' ) print( f"\nCARLA em desenvolvimento: {url}\nPorta de entrada: carla\n{identity}\nFrontend: recompila ao salvar; atualize a página para ver as mudanças.\nBackend: recarrega automaticamente. Logs em .runtime/.\nCtrl+C encerra todos os processos.\n", flush=True, ) if not args.no_open: webbrowser.open(url) while not stop: if any(p.poll() is not None for p in processes): raise RuntimeError("Um processo encerrou; consulte os logs em .runtime/.") time.sleep(0.5) except Exception as exc: print(str(exc), file=sys.stderr) sys.exit(1) finally: for process in processes: try: os.killpg(process.pid, signal.SIGTERM) except ProcessLookupError: pass for process in processes: try: process.wait(timeout=7) except subprocess.TimeoutExpired: try: os.killpg(process.pid, signal.SIGKILL) except ProcessLookupError: pass for log in logs: log.close()