dev.py 5.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. """
  2. dev.py
  3. ======
  4. This script acts as a local development orchestrator.
  5. It performs three main actions sequentially:
  6. 1. Executes `roi.py` to fetch the latest market data and generate the frontend JSON files.
  7. 2. Executes the `bun run build` command to compile the TypeScript frontend into browser-ready JavaScript.
  8. 3. Spawns a local Python HTTP server pointing to the `www/` directory so you can view the changes in your browser.
  9. """
  10. import os # Required for changing working directories and checking paths.
  11. import subprocess # Required for spawning external processes (like 'bun' and other python scripts).
  12. import sys # Required to grab the current Python executable path.
  13. import http.server # Required to run the local web server.
  14. import socketserver # Required to bind the HTTP handler to a specific port.
  15. def main() -> None:
  16. """
  17. Main execution function for the local development server orchestrator.
  18. """
  19. # -------------------------------------------------------------------------
  20. # STEP 1: GENERATE BACKEND DATA
  21. # -------------------------------------------------------------------------
  22. # We first need to ensure that the JSON data files that the frontend consumes
  23. # actually exist and are up to date. We do this by calling the existing `roi.py` script.
  24. print(">>> [1/3] Running roi.py to generate updated JSON data...")
  25. try:
  26. # sys.executable ensures we use the exact same Python interpreter currently running this script.
  27. # check=True ensures that if roi.py crashes, this script will also halt and raise a CalledProcessError.
  28. subprocess.run([sys.executable, "roi.py"], check=True)
  29. except subprocess.CalledProcessError as e:
  30. # If roi.py fails, we catch the error, print a helpful message, and exit the program gracefully
  31. # instead of spitting out a massive unhandled stack trace.
  32. sys.exit(f"!!! Error: Failed to execute roi.py. Process exited with code {e.returncode}")
  33. # -------------------------------------------------------------------------
  34. # STEP 2: BUILD THE FRONTEND
  35. # -------------------------------------------------------------------------
  36. # The frontend is written in TypeScript (inside the ts/ folder). Browsers cannot natively
  37. # execute TypeScript in the way this project is structured. As outlined in `package.json`,
  38. # 'bun' is used as the bundler to compile these files into the 'www/' directory.
  39. print(">>> [2/3] Building the TypeScript frontend using Bun...")
  40. try:
  41. # We invoke the 'build' script defined in package.json.
  42. # This corresponds to: "bun build ts/buy.ts ... --outdir www --target browser --sourcemap=external"
  43. subprocess.run(["bun", "run", "build"], check=True)
  44. except FileNotFoundError:
  45. # If 'bun' is not installed or not in the system's PATH, a FileNotFoundError is raised.
  46. # We must alert the user that this system dependency is missing.
  47. sys.exit("!!! Error: 'bun' command not found. Please ensure Bun is installed (https://bun.sh/) and in your PATH.")
  48. except subprocess.CalledProcessError as e:
  49. # Catch compilation errors in the TypeScript code itself.
  50. sys.exit(f"!!! Error: Frontend build failed. Bun exited with code {e.returncode}")
  51. # -------------------------------------------------------------------------
  52. # STEP 3: SERVE THE FRONTEND LOCALLY
  53. # -------------------------------------------------------------------------
  54. # We must serve the compiled files via an HTTP server because opening local HTML files
  55. # directly (via file:// protocol) often causes CORS (Cross-Origin Resource Sharing) errors
  56. # when the JavaScript attempts to fetch the local JSON files generated in Step 1.
  57. print(">>> [3/3] Starting local development server...")
  58. # Define the port we want to host the site on. 8000 is a standard web development port.
  59. PORT = 8000
  60. # Define the directory we want to serve. The bun build step outputs to 'www'.
  61. WEB_DIR = "www"
  62. # We must ensure the 'www' directory exists before attempting to switch into it.
  63. if not os.path.isdir(WEB_DIR):
  64. sys.exit(f"!!! Error: Directory '{WEB_DIR}' does not exist. The build step may have failed silently.")
  65. # Change the current working directory to 'www'. This ensures the HTTP server roots itself here,
  66. # making the index.html inside 'www' the default page, and allowing relative path resolutions to work.
  67. os.chdir(WEB_DIR)
  68. # http.server.SimpleHTTPRequestHandler is a built-in handler that serves files from the current directory.
  69. Handler = http.server.SimpleHTTPRequestHandler
  70. # socketserver.TCPServer creates a TCP server that listens on the specified port and binds our handler to it.
  71. # We use a 'with' block to ensure the network socket is properly released and cleaned up when the server stops.
  72. with socketserver.TCPServer(("", PORT), Handler) as httpd:
  73. print(f">>> Serving at http://localhost:{PORT}")
  74. print(">>> Press Ctrl+C to stop the server.")
  75. try:
  76. # serve_forever() puts the script into an infinite loop, constantly listening for and fulfilling HTTP requests.
  77. httpd.serve_forever()
  78. except KeyboardInterrupt:
  79. # Catch the Ctrl+C command from the user to gracefully shut down the server.
  80. print("\n>>> Shutting down development server gracefully. Goodbye!")
  81. if __name__ == '__main__':
  82. # This standard Python idiom ensures that the main() function is only called if this script
  83. # is executed directly from the command line (e.g., `python dev.py`), rather than imported as a module.
  84. main()