mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-11-15 07:41:20 +00:00
fix(setup): honor pinned pnpm version in host runner (#8183)
* fix(setup): honor pinned pnpm version in host runner * fix(setup): harden pnpm version probing * docs: drop changelog edit from pnpm PR
This commit is contained in:
parent
f086cce86e
commit
a68dc2a1a0
@ -76,6 +76,10 @@ make install
|
||||
make dev
|
||||
```
|
||||
|
||||
The local commands use the pnpm version pinned in `frontend/package.json`. If a
|
||||
different global pnpm is on `PATH`, they use Corepack when available; otherwise
|
||||
they report the version mismatch before installing frontend dependencies.
|
||||
|
||||
Run `make doctor` after setting model credentials. It reports obvious placeholder
|
||||
API keys as incomplete configuration; replace them with real keys before testing
|
||||
model calls.
|
||||
|
||||
@ -65,6 +65,85 @@ def test_runner_prefers_direct_pnpm_and_forwards_arguments(tmp_path: Path):
|
||||
assert "via Corepack" not in result.stderr
|
||||
|
||||
|
||||
@pytest.mark.parametrize("direct_version", ["11.25.0", "10.26.2-rc.1"])
|
||||
def test_runner_uses_corepack_when_direct_pnpm_conflicts_with_package_manager(tmp_path: Path, direct_version: str):
|
||||
bin_dir = tmp_path / "bin"
|
||||
bin_dir.mkdir()
|
||||
direct = _write_fake_command(bin_dir, "pnpm", "direct")
|
||||
if os.name == "nt":
|
||||
direct.write_text(
|
||||
f'@echo off\r\nif "%1"=="--version" (echo {direct_version} & exit /b 0)\r\necho direct^|%CD%^|%*\r\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
else:
|
||||
direct.write_text(
|
||||
f'#!/bin/sh\nif [ "$1" = "--version" ]; then echo {direct_version}; exit 0; fi\nprintf "direct|%s|%s\\n" "$PWD" "$*"\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
_write_fake_command(bin_dir, "corepack", "corepack")
|
||||
|
||||
result = _run_pnpm(bin_dir, "install", "--frozen-lockfile")
|
||||
|
||||
assert result.returncode == 0
|
||||
assert result.stdout.strip() == f"corepack|{FRONTEND_DIR}|pnpm install --frozen-lockfile"
|
||||
assert "via Corepack" in result.stderr
|
||||
|
||||
|
||||
def test_runner_rejects_mismatched_pnpm_without_corepack(tmp_path: Path):
|
||||
bin_dir = tmp_path / "bin"
|
||||
bin_dir.mkdir()
|
||||
direct = _write_fake_command(bin_dir, "pnpm", "direct")
|
||||
if os.name == "nt":
|
||||
direct.write_text("@echo off\r\necho 11.25.0\r\n", encoding="utf-8")
|
||||
else:
|
||||
direct.write_text("#!/bin/sh\necho 11.25.0\n", encoding="utf-8")
|
||||
|
||||
result = _run_pnpm(bin_dir, "install")
|
||||
|
||||
assert result.returncode == 127
|
||||
assert "pnpm 11.25.0 is on PATH" in result.stderr
|
||||
assert "pins 10.26.2" in result.stderr
|
||||
|
||||
|
||||
@pytest.mark.parametrize("package_content", [None, "{", "{}"])
|
||||
def test_runner_reports_unreadable_or_unpinned_package_manifest(tmp_path: Path, monkeypatch, capsys, package_content: str | None):
|
||||
package_json = tmp_path / "package.json"
|
||||
if package_content is not None:
|
||||
package_json.write_text(package_content, encoding="utf-8")
|
||||
run_pnpm = PNPM_MODULE["run_pnpm"]
|
||||
monkeypatch.setitem(run_pnpm.__globals__, "PACKAGE_JSON_PATH", package_json)
|
||||
monkeypatch.setitem(run_pnpm.__globals__, "find_pnpm_command", lambda: ["pnpm"])
|
||||
|
||||
assert run_pnpm(["install"]) != 0
|
||||
assert "frontend/package.json" in capsys.readouterr().err
|
||||
|
||||
|
||||
def test_runner_warns_when_direct_pnpm_version_is_unparseable(tmp_path: Path):
|
||||
bin_dir = tmp_path / "bin"
|
||||
bin_dir.mkdir()
|
||||
_write_fake_command(bin_dir, "pnpm", "direct")
|
||||
|
||||
result = _run_pnpm(bin_dir, "install")
|
||||
|
||||
assert result.returncode == 0
|
||||
assert "could not determine pnpm version" in result.stderr.lower()
|
||||
|
||||
|
||||
def test_version_probe_does_not_read_stdin(monkeypatch):
|
||||
run_pnpm = PNPM_MODULE["run_pnpm"]
|
||||
calls = []
|
||||
|
||||
def fake_run(*args, **kwargs):
|
||||
calls.append(kwargs)
|
||||
return SimpleNamespace(returncode=0, stdout="10.26.2\n")
|
||||
|
||||
monkeypatch.setitem(run_pnpm.__globals__, "find_pnpm_command", lambda: ["pnpm"])
|
||||
monkeypatch.setitem(run_pnpm.__globals__, "subprocess", SimpleNamespace(run=fake_run, DEVNULL=-3))
|
||||
|
||||
assert run_pnpm(["install"]) == 0
|
||||
assert calls[0]["stdin"] == -3
|
||||
|
||||
|
||||
def test_runner_prefers_cmd_shim_on_windows(monkeypatch):
|
||||
paths = {
|
||||
"pnpm": r"C:\tools\pnpm.exe",
|
||||
|
||||
@ -23,7 +23,9 @@ Cases marked for real Compose run only when a `docker` CLI is installed.
|
||||
`core.fileMode=false` or a non-POSIX filesystem does not keep executable bits.
|
||||
- **Host pnpm calls go through `scripts/pnpm.py`.** It chooses the launcher by
|
||||
Python's `os.name`, not by the invoking shell, so native Windows, MSYS and POSIX
|
||||
each get the right one.
|
||||
each get the right one. A direct pnpm with a version different from the
|
||||
`frontend/package.json` pin falls back to Corepack; without Corepack, the
|
||||
runner stops before running the mismatched pnpm.
|
||||
- **Shell filters stay compatible with GNU and BSD sed**, because macOS ships
|
||||
the BSD tools. `sandbox-mode.sh` uses Bash and awk only.
|
||||
- **Lookups that run before the environment exists use the standard library
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run pnpm directly when available, otherwise run it through Corepack."""
|
||||
"""Run the frontend's pinned pnpm version directly or through Corepack."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
@ -12,6 +14,15 @@ from pathlib import Path
|
||||
|
||||
FRONTEND_DIR = Path(__file__).resolve().parent.parent / "frontend"
|
||||
COREPACK_NOTICE = "Using pnpm via Corepack."
|
||||
PACKAGE_JSON_PATH = FRONTEND_DIR / "package.json"
|
||||
|
||||
|
||||
def find_corepack_command() -> list[str] | None:
|
||||
corepack_names = ("corepack.cmd", "corepack") if os.name == "nt" else ("corepack", "corepack.cmd")
|
||||
for name in corepack_names:
|
||||
if corepack_path := shutil.which(name):
|
||||
return [str(Path(corepack_path)), "pnpm"]
|
||||
return None
|
||||
|
||||
|
||||
def find_pnpm_command() -> list[str] | None:
|
||||
@ -21,11 +32,7 @@ def find_pnpm_command() -> list[str] | None:
|
||||
if pnpm_path := shutil.which(name):
|
||||
return [str(Path(pnpm_path))]
|
||||
|
||||
corepack_names = ("corepack.cmd", "corepack") if os.name == "nt" else ("corepack", "corepack.cmd")
|
||||
for name in corepack_names:
|
||||
if corepack_path := shutil.which(name):
|
||||
return [str(Path(corepack_path)), "pnpm"]
|
||||
return None
|
||||
return find_corepack_command()
|
||||
|
||||
|
||||
def run_pnpm(arguments: Sequence[str]) -> int:
|
||||
@ -42,6 +49,42 @@ def run_pnpm(arguments: Sequence[str]) -> int:
|
||||
)
|
||||
return 127
|
||||
|
||||
if Path(command[0]).stem.lower() == "pnpm":
|
||||
try:
|
||||
package_manager = json.loads(PACKAGE_JSON_PATH.read_text(encoding="utf-8"))["packageManager"]
|
||||
if not isinstance(package_manager, str) or not package_manager.startswith("pnpm@"):
|
||||
raise ValueError("packageManager must name a pinned pnpm version")
|
||||
pinned_version = package_manager.removeprefix("pnpm@")
|
||||
except (OSError, json.JSONDecodeError, KeyError, TypeError, ValueError) as exc:
|
||||
print(f"Error: Cannot read pinned pnpm version from frontend/package.json: {exc}", file=sys.stderr)
|
||||
return 127
|
||||
try:
|
||||
probe = subprocess.run(
|
||||
[command[0], "--version"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
stdin=subprocess.DEVNULL,
|
||||
check=False,
|
||||
shell=False,
|
||||
cwd=FRONTEND_DIR,
|
||||
)
|
||||
except OSError as exc:
|
||||
print(f"Error: Failed to check pnpm version via {command[0]}: {exc}", file=sys.stderr)
|
||||
return 126
|
||||
installed_version = probe.stdout.strip()
|
||||
if probe.returncode == 0 and re.fullmatch(r"\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?", installed_version):
|
||||
if installed_version != pinned_version:
|
||||
corepack_command = find_corepack_command()
|
||||
if corepack_command is None:
|
||||
print(
|
||||
f"Error: pnpm {installed_version} is on PATH, but frontend/package.json pins {pinned_version}. Install that version or make Corepack available.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 127
|
||||
command = corepack_command
|
||||
else:
|
||||
print("Warning: could not determine pnpm version; proceeding without pin check.", file=sys.stderr)
|
||||
|
||||
if Path(command[0]).stem.lower() == "corepack":
|
||||
print(COREPACK_NOTICE, file=sys.stderr)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user