Language servers start with no approval on every write_file/patch inside any git worktree. Hermes pointed pyright at the checkout's own .venv/venv python (pyright executes it), let Vue and typescript-language-server load the checkout's node_modules/typescript, and left svelte-language-server and rust-analyzer loading project config, build scripts and proc-macros. A workspace is now trusted only when it is the git worktree Hermes was launched in or sits under an lsp.trusted_workspaces entry. Elsewhere pyright keeps VIRTUAL_ENV or the Hermes-managed python, TypeScript/Vue are pinned to the Hermes-side SDK, svelte gets isTrusted:false and rust-analyzer runs without build scripts or proc-macros.
79 lines
3.1 KiB
Python
79 lines
3.1 KiB
Python
"""Pyright uses the project environment before the Hermes runtime fallback."""
|
|
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from pm import paths
|
|
from pm.lock import Facts, Lockfile
|
|
from pm.registry import get_package
|
|
from pm.store import current_target
|
|
|
|
|
|
def _seed_pm_python(tmp_path, monkeypatch):
|
|
"""Stage a pm bundled-install layout: HERMES_RUNTIME_DIR -> store with a
|
|
manifest sibling (bundled), a python entry, and facts recording it."""
|
|
payload = tmp_path / "payload"
|
|
store = payload / "tools"
|
|
monkeypatch.setenv("HERMES_HOME", str(tmp_path / "home"))
|
|
monkeypatch.setenv("HERMES_RUNTIME_DIR", str(store))
|
|
lock = Lockfile(paths.lockfile_path())
|
|
package = get_package("python")
|
|
target = current_target()
|
|
version = lock.version("python")
|
|
assert version is not None
|
|
python_entry = store / package.store_entry(version, target)
|
|
python_entry.mkdir(parents=True)
|
|
exe = package.binary(python_entry, target)
|
|
assert exe is not None
|
|
exe.parent.mkdir(parents=True, exist_ok=True)
|
|
exe.write_text("", encoding="utf-8")
|
|
(payload / "manifest.json").write_text("{}", encoding="utf-8")
|
|
Facts(paths.facts_path()).record(
|
|
"python", version, python_entry.name, package.env(python_entry, target), store,
|
|
target=target, artifacts=[a["sha256"] for a in lock.artifacts("python", target)],
|
|
)
|
|
return exe
|
|
|
|
|
|
def test_pyright_uses_the_project_interpreter_before_hermes(tmp_path, monkeypatch):
|
|
from agent.lsp import servers
|
|
|
|
project = tmp_path / "project"
|
|
project.mkdir()
|
|
monkeypatch.delenv("VIRTUAL_ENV", raising=False)
|
|
pm_python = _seed_pm_python(tmp_path, monkeypatch)
|
|
context = servers.ServerContext(
|
|
workspace_root=str(project), install_strategy="off",
|
|
binary_overrides={"pyright": [sys.executable]}, trusted=True,
|
|
)
|
|
server = servers.find_server_for_file(str(project / "app.py"))
|
|
assert server is not None
|
|
spec = server.build_spawn(str(project), context)
|
|
assert spec is not None
|
|
assert spec.initialization_options["python"]["pythonPath"] == str(pm_python)
|
|
|
|
pm_python.unlink()
|
|
spec = server.build_spawn(str(project), context)
|
|
assert spec is not None
|
|
assert "pythonPath" not in spec.initialization_options.get("python", {})
|
|
|
|
for environment in (project / ".venv", tmp_path / "explicit-environment"):
|
|
subprocess.run(
|
|
[sys.executable, "-m", "venv", "--without-pip", str(environment)],
|
|
check=True, capture_output=True, text=True, timeout=30,
|
|
)
|
|
python = environment / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
|
|
if environment.name == "explicit-environment":
|
|
monkeypatch.setenv("VIRTUAL_ENV", str(environment))
|
|
spec = server.build_spawn(str(project), context)
|
|
assert spec is not None
|
|
selected = spec.initialization_options["python"]["pythonPath"]
|
|
assert Path(selected) == python
|
|
child = subprocess.run(
|
|
[selected, "-I", "-c", "import sys; print(sys.prefix)"],
|
|
check=True, capture_output=True, text=True, timeout=10,
|
|
)
|
|
assert Path(child.stdout.strip()) == environment
|