""" dev.py ====== This script acts as a local development orchestrator. Instead of recreating the frontend in Python, which would violate the "Don't Repeat Yourself" (DRY) principle and introduce massive technical debt, this script leverages the existing architecture. It performs four main actions sequentially: 1. Executes `roi.py` to fetch the latest market data and generate the frontend JSON files. 2. Executes `bun install` to ensure frontend JS dependencies are present. 3. Executes `bun run build` to compile the TypeScript frontend into browser-ready JavaScript. 4. Spawns a local Python HTTP server pointing to the `www/` directory. """ import os # Required for changing working directories and checking paths. import subprocess # Required for spawning external processes (like 'bun' and other python scripts). import sys # Required to grab the current Python executable path. import http.server # Required to run the local web server. import socketserver # Required to bind the HTTP handler to a specific port. def main() -> None: """ Main execution function for the local development server orchestrator. """ # ------------------------------------------------------------------------- # STEP 1: GENERATE BACKEND DATA # ------------------------------------------------------------------------- print(">>> [1/4] Running roi.py to generate updated JSON data...") try: subprocess.run([sys.executable, "roi.py"], check=True) except subprocess.CalledProcessError as e: sys.exit(f"!!! Error: Failed to execute roi.py. Process exited with code {e.returncode}") # ------------------------------------------------------------------------- # STEP 2: INSTALL FRONTEND DEPENDENCIES # ------------------------------------------------------------------------- print(">>> [2/4] Installing frontend dependencies via Bun...") # EXTREME DETAIL: We create a copy of the current OS environment variables. # We then inject 'CI' = '1'. CI stands for Continuous Integration. # Most modern JS tools (like Bun, NPM, Yarn) look for this variable. If they see it, # they disable interactive terminal animations (like progress bars and emojis). # This prevents the child process from messing with the terminal driver and throwing # phantom SIGINT signals (KeyboardInterrupts) back to our Python script. env = os.environ.copy() env["CI"] = "1" try: # EXTREME DETAIL: stdin=subprocess.DEVNULL explicitly cuts the child process off # from reading keyboard input, further preventing terminal driver deadlocks. subprocess.run(["bun", "install"], check=True, env=env, stdin=subprocess.DEVNULL) except FileNotFoundError: sys.exit("!!! Error: 'bun' command not found. Please ensure Bun is installed (https://bun.sh/) and in your PATH.") except subprocess.CalledProcessError as e: sys.exit(f"!!! Error: Frontend dependency installation failed. Bun exited with code {e.returncode}") # ------------------------------------------------------------------------- # STEP 3: BUILD THE FRONTEND # ------------------------------------------------------------------------- print(">>> [3/4] Building the TypeScript frontend using Bun...") try: # We apply the same environment and stdin constraints to the build step for safety. subprocess.run(["bun", "run", "build"], check=True, env=env, stdin=subprocess.DEVNULL) except subprocess.CalledProcessError as e: sys.exit(f"!!! Error: Frontend build failed. Bun exited with code {e.returncode}") # ------------------------------------------------------------------------- # STEP 4: SERVE THE FRONTEND LOCALLY # ------------------------------------------------------------------------- print(">>> [4/4] Starting local development server...") PORT = 8000 WEB_DIR = "www" if not os.path.isdir(WEB_DIR): sys.exit(f"!!! Error: Directory '{WEB_DIR}' does not exist. The build step may have failed silently.") os.chdir(WEB_DIR) Handler = http.server.SimpleHTTPRequestHandler with socketserver.TCPServer(("", PORT), Handler) as httpd: print(f">>> Serving at http://localhost:{PORT}") print(">>> Press Ctrl+C to stop the server.") httpd.serve_forever() if __name__ == '__main__': try: main() except KeyboardInterrupt: sys.exit("\n>>> Process interrupted by user. Exiting gracefully. Goodbye!")