#!/usr/bin/env python3 """Static explorer with SPA fallback: unknown paths serve index.html (client router). API paths (/api/, /explorer-api/) are not rewritten so missing backends still 404 clearly.""" from __future__ import annotations import argparse import os import sys from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer from urllib.parse import urlparse def main() -> int: p = argparse.ArgumentParser(description="Serve explorer-monorepo/frontend/public with SPA fallback.") p.add_argument("port", nargs="?", type=int, default=8080, help="Listen port (default 8080)") p.add_argument( "--bind", default="127.0.0.1", help="Bind address (default 127.0.0.1)", ) args = p.parse_args() script_dir = os.path.dirname(os.path.abspath(__file__)) root = os.path.normpath(os.path.join(script_dir, "..", "frontend", "public")) if not os.path.isfile(os.path.join(root, "index.html")): print(f"ERROR: index.html not found under {root}", file=sys.stderr) return 1 class Handler(SimpleHTTPRequestHandler): def __init__(self, *a, **kw): super().__init__(*a, directory=root, **kw) def do_GET(self) -> None: # noqa: N802 path = urlparse(self.path).path if path.startswith("/api/") or path.startswith("/explorer-api"): return super().do_GET() rel = path.lstrip("/") if rel.startswith(".."): self.send_error(403, "Forbidden") return fs = os.path.join(root, rel) if rel else root if os.path.isfile(fs): return super().do_GET() self.path = "/index.html" return super().do_GET() httpd = ThreadingHTTPServer((args.bind, args.port), Handler) print(f"Serving SPA explorer: http://{args.bind}:{args.port}/ (root={root})", flush=True) try: httpd.serve_forever() except KeyboardInterrupt: print("\nStopped.", flush=True) return 0 if __name__ == "__main__": raise SystemExit(main())