When `nvm install <target>` found that `<target>` was already installed,
the post-`nvm use` steps
(`--reinstall-packages-from`, default packages, and `--alias`/`--default`)
were gated on `[ $EXIT_CODE -ne 0 ]`.
Since that branch is only entered when `nvm use` succeeded (EXIT_CODE == 0),
those conditions were always false, so the steps were silently skipped.
The fresh-install branch correctly uses `-eq 0`;
mirror that here so `--reinstall-packages-from` actually migrates global packages
(and the alias/default get set) when the target version already exists.
Includes a regression test covering the already-installed path.
Fixes#3858
The standalone `nvm reinstall-packages <version>`
command can migrate global npm packages between already-installed versions,
but it was only mentioned in `nvm --help`, not in the README.
Add a section covering it.
Refs #3858
- Help text for `nvm install`/`use`/`exec`/`run`/`which` now ends with
"Uses .nvmrc if version is omitted; otherwise errors."
so it's clear that omitting the version is not a free fallback.
- Help signatures for `use`/`exec`/`run` now show `[current | <version>]`
to mirror `nvm which` and document that `current` is accepted.
- `nvm current` description now spells out that it resolves via `$PATH`
and is not affected by `.nvmrc`.
- README: add `current` to the list of special aliases, with the same caveat.
The `.nvmrc` section now states that `nvm use`/`install`/`which`
exit with status 127 when neither a version nor an `.nvmrc` resolves,
notes the (current) `exec`/`run` fallback as undefined behavior,
and points readers at `current` for the explicit "active node" use case.
Refs #3755
Previously a number of subcommands dumped the entire `nvm --help`
output (~100 lines) when arguments were missing or invalid,
drowning the real error.
Replace each dump with a short,
command-specific usage block that names the expected syntax and points to `nvm --help` for full help.
The exit code (127) is unchanged.
Affected subcommands:
- `nvm install` (no version + no .nvmrc)
- `nvm use` (version unresolvable)
- `nvm run` (no version + no .nvmrc)
- `nvm which` (no version + no .nvmrc)
- `nvm cache` (unknown subcommand)
- `nvm uninstall` (wrong arg count)
- `nvm unalias` (wrong arg count)
- `nvm install-latest-npm` (wrong arg count)
- `nvm reinstall-packages` / `nvm copy-packages` (wrong arg count)
The catch-all unknown-subcommand handler still dumps full help, since
in that case the user has no narrower context to be reminded about.
Refs #3755
Previously the message read "No .nvmrc file found",
which obscured the fact that the user also did not pass a version.
The new wording names both halves of the actual problem.
Refs #3755
Currently these commands silently fall back to the active node version when neither a version argument nor an `.nvmrc` resolves,
making them invisibly dependent on shell state and impossible to script predictably (see #3755).
Print a stderr deprecation warning in this case (suppressed by `--silent`) and continue with the active node version,
so existing callers keep working.
The follow-up change will turn this into a hard error;
pass `current` explicitly (e.g. `nvm exec current node ...`) to silence the warning and lock in the new behavior now.
Refs #3755
The branch-reset step sent `force` as a string via `gh api -f "force=true"`,
which the Git refs API rejects ("not a boolean", HTTP 422), failing the release run.
Use `-F` so it is sent as a boolean.
Also resolve the version from the latest `vX.Y.Z` tag
(via the tags API) rather than `releases/latest`,
so a manual `workflow_dispatch` with no input works even before the GitHub release object for a freshly pushed tag is published.
Upstream's `.gitmodules` uses a relative submodule url (`../nvmrc.git`),
which resolves against the superproject's origin:
on a fork without its own `nvmrc` fork that is `<owner>/nvmrc`,
which 404s and fails `actions/checkout` with `submodules: true`.
Keep `submodules: true` (so a fork's own `nvmrc` is used when present),
but mark checkout `continue-on-error` and, only when it failed,
re-point the submodule at `nvm-sh/nvmrc` and update.
The mirror-supplied (untrusted) version flows into download URLs,
filesystem paths, and the checksum awk match.
Reject any version outside the node/io.js grammar
(`[0-9A-Za-z._+-]`) before it is used.
A blocklist of metacharacters is used rather than a strict semver allowlist so RCs, nightlies, v8-canary, and io.js versions still install.
Completes the remediation of GHSA-3c52-35h2-gfmm.
The awk program string-interpolated the slug
(which embeds the untrusted, mirror-supplied version)
into its source, so a crafted version such as
`v1"==$2){system("touch${IFS}/tmp/x")}#`
was executed by awk's `system()`.
Pass the value via `-v tarball=...` so awk treats it as data and never as code.
See GHSA-3c52-35h2-gfmm
(a second injection sink fed by the same untrusted version field that `nvm_download`'s eval was; the source-install path reaches this during a normal `nvm install <version>`).
`nvm_download` built a curl/wget command string and ran it with `eval`.
The download URLs embed the version string taken from the mirror's `index.tab`,
which is untrusted.
Wrapping each argument in double quotes inside the `eval` does not prevent command substitution,
so a version field such as `v1$(touch /tmp/proof)` was executed by the shell.
This bypassed the earlier quoting hardening in 0ce8f5a.
Pass every argument as a literal argv element instead of constructing a string for `eval`,
on both the curl and wget paths,
so URL arguments are never re-parsed by the shell.
The wget flag translation is now done per-argument with a POSIX
`set --` loop rather than `sed` over the joined string.
The auth header is sanitized and added once,
before invoking the downloader.
The wget path passed `NVM_AUTH_HEADER` as the raw header line
(e.g. `--header "Bearer secret-token"`),
omitting the `Authorization:` header name that the curl path includes.
Per the documented usage
(`NVM_AUTH_HEADER="Bearer secret-token"`) the value is the credential,
so wget was sending a malformed header.
Prefix it with `Authorization: ` to match the curl path.
The test's `rm -rf "$NVM_DIR"` intermittently failed with
`rm: cannot remove...: Directory not empty`
and aborted under `set -e`.
Cause: after the clone/fetch, git can spawn a detached background gc/maintenance process that keeps writing into the clone dir while `rm -rf` runs (a concurrent writer makes the final rmdir fail).
It is not egress- or version-related
(it reproduces with all endpoints allowed),
and it is environment-timing dependent
(recently became consistent on the GitHub runners).
Disable git's background work for the test (gc.autoDetach / maintenance.auto)
so all git operations finish synchronously, and retry the rm once as a safety net.
`[ "${head_ref}" != "${avoid_ref}"]` is missing the space before the closing bracket,
so the shell prints `[: missing ']'` and the avoid_ref assertion never actually runs
(it is inside an `if` condition, so the error was non-fatal and silently disabled the check)
CodeQL (actions/missing-workflow-permissions)
flagged the matrix, test, and finisher jobs of nvm-install-test.yml for not declaring permissions.
Every other test workflow already sets least-privilege permissions per job; add them here to match:
`contents: read` for the matrix and test jobs, `contents: none` for the no-op finisher.
harden-runner runs with `egress-policy: block`, and the allow-list only included
`production.cloudflare.docker.com`. DockerHub serves image blobs from either its
Cloudflare or its CloudFront CDN; when a pull was routed to CloudFront
(`production.cloudfront.docker.com`) the connection was dropped, causing
`error pulling image configuration: ... connect: connection refused` and exit
125 in the xenial, installation_node, and fast (httpbin) suites. Allow both CDNs.
Container-based suites and the `nvm_download` httpbin check hard-fail whenever DockerHub is briefly unreachable
(observed: `dial tcp ...:443: connect: connection refused` while pulling images),
even though the change under test is fine.
This is unrelated to any test logic.
- tests-xenial / tests-installation-node: retry the `docker pull` up to 5 times before `docker run`, mirroring the existing apt-get retry
- `nvm_download` test: retry the httpbin pull and skip the auth-header checks (rather than fail) when the image cannot be pulled or run, and make cleanup tolerant of a missing container.
2026-06-03 13:12:31 -07:00
25 changed files with 758 additions and 115 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 -->
@@ -31,6 +31,7 @@
- [Usage](#usage)
- [Long-term Support](#long-term-support)
- [Migrating Global Packages While Installing](#migrating-global-packages-while-installing)
- [Migrating Global Packages Between Installed Versions](#migrating-global-packages-between-installed-versions)
- [Offline Install](#offline-install)
- [Default Global Packages From File While Installing](#default-global-packages-from-file-while-installing)
- [io.js](#iojs)
@@ -105,10 +106,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 or 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.
@@ -135,7 +136,7 @@ Eg: `curl ... | NVM_DIR="path/to/nvm"`. Ensure that the `NVM_DIR` does not conta
- The installer can use `git`, `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.4/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.5/install.sh | bash'`
#### Installing in Docker
@@ -151,7 +152,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.4/install.sh |PROFILE="${BASH_ENV}" bash
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.5/install.sh |PROFILE="${BASH_ENV}" bash
RUNecho node > .nvmrc
RUN nvm install
```
@@ -169,7 +170,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.4/install.sh | bash
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.5/install.sh | bash
# set env
ENVNVM_DIR=/root/.nvm
@@ -195,7 +196,7 @@ After creation of the image you can start container interactively and run comman
@@ -320,7 +321,7 @@ If you have `git` installed (requires git v1.7.10+):
1. clone this repo in the root of your user profile
-`cd ~/` from anywhere then `git clone https://github.com/nvm-sh/nvm.git .nvm`
1.`cd ~/.nvm` and check out the latest version with `git checkout v0.40.4`
1.`cd ~/.nvm` and check out the latest version with `git checkout v0.40.5`
1. activate `nvm` by sourcing it from your shell: `. ./nvm.sh`
Now add these lines to your `~/.bashrc`, `~/.profile`, or `~/.zshrc` file to have it automatically sourced upon login:
@@ -429,6 +430,7 @@ In place of a version pointer like "14.7" or "16.3" or "12.22.1", you can use th
-`iojs`: this installs the latest version of [`io.js`](https://iojs.org/en/)
-`stable`: this alias is deprecated, and only truly applies to `node``v0.12` and earlier. Currently, this is an alias for `node`.
-`unstable`: this alias points to `node``v0.11` - the last "unstable" node release, since post-1.0, all node versions are stable. (in SemVer, versions communicate breakage, not stability).
-`current`: the version currently active in this shell (i.e. what `node` resolves to via `$PATH`). It is **not** affected by `.nvmrc`. Useful when you want to refer to the active version explicitly — e.g. `nvm which current` always prints the path to the active `node`, regardless of whether an `.nvmrc` file is present.
### Long-term Support
@@ -482,6 +484,17 @@ nvm install-latest-npm
If you've already gotten an error to the effect of "npm does not support Node.js", you'll need to (1) revert to a previous node version (`nvm ls` & `nvm use <your latest _working_ version from the ls>`), (2) delete the newly created node version (`nvm uninstall <your _broken_ version of node from the ls>`), then (3) rerun your `nvm install` with the `--latest-npm` flag.
### Migrating Global Packages Between Installed Versions
`--reinstall-packages-from` is tied to `nvm install`. To migrate global npm packages between versions you _already_ have installed, without (re)installing anything, `nvm use` the destination and run `nvm reinstall-packages` as a standalone command, pointing at the version you want to copy _from_:
```sh
nvm use 22.22.2
nvm reinstall-packages 22.20.0
```
This reinstalls all global packages from `22.20.0` into the currently-active version (`22.22.2`). As with `--reinstall-packages-from`, the npm version itself is not changed.
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).
Afterwards, `nvm use`, `nvm install`,`nvm exec`, `nvm run`, and `nvm which` will use the version specified in the `.nvmrc` file if no version is supplied on the command line.
Afterwards, `nvm use`, `nvm install`, and `nvm which` will use the version specified in the `.nvmrc` file if no version is supplied on the command line; if no `.nvmrc` is found either, they exit with status `127`. (`nvm exec` and `nvm run` follow the same `.nvmrc` lookup, but currently fall back to the active node if neither resolves — treat that fallback as undefined behavior; pass an explicit version if you need predictable scripting.) If you want the currently active version, pass `current` explicitly (e.g. `nvm which current`) — `current` is not affected by `.nvmrc`.
For example, to make nvm default to the latest 5.9 release, the latest LTS version, or the latest node version for the current directory:
@@ -945,13 +958,13 @@ 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` 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._
@@ -1054,9 +1067,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)
@@ -1114,7 +1127,7 @@ Here's what you will need to do:
If one of these broken versions is installed on your system, the above step will likely still succeed even if you didn't include the `--shared-zlib` flag.
However, later, when you attempt to `npm install` something using your old version of node.js, you will see `incorrect data check` errors.
If you want to avoid the possible hassle of dealing with this, include that flag.
For more details, see [this issue](https://github.com/nodejs/node/issues/39313) and [this comment](https://github.com/nodejs/node/issues/39313#issuecomment-90.40.476)
For more details, see [this issue](https://github.com/nodejs/node/issues/39313) and [this comment](https://github.com/nodejs/node/issues/39313#issuecomment-90.40.576)
- Exit back to your native shell.
@@ -1141,7 +1154,7 @@ Now you should be able to use node as usual.
nvm_echo ' --lts Uses automatic LTS (long-term support) alias `lts/*`, if available.'
nvm_echo ' --lts=<LTS name> Uses automatic alias for provided LTS line, if available.'
nvm_echo ' nvm run [<version>] [<args>] Run `node` on <version> with <args> as arguments. Uses .nvmrc if available and version is omitted.'
nvm_echo ' nvm run [current | <version>] [<args>] Run `node` on <version> with <args> as arguments. Uses .nvmrc if version is omitted; otherwise errors.'
nvm_echo ' The following optional arguments, if provided, must appear directly after `nvm run`:'
# nvm_download should fail to download wrong_install.sh
! nvm_download "https://raw.githubusercontent.com/nvm-sh/nvm/HEAD/wrong_install.sh" >/dev/null || die "nvm_download should fail to download no existing file"
# nvm_download should pass when calling with auth header
TERM=dumb nvm alias "${TEST_ALIAS}" | grep 'v9.10.0' > /dev/null || die "--alias was not set on already-installed v9.10.0"
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.