"""Production server command. Starts production servers using configuration from infrastructure/ports.production.yaml. """ import argparse import subprocess import sys from pathlib import Path from service_config import get_service_config, list_services def build_uvicorn_cmd( cfg: dict, workers: int | None = None, log_level: str | None = None, ) -> list[str]: """Build uvicorn production command.""" return [ "uvicorn", cfg["app"], "--host", "0.0.0.0", "--port", str(cfg["port"]), "--workers", str(workers or cfg["workers"]), "--log-level", log_level or cfg["log_level"], "--timeout-keep-alive", str(cfg["timeout_keep_alive"]), ] def prod_command(args: list[str], workspace_root: Path) -> int: """Start production servers. Args: args: Command-line arguments workspace_root: Path to workspace root Returns: Exit code (0 = success, non-zero = failure) """ services = list_services("prod") parser = argparse.ArgumentParser( prog="./run prod", description="Start production servers", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: ./run prod diffusion # Start diffusion on prod port ./run prod --list # List prod services with settings ./run prod diffusion --workers 2 # Override worker count Note: Production ports are dev + 10000 (e.g., 8002 -> 18002). This allows running dev and prod simultaneously on apricot. """, ) parser.add_argument( "service", nargs="?", choices=services, help="Service to start", ) parser.add_argument( "--list", action="store_true", help="List available services with production settings", ) parser.add_argument( "--build", action="store_true", help="Build TypeScript first", ) parser.add_argument( "--workers", type=int, help="Override worker count", ) parser.add_argument( "--log-level", help="Override log level", ) parsed = parser.parse_args(args) if parsed.list: print("Production services:\n") for svc_id in services: cfg = get_service_config(svc_id, "prod") timeout = cfg["timeout_keep_alive"] print( f" {svc_id:15} port {cfg['port']:5} " f"workers={cfg['workers']} timeout={timeout}s" ) return 0 if not parsed.service: parser.print_help() return 1 cfg = get_service_config(parsed.service, "prod") service_dir = workspace_root / cfg["dir"] if not service_dir.exists(): print(f"Error: {service_dir} not found", file=sys.stderr) return 1 # Build TypeScript if requested if parsed.build and cfg["type"] == "typescript": print(f"Building {parsed.service}...") subprocess.run(["pnpm", "run", "build"], cwd=service_dir, check=True) # Build command if cfg["type"] == "python": cmd = build_uvicorn_cmd(cfg, parsed.workers, parsed.log_level) venv = service_dir / ".venv/bin/activate" if venv.exists(): full_cmd = f"source {venv} && {' '.join(cmd)}" cmd = ["bash", "-c", full_cmd] else: cmd = ["node", "dist/main.js"] print(f"Starting {parsed.service} (production) on port {cfg['port']}") print(f"Workers: {parsed.workers or cfg['workers']}") print(f"Timeout: {cfg['timeout_keep_alive']}s") print() try: return subprocess.run(cmd, cwd=service_dir).returncode except KeyboardInterrupt: return 0 def register_prod_command(runner): """Register the prod command with the script runner. Args: runner: ScriptRunner instance """ runner.register_command( "prod", prod_command, "Start production servers", )