`start-local.ps1` failed on a machine that had everything it needed, and then blamed the one thing that was definitely not the problem: "the newest version nvm has installed is too old. Install a newer one" — printed on a machine holding 26.7.0 and 24.13.1, both well past the floor. `nvm use latest` does not mean "the newest version I have installed". nvm-windows resolves `latest` against the remote release list, and `newest` is the alias for the newest installed. The docstring stated the opposite and the code was written against it. Here that resolved to 26.8.1, which is not installed, so nvm reported `activation error: Version not installed`, left v18.16.1 running, and exited 0. Two things had to change, and fixing either alone leaves it broken. The version asked for is now pinned in `NODE_VERSION` rather than chosen by alias, so two machines run the same Node instead of whatever each happens to have installed, and there is one line to bump for both entry points. The alias names are recorded in the docstring anyway, because `latest` and `newest` are easy to swap back by accident and the difference is the whole of this bug. `Use-Node` no longer treats an alias as automatically successful. That special case is why the error was wrong rather than merely unhelpful: it short-circuited on `$Version -eq 'latest'` regardless of what was running, swallowing nvm's `activation error` — which the function had already captured in `$output` for exactly this purpose — and returned success holding v18. The floor check downstream then reported the only explanation left to it. An alias switch is now verified against nvm's own report, so a failure says what nvm said. Keeping that half matters even with a pinned version, because no caller passes an alias today. The bug was someone reaching for one, and the next person reaching for one gets a truthful failure rather than a confident wrong answer. The floor check survives as a backstop against pinning `NODE_VERSION` below 20, and its message now says that rather than describing installed versions — a version that is not installed is `Use-Node`'s error to report, and it reports nvm's reason. Verified from a real v18.16.1 baseline: the pinned switch takes 18.16.1 to 26.7.0; a concrete version that is not installed throws with nvm's reason; `latest` throws instead of silently succeeding. `start-local.ps1` then runs the whole way through — Node switch, migrations, backend build, ready. Parse check clean. Shared by `start-local.ps1` and `run-tests.ps1`, so this broke both and fixes both. Closes #198 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
169 lines
6.4 KiB
PowerShell
169 lines
6.4 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Switching the machine's Node version, shared by start-local.ps1 and
|
|
run-tests.ps1.
|
|
|
|
.DESCRIPTION
|
|
Both scripts need Node 20 or newer for the same reason: node-pg-migrate
|
|
pulls in an lru-cache that calls diagnostics_channel.tracingChannel(), which
|
|
does not exist before Node 19.9. On Node 18 the migration dies inside
|
|
minified library code with "(0 , U.tracingChannel) is not a function", a
|
|
message that says nothing about versions. ts-jest and Playwright are subject
|
|
to the same floor.
|
|
|
|
Kept in one file because two copies of a version switch would drift, and the
|
|
half that drifts is the half nobody runs.
|
|
|
|
nvm-windows rewrites a machine-global symlink (NVM_SYMLINK, typically
|
|
C:\nvm4w\nodejs) rather than changing one shell, so switching here changes
|
|
the Node version for every terminal on the machine. That is intended — the
|
|
point is to work in whatever shell is already open — but it is announced
|
|
rather than done quietly, and it is put back afterwards.
|
|
#>
|
|
|
|
function Get-NodeVersionString {
|
|
return (node --version 2>$null)
|
|
}
|
|
|
|
function Get-NodeMajor {
|
|
$raw = Get-NodeVersionString
|
|
if (-not $raw) { return 0 }
|
|
return [int](($raw -replace '^v', '') -split '\.')[0]
|
|
}
|
|
|
|
<#
|
|
Switches, then checks what is actually running.
|
|
|
|
nvm-windows exits 0 for switches that did not take: a version it cannot
|
|
find, a symlink it cannot rewrite without elevation, and — seen on this
|
|
machine — a rewrite immediately after another one, where the directory
|
|
symlink is briefly still the old target. So the result is verified rather
|
|
than trusted, and retried once, because reporting a version that is not the
|
|
one running is worse than not switching at all.
|
|
|
|
nvm's own output is captured rather than discarded. Suppressing it hid the
|
|
only message that explained a failed switch.
|
|
#>
|
|
function Use-Node {
|
|
param(
|
|
[Parameter(Mandatory)][string]$Version,
|
|
[Parameter(Mandatory)][string]$Why,
|
|
[scriptblock]$Step,
|
|
[scriptblock]$Note
|
|
)
|
|
|
|
if ($Step) { & $Step "Switching Node to $Version ($Why)" }
|
|
if ($Note) { & $Note 'nvm changes the version for every shell on this machine, not just this one' }
|
|
|
|
$output = $null
|
|
foreach ($attempt in 1..2) {
|
|
$output = (nvm use $Version 2>&1 | Out-String).Trim()
|
|
|
|
$raw = Get-NodeVersionString
|
|
if ($raw) {
|
|
# An alias resolves to whatever nvm picked, so there is no version
|
|
# string to compare it against — the caller checks the major
|
|
# instead. What is checked here is nvm's own report, because it
|
|
# announces a switch it could not make while still exiting 0.
|
|
# Treating an alias as always-successful is what turned "the version
|
|
# nvm wanted is not installed" into a claim that the newest install
|
|
# was too old, which sent the reader to `nvm install` holding a list
|
|
# that already had newer versions on it.
|
|
$switched = if ($Version -in @('latest', 'lts', 'newest')) {
|
|
$output -notmatch 'activation error|not installed'
|
|
}
|
|
else {
|
|
$raw.TrimStart('v') -eq $Version.TrimStart('v')
|
|
}
|
|
|
|
if ($switched) {
|
|
if ($Note) { & $Note "node $raw" }
|
|
return $raw
|
|
}
|
|
}
|
|
|
|
if ($attempt -eq 1) {
|
|
if ($Note) { & $Note 'the switch has not taken yet; retrying' }
|
|
Start-Sleep -Milliseconds 750
|
|
}
|
|
}
|
|
|
|
$running = Get-NodeVersionString
|
|
if (-not $running) {
|
|
throw "Node is not on PATH after 'nvm use $Version'. Check that nvm-windows is installed.`n`nnvm said:`n$output"
|
|
}
|
|
|
|
throw @"
|
|
Asked nvm for Node $Version, but $running is still what runs.
|
|
|
|
nvm said:
|
|
$output
|
|
|
|
nvm-windows rewrites a symlink at $env:NVM_SYMLINK, and can report success
|
|
without having rewritten it. Check the version is installed, and that this shell
|
|
can write that link:
|
|
|
|
nvm list
|
|
"@
|
|
}
|
|
|
|
# The version every local run uses. Pinned rather than asked for by alias, so
|
|
# two machines run the same Node instead of whatever each happens to have
|
|
# installed. Bump it here and it moves for start-local.ps1 and run-tests.ps1
|
|
# together; the floor check below is a backstop against pinning it too low, not
|
|
# the specification.
|
|
$script:NODE_VERSION = '26.7.0'
|
|
|
|
<#
|
|
Switches to the pinned version and insists it clears the floor.
|
|
|
|
Asking nvm for an alias is what this used to do, and the two alias names are
|
|
worth recording because they are easy to swap by accident. `newest` means
|
|
the newest version installed on this machine. `latest` means the newest that
|
|
exists — nvm-windows resolves it against the remote release list — so on a
|
|
machine whose newest install is behind the current release, `nvm use latest`
|
|
reports "activation error: Version not installed" and leaves the old version
|
|
running. This asked for `latest` and reported the failure as "the newest
|
|
version nvm has installed is too old", which was false and sent the reader
|
|
to install something they already had. See #198.
|
|
|
|
A version that is not installed is Use-Node's error to report, and it now
|
|
reports nvm's own reason, so there is nothing to say about it here.
|
|
#>
|
|
function Use-NodeLatest {
|
|
param([scriptblock]$Step, [scriptblock]$Note)
|
|
|
|
Use-Node -Version $script:NODE_VERSION -Why 'this project needs Node 20 or newer' -Step $Step -Note $Note | Out-Null
|
|
|
|
$major = Get-NodeMajor
|
|
if ($major -lt 20) {
|
|
throw @"
|
|
Node is v$major after switching to $($script:NODE_VERSION), which is below the 20 this project needs.
|
|
|
|
NODE_VERSION in scripts/NodeVersion.ps1 is pinned to a version that is too old.
|
|
node-pg-migrate, ts-jest and Playwright all need 20 or newer.
|
|
"@
|
|
}
|
|
}
|
|
|
|
<#
|
|
Best effort by design. A failure to switch back must not mask the error that
|
|
got us here, nor turn a passing run into a failing one — but it must still
|
|
say so, because leaving the machine on the wrong version silently is how the
|
|
next confusing failure starts.
|
|
#>
|
|
function Restore-Node {
|
|
param(
|
|
[Parameter(Mandatory)][string]$Version,
|
|
[scriptblock]$Step,
|
|
[scriptblock]$Note
|
|
)
|
|
|
|
try {
|
|
Use-Node -Version $Version -Why 'restoring the machine default' -Step $Step -Note $Note | Out-Null
|
|
}
|
|
catch {
|
|
if ($Note) { & $Note "could not restore Node ${Version}: $($_.Exception.Message)" }
|
|
}
|
|
}
|