diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 4bd0bc2e3..986289325 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -7,6 +7,9 @@ "author": "Nous Research", "type": "module", "main": "electron/main.cjs", + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, "scripts": { "dev": "concurrently -k \"npm:dev:renderer\" \"npm:dev:electron\"", "dev:fake-boot": "cross-env HERMES_DESKTOP_BOOT_FAKE=1 HERMES_DESKTOP_BOOT_FAKE_STEP_MS=650 npm run dev", diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 318508da1..b1197a071 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -714,19 +714,39 @@ 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` -- 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. Returns $true when a `node --version` +# string clears that floor. +function Test-NodeVersionOk { + param([string]$Version) + try { + $v = [version]($Version -replace '^v', '' -replace '-.*$', '') + } catch { + return $false + } + if ($v.Major -eq 20 -and $v.Minor -ge 19) { return $true } + if ($v.Major -ge 22 -and ($v.Major -gt 22 -or $v.Minor -ge 12)) { return $true } + return $false +} + function Test-Node { Write-Info "Checking Node.js (for browser tools)..." if (Get-Command node -ErrorAction SilentlyContinue) { $version = node --version - Write-Success "Node.js $version found" - $script:HasNode = $true - return $true + if (Test-NodeVersionOk $version) { + Write-Success "Node.js $version found" + $script:HasNode = $true + return $true + } + Write-Warn "Node.js $version is too old for the desktop build (need ^20.19 or >=22.12)" } - # Check our own managed install from a previous run + # Prefer a Hermes-managed Node from a previous run over a too-old system one. $managedNode = "$HermesHome\node\node.exe" - if (Test-Path $managedNode) { + if ((Test-Path $managedNode) -and (Test-NodeVersionOk (& $managedNode --version))) { $version = & $managedNode --version $env:Path = "$HermesHome\node;$env:Path" Write-Success "Node.js $version found (Hermes-managed)" @@ -734,7 +754,7 @@ function Test-Node { return $true } - Write-Info "Node.js not found -- installing Node.js $NodeVersion LTS..." + Write-Info "Installing Hermes-managed Node.js $NodeVersion LTS..." # Try the portable-zip path FIRST -- no UAC, no admin, no winget MSI. # winget install OpenJS.NodeJS.LTS triggers a system-wide MSI install @@ -1906,16 +1926,17 @@ function Install-Desktop { # so an "unpacked" build (electron-builder --dir) is enough — we # don't need to produce an NSIS/MSI artifact here. - if (-not $HasNode) { - # Cross-process driver mode: each `-Stage NAME` invocation runs in a - # fresh PowerShell process, so $script:HasNode set by Stage-Node - # in the previous process isn't visible. Re-detect rather than - # trusting the global. - if (-not (Get-Command npm -ErrorAction SilentlyContinue)) { - Write-Warn "Skipping desktop build (Node.js / npm not on PATH)" - $script:_StageSkippedReason = "Node.js not available" - return - } + # Always re-resolve Node here. Stages run in separate PowerShell processes, + # so $script:HasNode from Stage-Node isn't visible; more importantly Test-Node + # enforces the build floor (^20.19 || >=22.12) and prepends the Hermes-managed + # Node to PATH, so the build never runs on a too-old system Node -- the cause + # of the opaque "Build desktop app ... exit code 1" failure (Vite crashes on + # old Node). + Test-Node | Out-Null + if (-not (Get-Command npm -ErrorAction SilentlyContinue)) { + Write-Warn "Skipping desktop build (Node.js / npm not on PATH)" + $script:_StageSkippedReason = "Node.js not available" + return } $desktopDir = "$InstallDir\apps\desktop" diff --git a/scripts/install.sh b/scripts/install.sh index b62d87d46..4348d6bf5 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -702,26 +702,43 @@ 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. Returns 0 when the given `node --version` +# string clears that floor; anything below it is replaced with the Hermes- +# managed Node $NODE_VERSION LTS. +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 + return 1 +} + check_node() { log_info "Checking Node.js (for browser tools)..." - if command -v node &> /dev/null; then - local found_ver=$(node --version) - log_success "Node.js $found_ver found" + if command -v node &> /dev/null && node_satisfies_build "$(node --version)"; then + log_success "Node.js $(node --version) found" HAS_NODE=true return 0 fi - # Check our own managed install from a previous run - if [ -x "$HERMES_HOME/node/bin/node" ]; then + # Prefer a Hermes-managed Node from a previous run over a too-old system one. + if [ -x "$HERMES_HOME/node/bin/node" ] && node_satisfies_build "$("$HERMES_HOME/node/bin/node" --version)"; then export PATH="$HERMES_HOME/node/bin:$PATH" - local found_ver=$("$HERMES_HOME/node/bin/node" --version) - log_success "Node.js $found_ver found (Hermes-managed)" + log_success "Node.js $("$HERMES_HOME/node/bin/node" --version) found (Hermes-managed)" HAS_NODE=true return 0 fi - if [ "$DISTRO" = "termux" ]; then + if command -v node &> /dev/null; then + log_warn "Node.js $(node --version) is too old for the desktop build (need ^20.19 or >=22.12) — installing Hermes-managed Node $NODE_VERSION LTS..." + elif [ "$DISTRO" = "termux" ]; then log_info "Node.js not found — installing Node.js via pkg..." else log_info "Node.js not found — installing Node.js $NODE_VERSION LTS..." @@ -2235,11 +2252,12 @@ install_desktop() { # (--include-desktop / 'desktop' stage), so a missing toolchain is a hard # failure, not a silent skip — a silent skip yields a "complete" install # with no app and a confusing "couldn't find a built desktop" at launch. - # Try the Hermes-managed Node first (check_node adds $HERMES_HOME/node/bin - # to PATH or installs it) before giving up. - if ! command -v npm >/dev/null 2>&1; then - check_node - fi + # Always re-resolve Node here. Stages run in separate processes, so we can't + # trust an earlier check; more importantly check_node now enforces the build + # floor (^20.19 || >=22.12) and prepends the Hermes-managed Node to PATH, so + # the build never runs on a too-old system Node — the cause of the opaque + # "Build desktop app … exit code 1" failure (Vite crashes on old Node). + check_node if ! command -v npm >/dev/null 2>&1; then log_error "Cannot build desktop app: Node.js / npm unavailable" log_info "Install Node.js and retry: cd $desktop_dir && npm run pack"