mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-08-07 01:53:55 +00:00
fix(install): install npm 12 into the vendored Node 26 tree
The bundled-Node bootstrap unpacked the nodejs.org tarball and stopped.
Node 26.5.1 bundles npm 11.17.0, one minor below the root package.json's
own `engines.npm` floor of >=12 — and .npmrc sets `engine-strict=true`,
so that is fatal rather than a warning:
npm error code EBADENGINE
npm error notsup Required: {"node":">=26.0.0","npm":">=12.0.0"}
npm error notsup Actual: {"node":"v26.5.1","npm":"11.17.0"}
A brand-new install died at the first `npm ci` with "Desktop workspace
npm install failed". CI never saw it because the workflows run an
explicit `npm i -g npm@12`; the Python update path recovers through
hermes_cli/npm_engine.py, but the installer path had no such rung.
_nb_ensure_bundled_npm_range() now upgrades the managed tree's npm into
range right after the tarball lands, mirroring upgrade_managed_npm():
- temp cwd, so the checkout's own .npmrc (engine-strict,
min-release-age) does not gate the upgrade meant to satisfy it;
- npm_config_min_release_age=0, which also neutralises a user ~/.npmrc;
- explicit --prefix at the managed tree, because
_nb_configure_npm_prefix writes prefix=~/.local into its etc/npmrc
and a bare `npm i -g` would install a second npm elsewhere while the
managed tree stayed stale.
The range is read out of package.json rather than duplicated, so the two
cannot drift, with HERMES_NPM_TARGET_RANGE as an override and a >=12.0.0
fallback for a stripped install tree. An already-in-range npm skips the
network round-trip. Best-effort: a failed upgrade warns with the manual
command and keeps the working Node, since npm_engine.py still covers the
EBADENGINE that follows.
Verified against a real tree provisioned by this bootstrap: node v26.5.1
/ npm 12.0.2, bin/npm and bin/npx still relative-symlinked into the
upgraded lib/node_modules/npm, the ~/.local/bin links resolving to 12.0.2
through the tree, and no stray second npm under ~/.local/lib.
This commit is contained in:
@@ -76,6 +76,93 @@ _nb_node_major() {
|
||||
[[ "$v" =~ ^[0-9]+$ ]] && echo "$v" || echo 0
|
||||
}
|
||||
|
||||
# The npm range the checkout's root package.json demands. Read from the
|
||||
# manifest rather than duplicated here so the two can never drift; falls back
|
||||
# to the current floor when the manifest is unreadable (vendored copy of this
|
||||
# script, stripped install tree).
|
||||
_nb_npm_range() {
|
||||
if [ -n "${HERMES_NPM_TARGET_RANGE:-}" ]; then
|
||||
printf '%s\n' "$HERMES_NPM_TARGET_RANGE"
|
||||
return 0
|
||||
fi
|
||||
local repo_root manifest range
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." 2>/dev/null && pwd)"
|
||||
manifest="$repo_root/package.json"
|
||||
if [ -r "$manifest" ]; then
|
||||
# sed, not node: this runs before a usable node is guaranteed.
|
||||
range=$(sed -n '/"engines"/,/}/p' "$manifest" \
|
||||
| sed -n 's/.*"npm"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
|
||||
| head -1)
|
||||
if [ -n "$range" ]; then
|
||||
printf '%s\n' "$range"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
printf '>=12.0.0\n'
|
||||
}
|
||||
|
||||
# Upgrade the managed tree's bundled npm into the checkout's engines.npm range.
|
||||
#
|
||||
# The nodejs.org tarball ships whatever npm that Node major bundles — Node
|
||||
# 26.5.1 bundles npm 11.17.0, one minor below our own `engines.npm` floor of
|
||||
# >=12. With `engine-strict=true` in the repo .npmrc that is fatal, not a
|
||||
# warning, so a brand-new install died at the first `npm ci` with EBADENGINE.
|
||||
# The Python side recovers through hermes_cli/npm_engine.py; the installer path
|
||||
# had no such rung, so provision the right npm here instead of reacting later.
|
||||
#
|
||||
# Three details are load-bearing, all mirroring upgrade_managed_npm():
|
||||
# - a temp cwd, so the checkout's own .npmrc (engine-strict, min-release-age)
|
||||
# does not gate the very upgrade meant to satisfy it;
|
||||
# - npm_config_min_release_age=0, which also neutralises a user ~/.npmrc;
|
||||
# - an explicit --prefix at the managed tree, because
|
||||
# _nb_configure_npm_prefix wrote prefix=~/.local into its etc/npmrc, and
|
||||
# without the override this installs a second npm elsewhere while the
|
||||
# managed tree stays stale.
|
||||
#
|
||||
# Best-effort: a failure here leaves a working Node with an old npm, which is
|
||||
# strictly better than no Node at all, and npm_engine.py still covers the
|
||||
# EBADENGINE that follows.
|
||||
_nb_ensure_bundled_npm_range() {
|
||||
local npm_bin="$HERMES_HOME/node/bin/npm"
|
||||
[ -x "$npm_bin" ] || return 0
|
||||
|
||||
local range have want
|
||||
range="$(_nb_npm_range)"
|
||||
[ -n "$range" ] || return 0
|
||||
|
||||
# Skip the network round-trip when the bundled npm already satisfies the
|
||||
# range. Only the ">=N" shape we actually author is checked; anything more
|
||||
# exotic falls through to letting npm itself decide.
|
||||
if [[ "$range" =~ ^\>=([0-9]+) ]]; then
|
||||
want="${BASH_REMATCH[1]}"
|
||||
have=$("$npm_bin" --version 2>/dev/null | cut -d. -f1)
|
||||
if [[ "$have" =~ ^[0-9]+$ ]] && [ "$have" -ge "$want" ]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
_nb_log "Upgrading bundled npm to satisfy $range..."
|
||||
local tmp_cwd
|
||||
tmp_cwd=$(mktemp -d)
|
||||
if (
|
||||
cd "$tmp_cwd" || exit 1
|
||||
CI=1 npm_config_min_release_age=0 \
|
||||
"$npm_bin" install --global \
|
||||
--prefix "$HERMES_HOME/node" \
|
||||
"npm@$range" \
|
||||
--no-fund --no-audit --progress=false >/dev/null 2>&1
|
||||
); then
|
||||
rm -rf "$tmp_cwd"
|
||||
_nb_ok "npm $("$npm_bin" --version 2>/dev/null) installed"
|
||||
return 0
|
||||
fi
|
||||
|
||||
rm -rf "$tmp_cwd"
|
||||
_nb_warn "Could not upgrade bundled npm to $range — \`npm ci\` may fail with EBADENGINE."
|
||||
_nb_warn "Fix manually: npm install -g --prefix \"$HERMES_HOME/node\" npm@\"$range\""
|
||||
return 1
|
||||
}
|
||||
|
||||
_nb_have_modern_node() {
|
||||
command -v node >/dev/null 2>&1 || return 1
|
||||
[ "$(_nb_node_major)" -ge "$HERMES_NODE_MIN_VERSION" ]
|
||||
@@ -232,6 +319,9 @@ _nb_install_bundled_node() {
|
||||
|
||||
_nb_have_modern_node || return 1
|
||||
_nb_ok "Node $(node --version) installed to $HERMES_HOME/node/"
|
||||
# The tarball's bundled npm is usually below the repo's engines.npm floor.
|
||||
# Best-effort: an old npm still beats no Node.
|
||||
_nb_ensure_bundled_npm_range || true
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user