# -*- coding: utf-8 -*- """빌드판 서버(555-2) — 앱 · CAD 를 빌드하고 `vite preview` 를 띄운다(폰 · 외부 접속용). 실행: ./venv/Scripts/python.exe tools/preview_up.py (빌드 + 띄움 · 수십 초) ./venv/Scripts/python.exe tools/preview_up.py --stop (끄기만) - 개발 서버(vite)는 파일 수백 개를 하나씩 보내 느린 연결(Tailscale · 폰)에서 첫 화면이 늦다 — 빌드판은 묶은 파일 몇 개만 보낸다. - 포트 = 개발 포트 − 1000(메인 5173 → 4173 · 서브 5181 → 4181) · 개발 포트 · API 포트는 환경변수(AISLO_VITE_PORT · AISLO_API_PORT) > `OWNERS.md` 이 폴더 줄 > 8000 · 5173. - /api · /b07-cad 는 같은 폴더 백엔드로 넘김(`vite.config.ts` preview) — 백엔드는 따로 켜 둠. - 빌드 산출물 = config/node_modules/.build · CAD dist(둘 다 git 밖) · 로그 tmp/preview.log. - 다시 부르면 옛 것을 끄고 새로 빌드해 띄움(preview 는 띄울 때의 파일 목록을 기억). """ import os import re import subprocess import sys import time import urllib.request ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) TMP = os.path.join(ROOT, "tmp") PID_FILE = os.path.join(TMP, "preview.pid") LOG = os.path.join(TMP, "preview.log") VITE = os.path.join(ROOT, "config", "node_modules", "vite", "bin", "vite.js") def owners_ports() -> tuple[int, int] | None: """OWNERS.md 표에서 이 폴더 줄의 (API, vite) 포트 — 짝 = `.claude/dev_up.py`.""" try: with open(os.path.join(ROOT, "OWNERS.md"), encoding="utf-8-sig") as owners: lines = owners.read().splitlines() except OSError: return None here = os.path.normcase(os.path.normpath(ROOT)) for line in lines: cells = [cell.strip().strip("`") for cell in line.strip().strip("|").split("|")] if len(cells) >= 5 and os.path.normcase(os.path.normpath(cells[2])) == here: ports = re.findall(r"\d{4,5}", cells[4]) if len(ports) == 2: return int(ports[0]), int(ports[1]) return None def stop() -> None: try: with open(PID_FILE, encoding="utf-8") as f: pid = int(f.read().strip()) except (OSError, ValueError): return subprocess.run(["taskkill", "/T", "/F", "/PID", str(pid)], capture_output=True, check=False) os.remove(PID_FILE) def run(cmd: list[str], env: dict[str, str]) -> None: print("RUN", " ".join(cmd), flush=True) subprocess.run(cmd, cwd=ROOT, env=env, check=True, shell=os.name == "nt" and cmd[0] == "npm") def main() -> int: os.makedirs(TMP, exist_ok=True) stop() if "--stop" in sys.argv: print("PREVIEW=stopped") return 0 api, vite = owners_ports() or (8000, 5173) api = int(os.environ.get("AISLO_API_PORT") or api) vite = int(os.environ.get("AISLO_VITE_PORT") or vite) port = vite - 1000 env = {**os.environ, "AISLO_API_PORT": str(api), "FRONTEND_DEV_PORT": str(vite)} run(["node", VITE, "build", "--configLoader", "runner"], env) run(["npm", "run", "build:b07-cad"], env) log = open(LOG, "ab") # noqa: SIM115 — 자식 프로세스가 물려받는다 flags = subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP process = subprocess.Popen( ["node", VITE, "preview", "--configLoader", "runner"], cwd=ROOT, env=env, stdout=log, stderr=subprocess.STDOUT, creationflags=flags, ) with open(PID_FILE, "w", encoding="utf-8") as f: f.write(str(process.pid)) url = f"http://127.0.0.1:{port}/" for _ in range(60): try: urllib.request.urlopen(url, timeout=2) print(f"PREVIEW=up {url} (API {api}) · 로그 {LOG}") return 0 except OSError: time.sleep(0.5) print(f"PREVIEW=TIMEOUT — {LOG} 확인") return 1 if __name__ == "__main__": sys.exit(main())