mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-24 10:52:21 +00:00
Merge pull request #50398 from helix4u/fix/windows-npm-path-fallback
fix(windows): prefer cmd npm shim on PATH fallback
This commit is contained in:
commit
bb59075b25
2 changed files with 60 additions and 1 deletions
|
|
@ -290,13 +290,41 @@ def find_hermes_node_executable(command: str) -> str | None:
|
|||
return None
|
||||
|
||||
|
||||
def find_node_executable_on_path(command: str) -> str | None:
|
||||
"""Return a Node/npm executable from PATH with Windows shim ordering.
|
||||
|
||||
``shutil.which("npm")`` can resolve an extensionless npm shim before the
|
||||
``.cmd`` shim on Windows. Python's CreateProcess cannot execute that shim
|
||||
directly, so prefer the launchable variants explicitly for Hermes-owned
|
||||
subprocesses.
|
||||
"""
|
||||
if sys.platform != "win32":
|
||||
return shutil.which(command)
|
||||
|
||||
command_str = str(command)
|
||||
has_path_separator = any(
|
||||
sep and sep in command_str for sep in (os.sep, os.altsep, "/", "\\")
|
||||
)
|
||||
if has_path_separator:
|
||||
return command_str if Path(command_str).is_file() else None
|
||||
|
||||
for name in _candidate_node_command_names(command_str):
|
||||
for directory in os.environ.get("PATH", "").split(os.pathsep):
|
||||
if not directory:
|
||||
continue
|
||||
candidate = Path(directory) / name
|
||||
if candidate.is_file():
|
||||
return str(candidate)
|
||||
return None
|
||||
|
||||
|
||||
def find_node_executable(command: str) -> str | None:
|
||||
"""Resolve a Node.js command, preferring Hermes-managed installs.
|
||||
|
||||
This is for Hermes-owned subprocesses that should not be broken by a bad,
|
||||
missing, or elevation-triggering system Node/npm on PATH.
|
||||
"""
|
||||
return find_hermes_node_executable(command) or shutil.which(command)
|
||||
return find_hermes_node_executable(command) or find_node_executable_on_path(command)
|
||||
|
||||
|
||||
def with_hermes_node_path(env: dict[str, str] | None = None) -> dict[str, str]:
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import hermes_constants
|
|||
from hermes_constants import (
|
||||
VALID_REASONING_EFFORTS,
|
||||
find_hermes_node_executable,
|
||||
find_node_executable,
|
||||
find_node_executable_on_path,
|
||||
get_default_hermes_root,
|
||||
get_hermes_home,
|
||||
iter_hermes_node_dirs,
|
||||
|
|
@ -131,6 +133,35 @@ class TestHermesManagedNode:
|
|||
|
||||
assert find_hermes_node_executable("npm") == str(npm_cmd)
|
||||
|
||||
def test_windows_path_fallback_prefers_npm_cmd(self, tmp_path, monkeypatch):
|
||||
bin_dir = tmp_path / "nodejs"
|
||||
bin_dir.mkdir()
|
||||
extensionless = bin_dir / "npm"
|
||||
powershell = bin_dir / "npm.ps1"
|
||||
npm_cmd = bin_dir / "npm.cmd"
|
||||
extensionless.write_text("#!/usr/bin/env node\n")
|
||||
powershell.write_text("Write-Output npm\n")
|
||||
npm_cmd.write_text("@echo off\n")
|
||||
monkeypatch.setattr(hermes_constants.sys, "platform", "win32")
|
||||
monkeypatch.setenv("PATH", str(bin_dir))
|
||||
|
||||
assert find_node_executable_on_path("npm") == str(npm_cmd)
|
||||
|
||||
def test_windows_node_executable_falls_back_to_safe_path_shim(self, tmp_path, monkeypatch):
|
||||
home = tmp_path / "hermes"
|
||||
home.mkdir()
|
||||
bin_dir = tmp_path / "nodejs"
|
||||
bin_dir.mkdir()
|
||||
extensionless = bin_dir / "npm"
|
||||
npm_cmd = bin_dir / "npm.cmd"
|
||||
extensionless.write_text("#!/usr/bin/env node\n")
|
||||
npm_cmd.write_text("@echo off\n")
|
||||
monkeypatch.setattr(hermes_constants.sys, "platform", "win32")
|
||||
monkeypatch.setenv("HERMES_HOME", str(home))
|
||||
monkeypatch.setenv("PATH", str(bin_dir))
|
||||
|
||||
assert find_node_executable("npm") == str(npm_cmd)
|
||||
|
||||
def test_with_hermes_node_path_prepends_existing_managed_dirs(self, tmp_path, monkeypatch):
|
||||
home = tmp_path / "hermes"
|
||||
node_dir = home / "node"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue