feat(runtime): heal outdated managed Node trees up to the target major

Existing users who only ever launch Hermes (never re-run an installer)
kept their managed Node 22 tree forever: the heal path only fired for
*broken* trees, and a healthy 22 passes the --version probe. Now
"outdated" heals the same way "broken" does, on both sides of the mirror:

- hermes_constants.py: find_hermes_node_executable() checks
  _managed_node_tree_outdated() (managed node major <
  _HERMES_NODE_TARGET_MAJOR) and routes through the existing
  once-per-process heal_hermes_managed_node(), which redownloads
  latest-v26.x. When the heal fails (offline, download error) the
  outdated-but-runnable tree is still returned — old Node beats no Node.
- scripts/lib/node-bootstrap.sh: _nb_managed_node_needs_heal() gains the
  matching _nb_managed_node_outdated() rung, so heal_managed_node agrees
  with the Python side.

This is the same shape as the managed-uv flow: resolve the managed
runtime, notice it can't satisfy the requirement, provision the right one
in place, fall back gracefully.

Tests (tests/test_hermes_constants.py): outdated tree triggers heal and
returns the upgraded binary; failed heal still serves the old tree; an
at-target tree never heals (heal stub raises).
This commit is contained in:
ethernet
2026-08-01 21:14:42 -04:00
parent 713a983e4a
commit b13148d354
3 changed files with 134 additions and 16 deletions
+19 -1
View File
@@ -255,6 +255,24 @@ _nb_managed_tool_broken() {
return 1
}
# The managed node runs but is below HERMES_NODE_TARGET_MAJOR — an old tree
# from a previous install (e.g. 22). Outdated heals the same way broken does,
# so existing users get upgraded on the next heal probe, not just on a full
# installer re-run. Mirrors _managed_node_tree_outdated() in
# hermes_constants.py.
_nb_managed_node_outdated() {
local probe ver major
for probe in "$HERMES_HOME/node/bin/node" "$HERMES_HOME/node/node"; do
[ -x "$probe" ] || continue
ver="$("$probe" --version 2>/dev/null)" || return 1
major="${ver#v}"; major="${major%%.*}"
case "$major" in ''|*[!0-9]*) return 1 ;; esac
[ "$major" -lt "$HERMES_NODE_TARGET_MAJOR" ] && return 0
return 1
done
return 1
}
_nb_managed_node_needs_heal() {
local tool
for tool in node npm npx; do
@@ -262,7 +280,7 @@ _nb_managed_node_needs_heal() {
return 0
fi
done
return 1
_nb_managed_node_outdated
}
# Redownload the pinned nodejs.org tarball when a managed tree exists but