File size: 3,692 Bytes
8d6541a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | """A CLI install must never resolve the desktop workspace.
``apps/desktop`` declares ``node-pty``, which ships no Linux prebuild and so
falls back to ``node-gyp rebuild``. A bare ``npm install`` at the repo root
resolves the root ``apps/*`` workspace glob and drags it in, so a host with no
make/gcc cannot finish an install for a machine that will never launch Electron
or a PTY addon (#38311, #38772). Since #85297 a failed npm install aborts the
whole install, so this is the difference between a working CLI install and none.
These exercise the real ``node_deps_workspace_args`` from ``scripts/install.sh``.
``--manifest`` makes the installer print its stage manifest and stop before
``main``, so sourcing it defines every function without running an install.
"""
import shutil
import subprocess
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent
INSTALL_SH = REPO_ROOT / "scripts" / "install.sh"
pytestmark = pytest.mark.skipif(shutil.which("bash") is None, reason="needs bash")
def workspace_args(install_dir: Path) -> list[str]:
"""Return the npm arguments install.sh would use for ``install_dir``."""
script = (
f'source "{INSTALL_SH}" --manifest >/dev/null\n'
f'node_deps_workspace_args "{install_dir}"\n'
'printf "%s\\n" "${NODE_DEPS_WORKSPACE_ARGS[@]}"\n'
)
result = subprocess.run(
["bash", "-c", script],
capture_output=True,
text=True,
check=True,
)
return result.stdout.split()
def make_checkout(root: Path, workspaces: tuple[str, ...]) -> Path:
"""Lay out a checkout with a root package and the given workspace dirs."""
(root / "package.json").write_text('{"workspaces": ["apps/*", "ui-tui", "web"]}')
for workspace in ("apps/desktop", *workspaces):
directory = root / workspace
directory.mkdir(parents=True)
(directory / "package.json").write_text("{}")
return root
def selected_workspaces(args: list[str]) -> list[str]:
return [value for flag, value in zip(args, args[1:]) if flag == "--workspace"]
@pytest.mark.parametrize(
"workspaces",
[("ui-tui", "web"), ("ui-tui",), ("web",), ()],
ids=["full", "tui-only", "web-only", "bare"],
)
def test_desktop_workspace_is_never_selected(tmp_path, workspaces):
"""No checkout shape may let apps/desktop (and its node-pty) resolve."""
args = workspace_args(make_checkout(tmp_path, workspaces))
assert "desktop" not in " ".join(args)
# Constraining npm is the whole point: an empty argument list would leave
# npm resolving every workspace, including apps/*.
assert args, "npm must be constrained, otherwise apps/* resolves"
assert "--workspaces=false" in args or selected_workspaces(args)
def test_present_workspaces_are_installed_alongside_the_root(tmp_path):
"""ui-tui and web are what a CLI install needs; the root owns shared
devDependencies that a scoped install would otherwise prune."""
args = workspace_args(make_checkout(tmp_path, ("ui-tui", "web")))
assert selected_workspaces(args) == ["ui-tui", "web"]
assert "--include-workspace-root" in args
def test_absent_workspace_is_not_named(tmp_path):
"""npm fails hard on a workspace it cannot find, so a partial checkout
must only name the workspaces that exist."""
args = workspace_args(make_checkout(tmp_path, ("ui-tui",)))
assert selected_workspaces(args) == ["ui-tui"]
def test_bare_checkout_installs_the_root_only(tmp_path):
"""With no installable workspace, npm still must not walk apps/*."""
args = workspace_args(make_checkout(tmp_path, ()))
assert args == ["--workspaces=false"]
|