diff --git a/pyproject.toml b/pyproject.toml index d7317fc8b0..1e46cc92d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,24 @@ version = {attr = "unsloth.models._utils.__version__"} include-package-data = true [tool.setuptools.package-data] -studio = ["frontend/dist/**/*"] +studio = [ + "*.sh", + "*.ps1", + "*.bat", + "frontend/dist/**/*", + "frontend/public/**/*", + "frontend/src/**/*", + "frontend/*.json", + "frontend/*.ts", + "frontend/*.js", + "frontend/*.lock", + "frontend/*.html", + "frontend/*.yaml", + "frontend/.git*", + "backend/requirements/**/*", + "backend/core/data_recipe/oxc-validator/*.json", + "backend/core/data_recipe/oxc-validator/*.mjs", +] [tool.setuptools.packages.find] exclude = ["images*", "tests*", "kernels/moe*"] diff --git a/studio/backend/main.py b/studio/backend/main.py index be46272847..3ab846306e 100644 --- a/studio/backend/main.py +++ b/studio/backend/main.py @@ -255,6 +255,22 @@ async def get_hardware_info(): # ============ Serve Frontend (Optional) ============ +def _strip_crossorigin(html_bytes: bytes) -> bytes: + """Remove ``crossorigin`` attributes from script/link tags. + + Vite adds ``crossorigin`` by default which forces CORS mode on font + subresource loads. When Studio is served over plain HTTP, Firefox + HTTPS-Only Mode does not exempt CORS font requests -- causing all + @font-face downloads to fail silently. Stripping the attribute + makes them regular same-origin fetches that work on any protocol. + """ + import re as _re + + html = html_bytes.decode("utf-8") + html = _re.sub(r'\s+crossorigin(?:="[^"]*")?', "", html) + return html.encode("utf-8") + + def _inject_bootstrap(html_bytes: bytes, app: FastAPI) -> bytes: """Inject bootstrap credentials into HTML when password change is required. @@ -296,6 +312,7 @@ def setup_frontend(app: FastAPI, build_path: Path): @app.get("/") async def serve_root(): content = (build_path / "index.html").read_bytes() + content = _strip_crossorigin(content) content = _inject_bootstrap(content, app) return Response( content = content, @@ -319,6 +336,7 @@ def setup_frontend(app: FastAPI, build_path: Path): # Serve index.html as bytes — avoids Content-Length mismatch content = (build_path / "index.html").read_bytes() + content = _strip_crossorigin(content) content = _inject_bootstrap(content, app) return Response( content = content, diff --git a/studio/setup.sh b/studio/setup.sh index a9e3085efd..3fc01feb2b 100755 --- a/studio/setup.sh +++ b/studio/setup.sh @@ -121,8 +121,33 @@ echo "✅ Node $(node -v) | npm $(npm -v)" echo "" echo "Building frontend..." cd "$SCRIPT_DIR/frontend" + +# Tailwind v4's oxide scanner respects .gitignore in parent directories. +# Python venvs create a .gitignore with "*" (ignore everything), which +# prevents Tailwind from scanning .tsx source files for class names. +# Temporarily hide any such .gitignore during the build, then restore it. +_HIDDEN_GITIGNORES=() +_dir="$(pwd)" +while [ "$_dir" != "/" ]; do + _dir="$(dirname "$_dir")" + if [ -f "$_dir/.gitignore" ] && grep -qx '\*' "$_dir/.gitignore" 2>/dev/null; then + mv "$_dir/.gitignore" "$_dir/.gitignore._twbuild" + _HIDDEN_GITIGNORES+=("$_dir/.gitignore") + fi +done + +_restore_gitignores() { + for _gi in "${_HIDDEN_GITIGNORES[@]}"; do + mv "${_gi}._twbuild" "$_gi" 2>/dev/null || true + done +} +trap _restore_gitignores EXIT + run_quiet "npm install" npm install run_quiet "npm run build" npm run build + +_restore_gitignores +trap - EXIT cd "$SCRIPT_DIR/backend/core/data_recipe/oxc-validator" run_quiet "npm install (oxc validator runtime)" npm install cd "$SCRIPT_DIR"