Utility module for accessing frontend in backend code.
This commit is contained in:
60
passkey/util/frontend.py
Normal file
60
passkey/util/frontend.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from importlib import resources
|
||||
from pathlib import Path
|
||||
|
||||
__all__ = ["path", "file", "run_dev"]
|
||||
|
||||
|
||||
def _resolve_static_dir() -> Path:
|
||||
# Try packaged path via importlib.resources (works for wheel/installed).
|
||||
try: # pragma: no cover - trivial path resolution
|
||||
pkg_dir = resources.files("passkey") / "frontend-build"
|
||||
fs_path = Path(str(pkg_dir))
|
||||
if fs_path.is_dir():
|
||||
return fs_path
|
||||
except Exception: # pragma: no cover - defensive
|
||||
pass
|
||||
# Fallback for editable/development before build.
|
||||
return Path(__file__).parent.parent / "frontend-build"
|
||||
|
||||
|
||||
path: Path = _resolve_static_dir()
|
||||
|
||||
|
||||
def file(*parts: str) -> Path:
|
||||
"""Return a child path under the static root."""
|
||||
return path.joinpath(*parts)
|
||||
|
||||
|
||||
def run_dev():
|
||||
"""Spawn the frontend dev server (bun or npm) as a background process."""
|
||||
import atexit
|
||||
import shutil
|
||||
import signal
|
||||
import subprocess
|
||||
|
||||
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}"
|
||||
)
|
||||
bun = shutil.which("bun")
|
||||
npm = shutil.which("npm") if bun is None else None
|
||||
if not bun and not npm:
|
||||
raise RuntimeError("Neither bun nor npm found on PATH for dev server")
|
||||
cmd: list[str] = [bun, "--bun", "run", "dev"] if bun else [npm, "run", "dev"] # type: ignore[list-item]
|
||||
proc = subprocess.Popen(cmd, cwd=str(devpath))
|
||||
|
||||
def _terminate():
|
||||
if proc.poll() is None:
|
||||
proc.terminate()
|
||||
|
||||
atexit.register(_terminate)
|
||||
|
||||
def _signal_handler(signum, frame):
|
||||
_terminate()
|
||||
raise SystemExit(0)
|
||||
|
||||
for sig in (signal.SIGINT, signal.SIGTERM):
|
||||
signal.signal(sig, _signal_handler)
|
||||
Reference in New Issue
Block a user