fix(windows): prefer managed node for whatsapp and desktop

This commit is contained in:
helix4u 2026-06-19 13:55:15 -06:00 committed by kshitijk4poor
parent 38f1a923af
commit 7a7b56d498
5 changed files with 173 additions and 31 deletions

View file

@ -2363,6 +2363,7 @@ def cmd_whatsapp(args):
"""Set up WhatsApp: choose mode, configure, install bridge, pair via QR."""
_require_tty("whatsapp")
from hermes_cli.config import get_env_value, save_env_value
from hermes_constants import find_node_executable, with_hermes_node_path
print()
print("⚕ WhatsApp Setup")
@ -2477,7 +2478,7 @@ def cmd_whatsapp(args):
print(
"\n→ Installing WhatsApp bridge dependencies (this can take a few minutes)..."
)
npm = shutil.which("npm")
npm = find_node_executable("npm")
if not npm:
print(" ✗ npm not found on PATH — install Node.js first")
return
@ -2490,6 +2491,7 @@ def cmd_whatsapp(args):
text=True,
encoding="utf-8",
errors="replace",
env=with_hermes_node_path(),
)
except KeyboardInterrupt:
print("\n ✗ Install cancelled")
@ -2546,8 +2548,15 @@ def cmd_whatsapp(args):
try:
subprocess.run(
["node", str(bridge_script), "--pair-only", "--session", str(session_dir)],
[
find_node_executable("node") or "node",
str(bridge_script),
"--pair-only",
"--session",
str(session_dir),
],
cwd=str(bridge_dir),
env=with_hermes_node_path(),
)
except KeyboardInterrupt:
pass
@ -4535,6 +4544,7 @@ def _run_with_idle_timeout(
*,
idle_timeout_seconds: int = 180,
indent: str = " ",
env: dict[str, str] | None = None,
) -> subprocess.CompletedProcess:
"""Run a subprocess that streams output, with an idle-output timeout.
@ -4569,6 +4579,7 @@ def _run_with_idle_timeout(
encoding="utf-8",
errors="replace",
bufsize=1,
env=env,
)
except OSError as exc:
# E.g. npm not on PATH between the which() check and now.
@ -4760,12 +4771,15 @@ def _build_web_ui(web_dir: Path, *, fatal: bool = False) -> bool:
encoding = getattr(sys.stdout, "encoding", None) or "ascii"
print(text.encode(encoding, errors="replace").decode(encoding, errors="replace"))
npm = shutil.which("npm")
from hermes_constants import find_node_executable, with_hermes_node_path
npm = find_node_executable("npm")
if not npm:
if fatal:
_say("Web UI frontend not built and npm is not available.")
_say("Install Node.js, then run: cd web && npm install && npm run build")
return not fatal
build_env = with_hermes_node_path()
_say("→ Building web UI...")
def _relay(result: "subprocess.CompletedProcess") -> None:
@ -4797,6 +4811,7 @@ def _build_web_ui(web_dir: Path, *, fatal: bool = False) -> bool:
npm,
npm_cwd,
extra_args=(*npm_workspace_args, "--silent"),
env=build_env,
)
if r1.returncode != 0:
_say(
@ -4812,13 +4827,13 @@ def _build_web_ui(web_dir: Path, *, fatal: bool = False) -> bool:
# users react by rebooting, which leaves the editable install in a
# half-state. Streaming + idle-kill makes failures observable AND
# recoverable (the stale-dist fallback below handles the kill path).
r2 = _run_with_idle_timeout([npm, "run", "build"], cwd=web_dir)
r2 = _run_with_idle_timeout([npm, "run", "build"], cwd=web_dir, env=build_env)
if r2.returncode != 0:
# Retry once after a short delay — covers boot-time races on Windows
# (antivirus scanning Node.js binaries, npm cache not ready, transient
# I/O when launched via Scheduled Task at logon). See issue #23817.
_time.sleep(3)
r2 = _run_with_idle_timeout([npm, "run", "build"], cwd=web_dir)
r2 = _run_with_idle_timeout([npm, "run", "build"], cwd=web_dir, env=build_env)
if r2.returncode != 0:
# _run_with_idle_timeout merges stderr into stdout; older callers
@ -5197,7 +5212,9 @@ def _redownload_electron_dist(
installer = electron_dir / "install.js"
if not installer.is_file():
return False
node = shutil.which("node")
from hermes_constants import find_node_executable, with_hermes_node_path
node = find_node_executable("node")
if not node:
return False
@ -5208,7 +5225,7 @@ def _redownload_electron_dist(
except OSError:
pass
dl_env = dict(env)
dl_env = with_hermes_node_path(env)
if mirror:
dl_env["ELECTRON_MIRROR"] = mirror
try:
@ -5388,7 +5405,9 @@ def cmd_gui(args: argparse.Namespace):
except Exception:
pass
env = os.environ.copy()
from hermes_constants import find_node_executable, with_hermes_node_path
env = with_hermes_node_path(os.environ.copy())
if getattr(args, "fake_boot", False):
env["HERMES_DESKTOP_BOOT_FAKE"] = "1"
if getattr(args, "ignore_existing", False):
@ -5405,7 +5424,7 @@ def cmd_gui(args: argparse.Namespace):
packaged_executable = _desktop_packaged_executable(desktop_dir)
if source_mode or not skip_build:
npm = shutil.which("npm")
npm = find_node_executable("npm")
if not npm:
print("Desktop GUI requires Node.js/npm, but npm was not found on PATH.")
print("Install Node.js, then run: hermes gui")
@ -7637,7 +7656,9 @@ def _ensure_uv_for_termux(pip_cmd: list[str]) -> str | None:
def _update_node_dependencies() -> None:
npm = shutil.which("npm")
from hermes_constants import find_node_executable, with_hermes_node_path
npm = find_node_executable("npm")
if not npm:
return
@ -7654,7 +7675,7 @@ def _update_node_dependencies() -> None:
print("→ Updating Node.js dependencies...")
extra_args = ["--no-fund", "--no-audit", "--progress=false"]
nixos_env = _nixos_build_env()
nixos_env = with_hermes_node_path(_nixos_build_env())
# Step 1: root install (no workspace recursion).
root_args = [*extra_args, "--workspaces=false"]