""" Custom Hatchling build hook. Only runs when the environment variable ASTRBOT_BUILD_DASHBOARD=1 is set, so that `uv sync` / editable installs are never affected. Usage: ASTRBOT_BUILD_DASHBOARD=1 uv build When enabled, this hook: 1. Runs `npm run build` inside the `dashboard/` directory. 2. Copies the resulting `dashboard/dist/` tree into `astrbot/dashboard/dist/` so the static assets are shipped inside the Python wheel. """ import os import shutil import subprocess from logging import getLogger from pathlib import Path from hatchling.builders.hooks.plugin.interface import BuildHookInterface logger = getLogger(__name__) class CustomBuildHook(BuildHookInterface): PLUGIN_NAME = "custom" def initialize(self, version: str, build_data: dict) -> None: root = Path(self.root) dashboard_src = root / "dashboard" dist_src = dashboard_src / "dist" dist_target = root / "astrbot" / "dashboard" / "dist" if os.environ.get("ASTRBOT_BUILD_DASHBOARD", "").strip() != "1": # Ensure the target directory and a placeholder exist so the wheel # build does not fail when artifacts = ["..."] is declared. # Handle the case where dist_target might be a broken symlink (e.g. in AUR builds) if dist_target.is_symlink() or dist_target.is_file(): dist_target.unlink() dist_target.mkdir(parents=True, exist_ok=True) if not any(dist_target.iterdir()): placeholder = dist_target / ".placeholder" placeholder.write_text("# dashboard placeholder\n") return if not dashboard_src.exists(): logger.warning( "[hatch_build] 'dashboard/' directory not found: skipping dashboard build." ) if dist_target.is_symlink() or dist_target.is_file(): dist_target.unlink() dist_target.mkdir(parents=True, exist_ok=True) placeholder = dist_target / ".placeholder" placeholder.write_text("# dashboard placeholder\n") return # ── Install Node dependencies if node_modules is absent ───────────── if not (dashboard_src / "node_modules").exists(): logger.info("[hatch_build] Installing dashboard Node dependencies...") subprocess.run( ["npm", "install"], cwd=dashboard_src, check=True, ) # ── Build the Vue/Vite dashboard ────────────────────────────────────── logger.info("[hatch_build] Building Vue dashboard (npm run build)...") subprocess.run( ["npm", "run", "build"], cwd=dashboard_src, check=True, ) if not dist_src.exists(): logger.warning( "[hatch_build] dashboard/dist not found after build: skipping copy." ) if dist_target.is_symlink() or dist_target.is_file(): dist_target.unlink() dist_target.mkdir(parents=True, exist_ok=True) placeholder = dist_target / ".placeholder" placeholder.write_text("# dashboard placeholder\n") return # ── Copy into the Python package tree ─────────────────────────────── if dist_target.is_symlink() or dist_target.is_file(): dist_target.unlink() elif dist_target.exists(): shutil.rmtree(dist_target) shutil.copytree(dist_src, dist_target) logger.info( f"[hatch_build] Dashboard dist copied → {dist_target.relative_to(root)}" )