diff --git a/npm_install.py b/npm_install.py index 7cf7424..86530c2 100644 --- a/npm_install.py +++ b/npm_install.py @@ -582,6 +582,53 @@ def install_node_and_yarn(node_pkg: str): if not Path("/usr/bin/yarn").exists() and Path("/usr/bin/yarnpkg").exists(): os.symlink("/usr/bin/yarnpkg","/usr/bin/yarn") +def _prepare_sass(frontend_dir: Path): + pj = frontend_dir / "package.json" + if not pj.exists(): + return + + import json, re, os + try: + data = json.loads(pj.read_text(encoding="utf-8")) + except Exception: + return + + deps = data.get("dependencies", {}) or {} + dev = data.get("devDependencies", {}) or {} + has_node_sass = ("node-sass" in deps) or ("node-sass" in dev) + if not has_node_sass: + return + + use_dart = (os.environ.get("USE_DART_SASS","").strip() == "1") + + data.setdefault("dependencies", {}) + data.setdefault("devDependencies", {}) + + if use_dart: + data["dependencies"].pop("node-sass", None) + data["devDependencies"].pop("node-sass", None) + if "sass" not in data["dependencies"] and "sass" not in data["devDependencies"]: + data["devDependencies"]["sass"] = "^1.77.0" + scripts = (data.get("scripts") or {}) + new_scripts = {} + for k, v in scripts.items(): + new_scripts[k] = re.sub(r"\bnode-sass\b", "sass", v or "") + data["scripts"] = new_scripts + else: + target = "^9.0.0" + if "node-sass" in data["dependencies"]: + data["dependencies"]["node-sass"] = target + else: + data["devDependencies"]["node-sass"] = target + res = (data.get("resolutions") or {}) + res["node-gyp"] = "^10.0.0" + res["node-sass"] = "^9.0.0" + data["resolutions"] = res + os.environ["npm_config_node_sass_binary_site"] = "https://github.com/sass/node-sass/releases/download" + + pj.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + + def _build_frontend(src_frontend: Path, dest_frontend: Path): def _semver(s: str) -> bool: return bool(re.match(r"^\d+(?:\.\d+){1,3}$", (s or "").strip())) @@ -602,6 +649,50 @@ def _build_frontend(src_frontend: Path, dest_frontend: Path): return ["npx", "-y", "yarn@stable"] return None + def _ensure_yarn_installed(): + if not shutil.which("npm"): + try: + apt_try_install(["npm"]) + except Exception: + run(["apt-get", "update"], check=False) + run(["apt-get", "install", "-y", "npm"]) + run(["npm", "install", "-g", "yarn"], check=False) + + yarn_cmd = _pick_yarn_cmd() + if not yarn_cmd: + _ensure_yarn_installed() + yarn_cmd = _pick_yarn_cmd() + + if not yarn_cmd: + raise RuntimeError( + "Unable to detect or install a valid Yarn. " + "Try: apt-get install -y npm && npm i -g yarn." + ) + + with step("Installing frontend dependencies (yarn)"): + os.environ["NODE_ENV"] = "development" + os.chdir(src_frontend) + + _prepare_sass(src_frontend) + + cache_dir = (run_out(yarn_cmd + ["cache", "dir"], check=False) or "").strip() + if cache_dir and not Path(cache_dir).exists(): + Path(cache_dir).mkdir(parents=True, exist_ok=True) + + run(yarn_cmd + ["cache", "clean"], check=False) + run(yarn_cmd + ["install"]) + + with step("Building frontend (yarn build)"): + env = os.environ.copy() + env["NODE_OPTIONS"] = "--openssl-legacy-provider" + run(yarn_cmd + ["build"], env=env) + + with step("Copying frontend artifacts"): + shutil.copytree(src_frontend / "dist", dest_frontend, dirs_exist_ok=True) + if (src_frontend / "app-images").exists(): + shutil.copytree(src_frontend / "app-images", dest_frontend / "images", dirs_exist_ok=True) + + def _ensure_yarn_installed(): if not shutil.which("npm"): try: