fix(install): sync the lockfile engines mirrors with the manifests
`hermes desktop` still failed with EBADENGINE demanding Node >=26 after #76562, on a machine whose `apps/desktop/package.json` already said `^20.19.0 || >=22.12.0`. #76562 fixed the manifest but not its mirror in `package-lock.json`, and `npm ci` reads engines from the lockfile: package.json apps/desktop -> {'node': '^20.19.0 || >=22.12.0'} package-lock apps/desktop -> {'node': '>=26.0.0'} <- what gated Chasing that exposed a second, pre-existing problem: the floor #76562 declared was too generous. Running the real `npm ci` against the whole workspace on Node 22.21.1 fails on a transitive dependency — npm error notsup Not compatible with your version of node/npm: react-router@8.3.0 npm error notsup Required: {"node":">=22.22.0"} react-router 8.3.0 (a direct dependency of both `apps/desktop` and `web`) declares `>=22.22.0`, which is tighter than Vite's `^20.19 || >=22.12` and excludes all of Node 20. So `>=20.0.0` promised support the tree cannot deliver: an install on Node 20 or early 22 passed the installer's gate and then died inside `npm ci` on someone else's package. All four engine declarations now state the floor the dependency tree actually has, `>=22.22.0`: root `package.json`, `apps/desktop/package.json`, and both of their `package-lock.json` mirrors. The installer gates move with them (`node_satisfies_build` in install.sh, `Test-NodeVersionOk` in install.ps1) so a too-old system Node is replaced with the managed one *before* npm runs, and the failure a user does see names hermes-agent rather than a transitive package. NODE_VERSION stays 22 — latest-v22.x is 22.23.2, comfortably above the floor. The invariant test gains the case that would have caught the mirror drift on its own: the desktop assertion now pins the tightest floor a dependency actually declares, and the managed-runtime check compares majors, since install.sh fetches latest-v{major}.x rather than {major}.0.0. Verified with real `npm ci --dry-run` over the full workspace: - node 22.23.2 (what install.sh provisions) -> 1258 packages - node 26.5.1 -> 1189 packages - node 22.21.1 (below the floor) -> EBADENGINE naming hermes-agent, i.e. our own manifest, not react-router
This commit is contained in:
parent
8ced76d619
commit
63ff4b87b6
|
|
@ -8,7 +8,7 @@
|
|||
"type": "module",
|
||||
"main": "dist/electron-main.mjs",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
"node": ">=22.22.0"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "npm run clean:e2e && npm run clean:renderer && npm run clean:electron",
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
"typescript-eslint": "8.64.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0",
|
||||
"node": ">=22.22.0",
|
||||
"npm": "<11.10.0 || >=11.17.0"
|
||||
}
|
||||
},
|
||||
|
|
@ -168,7 +168,7 @@
|
|||
"wait-on": "9.0.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=26.0.0"
|
||||
"node": ">=22.22.0"
|
||||
}
|
||||
},
|
||||
"apps/desktop/node_modules/ignore": {
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@
|
|||
"brace-expansion": "5.0.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0",
|
||||
"node": ">=22.22.0",
|
||||
"npm": "<11.10.0 || >=11.17.0"
|
||||
},
|
||||
"allowScripts": {
|
||||
|
|
|
|||
|
|
@ -1190,11 +1190,11 @@ function Set-GitBashEnvVar {
|
|||
Write-Info "If needed, set HERMES_GIT_BASH_PATH manually to your bash.exe path."
|
||||
}
|
||||
|
||||
# The desktop build runs Vite ^8, which refuses to start on Node outside
|
||||
# `^20.19 || >=22.12`. That toolchain floor is the real constraint; do NOT
|
||||
# raise it past what a dependency actually demands, or every user on a working
|
||||
# Node gets their toolchain replaced for nothing. Returns $true when a
|
||||
# `node --version` string clears that floor.
|
||||
# The dependency tree's real Node floor is >=22.22.0, set by react-router 8.3.0
|
||||
# (`engines.node`). Keep this in sync with the root package.json: looser lets an
|
||||
# install reach a `npm ci` that dies with EBADENGINE, stricter replaces a working
|
||||
# user toolchain for nothing. Returns $true when a `node --version` string
|
||||
# clears that floor.
|
||||
function Test-NodeVersionOk {
|
||||
param([string]$Version)
|
||||
try {
|
||||
|
|
@ -1202,8 +1202,7 @@ function Test-NodeVersionOk {
|
|||
} catch {
|
||||
return $false
|
||||
}
|
||||
if ($v.Major -eq 20) { return ($v.Minor -ge 19) }
|
||||
if ($v.Major -eq 22) { return ($v.Minor -ge 12) }
|
||||
if ($v.Major -eq 22) { return ($v.Minor -ge 22) }
|
||||
return ($v.Major -gt 22)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -780,22 +780,20 @@ check_git() {
|
|||
exit 1
|
||||
}
|
||||
|
||||
# The desktop build runs Vite ^8, which refuses to start on Node outside
|
||||
# `^20.19 || >=22.12` — older Node lacks `node:util.styleText`, so `vite build`
|
||||
# crashes with a SyntaxError that surfaces only as the opaque "Build desktop
|
||||
# app … exit code 1" install failure. That toolchain floor is the real
|
||||
# constraint; do NOT raise it past what a dependency actually demands, or every
|
||||
# user on a working Node gets their toolchain replaced for nothing. Returns 0
|
||||
# when the given `node --version` string clears the floor; anything below it is
|
||||
# replaced with the Hermes-managed Node $NODE_VERSION.
|
||||
# The dependency tree's real Node floor is >=22.22.0, set by react-router 8.3.0
|
||||
# (`engines.node`), with Vite ^8 next at `^20.19 || >=22.12`. Keep this in sync
|
||||
# with the root package.json — a gate looser than the manifest lets an install
|
||||
# proceed to a `npm ci` that then dies with EBADENGINE, and a gate stricter than
|
||||
# the manifest replaces a working user toolchain for nothing. Returns 0 when the
|
||||
# given `node --version` string clears the floor; anything below it is replaced
|
||||
# with the Hermes-managed Node $NODE_VERSION.
|
||||
node_satisfies_build() {
|
||||
local ver="${1#v}"
|
||||
local major="${ver%%.*}"
|
||||
local minor="${ver#*.}"; minor="${minor%%.*}"
|
||||
case "$major" in ''|*[!0-9]*) return 1 ;; esac
|
||||
case "$minor" in ''|*[!0-9]*) minor=0 ;; esac
|
||||
if [ "$major" -eq 20 ] && [ "$minor" -ge 19 ]; then return 0; fi
|
||||
if [ "$major" -ge 22 ] && { [ "$major" -gt 22 ] || [ "$minor" -ge 12 ]; }; then return 0; fi
|
||||
if [ "$major" -ge 22 ] && { [ "$major" -gt 22 ] || [ "$minor" -ge 22 ]; }; then return 0; fi
|
||||
return 1
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ declare and the toolchain that has to satisfy it.
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
|
@ -116,9 +117,17 @@ class TestEnginesAreSatisfiable:
|
|||
else: # pragma: no cover - install.sh always defines it
|
||||
pytest.fail("install.sh does not define NODE_VERSION")
|
||||
|
||||
assert _satisfies_range(f"{managed_major}.0.0", node_range), (
|
||||
# install.sh fetches latest-v{major}.x, not {major}.0.0, so compare on
|
||||
# the major: the newest release of that line must be able to clear the
|
||||
# floor. A floor in a HIGHER major than we provision can never be met.
|
||||
floor_majors = [
|
||||
int(m.group(1))
|
||||
for m in re.finditer(r">=\s*v?(\d+)", node_range)
|
||||
]
|
||||
assert floor_majors, f"cannot read a floor out of {node_range!r}"
|
||||
assert managed_major >= min(floor_majors), (
|
||||
f"engines.node is {node_range!r} but install.sh provisions Node "
|
||||
f"{managed_major}. The runtime we ship must satisfy the floor we "
|
||||
f"{managed_major}.x. The runtime we ship must satisfy the floor we "
|
||||
"declare, or the install we just performed cannot install deps."
|
||||
)
|
||||
|
||||
|
|
@ -131,9 +140,10 @@ class TestEnginesAreSatisfiable:
|
|||
"""
|
||||
desktop = json.loads((REPO_ROOT / "apps" / "desktop" / "package.json").read_text())
|
||||
node_range = desktop["engines"]["node"]
|
||||
# Vite 8's own floor. If this ever legitimately rises, the assertion
|
||||
# The tightest floor any dependency actually declares (react-router
|
||||
# 8.3.0 -> >=22.22.0). If this legitimately rises, the assertion
|
||||
# documents the reason for the bump rather than blocking it.
|
||||
assert _satisfies_range("22.12.0", node_range), (
|
||||
assert _satisfies_range("22.22.0", node_range), (
|
||||
f"apps/desktop engines.node is {node_range!r}, which rejects Node "
|
||||
"22.12 — stricter than Vite requires. A desktop floor above the "
|
||||
"build toolchain's own floor replaces working user toolchains for "
|
||||
|
|
|
|||
Loading…
Reference in New Issue