diff --git a/installers/windows/build.py b/installers/windows/build.py index 6e23ed969..2e74e7bec 100644 --- a/installers/windows/build.py +++ b/installers/windows/build.py @@ -159,9 +159,15 @@ def install_requirements(python_dir: Path) -> None: def build_frontend() -> Path: - """Run ``npm ci && npm run build`` and return the dist path.""" + """Run ``npm ci && npm run build`` and return the build output path. + + Vite is configured with ``outDir: '../static'`` (see + ``frontend/vite.config.ts``), so the bundle lands at ``/static/`` + — NOT ``frontend/dist/``. The path matches the runtime expectation in + ``backend/app/core/config.py`` (``static_dir = _app_dir / "static"``). + """ frontend = REPO_ROOT / "frontend" - dist = frontend / "dist" + dist = REPO_ROOT / "static" log("running npm ci in frontend/") npm = shutil.which("npm") if not npm: @@ -198,9 +204,28 @@ def stage_backend(frontend_dist: Path) -> None: ), ) - # Frontend bundle — FastAPI's StaticFiles mounts from app/static + # Frontend bundle — FastAPI's StaticFiles mounts from app/static. + # Strip macOS metadata files (.DS_Store, ._.*) that the dev box leaks + # in; they'd just bloat the installer and never be served anyway. log("staging frontend bundle") - shutil.copytree(frontend_dist, app / "static") + shutil.copytree( + frontend_dist, + app / "static", + ignore=shutil.ignore_patterns(".DS_Store", "._*"), + ) + + # gcode_viewer/ is a vendored 3D-preview iframe served via explicit + # routes in main.py (looked up via static_dir.parent / "gcode_viewer"). + # In the staged layout STAGING/app/static/'s sibling is STAGING/app/, + # so place the directory next to static/ to match runtime resolution. + gcode_viewer_src = REPO_ROOT / "gcode_viewer" + if gcode_viewer_src.exists(): + log("staging gcode_viewer/") + shutil.copytree( + gcode_viewer_src, + app / "gcode_viewer", + ignore=shutil.ignore_patterns(".DS_Store", "._*"), + ) def stage_nssm() -> None: