61 lines
		
	
	
		
			1.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			61 lines
		
	
	
		
			1.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 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)
 | 
