|
|
@@ -1,11 +1,15 @@
|
|
|
"""
|
|
|
dev.py
|
|
|
======
|
|
|
-This script acts as a local development orchestrator.
|
|
|
-It performs three main actions sequentially:
|
|
|
+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 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.
|
|
|
+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.
|
|
|
@@ -21,74 +25,59 @@ def main() -> None:
|
|
|
# -------------------------------------------------------------------------
|
|
|
# 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...")
|
|
|
+ print(">>> [1/4] 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
|
|
|
+ # STEP 2: INSTALL FRONTEND DEPENDENCIES
|
|
|
# -------------------------------------------------------------------------
|
|
|
- # 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...")
|
|
|
+ # Unlike 'uv run', 'bun run' does not automatically install missing dependencies
|
|
|
+ # defined in package.json. We must explicitly invoke 'bun install' first to
|
|
|
+ # populate the node_modules/ directory.
|
|
|
+ print(">>> [2/4] Installing frontend dependencies via 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)
|
|
|
+ subprocess.run(["bun", "install"], 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.
|
|
|
+ # We catch the FileNotFoundError here because this is the first time 'bun' is invoked.
|
|
|
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 dependency installation failed. Bun exited with code {e.returncode}")
|
|
|
+
|
|
|
+ # -------------------------------------------------------------------------
|
|
|
+ # STEP 3: BUILD THE FRONTEND
|
|
|
+ # -------------------------------------------------------------------------
|
|
|
+ # Compiles the TypeScript inside ts/ to JavaScript inside www/
|
|
|
+ print(">>> [3/4] Building the TypeScript frontend using Bun...")
|
|
|
+ try:
|
|
|
+ subprocess.run(["bun", "run", "build"], check=True)
|
|
|
+ except subprocess.CalledProcessError as e:
|
|
|
sys.exit(f"!!! Error: Frontend build failed. Bun exited with code {e.returncode}")
|
|
|
|
|
|
# -------------------------------------------------------------------------
|
|
|
- # STEP 3: SERVE THE FRONTEND LOCALLY
|
|
|
+ # STEP 4: 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...")
|
|
|
+ print(">>> [4/4] 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()
|