Bug Fixes
- `nvm_alias`, `nvm_version_path`: reject `..` path components
- `nvm_get_make_jobs`: count cores on platforms the `case` does not name
- `nvm-exec`: improve the no-version failure message
- `nvm use`: do not clobber `man`'s default search path (#3890)
- `nvm_err`, `nvm_err_with_colors`: do not fail when stderr is closed (#3907)
- `nvm_hash_reset`: only run `hash -r` where the shell supports it
- `nvm_alias`: unset zsh's `extendedglob` while reading an alias file (#3891)
- zsh: restore `nomatch` and `markdirs` rather than clobbering them
- `nvm --help`: render the set-colors legend in color (#3915)
- `nvm unload`: drop the undefined `nvm_format_help_message_colors`
- `nvm_get_mirror`: allow ports, userinfo, IPv6 hosts, and percent-encoding
- `nvm_sanitize_auth_header`: allow `~`, completing RFC 7235 `token68` (#3751)
Refactors
- `nvm --help`: extract the set-colors legend into `nvm_print_color_legend`
Performance
- Optimize nvm_tree_contains_path with in-memory POSIX parent-walk (#3912)
Docs
- [readme] document platforms with no official node binaries
- `install.sh`: Fix missing words in bash/zsh completion message (#3887)
- Remove stray closing HTML tag (#3919)
Misc
- [meta] remove CLAUDE.md symlink in favor of AGENTS.md (#3928)
- [meta] add `release-notes.sh`, the release notes generator
Tests
- homebrew/actions changed their default branch
- [actions] WSL: pin every distribution, and test the newest of each line
- `nvm_print_color_legend`: look up the function with `PATH` emptied
- `nvm_print_color_legend`: check for the function, not a same-named file
- `nvm_print_color_legend`: fix the `fast` suite on GNU userlands
- [actions] `release.yml`: provide `GH_TOKEN` so release-notes PR lookup can run
`nvm_alias` concatenated the requested name onto `$NVM_DIR/alias` and read whatever that resolved to,
so a name with a `..` component escaped the alias dir
- under the default layout, `../../.npmrc` reaches `$HOME/.npmrc`.
`nvm_print_alias_file` echoes every non-comment line of what it opens,
and `nvm use` reports the first one in its "is not yet installed" error,
so an untrusted `.nvmrc` could disclose any file the invoking user can read.
The write side already rejected `..` in an alias name (9275c5ba);
apply the same check on the read side, and to `nvm_version_path`,
which composes a version into a path with no check of its own.
Slashes stay legal, since `lts/iron` and the `lts/*` alias file depend on them.
`nvm_get_os` also returns `win` (Cygwin/MSYS/MinGW) and the empty string,
neither of which had an arm,
so `NVM_CPU_CORES` went unassigned
and nvm asked the user to report a gap it already knew about.
On an unrecognized `uname` that also meant a single-threaded source build;
`win` refuses source builds a few lines later either way.
`NUMBER_OF_PROCESSORS` comes last because Windows scopes it to the calling process' processor group.
Assigning `NVM_CPU_CORES` in every arm also keeps the
`nvm_is_natural_num` check below from aborting dash and ksh under `set -u`.
The README never said which platforms nvm can detect but nodejs.org does not build for,
so a user on one of them only finds out from a slow, silent, source-build fallback.
Also corrects the Alpine section:
nvm remaps Alpine to a musl arch itself now,
and nodejs.org has published `linux-x64-musl` since v24.20.0,
so both the "no concrete plans" and the "does not exist errors" claims are out of date.
Coverage bounds are stated per release line rather than per patch,
since the newest of them are still shipping.
The message claimed "no .nvmrc file found" even when one was found and its version simply was not installed,
and it never mentioned that `NODE_VERSION=default` selects the `default` alias.
Refs #3810
Replace looping subshell process forks (dirname)
in nvm_tree_contains_path with case-guarded in-memory POSIX parameter expansion () and exact string equality comparisons.
Ensures literal string matching for glob metacharacters (*, ?, [])
and full zsh compatibility across all platforms.
`man` consults its own configured search path only when MANPATH holds an empty entry;
a list without one replaces the default outright.
`nvm use` produced exactly that,
so activating a version could hide every system man page.
Contribute the empty entry as a trailing one,
so that nvm's directory keeps precedence over the system's,
and only when the list has none already,
so that repeated `nvm use` calls are idempotent and an empty entry the user placed stays where they put it.
The `$(manpath)` snapshot this replaces went stale the moment man's configuration changed,
and cost a subprocess per `nvm use`.
It was also dead: `local MANPATH` scoped the assignment to `nvm()`,
so `export` did not outlive the call and MANPATH went untouched whenever it started out unset.
Only ksh, where `local` is not a builtin, ever ran it.
`nvm deactivate` now unsets MANPATH where nvm's was the only real entry,
rather than leaving a bare `:` behind.
Reported and diagnosed by @al0ksar in #3890, which used a leading empty entry instead.
That one accrues one more colon on every `nvm use`,
which `nvm deactivate` then leaves behind,
and it loses to the system's man pages wherever nvm's bin directory is not first on PATH.
Supersedes #2077.
Refs #3890
Refs #2077
The Ubuntu entries were pinned but `Debian` and `Alpine` were floating
aliases, so what those jobs tested drifted whenever the action updated.
Every line now carries a pin and the newest release setup-wsl offers for
it: Debian 12 and 13, Ubuntu 18.04, 20.04 and 24.04, Alpine 3.17 and 3.23.
`Alpine-3.17` is the image the unofficial jobs already ran, named
explicitly. Debian's pin is 12 rather than 11 because `Debian` and
`Debian-11` share a floating `aka.ms/wsl-debian-gnulinux` redirect and so
cannot be held still at all, where `Debian-12` is an immutable
salsa.debian.org artifact. The Debian-only steps key off `startsWith`, so
both Debian entries take the same path and moving a pin will not strand them.
There is no floating "newest" name to use instead: `kali-linux` is the only
unversioned distribution left, and setup-wsl's own CI disables
`additional-packages` on it. The newest only moves when the action's major
does, so the `uses:` ref is the thing to keep current.
Which is the other half of this: move that ref from v6 to v7. v6 is a dead
major, five months stale at v6.1.0 with no v6.2.0 and v7.0.0 tagged the next
day, so new distributions will only ever appear on v7 and later. v7's only
removals are the deprecated bare `Debian` and `Alpine`, which pinning has
already stopped using, and every name here is valid on both. It also drops
the Node.js 20 deprecation warning each WSL job currently emits, and it
tightens two pins that v6 left floating: Ubuntu 20.04 moves from
`aka.ms/wslubuntu2004` to a 20.04.6 image on releases.ubuntu.com, and 24.04
to a 24.04.4 one. Six of the seven platforms are now immutable artifacts;
Ubuntu 18.04 predates the format and still redirects through aka.ms.
Choosing a supported Debian also settles the 404s that had been failing
every Debian job: bullseye left LTS on 2026-08-31 and its security suite is
between homes, with `security.debian.org` still publishing an index whose
pool is being pruned and `archive.debian.org` carrying no `debian-security`
for bullseye yet. bookworm is on live mirrors, so `ca-certificates` is
current again, which matters for a job whose whole purpose is to fetch nvm
and node over TLS.
To keep a pin working once its release ages out in turn, derive sources.list
from the image's own codename and list every layout a release passes through:
main and security move to the archive on separate schedules, and the security
suite is renamed to `<codename>/updates` on the way. `apt-get update`
discards whichever entries 404, and where an index outlives its pool we fall
back once to main alone. Debian 13 moved to deb822, so clear both formats
first and leave what we write as the only thing apt reads.
Drop the blanket `apt-get upgrade` with it: it fetched 77 packages the test
never uses, and `apt-get install` already takes the newest version any
working mirror offers.
Under bash's `set +h`, `hash -r` errors with "hash: hashing disabled",
which is then printed on every `nvm use`, including the one at shell startup.
Guarding on `$-` alone would be wrong:
`zsh` never puts `h` in `$-` - there `h` is `HIST_IGNORE_DUPS` -
and `dash` has no `h` flag at all,
so it would skip the call in 3 of the 4 shells the fast suite runs.
Gate on the shell instead, and skip only where the call fails:
bash under `set +h`, and `ksh93`, whose `hash` takes no `-r` and whose
usage error aborts `nvm use` before it can export `NVM_BIN`.
`ksh` is detected by probing rather than by version,
so `mksh` and `pdksh`, which do accept `-r`, keep getting the call.
The `${-#*h}` test is from #3910.
Refs #2065
Refs #3247
2026-09-04 09:16:10 -07:00
19 changed files with 659 additions and 65 deletions
# Node Version Manager [][3] [][4] [](https://bestpractices.dev/projects/684)
# Node Version Manager [][3] [][4] [](https://bestpractices.dev/projects/684)
<!-- To update this table of contents, ensure you have run `npm install` then `npm run doctoc` -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
- [Calling `nvm use` automatically in a directory with a `.nvmrc` file](#calling-nvm-use-automatically-in-a-directory-with-a-nvmrc-file)
@@ -106,10 +107,10 @@ nvm is a version manager for [node.js](https://nodejs.org/en/), designed to be i
To **install** or **update** nvm, you should run the [install script][2]. To do that, you may either download and run the script manually, or use the following [cURL](https://curl.se) or [Wget](https://www.gnu.org/software/wget/) command:
Running either of the above commands downloads a script and runs it. The script clones the nvm repository to `~/.nvm`, and attempts to add the source lines from the snippet below to the correct profile file (`~/.bashrc`, `~/.bash_profile`, `~/.zshrc`, or `~/.profile`). If you find the install script is updating the wrong profile file, set the `$PROFILE` env var to the profile file’s path, and then rerun the installation script.
- If the environment variable `$XDG_CONFIG_HOME` is present, it will place the `nvm` files there.</sub>
- If the environment variable `$XDG_CONFIG_HOME` is present, it will place the `nvm` files there.
- You can add `--no-use` to the end of the above script to postpone using `nvm` until you manually [`use`](#usage) it:
@@ -136,7 +137,7 @@ Eg: `curl ... | NVM_DIR="path/to/nvm"`. Ensure that the `NVM_DIR` does not conta
- The installer can use [`git`](https://git-scm.com/), `curl`, or `wget` to download `nvm`, whichever is available.
- You can instruct the installer to not edit your shell config (for example if you already get completions via a [zsh nvm plugin](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/nvm)) by setting `PROFILE=/dev/null` before running the `install.sh` script. Here's an example one-line command to do that: `PROFILE=/dev/null bash -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.7/install.sh | bash'`
- You can instruct the installer to not edit your shell config (for example if you already get completions via a [zsh nvm plugin](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/nvm)) by setting `PROFILE=/dev/null` before running the `install.sh` script. Here's an example one-line command to do that: `PROFILE=/dev/null bash -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.8/install.sh | bash'`
#### Installing in Docker
@@ -152,7 +153,7 @@ RUN touch "${BASH_ENV}"
RUNecho'. "${BASH_ENV}"' >> ~/.bashrc
# Download and install nvm
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.7/install.sh |PROFILE="${BASH_ENV}" bash
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.8/install.sh |PROFILE="${BASH_ENV}" bash
RUNecho node > .nvmrc
RUN nvm install
```
@@ -170,7 +171,7 @@ ARG NODE_VERSION=20
RUN apt update && apt install curl -y
# install nvm
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.7/install.sh | bash
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.8/install.sh | bash
# set env
ENVNVM_DIR=/root/.nvm
@@ -196,7 +197,7 @@ After creation of the image you can start container interactively and run comman
Some platforms and architectures have no binary on the default host at all; see [Platforms without official binaries](#platforms-without-official-binaries).
`nvm use` will not, by default, create a "current" symlink. Set `$NVM_SYMLINK_CURRENT` to "true" to enable this behavior, which is sometimes useful for IDEs. Note that using `nvm` in multiple shell tabs with this environment variable enabled can cause race conditions.
#### Pass Authorization header to mirror
@@ -655,6 +658,31 @@ To pass an Authorization header through to the mirror url, set `$NVM_AUTH_HEADER
nodejs.org does not publish a binary for every platform and architecture nvm can detect. Where it does not, `nvm install` falls back by default to compiling from source, which is slow and needs a C++ toolchain (see [Important Notes](#important-notes)); set `$NVM_NO_SOURCE_FALLBACK` to `1` to make a missing binary an error instead.
[unofficial-builds.nodejs.org](https://unofficial-builds.nodejs.org/download/release/) ([nodejs/unofficial-builds](https://github.com/nodejs/unofficial-builds)) publishes binaries for platforms the Node.js project does not officially support. To use them, point [`$NVM_NODEJS_ORG_MIRROR`](#use-a-mirror-of-node-binaries) at it:
| `linux-x86` (32-bit) | v9.x and earlier | v8.16.0 through v21.x |
| `linux-armv6l` | v11.x and earlier | v8.16.0 through v22.x |
| `linux-armv7l` | v23.x and earlier | none |
| `linux-x64-musl` (Alpine) | v24.20.0+ and v26.8.0+ only | v8.16.0 and later |
| `linux-arm64-musl` (Alpine) | none | v20.20.1 and later |
| `linux-loong64` | none | v18.18.0 and later |
| `linux-riscv64` | none | v17.7.1 through v26.0.0 |
| `win-x86` (32-bit) | v22.x and earlier | none |
Neither host is gapless, and both change over time; the `index.tab` at the root of each is authoritative. Where neither has a binary, `nvm install` compiles from source, except on non-WSL Windows, which nvm cannot build on; on FreeBSD and OpenBSD it skips the download and goes straight to source. Alpine has its own section: [Installing nvm on Alpine Linux](#installing-nvm-on-alpine-linux).
### .nvmrc
You can create a `.nvmrc` file containing a node version number (or any other string that `nvm` understands; see `nvm --help` for details) in the project root directory (or any parent directory).
@@ -870,7 +898,7 @@ nvm exposes the following environment variables:
-`NVM_CD_FLAGS` - used to maintain compatibility with zsh.
-`NVM_RC_VERSION` - version from .nvmrc file if being used.
Additionally, nvm modifies `PATH`, and, if present, `MANPATH` and `NODE_PATH` when changing versions.
Additionally, nvm modifies `PATH` when changing versions, along with `MANPATH` wherever a `manpath` command exists, and `NODE_PATH`, if present.
The following environment variables can be set to configure `nvm install`:
@@ -957,7 +985,7 @@ set -e
In order to provide the best performance (and other optimizations), nvm will download and install pre-compiled binaries for Node (and npm) when you run `nvm install X`. The Node project compiles, tests and hosts/provides these pre-compiled binaries which are built for mainstream/traditional Linux distributions (such as Debian, Ubuntu, [CentOS](https://www.centos.org), [RedHat](https://www.redhat.com) et al).
[Alpine Linux](https://www.alpinelinux.org), unlike mainstream/traditional Linux distributions, is based on [BusyBox](https://www.busybox.net/), a very compact (~5MB) Linux distribution. BusyBox (and thus Alpine Linux) uses a different C/C++ stack to most mainstream/traditional Linux distributions - [musl](https://www.musl-libc.org/). This makes binary programs built for such mainstream/traditional incompatible with Alpine Linux, thus we cannot simply `nvm install X` on Alpine Linux and expect the downloaded binary to run correctly - you'll likely see "...does not exist" errors if you try that.
[Alpine Linux](https://www.alpinelinux.org), unlike mainstream/traditional Linux distributions, is based on [BusyBox](https://www.busybox.net/), a very compact (~5MB) Linux distribution. BusyBox (and thus Alpine Linux) uses a different C/C++ stack to most mainstream/traditional Linux distributions - [musl](https://www.musl-libc.org/). This makes binary programs built for such mainstream/traditional distributions incompatible with Alpine Linux, so on `x64` and `arm64` nvm requests a `musl` build instead. nodejs.org publishes those only for some recent `x64` releases, so for anything else the download 404s and `nvm install X` falls back to compiling from source, unless you point it at a mirror that has one (see [Platforms without official binaries](#platforms-without-official-binaries)).
There is a `-s` flag for `nvm install` which requests nvm download Node source and compile it locally.
@@ -966,18 +994,18 @@ If installing nvm on Alpine Linux *is* still what you want or need to do, you sh
_Note: Alpine 3.5 can only install NodeJS versions up to v6.9.5, Alpine 3.6 can only install versions up to v6.10.3, Alpine 3.7 installs versions up to v8.9.3, Alpine 3.8 installs versions up to v8.14.0, Alpine 3.9 installs versions up to v10.19.0, Alpine 3.10 installs versions up to v10.24.1, Alpine 3.11 installs versions up to v12.22.6, Alpine 3.12 installs versions up to v12.22.12, Alpine 3.13 & 3.14 install versions up to v14.20.0, Alpine 3.15 & 3.16 install versions up to v16.16.0 (**These are all versions on the main branch**). Alpine 3.5 - 3.12 required the package [`python2`](https://www.python.org/) to build NodeJS, as they are older versions to build. Alpine 3.13+ requires `python3` to successfully build newer NodeJS versions, but you can use `python2` with Alpine 3.13+ if you need to build versions of node supported in Alpine 3.5 - 3.15, you just need to specify what version of NodeJS you need to install in the package install script._
The Node project has some desire but no concrete plans (due to the overheads of building, testing and support) to offer Alpine-compatible binaries.
The Node project now publishes an official `linux-x64-musl` binary for some recent releases (v24.20.0+ and v26.8.0+; no v25 release has one). There is no official `arm64` musl binary.
As a potential alternative, [@mhart](https://github.com/mhart) (a Node contributor) has some [Docker images for Alpine Linux with Node and optionally, npm, pre-installed](https://github.com/mhart/alpine-node).
@@ -1072,9 +1100,9 @@ You have to make sure that the user directory name in `$HOME` and the user direc
To change the user directory and/or account name follow the instructions [here](https://support.apple.com/en-us/HT201548)
@@ -29,4 +29,11 @@ nvm_tree_contains_path tmp2 tmp2/node || die '"tmp2" should contain "tmp2/node"'
nvm_tree_contains_path tmp2 tmp/node && die '"tmp2" should not contain "tmp/node"'
nvm_tree_contains_path tmp/ tmp/node || die '"tmp/" should contain "tmp/node"'
nvm_tree_contains_path tmp// tmp/node || die '"tmp//" should contain "tmp/node"'
nvm_tree_contains_path / /tmp/node || die '"/" should contain "/tmp/node"'
nvm_tree_contains_path "tmp[glob]" "tmp[glob]/node" || die '"tmp[glob]" should contain "tmp[glob]/node"'
nvm_tree_contains_path "tmp" "tmp[glob]/node" && die '"tmp" should not contain "tmp[glob]/node"'
cleanup
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.