Files
computer-playbook/utils/install/npm.py
kevinveenbirkenbachandClaude Opus 5.5 30268cf552 fix(install): replace an end-of-life distro Node.js with the current LTS
Debian trixie ships nodejs 20.19, whose release line reached its end of
life on 2026-04-30. `@mermaid-js/mermaid-cli` 12 and its puppeteer
declare `node >=22.13.0`, so `make install-lint` failed in every fresh
Debian stack container with `All providers failed for
chrome-headless-shell` (#1088).

`ensure_node_present()` still installs the distro package first, then
reads the release line of `node` on PATH and looks it up in the
nodejs/Release schedule. A line past its `end` date is replaced by the
newest LTS from nodejs.org/dist/index.json: SHASUMS256.txt is verified
with gpgv against the active Node.js release keys from
nodejs/release-keys, the tarball against its SHA-256 entry, and the
release is unpacked into /usr/local, after which `node` on PATH must be
that release. Nothing pins a major: the next LTS line is picked up the
same way. Ubuntu 26.04's Node.js 22, supported until 2027-04-30, stays.

`ensure_npm_present()` calls it first, so a container that already holds
the end-of-life node is upgraded too rather than skipped because npm
exists. The call runs once per process.

Closes #1088

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 16:02:52 +02:00

67 lines
1.9 KiB
Python

"""npm-based installer helpers."""
from __future__ import annotations
import shutil
import subprocess
from pathlib import Path
from utils.install.primitives import ensure_dir_on_path, install_with_optional_sudo, log
def ensure_npm_present() -> None:
from utils.install.node import ensure_node_present
ensure_node_present()
if shutil.which("npm") is not None:
return
log("npm missing; attempting Node.js/npm install via system package manager.")
from utils.install.system_pkg import install_command_via_pkg
install_command_via_pkg("npm")
if shutil.which("npm") is None:
raise RuntimeError("npm not found and could not be installed")
def npm_install_global(package: str) -> None:
ensure_npm_present()
log(f"Installing '{package}' via npm (global)")
try:
install_with_optional_sudo(["npm", "install", "-g", package])
except subprocess.CalledProcessError:
pass
else:
return
prefix = Path("~/.npm-global").expanduser()
prefix.mkdir(parents=True, exist_ok=True)
try:
subprocess.run(
["npm", "install", "-g", "--prefix", str(prefix), package],
check=True,
)
except subprocess.CalledProcessError as exc:
raise RuntimeError(
f"npm install -g {package} failed (both global and --prefix)"
) from exc
ensure_dir_on_path(str(prefix / "bin"))
def npm_install_local_in_repo(repo_root: str) -> None:
ensure_npm_present()
has_lock = (Path(repo_root) / "package-lock.json").is_file()
argv = (
["npm", "ci", "--no-fund", "--no-audit"]
if has_lock
else ["npm", "install", "--no-fund", "--no-audit"]
)
try:
subprocess.run(argv, check=True, cwd=repo_root)
except subprocess.CalledProcessError as exc:
raise RuntimeError(f"npm install in {repo_root} failed") from exc
__all__ = ["ensure_npm_present", "npm_install_global", "npm_install_local_in_repo"]