fix(ui): ignore inherited esbuild binary overrides
This commit is contained in:
+11
-2
@@ -2189,6 +2189,15 @@ def _ensure_tui_workspace(tui_dir: Path) -> None:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _npm_install_env(env: dict[str, str] | None = None) -> dict[str, str]:
|
||||
"""Build a clean environment for installing the pinned UI toolchain."""
|
||||
run_env = {**os.environ, **(env or {}), "CI": "1"}
|
||||
# esbuild treats this as an executable override. If a shell points it at a
|
||||
# different release, the pinned package's postinstall rejects that binary.
|
||||
run_env.pop("ESBUILD_BINARY_PATH", None)
|
||||
return run_env
|
||||
|
||||
|
||||
def _make_tui_argv(tui_dir: Path, tui_dev: bool) -> tuple[list[str], Path]:
|
||||
"""TUI: --dev → tsx src; else node dist (HERMES_TUI_DIR prebuilt or esbuild)."""
|
||||
_ensure_tui_node()
|
||||
@@ -2319,7 +2328,7 @@ def _make_tui_argv(tui_dir: Path, tui_dev: bool) -> tuple[list[str], Path]:
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
env={**with_hermes_node_path(), "CI": "1"},
|
||||
env=_npm_install_env(with_hermes_node_path()),
|
||||
)
|
||||
|
||||
result = _run_tui_install()
|
||||
@@ -5942,7 +5951,7 @@ def _run_npm_install_deterministic(
|
||||
# unicode-animations' postinstall animates to /dev/tty (bypasses
|
||||
# --silent/capture_output). It no-ops when CI is set — same as the TUI
|
||||
# install path and nix/lib.nix npm ci hooks.
|
||||
run_env = {**os.environ, **(env or {}), "CI": "1"}
|
||||
run_env = _npm_install_env(env)
|
||||
|
||||
def _run(cmd: list[str]) -> subprocess.CompletedProcess:
|
||||
return _run_npm_watching_for_engine_failure(
|
||||
|
||||
@@ -137,14 +137,15 @@ def test_no_stray_lockfiles_in_workspace_subdirs(main_mod) -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_make_tui_argv_omits_workspace_when_tui_has_own_lockfile(
|
||||
def test_make_tui_argv_omits_workspace_and_scrubs_esbuild_override(
|
||||
tmp_path: Path, main_mod, monkeypatch
|
||||
) -> None:
|
||||
"""When ui-tui/ has its own package-lock.json, _workspace_root returns
|
||||
tui_dir itself. npm install --workspace ui-tui would fail in that case
|
||||
because npm cannot find a workspace named "ui-tui" inside ui-tui/.
|
||||
The fix omits --workspace and runs plain npm install from tui_dir.
|
||||
See #42973.
|
||||
See #42973. The npm child must also ignore an inherited esbuild binary
|
||||
override: a version mismatch makes esbuild's postinstall abort (#87405).
|
||||
"""
|
||||
tui_dir = tmp_path / "ui-tui"
|
||||
tui_dir.mkdir()
|
||||
@@ -156,6 +157,7 @@ def test_make_tui_argv_omits_workspace_when_tui_has_own_lockfile(
|
||||
|
||||
monkeypatch.delenv("TERMUX_VERSION", raising=False)
|
||||
monkeypatch.setenv("PREFIX", "/usr")
|
||||
monkeypatch.setenv("ESBUILD_BINARY_PATH", "/opt/esbuild-0.28.2")
|
||||
monkeypatch.setattr(main_mod, "_tui_need_npm_install", lambda _root: True)
|
||||
monkeypatch.setattr(main_mod.shutil, "which", lambda name: f"/bin/{name}")
|
||||
calls = []
|
||||
@@ -173,6 +175,8 @@ def test_make_tui_argv_omits_workspace_when_tui_has_own_lockfile(
|
||||
assert "--workspace" not in install_cmd, (
|
||||
f"npm install should omit --workspace when tui_dir has its own lockfile, got: {install_cmd}"
|
||||
)
|
||||
assert install_cmd[:2] == ["/bin/npm", "install"]
|
||||
assert Path(install_cmd[0]).name in {"npm", "npm.cmd"}
|
||||
assert install_cmd[1] == "install"
|
||||
# cwd must be tui_dir (standalone), not parent
|
||||
assert calls[0][1]["cwd"] == str(tui_dir)
|
||||
assert "ESBUILD_BINARY_PATH" not in calls[0][1]["env"]
|
||||
|
||||
@@ -123,7 +123,7 @@ class TestBuildWebUISkipsWhenFresh:
|
||||
|
||||
|
||||
|
||||
def test_web_install_omits_workspace_when_web_has_own_lockfile(
|
||||
def test_web_install_omits_workspace_and_scrubs_esbuild_override(
|
||||
self, tmp_path, monkeypatch
|
||||
):
|
||||
"""web/ with its own lockfile => _workspace_root returns web_dir, so
|
||||
@@ -132,13 +132,16 @@ class TestBuildWebUISkipsWhenFresh:
|
||||
Symmetric to the TUI fix in test_tui_npm_install.py. See #42973.
|
||||
|
||||
With web's own lockfile present at cwd, _run_npm_install_deterministic
|
||||
uses ``npm ci`` (not ``npm install``).
|
||||
uses ``npm ci`` (not ``npm install``). The shared installer must also
|
||||
remove an inherited esbuild binary override so package/binary versions
|
||||
cannot diverge (#87405).
|
||||
"""
|
||||
web_dir, _ = _make_web_dir(tmp_path)
|
||||
(web_dir / "package-lock.json").write_text("{}", encoding="utf-8")
|
||||
(tmp_path / "package-lock.json").write_text("{}", encoding="utf-8")
|
||||
monkeypatch.delenv("TERMUX_VERSION", raising=False)
|
||||
monkeypatch.setenv("PREFIX", "/usr")
|
||||
monkeypatch.setenv("ESBUILD_BINARY_PATH", "/opt/esbuild-0.28.2")
|
||||
|
||||
install_cp = __import__("subprocess").CompletedProcess([], 0, stdout="", stderr="")
|
||||
build_cp = __import__("subprocess").CompletedProcess([], 0, stdout="", stderr="")
|
||||
@@ -150,8 +153,10 @@ class TestBuildWebUISkipsWhenFresh:
|
||||
assert result is True
|
||||
args, kwargs = mock_run.call_args
|
||||
assert "--workspace" not in args[0]
|
||||
assert args[0] == ["/usr/bin/npm", "ci", "--include=dev", "--silent", "--prefer-offline"]
|
||||
assert Path(args[0][0]).name in {"npm", "npm.cmd"}
|
||||
assert args[0][1:] == ["ci", "--include=dev", "--silent", "--prefer-offline"]
|
||||
assert kwargs["cwd"] == web_dir
|
||||
assert "ESBUILD_BINARY_PATH" not in kwargs["env"]
|
||||
|
||||
def test_workspace_root_install_names_update_closure(self, tmp_path, monkeypatch):
|
||||
"""From the workspace root, _build_web_ui must install the SAME
|
||||
|
||||
Reference in New Issue
Block a user