|
|
@@ -0,0 +1,97 @@
|
|
|
+"""
|
|
|
+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 three main actions sequentially:
|
|
|
+1. Executes `roi.py` to fetch the latest market data and generate the frontend JSON files.
|
|
|
+2. Executes the `bun run build` command to compile the TypeScript frontend into browser-ready JavaScript.
|
|
|
+3. Spawns a local Python HTTP server pointing to the `www/` directory so you can view the changes in your browser.
|
|
|
+"""
|
|
|
+
|
|
|
+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
|
|
|
+ # -------------------------------------------------------------------------
|
|
|
+ # We first need to ensure that the JSON data files that the frontend consumes
|
|
|
+ # actually exist and are up to date. We do this by calling the existing `roi.py` script.
|
|
|
+ print(">>> [1/3] Running roi.py to generate updated JSON data...")
|
|
|
+ try:
|
|
|
+ # sys.executable ensures we use the exact same Python interpreter currently running this script.
|
|
|
+ # check=True ensures that if roi.py crashes, this script will also halt and raise a CalledProcessError.
|
|
|
+ subprocess.run([sys.executable, "roi.py"], check=True)
|
|
|
+ except subprocess.CalledProcessError as e:
|
|
|
+ # If roi.py fails, we catch the error, print a helpful message, and exit the program gracefully
|
|
|
+ # instead of spitting out a massive unhandled stack trace.
|
|
|
+ sys.exit(f"!!! Error: Failed to execute roi.py. Process exited with code {e.returncode}")
|
|
|
+
|
|
|
+ # -------------------------------------------------------------------------
|
|
|
+ # STEP 2: BUILD THE FRONTEND
|
|
|
+ # -------------------------------------------------------------------------
|
|
|
+ # The frontend is written in TypeScript (inside the ts/ folder). Browsers cannot natively
|
|
|
+ # execute TypeScript in the way this project is structured. As outlined in `package.json`,
|
|
|
+ # 'bun' is used as the bundler to compile these files into the 'www/' directory.
|
|
|
+ print(">>> [2/3] Building the TypeScript frontend using Bun...")
|
|
|
+ try:
|
|
|
+ # We invoke the 'build' script defined in package.json.
|
|
|
+ # This corresponds to: "bun build ts/buy.ts ... --outdir www --target browser --sourcemap=external"
|
|
|
+ subprocess.run(["bun", "run", "build"], check=True)
|
|
|
+ except FileNotFoundError:
|
|
|
+ # If 'bun' is not installed or not in the system's PATH, a FileNotFoundError is raised.
|
|
|
+ # We must alert the user that this system dependency is missing.
|
|
|
+ sys.exit("!!! Error: 'bun' command not found. Please ensure Bun is installed (https://bun.sh/) and in your PATH.")
|
|
|
+ except subprocess.CalledProcessError as e:
|
|
|
+ # Catch compilation errors in the TypeScript code itself.
|
|
|
+ sys.exit(f"!!! Error: Frontend build failed. Bun exited with code {e.returncode}")
|
|
|
+
|
|
|
+ # -------------------------------------------------------------------------
|
|
|
+ # STEP 3: SERVE THE FRONTEND LOCALLY
|
|
|
+ # -------------------------------------------------------------------------
|
|
|
+ # We must serve the compiled files via an HTTP server because opening local HTML files
|
|
|
+ # directly (via file:// protocol) often causes CORS (Cross-Origin Resource Sharing) errors
|
|
|
+ # when the JavaScript attempts to fetch the local JSON files generated in Step 1.
|
|
|
+ print(">>> [3/3] Starting local development server...")
|
|
|
+
|
|
|
+ # Define the port we want to host the site on. 8000 is a standard web development port.
|
|
|
+ PORT = 8000
|
|
|
+ # Define the directory we want to serve. The bun build step outputs to 'www'.
|
|
|
+ WEB_DIR = "www"
|
|
|
+
|
|
|
+ # We must ensure the 'www' directory exists before attempting to switch into it.
|
|
|
+ if not os.path.isdir(WEB_DIR):
|
|
|
+ sys.exit(f"!!! Error: Directory '{WEB_DIR}' does not exist. The build step may have failed silently.")
|
|
|
+
|
|
|
+ # Change the current working directory to 'www'. This ensures the HTTP server roots itself here,
|
|
|
+ # making the index.html inside 'www' the default page, and allowing relative path resolutions to work.
|
|
|
+ os.chdir(WEB_DIR)
|
|
|
+
|
|
|
+ # http.server.SimpleHTTPRequestHandler is a built-in handler that serves files from the current directory.
|
|
|
+ Handler = http.server.SimpleHTTPRequestHandler
|
|
|
+
|
|
|
+ # socketserver.TCPServer creates a TCP server that listens on the specified port and binds our handler to it.
|
|
|
+ # We use a 'with' block to ensure the network socket is properly released and cleaned up when the server stops.
|
|
|
+ with socketserver.TCPServer(("", PORT), Handler) as httpd:
|
|
|
+ print(f">>> Serving at http://localhost:{PORT}")
|
|
|
+ print(">>> Press Ctrl+C to stop the server.")
|
|
|
+ try:
|
|
|
+ # serve_forever() puts the script into an infinite loop, constantly listening for and fulfilling HTTP requests.
|
|
|
+ httpd.serve_forever()
|
|
|
+ except KeyboardInterrupt:
|
|
|
+ # Catch the Ctrl+C command from the user to gracefully shut down the server.
|
|
|
+ print("\n>>> Shutting down development server gracefully. Goodbye!")
|
|
|
+
|
|
|
+if __name__ == '__main__':
|
|
|
+ # This standard Python idiom ensures that the main() function is only called if this script
|
|
|
+ # is executed directly from the command line (e.g., `python dev.py`), rather than imported as a module.
|
|
|
+ main()
|