Refactor dev mode into a source repo script (remove dev subcommand from package).

This commit is contained in:
2025-12-05 18:36:13 +00:00
parent 0355c55fc0
commit 8609f2fe69
4 changed files with 157 additions and 129 deletions
+1 -84
View File
@@ -1,43 +1,15 @@
import asyncio
import atexit
import mimetypes
import os
import shutil
import signal
import subprocess
from importlib import resources
from pathlib import Path
from sys import stderr
from threading import Thread
import httpx
__all__ = ["path", "file", "read", "run_dev", "is_dev_mode"]
__all__ = ["path", "file", "read", "is_dev_mode"]
DEV_SERVER = "http://localhost:4403"
NO_FRONTEND_TOOL = """\
┃ ⚠️ deno, npm or bunx needed to run the frontend server.
"""
BUN_BUG = """\
┃ ⚠️ Bun cannot correctly proxy API requests to the backend.
┃ Bug report: https://github.com/oven-sh/bun/issues/9882
┃ Options:
┃ - sudo caddy run --config caddy/Caddyfile.dev
┃ - Install deno or npm instead
┃ Caddy will skip the Vite for API calls and serve everything at port 443.
┃ Otherwise Vite serves at port 8077 and proxies to backend (broken with bun).
"""
NO_FRONTEND = """\
┃ Note: only static build of the frontend is served at localhost:4402.
┃ The page will not update with frontend code changes.
"""
def _resolve_static_dir() -> Path:
# Try packaged path via importlib.resources (works for wheel/installed).
@@ -97,58 +69,3 @@ async def read(filepath: str) -> tuple[bytes, int, dict[str, str]]:
async def _read_file_async(file_path: Path) -> bytes:
"""Read file asynchronously using asyncio.to_thread."""
return await asyncio.to_thread(file_path.read_bytes)
def run_dev():
"""Spawn the frontend dev server (deno, npm, or bunx) as a background process."""
devpath = Path(__file__).parent.parent.parent / "frontend"
if not (devpath / "package.json").exists():
raise RuntimeError(
"Dev frontend is only available when running from git."
if "site-packages" in devpath.parts
else f"Frontend source code not found at {devpath}"
)
options = [
("deno", "run", "dev"),
("npm", "run", "dev", "--"),
("bunx", "--bun", "vite"),
]
cmd = None
tool_name = None
for option in options:
if tool := shutil.which(option[0]):
cmd = [tool, *option[1:]]
tool_name = option[0]
break
vite_process = None
def start_vite():
nonlocal vite_process
if cmd is None:
stderr.write(NO_FRONTEND_TOOL)
stderr.write(NO_FRONTEND)
return
assert tool_name is not None
try:
if tool_name == "bunx":
stderr.write(BUN_BUG)
stderr.write(f">>> {' '.join([tool_name, *cmd[1:]])}\n")
vite_process = subprocess.Popen(cmd, cwd=str(devpath), shell=False)
except Exception as e:
stderr.write(f"┃ ⚠️ Vite couldn't start: {e}\n")
stderr.write(NO_FRONTEND)
def cleanup():
vite_process.terminate()
vite_process.wait()
# Start Vite in a separate thread
vite_thread = Thread(target=start_vite, daemon=True)
vite_thread.start()
atexit.register(cleanup)
signal.signal(signal.SIGTERM, lambda *_: cleanup())
signal.signal(signal.SIGINT, lambda *_: cleanup())