Compare commits

...
5 Commits
Author SHA1 Message Date
Jordan Harband 65ded65d46 [readme] link every referenced person and project to its canonical page
Code scanning - action / CodeQL-Build (push) Canceled after 0s
Tests: `nvm install-latest-npm` / matrix (push) Canceled after 0s
Tests: linting / eclint (push) Canceled after 0s
Tests: linting / dockerfile_lint (push) Canceled after 0s
Tests: linting / doctoc (push) Canceled after 0s
Tests: linting / test_naming (push) Canceled after 0s
Tests: linting / check_exec (push) Canceled after 0s
Tests: release process / release (push) Canceled after 0s
Tests: shellcheck / shellcheck_matrix (bash_completion, bash) (push) Canceled after 0s
Tests: shellcheck / shellcheck_matrix (install.sh, bash) (push) Canceled after 0s
Tests: shellcheck / shellcheck_matrix (nvm-exec, bash) (push) Canceled after 0s
Tests: shellcheck / shellcheck_matrix (nvm.sh, bash) (push) Canceled after 0s
Tests: shellcheck / shellcheck_matrix (nvm.sh, dash) (push) Canceled after 0s
Tests: shellcheck / shellcheck_matrix (nvm.sh, ksh) (push) Canceled after 0s
Tests: shellcheck / shellcheck_matrix (nvm.sh, sh) (push) Canceled after 0s
Tests: alpine / fast arm64 (alpine 3, bash) (push) Canceled after 0s
Tests: alpine / fast arm64 (alpine 3, dash) (push) Canceled after 0s
Tests: alpine / fast arm64 (alpine 3, sh) (push) Canceled after 0s
Tests: alpine / fast arm64 (alpine 3, zsh) (push) Canceled after 0s
Tests: alpine / fast x64 (alpine 3, bash) (push) Canceled after 0s
Tests: alpine / fast x64 (alpine 3, dash) (push) Canceled after 0s
Tests: alpine / fast x64 (alpine 3, sh) (push) Canceled after 0s
Tests: alpine / fast x64 (alpine 3, zsh) (push) Canceled after 0s
Tests: alpine / fast arm64 (alpine 3.15, bash) (push) Canceled after 0s
Tests: alpine / fast arm64 (alpine 3.15, dash) (push) Canceled after 0s
Tests: alpine / fast arm64 (alpine 3.15, sh) (push) Canceled after 0s
Tests: alpine / fast arm64 (alpine 3.15, zsh) (push) Canceled after 0s
Tests: alpine / fast x64 (alpine 3.15, bash) (push) Canceled after 0s
Tests: alpine / fast x64 (alpine 3.15, dash) (push) Canceled after 0s
Tests: alpine / fast x64 (alpine 3.15, sh) (push) Canceled after 0s
Tests: alpine / fast x64 (alpine 3.15, zsh) (push) Canceled after 0s
Tests: alpine / fast arm64 (alpine 3.19, bash) (push) Canceled after 0s
Tests: alpine / fast arm64 (alpine 3.19, dash) (push) Canceled after 0s
Tests: alpine / fast arm64 (alpine 3.19, sh) (push) Canceled after 0s
Tests: alpine / fast arm64 (alpine 3.19, zsh) (push) Canceled after 0s
Tests: alpine / fast x64 (alpine 3.19, bash) (push) Canceled after 0s
Tests: alpine / fast x64 (alpine 3.19, dash) (push) Canceled after 0s
Tests: alpine / fast x64 (alpine 3.19, sh) (push) Canceled after 0s
Tests: alpine / fast x64 (alpine 3.19, zsh) (push) Canceled after 0s
Tests: alpine / musl-binary arm64 (alpine 3, node v22.21.1) (push) Canceled after 0s
Tests: alpine / musl-binary arm64 (alpine 3, node v24.9.0) (push) Canceled after 0s
Tests: alpine / musl-binary x64 (alpine 3, node v22.12.0) (push) Canceled after 0s
Tests: alpine / musl-binary x64 (alpine 3.10, node v10.24.1) (push) Canceled after 0s
Tests: alpine / musl-binary x64 (alpine 3.10, node v8.17.0) (push) Canceled after 0s
Tests: alpine / musl-binary x64 (alpine 3.12, node v12.22.12) (push) Canceled after 0s
Tests: alpine / musl-binary x64 (alpine 3.15, node v14.21.3) (push) Canceled after 0s
Tests: alpine / musl-binary x64 (alpine 3.16, node v16.20.2) (push) Canceled after 0s
Tests: alpine / musl-binary x64 (alpine 3.18, node v18.20.4) (push) Canceled after 0s
Tests: alpine / musl-binary arm64 (alpine 3.20, node v20.20.1) (push) Canceled after 0s
Tests: alpine / musl-binary x64 (alpine 3.20, node v20.18.1) (push) Canceled after 0s
Tests: installation_node / installation_node (bash) (push) Canceled after 0s
Tests: installation_node / installation_node (bash, without curl) (push) Canceled after 0s
Tests: installation_node / installation_node (dash) (push) Canceled after 0s
Tests: fast / fast (bash, gawk) (push) Canceled after 0s
Tests: fast / fast (dash, gawk) (push) Canceled after 0s
Tests: fast / fast (sh, gawk) (push) Canceled after 0s
Tests: fast / fast (zsh, gawk) (push) Canceled after 0s
Tests: fast / fast (bash, mawk) (push) Canceled after 0s
Tests: fast / fast (dash, mawk) (push) Canceled after 0s
Tests: fast / fast (sh, mawk) (push) Canceled after 0s
Tests: fast / fast (zsh, mawk) (push) Canceled after 0s
Tests: installation_iojs / installation_iojs without curl (bash) (push) Canceled after 0s
Tests: installation_iojs / installation_iojs without curl (dash) (push) Canceled after 0s
Tests: installation_iojs / installation_iojs without curl (sh) (push) Canceled after 0s
Tests: installation_iojs / installation_iojs without curl (zsh) (push) Canceled after 0s
Tests: installation_iojs / actually compile io.js from source (gcc 4.9 container) (push) Canceled after 0s
Tests: installation_node / installation_node (dash, without curl) (push) Canceled after 0s
Tests: installation_node / installation_node (sh) (push) Canceled after 0s
Tests: installation_node / installation_node (sh, without curl) (push) Canceled after 0s
Tests: installation_node / installation_node (zsh) (push) Canceled after 0s
Tests: installation_node / installation_node (zsh, without curl) (push) Canceled after 0s
Tests: xenial / xenial (bash) (push) Canceled after 0s
Tests: xenial / xenial (dash) (push) Canceled after 0s
Tests: xenial / xenial (sh) (push) Canceled after 0s
Tests: xenial / xenial (zsh) (push) Canceled after 0s
urchin tests / tests (bash, install_script) (push) Canceled after 0s
urchin tests / tests (bash, installation_iojs) (push) Canceled after 0s
urchin tests / tests (bash, slow) (push) Canceled after 0s
urchin tests / tests (bash, sourcing) (push) Canceled after 0s
urchin tests / tests (dash, installation_iojs) (push) Canceled after 0s
urchin tests / tests (dash, slow) (push) Canceled after 0s
urchin tests / tests (dash, sourcing) (push) Canceled after 0s
urchin tests / tests (sh, installation_iojs) (push) Canceled after 0s
urchin tests / tests (sh, slow) (push) Canceled after 0s
urchin tests / tests (sh, sourcing) (push) Canceled after 0s
urchin tests / tests (zsh, installation_iojs) (push) Canceled after 0s
urchin tests / tests (zsh, slow) (push) Canceled after 0s
urchin tests / tests (zsh, sourcing) (push) Canceled after 0s
update readme TOC / update readme TOC (push) Canceled after 0s
Tests on Windows: `nvm install` / MSYS fail prefix nvm install (push) Canceled after 0s
Tests on Windows: `nvm install` / MSYS nvm install (--default 12) (push) Canceled after 0s
Tests on Windows: `nvm install` / MSYS nvm install (--lts) (push) Canceled after 0s
Tests on Windows: `nvm install` / MSYS nvm install (--no-progress 10) (push) Canceled after 0s
Tests on Windows: `nvm install` / Cygwin nvm install (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 10, Debian) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 10, Ubuntu-18.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 10, Ubuntu-20.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 12, Debian) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 12, Ubuntu-18.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 12, Ubuntu-20.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 14, Debian) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 14, Ubuntu-18.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 14, Ubuntu-20.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 16, Debian) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 16, Ubuntu-18.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 16, Ubuntu-20.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 18, Debian) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 18, Ubuntu-20.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 21, Debian) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 21, Ubuntu-20.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 10, Debian) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 10, Ubuntu-18.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 10, Ubuntu-20.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 12, Debian) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 12, Ubuntu-18.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 12, Ubuntu-20.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 14, Debian) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 14, Ubuntu-18.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 14, Ubuntu-20.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 16, Debian) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 16, Ubuntu-18.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 16, Ubuntu-20.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 18, Debian) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 18, Ubuntu-20.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 21, Debian) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 21, Ubuntu-20.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, --lts, Alpine) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 10, Alpine) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 11, Alpine) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 12, Alpine) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 14, Alpine) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 16, Alpine) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 18, Alpine) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 21, Alpine) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, --lts, Alpine) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 10, Alpine) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 11, Alpine) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 12, Alpine) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 14, Alpine) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 16, Alpine) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 18, Alpine) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 21, Alpine) (push) Canceled after 0s
Tests: `nvm install-latest-npm` / nvm install-latest-npm (${{ fromJson(needs.matrix.outputs.latest) }}) (push) Canceled after 0s
Tests: `nvm install-latest-npm` / nvm install-latest-npm (0.10) (push) Canceled after 0s
Tests: `nvm install-latest-npm` / nvm install-latest-npm (0.12) (push) Canceled after 0s
Tests: `nvm install-latest-npm` / nvm install-latest-npm (14.16) (push) Canceled after 0s
Tests: `nvm install-latest-npm` / nvm install-latest-npm (14.17) (push) Canceled after 0s
Tests: `nvm install-latest-npm` / nvm install-latest-npm (20.4) (push) Canceled after 0s
Tests: `nvm install-latest-npm` / nvm install-latest-npm (20.5) (push) Canceled after 0s
Tests: `nvm install-latest-npm` / nvm install-latest-npm (21) (push) Canceled after 0s
Tests: `nvm install-latest-npm` / nvm install-latest-npm (4.4) (push) Canceled after 0s
Tests: `nvm install-latest-npm` / nvm install-latest-npm (4.5) (push) Canceled after 0s
Tests: `nvm install-latest-npm` / nvm install-latest-npm (4.6) (push) Canceled after 0s
Tests: `nvm install-latest-npm` / nvm install-latest-npm (5.9) (push) Canceled after 0s
Tests: `nvm install-latest-npm` / nvm install-latest-npm (6.1) (push) Canceled after 0s
Tests: `nvm install-latest-npm` / nvm install-latest-npm (9.0) (push) Canceled after 0s
Tests: `nvm install-latest-npm` / nvm install-latest-npm (9.1) (push) Canceled after 0s
Tests: `nvm install-latest-npm` / nvm install-latest-npm (9.2) (push) Canceled after 0s
Tests: `nvm install-latest-npm` / nvm install-latest-npm (push) Canceled after 0s
Tests: linting / all linting (push) Canceled after 0s
Tests: shellcheck / shellcheck (push) Canceled after 0s
Tests: alpine / all alpine tests (push) Canceled after 0s
Tests: fast / all fast tests (push) Canceled after 0s
Tests: installation_iojs / all installation_iojs tests (push) Canceled after 0s
Tests: installation_node / all installation_node tests (push) Canceled after 0s
Tests: xenial / all xenial tests (push) Canceled after 0s
urchin tests / all test suites, all shells (push) Canceled after 0s
Tests on Windows: `nvm install` / tests, on windows (push) Canceled after 0s
Link the first mention of each named person to their GitHub and each named project, tool, or OS to its official homepage or repo.

Mentions that appear only inside a heading (Ansible),
an HTML comment (doctoc),
or a multi-word code span (grunt) are left unlinked,
since a link there would
break the generated table-of-contents anchors or fail to render.
2026-07-24 11:11:57 -07:00
Jordan Harband 809f3ee5ab [New] nvm install: serialize concurrent installs of the same version
Two `nvm install <same version>` runs could race on the version directory
- one removing or replacing it while the other reads or writes it.
Take a per-version advisory lock
(an atomically-created directory under $NVM_DIR/.cache locks)
around the binary/source install,
so a second run of the same version waits for the first;
installs of different versions never contend.

NVM_INSTALL_LOCK_TIMEOUT (seconds, default 600) bounds the wait,
after which nvm reports the lock path so a lock left by a killed install can be removed.
NVM_INSTALL_LOCK_STALE (minutes, default 0 / off)
opts into automatically stealing a lock older than that,
for unattended or CI use.
2026-07-24 10:55:58 -07:00
Jordan Harband 9570a22bfe [New] nvm install: add NVM_NO_SOURCE_FALLBACK to disable the source fallback
By default a failed binary download falls back to compiling node from source,
which is slow and pointless on platforms that always have prebuilt binaries.
`-b` already disables the fallback per-invocation;
`NVM_NO_SOURCE_FALLBACK=1` makes it the default for every `nvm install`,
so CI images and dev setups need not thread `-b` through every callsite.

It behaves exactly like `-b`
- aborting with a non-zero exit on a failed binary download
- and is mutually exclusive with `-s`, for the same reason `-b` is.
2026-07-24 10:54:17 -07:00
Jordan Harband aee1f83f0f [Robustness] nvm install: reject a structurally broken installed version
`nvm_is_version_installed` only checks that `bin/node` has the execute bit,
which a zero-byte binary and a dangling `npm` symlink both pass, so a partial
install could be reported as a success — and a broken existing version could
short-circuit `nvm install` as "already installed".

Add `nvm_validate_install`, which requires a non-empty `bin/node` and an `npm`
entry that resolves, and use it in two places: gate the "already installed"
shortcut on it, so a broken version is reinstalled rather than reused, and
re-check it after an install reports success, so a broken result fails loudly
instead of being activated.

It checks layout, not execution: a correctly installed binary can still fail
to run on an incompatible host (e.g. a newer node on an older glibc), which is
not a broken install, and a corrupt download is already rejected by the
checksum check before extraction.
2026-07-24 10:51:53 -07:00
Jordan Harband 72a878447f [Robustness] nvm_install_binary_extract: replace a broken version dir atomically
A version directory left without a working `bin/node`
- e.g. a partial or interrupted earlier install
- wedged reinstallation:
`nvm install` saw the version as not installed and re-extracted,
but the per-entry `mv` refused to overwrite the leftover non-empty `bin/`,
`lib/`, … subdirectories and left a half-updated tree behind
(and, without `-b`, fell back to a from-source compile)

Remove any pre-existing version directory and move the freshly extracted tree into place with a single rename,
so a version is either fully installed or not present at all.
The removal is safe: it runs only after the tarball has downloaded and extracted successfully into the cache.
Fall back to the previous per-entry move when a single rename cannot cross filesystems.
2026-07-24 10:50:47 -07:00
9 changed files with 563 additions and 34 deletions
+29 -21
View File
@@ -74,7 +74,7 @@
## Intro
`nvm` allows you to quickly install and use different versions of node via the command line.
`nvm` allows you to quickly install and use different versions of [node](https://nodejs.org) via the command line.
**Example:**
```sh
@@ -96,7 +96,7 @@ Simple as that!
## About
nvm is a version manager for [node.js](https://nodejs.org/en/), designed to be installed per-user, and invoked per-shell. `nvm` works on any POSIX-compliant shell (sh, dash, ksh, zsh, bash), in particular on these platforms: unix, macOS, and [Windows WSL](https://github.com/nvm-sh/nvm#important-notes).
nvm is a version manager for [node.js](https://nodejs.org/en/), designed to be installed per-user, and invoked per-shell. `nvm` works on any POSIX-compliant shell (sh, [dash](https://git.kernel.org/pub/scm/utils/dash/dash.git), [ksh](https://github.com/ksh93/ksh), [zsh](https://www.zsh.org), [bash](https://www.gnu.org/software/bash/)), in particular on these platforms: unix, [macOS](https://www.apple.com/macos/), and [Windows WSL](https://github.com/nvm-sh/nvm#important-notes).
<a id="installation-and-update"></a>
<a id="install-script"></a>
@@ -104,7 +104,7 @@ nvm is a version manager for [node.js](https://nodejs.org/en/), designed to be i
### Install & Update Script
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:
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:
```sh
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.6/install.sh | bash
```
@@ -134,13 +134,13 @@ export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || pr
- You can customize the install source, directory, profile, and version using the `NVM_SOURCE`, `NVM_DIR`, `PROFILE`, and `NODE_VERSION` variables.
Eg: `curl ... | NVM_DIR="path/to/nvm"`. Ensure that the `NVM_DIR` does not contain a trailing slash.
- The installer can use `git`, `curl`, or `wget` to download `nvm`, whichever is available.
- 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.6/install.sh | bash'`
#### Installing in Docker
When invoking bash as a non-interactive shell, like in a Docker container, none of the regular profile files are sourced. In order to use `nvm`, `node`, and `npm` like normal, you can instead specify the special `BASH_ENV` variable, which bash sources when invoked non-interactively.
When invoking bash as a non-interactive shell, like in a [Docker](https://www.docker.com) container, none of the regular profile files are sourced. In order to use `nvm`, `node`, and [`npm`](https://www.npmjs.com) like normal, you can instead specify the special `BASH_ENV` variable, which bash sources when invoked non-interactively.
```Dockerfile
# Use bash for the shell
@@ -215,7 +215,7 @@ user@host:/tmp/test $ docker run --rm -it nvmimage npm -v
#### Troubleshooting on Linux
On Linux, after running the install script, if you get `nvm: command not found` or see no feedback from your terminal after you type `command -v nvm`, simply close your current terminal, open a new terminal, and try verifying again.
On [Linux](https://www.kernel.org/), after running the install script, if you get `nvm: command not found` or see no feedback from your terminal after you type `command -v nvm`, simply close your current terminal, open a new terminal, and try verifying again.
Alternatively, you can run the following commands for the different shells on the command line:
*bash*: `source ~/.bashrc`
@@ -228,7 +228,7 @@ These should pick up the `nvm` command.
#### Troubleshooting on macOS
Since OS X 10.9, `/usr/bin/git` has been preset by Xcode command line tools, which means we can't properly detect if Git is installed or not. You need to manually install the Xcode command line tools before running the install script, otherwise, it'll fail. (see [#1782](https://github.com/nvm-sh/nvm/issues/1782))
Since OS X 10.9, `/usr/bin/git` has been preset by [Xcode](https://developer.apple.com/xcode/) command line tools, which means we can't properly detect if Git is installed or not. You need to manually install the Xcode command line tools before running the install script, otherwise, it'll fail. (see [#1782](https://github.com/nvm-sh/nvm/issues/1782))
If you get `nvm: command not found` after running the install script, one of the following might be the reason:
@@ -278,9 +278,9 @@ which should output `nvm` if the installation was successful. Please note that `
### Important Notes
If you're running a system without prepackaged binary available, which means you're going to install node or io.js from its source code, you need to make sure your system has a C++ compiler. For OS X, Xcode will work, for Debian/Ubuntu based GNU/Linux, the `build-essential` and `libssl-dev` packages work.
If you're running a system without prepackaged binary available, which means you're going to install node or [io.js](https://iojs.org) from its source code, you need to make sure your system has a C++ compiler. For OS X, Xcode will work, for [Debian](https://www.debian.org)/[Ubuntu](https://ubuntu.com) based GNU/Linux, the `build-essential` and `libssl-dev` packages work.
**Note:** `nvm` also supports Windows in some cases. It should work through WSL (Windows Subsystem for Linux) depending on the version of WSL. It should also work with [Git Bash](https://gitforwindows.org/) (MSYS) or [Cygwin](https://cygwin.com). Otherwise, for Windows, a few alternatives exist, which are neither supported nor developed by us:
**Note:** `nvm` also supports Windows in some cases. It should work through WSL (Windows Subsystem for Linux) depending on the version of WSL. It should also work with [Git Bash](https://gitforwindows.org/) ([MSYS](https://www.msys2.org/)) or [Cygwin](https://cygwin.com). Otherwise, for Windows, a few alternatives exist, which are neither supported nor developed by us:
- [nvm-windows](https://github.com/coreybutler/nvm-windows)
- [nodist](https://github.com/marcelklehr/nodist)
@@ -294,7 +294,7 @@ If you're running a system without prepackaged binary available, which means you
- [nvm.fish](https://github.com/jorgebucaran/nvm.fish) - The Node.js version manager you'll adore, crafted just for Fish
- [fish-nvm](https://github.com/FabioAntunes/fish-nvm) - Wrapper around nvm for fish, delays sourcing nvm until it's actually used.
**Note:** We still have some problems with FreeBSD, because there is no official pre-built binary for FreeBSD, and building from source may need [patches](https://www.freshports.org/www/node/files/patch-deps_v8_src_base_platform_platform-posix.cc); see the issue ticket:
**Note:** We still have some problems with [FreeBSD](https://www.freebsd.org), because there is no official pre-built binary for FreeBSD, and building from source may need [patches](https://www.freshports.org/www/node/files/patch-deps_v8_src_base_platform_platform-posix.cc); see the issue ticket:
- [[#900] [Bug] node on FreeBSD may need to be patched](https://github.com/nvm-sh/nvm/issues/900)
- [nodejs/node#3716](https://github.com/nodejs/node/issues/3716)
@@ -309,11 +309,11 @@ If you're running a system without prepackaged binary available, which means you
- If you have an `~/.npmrc` file, make sure it does not contain any `prefix` settings (which is not compatible with `nvm`)
- You can (but should not?) keep your previous "system" node install, but `nvm` will only be available to your user account (the one used to install nvm). This might cause version mismatches, as other users will be using `/usr/local/lib/node_modules/*` VS your user account using `~/.nvm/versions/node/vX.X.X/lib/node_modules/*`
Homebrew installation is not supported. If you have issues with homebrew-installed `nvm`, please `brew uninstall` it, and install it using the instructions below, before filing an issue.
[Homebrew](https://brew.sh) installation is not supported. If you have issues with homebrew-installed `nvm`, please `brew uninstall` it, and install it using the instructions below, before filing an issue.
**Note:** If you're using `zsh` you can easily install `nvm` as a zsh plugin. Install [`zsh-nvm`](https://github.com/lukechilds/zsh-nvm) and run `nvm upgrade` to upgrade ([you can set](https://github.com/lukechilds/zsh-nvm#auto-use) `NVM_AUTO_USE=true` to have it automatically detect and use `.nvmrc` files).
**Note:** Git versions before v1.7 may face a problem of cloning `nvm` source from GitHub via https protocol, and there is also different behavior of git before v1.6, and git prior to [v1.17.10](https://github.com/git/git/commit/5a7d5b683f869d3e3884a89775241afa515da9e7) can not clone tags, so the minimum required git version is v1.7.10. If you are interested in the problem we mentioned here, please refer to GitHub's [HTTPS cloning errors](https://help.github.com/articles/https-cloning-errors/) article.
**Note:** Git versions before v1.7 may face a problem of cloning `nvm` source from [GitHub](https://github.com) via https protocol, and there is also different behavior of git before v1.6, and git prior to [v1.17.10](https://github.com/git/git/commit/5a7d5b683f869d3e3884a89775241afa515da9e7) can not clone tags, so the minimum required git version is v1.7.10. If you are interested in the problem we mentioned here, please refer to GitHub's [HTTPS cloning errors](https://help.github.com/articles/https-cloning-errors/) article.
### Git Install
@@ -429,7 +429,7 @@ In place of a version pointer like "14.7" or "16.3" or "12.22.1", you can use th
- `node`: this installs the latest version of [`node`](https://nodejs.org/en/)
- `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).
- `unstable`: this alias points to `node` `v0.11` - the last "unstable" node release, since post-1.0, all node versions are stable. (in [SemVer](https://semver.org), 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 &mdash; e.g. `nvm which current` always prints the path to the active `node`, regardless of whether an `.nvmrc` file is present.
### Long-term Support
@@ -872,6 +872,14 @@ nvm exposes the following environment variables:
Additionally, nvm modifies `PATH`, and, if present, `MANPATH` and `NODE_PATH` when changing versions.
The following environment variables can be set to configure `nvm install`:
- `NVM_NO_SOURCE_FALLBACK` - when `1`, a failed binary download aborts instead of silently falling back to a (much slower) from-source compile; the persistent equivalent of the `-b` flag, and mutually exclusive with `-s`.
- `NVM_INSTALL_LOCK_TIMEOUT` - seconds to wait for a concurrent install of the same version to finish before giving up (default `600`). On timeout, nvm prints the lock path so a lock left behind by a killed install can be removed.
- `NVM_INSTALL_LOCK_STALE` - minutes after which an install lock is assumed abandoned and stolen automatically; `0` (the default) never steals.
`nvm install <version>` takes a per-version advisory lock (a directory under `$NVM_DIR/.cache/locks`), so two shells installing the same version at once cannot corrupt its version directory; installs of *different* versions never block each other.
## Bash Completion
@@ -947,9 +955,9 @@ set -e
## Installing nvm on Alpine Linux
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, RedHat et al).
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, 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 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.
There is a `-s` flag for `nvm install` which requests nvm download Node source and compile it locally.
@@ -967,11 +975,11 @@ apk add -U curl bash ca-certificates openssl ncurses coreutils python2 make gcc
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.6/install.sh | bash
```
_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._
_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.
As a potential alternative, @mhart (a Node contributor) has some [Docker images for Alpine Linux with Node and optionally, npm, pre-installed](https://github.com/mhart/alpine-node).
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).
<a id="removal"></a>
## Uninstalling / Removal
@@ -1043,7 +1051,7 @@ nvm install -s 0.8.6
## macOS Troubleshooting
**nvm node version not found in vim shell**
**nvm node version not found in [vim](https://www.vim.org) shell**
If you set node version to a version other than your system node version `nvm use 6.2.1` and open vim and run `:!node -v` you should see `v6.2.1` if you see your system version `v0.12.7`. You need to run:
@@ -1084,7 +1092,7 @@ Homebrew causes insecure directories like `/usr/local/share/zsh/site-functions`
Experimental support for the Apple Silicon chip architecture was added in node.js v15.3 and full support was added in v16.0.
Because of this, if you try to install older versions of node as usual, you will probably experience either compilation errors when installing node or out-of-memory errors while running your code.
So, if you want to run a version prior to v16.0 on an Apple Silicon Mac, it may be best to compile node targeting the `x86_64` Intel architecture so that Rosetta 2 can translate the `x86_64` processor instructions to ARM-based Apple Silicon instructions.
So, if you want to run a version prior to v16.0 on an Apple Silicon Mac, it may be best to compile node targeting the `x86_64` Intel architecture so that [Rosetta 2](https://support.apple.com/en-us/HT211861) can translate the `x86_64` processor instructions to ARM-based Apple Silicon instructions.
Here's what you will need to do:
- Install Rosetta, if you haven't already done so
@@ -1102,7 +1110,7 @@ Here's what you will need to do:
$ arch -x86_64 zsh
```
Note: This same thing can also be accomplished by finding the Terminal or iTerm App in Finder, right clicking, selecting "Get Info", and then checking the box labeled "Open using Rosetta".
Note: This same thing can also be accomplished by finding the Terminal or [iTerm](https://iterm2.com) App in Finder, right clicking, selecting "Get Info", and then checking the box labeled "Open using Rosetta".
Note: This terminal session is now running in `zsh`.
If `zsh` is not the shell you typically use, `nvm` may not be `source`'d automatically like it probably is for your usual shell through your dotfiles.
@@ -1120,7 +1128,7 @@ Here's what you will need to do:
```
Note: You're probably curious why `--shared-zlib` is included.
There's a bug in recent versions of Apple's system `clang` compiler.
There's a bug in recent versions of Apple's system [`clang`](https://clang.llvm.org/) compiler.
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.
+174 -12
View File
@@ -218,6 +218,52 @@ nvm_is_version_installed() {
return 1
}
# Sanity-check an installed version's layout: a non-empty, executable bin/node
# and, if present, an npm entry that resolves. nvm_is_version_installed only
# tests the bin/node exec bit, which a zero-byte binary and a dangling npm
# symlink both pass; this catches those, so a partial install is not reported
# as a success. It deliberately does NOT execute node: a correctly installed
# binary can still fail to run on an incompatible host (e.g. a newer node on an
# older glibc), which is not a broken install - and a corrupt download is
# already rejected by the checksum check before extraction.
nvm_validate_install() {
local VERSION
VERSION="${1-}"
if [ -z "${VERSION}" ]; then
return 1
fi
local VERSION_PATH
VERSION_PATH="$(nvm_version_path "${VERSION}" 2>/dev/null)"
if [ -z "${VERSION_PATH}" ] || [ ! -d "${VERSION_PATH}" ]; then
return 1
fi
local NVM_NODE_BINARY
NVM_NODE_BINARY='node'
if [ "_$(nvm_get_os)" = '_win' ]; then
NVM_NODE_BINARY='node.exe'
fi
local NVM_NODE_PATH
NVM_NODE_PATH="${VERSION_PATH}/bin/${NVM_NODE_BINARY}"
# A zero-byte file with the exec bit set still passes `[ -x ]` (the shell
# would run it as an empty script), so require a non-empty executable.
if [ ! -s "${NVM_NODE_PATH}" ] || [ ! -x "${NVM_NODE_PATH}" ]; then
nvm_err "The installed node binary at ${NVM_NODE_PATH} is missing or empty."
return 1
fi
# npm ships with every supported node/io.js version; if its entry is a
# symlink it must resolve (one pointing at a removed target counts as broken).
if [ -h "${VERSION_PATH}/bin/npm" ] && [ ! -e "${VERSION_PATH}/bin/npm" ]; then
nvm_err "npm for ${VERSION} is a dangling symlink."
return 1
fi
return 0
}
nvm_print_npm_version() {
if nvm_has "npm"; then
local NPM_VERSION
@@ -2377,22 +2423,39 @@ nvm_install_binary_extract() {
if [ "${NVM_OS}" = 'win' ]; then
VERSION_PATH="${VERSION_PATH}/bin"
command unzip -q "${TARBALL}" -d "${TMPDIR}" || return 1
# For non Windows system (including WSL running on Windows)
else
nvm_extract_tarball "${NVM_OS}" "${VERSION}" "${TARBALL}" "${TMPDIR}"
fi
# Replace any pre-existing (possibly broken or partial) install so the
# move below cannot collide with leftover files. Safe here: the archive
# has already downloaded and unzipped successfully into TMPDIR.
command rm -rf "${VERSION_PATH}"
command mkdir -p "${VERSION_PATH}" || return 1
if [ "${NVM_OS}" = 'win' ]; then
command mv "${TMPDIR}/"*/* "${VERSION_PATH}/" || return 1
command chmod +x "${VERSION_PATH}"/node.exe || return 1
command chmod +x "${VERSION_PATH}"/npm || return 1
command chmod +x "${VERSION_PATH}"/npx 2>/dev/null
else
command mv "${TMPDIR}/"* "${VERSION_PATH}" || return 1
command rm -rf "${TMPDIR}"
return 0
fi
# For non-Windows systems (including WSL running on Windows)
nvm_extract_tarball "${NVM_OS}" "${VERSION}" "${TARBALL}" "${TMPDIR}" || return 1
# Install atomically: replace any pre-existing version directory with a
# single rename, so a partial or broken tree is never observed as installed.
# A leftover directory without a working bin/node otherwise wedges the
# install - the per-entry `mv` refuses to overwrite the non-empty bin/, lib/,
# ... subdirectories and leaves a half-updated tree behind. Removing it first
# is safe: the tarball has already downloaded and extracted into TMPDIR.
command rm -rf "${VERSION_PATH}" || return 1
command mkdir -p "$(dirname "${VERSION_PATH}")" || return 1
if command mv "${TMPDIR}" "${VERSION_PATH}" 2>/dev/null; then
return 0
fi
# Fall back to a per-entry move when a single rename is not possible (e.g.
# TMPDIR and the versions directory are on different filesystems).
command rm -rf "${VERSION_PATH}"
command mkdir -p "${VERSION_PATH}" || return 1
command mv "${TMPDIR}/"* "${VERSION_PATH}" || return 1
command rm -rf "${TMPDIR}"
return 0
@@ -3167,6 +3230,80 @@ nvm_cache_dir() {
nvm_echo "${NVM_DIR}/.cache"
}
# Turn a version into a filesystem-safe lock name. Versions that reach here are
# already restricted to [0-9A-Za-z._+-], but be defensive about anything else.
nvm_install_lock_name() {
command printf '%s' "${1-}" | command tr -c '0-9A-Za-z._+-' '_'
}
# Acquire an advisory, per-version install lock so two concurrent
# `nvm install <same version>` runs cannot race on the same version directory
# (one removing/replacing it while the other reads or writes it). The lock is a
# directory created with `mkdir`, which is atomic across POSIX filesystems.
#
# Tunables (env vars):
# NVM_INSTALL_LOCK_TIMEOUT seconds to wait for a held lock (default 600)
# NVM_INSTALL_LOCK_STALE minutes after which a lock is assumed abandoned
# and stolen; 0 (default) never steals
#
# On success the lock path is recorded in NVM_INSTALL_LOCK for the matching
# nvm_release_install_lock.
nvm_acquire_install_lock() {
local VERSION
VERSION="${1-}"
if [ -z "${VERSION}" ]; then
return 0
fi
local LOCK_ROOT
LOCK_ROOT="$(nvm_cache_dir)/locks"
# If the lock directory can't be created, don't block the install over it.
command mkdir -p "${LOCK_ROOT}" 2>/dev/null || return 0
local LOCK
LOCK="${LOCK_ROOT}/$(nvm_install_lock_name "${VERSION}")"
local TIMEOUT
TIMEOUT="${NVM_INSTALL_LOCK_TIMEOUT:-600}"
local STALE
STALE="${NVM_INSTALL_LOCK_STALE:-0}"
local WAITED
WAITED=0
local ANNOUNCED
ANNOUNCED=0
while ! command mkdir "${LOCK}" 2>/dev/null; do
# Steal a lock left behind by a crashed install once it is old enough.
if [ "${STALE}" != '0' ] && [ -n "$(command find "${LOCK}" -maxdepth 0 -type d -mmin "+${STALE}" 2>/dev/null)" ]; then
nvm_err "Removing stale install lock for ${VERSION} (older than ${STALE} minute(s))"
command rm -rf "${LOCK}" 2>/dev/null
continue
fi
if [ "${WAITED}" -ge "${TIMEOUT}" ]; then
nvm_err "Timed out after ${TIMEOUT}s waiting for another install of ${VERSION} to finish."
nvm_err "If no other install is running, remove ${LOCK} and try again."
return 1
fi
if [ "${ANNOUNCED}" -eq 0 ]; then
nvm_err "Waiting for another install of ${VERSION} to finish..."
ANNOUNCED=1
fi
command sleep 1
WAITED=$((WAITED + 1))
done
NVM_INSTALL_LOCK="${LOCK}"
return 0
}
# Release the lock acquired by nvm_acquire_install_lock, if any.
nvm_release_install_lock() {
if [ -n "${NVM_INSTALL_LOCK-}" ]; then
command rmdir "${NVM_INSTALL_LOCK}" 2>/dev/null || command rm -rf "${NVM_INSTALL_LOCK}" 2>/dev/null || true
unset NVM_INSTALL_LOCK
fi
}
# args: pattern
# Lists versions available in the local cache (not yet installed).
# Returns version numbers like "v18.20.4", one per line, sorted.
@@ -3299,6 +3436,7 @@ nvm() {
nvm_echo ' The following optional arguments, if provided, must appear directly after `nvm install`:'
nvm_echo ' -s Skip binary download, install from source only.'
nvm_echo ' -b Skip source download, install from binary only.'
nvm_echo ' (set NVM_NO_SOURCE_FALLBACK=1 to make this the default for every install)'
nvm_echo ' --reinstall-packages-from=<version> When installing, reinstall packages installed in <node|iojs|node version number>'
nvm_echo ' --lts When installing, only select from LTS (long-term support) versions'
nvm_echo ' --lts=<LTS name> When installing, only select from versions for a specific LTS line'
@@ -3647,6 +3785,17 @@ nvm() {
esac
done
# NVM_NO_SOURCE_FALLBACK=1 makes `nvm install` behave as if `-b` were always passed:
# a failed binary download aborts instead of silently falling back to a from-source compile.
# It is a persistent policy so callers need not thread `-b` through every invocation.
if [ "${NVM_NO_SOURCE_FALLBACK-}" = '1' ] && [ $nosource -ne 1 ]; then
if [ $nobinary -eq 1 ]; then
nvm_err '-s cannot be combined with NVM_NO_SOURCE_FALLBACK=1 since that would skip install from both binary and source'
return 6
fi
nosource=1
fi
if [ "${NVM_OFFLINE}" != 1 ] && ! nvm_has_executable "curl" && ! nvm_has_executable "wget"; then
nvm_err 'nvm needs curl or wget to proceed.'
return 1
@@ -3800,7 +3949,7 @@ nvm() {
EXIT_CODE=0
if nvm_is_version_installed "${VERSION}"; then
if nvm_is_version_installed "${VERSION}" && nvm_validate_install "${VERSION}"; then
nvm_err "${VERSION} is already installed."
nvm use "${VERSION}"
EXIT_CODE=$?
@@ -3858,6 +4007,11 @@ nvm() {
fi
EXIT_CODE=0
else
# Serialize concurrent installs of this version so two runs cannot race
# on its version directory (one replacing it while the other reads it).
if ! nvm_acquire_install_lock "${VERSION}"; then
return 1
fi
if [ "_${NVM_OS}" = "_freebsd" ]; then
# node.js and io.js do not have a FreeBSD binary
@@ -3900,6 +4054,13 @@ nvm() {
EXIT_CODE=$?
fi
fi
nvm_release_install_lock
fi
if [ $EXIT_CODE -eq 0 ] && ! nvm_validate_install "${VERSION}"; then
nvm_err "The install of ${VERSION} reported success but failed verification; not activating it."
EXIT_CODE=1
fi
if [ $EXIT_CODE -eq 0 ]; then
@@ -4772,7 +4933,8 @@ nvm() {
nvm_echo nvm_err nvm_grep nvm_cd \
nvm_die_on_prefix nvm_get_make_jobs nvm_get_minor_version \
nvm_has_solaris_binary nvm_is_merged_node_version \
nvm_is_natural_num nvm_is_version_installed \
nvm_is_natural_num nvm_is_version_installed nvm_validate_install \
nvm_install_lock_name nvm_acquire_install_lock nvm_release_install_lock \
nvm_list_aliases nvm_make_alias nvm_print_alias_path \
nvm_print_default_alias nvm_print_formatted_alias nvm_resolve_local_alias \
nvm_sanitize_path nvm_has_colors nvm_process_parameters \
@@ -4786,7 +4948,7 @@ nvm() {
nvm_write_nvmrc \
>/dev/null 2>&1
unset NVM_NODEJS_ORG_MIRROR NVM_IOJS_ORG_MIRROR NVM_DIR \
NVM_CD_FLAGS NVM_BIN NVM_INC NVM_MAKE_JOBS \
NVM_CD_FLAGS NVM_BIN NVM_INC NVM_MAKE_JOBS NVM_INSTALL_LOCK \
NVM_COLORS INSTALLED_COLOR SYSTEM_COLOR \
CURRENT_COLOR NOT_INSTALLED_COLOR DEFAULT_COLOR LTS_COLOR \
>/dev/null 2>&1
+50
View File
@@ -0,0 +1,50 @@
#!/bin/sh
cleanup() {
[ -n "${NVM_DIR}" ] && [ -d "${NVM_DIR}" ] && rm -rf "${NVM_DIR}"
unset -f die cleanup nvm_remote_version nvm_has_executable nvm_binary_available \
nvm_install_binary nvm_install_source nvm_get_make_jobs
unset NVM_DIR version SOURCE_MARKER OUTPUT EXIT_CODE
}
die() { echo "$@" ; cleanup ; exit 1; }
: nvm.sh
\. ../../../nvm.sh
unset npm_config_prefix NPM_CONFIG_PREFIX
NVM_DIR="$(mktemp -d)"
[ -n "${NVM_DIR}" ] || die 'unable to create temp NVM_DIR'
version='v20.0.0'
SOURCE_MARKER="${NVM_DIR}/source-was-attempted"
# Stay offline; make the binary download always fail and record any attempt to
# compile from source.
nvm_remote_version() { nvm_echo "${version}"; }
nvm_has_executable() { return 0; }
nvm_binary_available() { return 0; }
nvm_get_make_jobs() { NVM_MAKE_JOBS=1; }
nvm_install_binary() { return 1; }
nvm_install_source() { command touch "${SOURCE_MARKER}"; return 1; }
# Default: a failed binary download falls back to a source compile.
rm -f "${SOURCE_MARKER}"
nvm install "${version}" > /dev/null 2>&1
[ -f "${SOURCE_MARKER}" ] || die 'default behaviour should fall back to source when the binary fails'
# NVM_NO_SOURCE_FALLBACK=1: a failed binary download must NOT compile from source.
rm -f "${SOURCE_MARKER}"
NVM_NO_SOURCE_FALLBACK=1 nvm install "${version}" > /dev/null 2>&1; EXIT_CODE=$?
[ ! -f "${SOURCE_MARKER}" ] || die 'NVM_NO_SOURCE_FALLBACK=1 should not fall back to source'
[ "${EXIT_CODE}" != "0" ] || die 'a failed binary install with NVM_NO_SOURCE_FALLBACK=1 should be non-zero'
# NVM_NO_SOURCE_FALLBACK=1 combined with -s would skip both methods: a conflict.
OUTPUT="$(NVM_NO_SOURCE_FALLBACK=1 nvm install -s "${version}" 2>&1)"; EXIT_CODE=$?
[ "${EXIT_CODE}" = "6" ] || die "expected exit code 6 for -s with NVM_NO_SOURCE_FALLBACK=1, got ${EXIT_CODE}"
case "${OUTPUT}" in
*'skip install from both binary and source'*) ;;
*) die "expected a conflict message, got >${OUTPUT}<" ;;
esac
cleanup
@@ -0,0 +1,71 @@
#!/bin/sh
cleanup() {
[ -n "${NVM_DIR}" ] && [ -d "${NVM_DIR}" ] && rm -rf "${NVM_DIR}"
unset -f die cleanup make_healthy make_broken \
nvm_remote_version nvm_has_executable nvm_binary_available \
nvm_install_binary nvm_install_source nvm_use_if_needed \
nvm_install_npm_if_needed nvm_install_default_packages nvm_ensure_default_set
unset NVM_DIR version version_path REINSTALL_MARKER OUTPUT EXIT_CODE
}
die() { echo "$@" ; cleanup ; exit 1; }
: nvm.sh
\. ../../../nvm.sh
unset npm_config_prefix NPM_CONFIG_PREFIX
NVM_DIR="$(mktemp -d)"
[ -n "${NVM_DIR}" ] || die 'unable to create temp NVM_DIR'
version='v20.0.0'
version_path="${NVM_DIR}/versions/node/${version}"
REINSTALL_MARKER="${NVM_DIR}/reinstalled"
# Stay offline and keep the post-install activation path inert; only the
# verification behaviour is under test here.
nvm_remote_version() { nvm_echo "${version}"; }
nvm_has_executable() { return 0; }
nvm_binary_available() { return 0; }
nvm_install_source() { return 1; }
nvm_use_if_needed() { return 0; }
nvm_install_npm_if_needed() { return 0; }
nvm_install_default_packages() { return 0; }
nvm_ensure_default_set() { return 0; }
make_healthy() {
mkdir -p "${version_path}/bin"
printf '#!/bin/sh\necho "%s"\n' "${version}" > "${version_path}/bin/node"
chmod +x "${version_path}/bin/node"
}
make_broken() {
mkdir -p "${version_path}/bin"
: > "${version_path}/bin/node" # zero-byte but +x: passes nvm_is_version_installed
chmod +x "${version_path}/bin/node"
}
# 1) An install that reports success but leaves a broken node must fail loudly
# and must not be activated.
rm -rf "${version_path}"
nvm_install_binary() { make_broken; return 0; }
OUTPUT="$(nvm install -b "${version}" 2>&1)"; EXIT_CODE=$?
[ "${EXIT_CODE}" != "0" ] || die "a broken install should fail; got exit 0, output >${OUTPUT}<"
case "${OUTPUT}" in
*'failed verification'*) ;;
*) die "expected a verification-failure message; got >${OUTPUT}<" ;;
esac
# 2) An already-present but broken version must not short-circuit as "already
# installed"; it must be reinstalled.
rm -rf "${version_path}"
make_broken
nvm_install_binary() { command touch "${REINSTALL_MARKER}"; make_healthy; return 0; }
OUTPUT="$(nvm install -b "${version}" 2>&1)"; EXIT_CODE=$?
case "${OUTPUT}" in
*'is already installed'*) die "a broken version should not be treated as already installed: >${OUTPUT}<" ;;
esac
[ -f "${REINSTALL_MARKER}" ] || die 'a broken already-present version was not reinstalled'
[ "${EXIT_CODE}" = "0" ] || die "the healing reinstall should succeed; got ${EXIT_CODE}, output >${OUTPUT}<"
cleanup
+56
View File
@@ -0,0 +1,56 @@
#!/bin/sh
cleanup() {
[ -n "${NVM_DIR}" ] && [ -d "${NVM_DIR}" ] && rm -rf "${NVM_DIR}"
unset -f die cleanup
unset NVM_DIR NVM_INSTALL_LOCK NVM_INSTALL_LOCK_TIMEOUT NVM_INSTALL_LOCK_STALE \
version lock OUTPUT EXIT_CODE
}
die() { echo "$@" ; cleanup ; exit 1; }
: nvm.sh
\. ../../../nvm.sh
type nvm_acquire_install_lock > /dev/null 2>&1 || die 'nvm_acquire_install_lock is not available'
NVM_DIR="$(mktemp -d)"
[ -n "${NVM_DIR}" ] || die 'unable to create temp NVM_DIR'
version='v20.0.0'
lock="$(nvm_cache_dir)/locks/$(nvm_install_lock_name "${version}")"
# An empty version is a no-op that succeeds and takes no lock.
unset NVM_INSTALL_LOCK
nvm_acquire_install_lock '' || die 'acquiring with an empty version should succeed as a no-op'
[ -z "${NVM_INSTALL_LOCK-}" ] || die 'an empty-version acquire should not record a lock'
# Acquiring a free lock succeeds, creates the lock dir, and records its path.
nvm_acquire_install_lock "${version}" || die 'acquiring a free lock should succeed'
[ -d "${lock}" ] || die 'acquire should create the lock directory'
[ "${NVM_INSTALL_LOCK}" = "${lock}" ] || die 'acquire should record NVM_INSTALL_LOCK'
# Release it so the following contention checks start from a held-by-someone-else state.
command rmdir "${lock}" 2>/dev/null
unset NVM_INSTALL_LOCK
# A lock held by "another process", with no wait budget, fails and names the path.
command mkdir -p "${lock}"
OUTPUT="$(NVM_INSTALL_LOCK_TIMEOUT=0 nvm_acquire_install_lock "${version}" 2>&1)"; EXIT_CODE=$?
[ "${EXIT_CODE}" != "0" ] || die 'acquire should fail when the lock is held and the timeout is 0'
case "${OUTPUT}" in
*"${lock}"*) ;;
*) die "the timeout message should name the lock path; got >${OUTPUT}<" ;;
esac
[ -d "${lock}" ] || die 'a failed acquire must not remove the held lock'
# By default, a held lock is never stolen no matter how old it is.
touch -t 202001010000 "${lock}" 2>/dev/null || die 'unable to age the lock dir'
NVM_INSTALL_LOCK_TIMEOUT=0 nvm_acquire_install_lock "${version}" 2>/dev/null && die 'an old lock must not be stolen unless NVM_INSTALL_LOCK_STALE is set'
[ -d "${lock}" ] || die 'the old lock should still be held'
# With NVM_INSTALL_LOCK_STALE set, a sufficiently old lock is stolen and re-acquired.
unset NVM_INSTALL_LOCK
NVM_INSTALL_LOCK_STALE=1 NVM_INSTALL_LOCK_TIMEOUT=0 nvm_acquire_install_lock "${version}" || die 'a stale lock should be stolen when NVM_INSTALL_LOCK_STALE is set'
[ "${NVM_INSTALL_LOCK}" = "${lock}" ] || die 'stealing a stale lock should acquire it'
cleanup
@@ -0,0 +1,62 @@
#!/bin/sh
cleanup() {
[ -n "${tmp_dir}" ] && [ -d "${tmp_dir}" ] && rm -rf "${tmp_dir}"
[ -n "${NVM_DIR}" ] && [ -d "${NVM_DIR}" ] && rm -rf "${NVM_DIR}"
unset -f die cleanup nvm_supports_xz
unset NVM_DIR tmp_dir version archi node_dir version_path
}
die() { echo "$@" ; cleanup ; exit 1; }
: nvm.sh
\. ../../../nvm.sh
# Use gzip so the test does not depend on xz being installed.
nvm_supports_xz() { return 1; }
set -ex
type nvm_install_binary_extract > /dev/null 2>&1 || die 'nvm_install_binary_extract is not available'
NVM_DIR="$(mktemp -d)"
tmp_dir="$(mktemp -d)"
[ -n "${NVM_DIR}" ] && [ -n "${tmp_dir}" ] || die 'Unable to create temporary folders'
version='v14.15.4'
archi='linux-x64'
node_dir="${tmp_dir}/node-${version}-${archi}"
# Build a fake binary tarball: a runnable node, an npm symlink, and a lib tree.
mkdir -p "${node_dir}/bin" "${node_dir}/lib/node_modules/npm/bin" || die 'setup mkdir failed'
printf '#!/bin/sh\necho "%s"\n' "${version}" > "${node_dir}/bin/node"
chmod +x "${node_dir}/bin/node"
echo 'npm-cli' > "${node_dir}/lib/node_modules/npm/bin/npm-cli.js"
( cd "${node_dir}/bin" && ln -s ../lib/node_modules/npm/bin/npm-cli.js npm )
echo 'fresh' > "${node_dir}/lib/FRESHFILE"
( cd "${tmp_dir}" && tar -czf "${node_dir}.tar.gz" "node-${version}-${archi}" ) || die 'unable to create fake tarball'
# Pre-create a BROKEN, non-empty version directory: npm and a stale lib file
# are present, but bin/node is missing - exactly the shape that wedges a
# per-entry move onto existing non-empty subdirectories.
version_path="${NVM_DIR}/versions/node/${version}"
mkdir -p "${version_path}/bin" "${version_path}/lib" || die 'unable to stage broken dir'
echo 'stale-npm' > "${version_path}/bin/npm"
echo 'stale' > "${version_path}/lib/STALEFILE"
[ -e "${version_path}/bin/node" ] && die 'precondition: broken dir should have no bin/node'
# Extract over the broken directory.
nvm_install_binary_extract 'linux' "${version}" "$(expr "${version}" : '.\(.*\)')" "${node_dir}.tar.gz" "${tmp_dir}/files" || die 'nvm_install_binary_extract failed over a broken version dir'
# bin/node is restored and runnable.
[ -x "${version_path}/bin/node" ] || die 'bin/node was not restored'
[ "$("${version_path}/bin/node")" = "${version}" ] || die 'restored bin/node has wrong contents'
# The stale file is gone: the whole directory was replaced, not merged into.
[ ! -e "${version_path}/lib/STALEFILE" ] || die 'stale lib file survived: install was not atomic'
[ -e "${version_path}/lib/FRESHFILE" ] || die 'fresh lib content missing after install'
# npm resolves to the freshly installed target.
[ -e "${version_path}/bin/npm" ] || die 'npm missing after install'
cleanup
+32
View File
@@ -0,0 +1,32 @@
#!/bin/sh
cleanup() {
[ -n "${NVM_DIR}" ] && [ -d "${NVM_DIR}" ] && rm -rf "${NVM_DIR}"
unset -f die cleanup
unset NVM_DIR
}
die() { echo "$@" ; cleanup ; exit 1; }
: nvm.sh
\. ../../../nvm.sh
type nvm_install_lock_name > /dev/null 2>&1 || die 'nvm_install_lock_name is not available'
NVM_DIR="$(mktemp -d)"
[ -n "${NVM_DIR}" ] || die 'unable to create temp NVM_DIR'
# A normal version passes through unchanged.
[ "$(nvm_install_lock_name 'v20.0.0')" = 'v20.0.0' ] || die "v20.0.0 => >$(nvm_install_lock_name 'v20.0.0')<"
# Dots, hyphens, plus, and underscores are all preserved.
[ "$(nvm_install_lock_name 'iojs-v1.0.0')" = 'iojs-v1.0.0' ] || die "iojs-v1.0.0 => >$(nvm_install_lock_name 'iojs-v1.0.0')<"
# Path separators and other unsafe characters become underscores.
[ "$(nvm_install_lock_name 'lts/*')" = 'lts__' ] || die "lts/* => >$(nvm_install_lock_name 'lts/*')<"
[ "$(nvm_install_lock_name 'a b/c')" = 'a_b_c' ] || die "a b/c => >$(nvm_install_lock_name 'a b/c')<"
# The empty string maps to the empty string (no trailing-newline artifact).
[ "$(nvm_install_lock_name '')" = '' ] || die "empty => >$(nvm_install_lock_name '')<"
cleanup
+35
View File
@@ -0,0 +1,35 @@
#!/bin/sh
cleanup() {
[ -n "${NVM_DIR}" ] && [ -d "${NVM_DIR}" ] && rm -rf "${NVM_DIR}"
unset -f die cleanup
unset NVM_DIR NVM_INSTALL_LOCK version lock
}
die() { echo "$@" ; cleanup ; exit 1; }
: nvm.sh
\. ../../../nvm.sh
type nvm_release_install_lock > /dev/null 2>&1 || die 'nvm_release_install_lock is not available'
NVM_DIR="$(mktemp -d)"
[ -n "${NVM_DIR}" ] || die 'unable to create temp NVM_DIR'
version='v20.0.0'
lock="$(nvm_cache_dir)/locks/$(nvm_install_lock_name "${version}")"
# Releasing when nothing is held is a no-op that succeeds.
unset NVM_INSTALL_LOCK
nvm_release_install_lock || die 'releasing with no lock held should succeed'
# After acquiring, releasing removes the lock directory and clears the marker.
nvm_acquire_install_lock "${version}" || die 'setup: acquire failed'
[ -d "${lock}" ] || die 'setup: lock dir should exist after acquire'
nvm_release_install_lock || die 'release should succeed'
[ ! -d "${lock}" ] || die 'release should remove the lock directory'
[ -z "${NVM_INSTALL_LOCK-}" ] || die 'release should clear NVM_INSTALL_LOCK'
# Releasing again is a harmless no-op.
nvm_release_install_lock || die 'a second release should be a no-op'
cleanup
+53
View File
@@ -0,0 +1,53 @@
#!/bin/sh
cleanup() {
[ -n "${NVM_DIR}" ] && [ -d "${NVM_DIR}" ] && rm -rf "${NVM_DIR}"
unset -f die cleanup
unset NVM_DIR version version_path
}
die() { echo "$@" ; cleanup ; exit 1; }
: nvm.sh
\. ../../../nvm.sh
type nvm_validate_install > /dev/null 2>&1 || die 'nvm_validate_install is not available'
NVM_DIR="$(mktemp -d)"
[ -n "${NVM_DIR}" ] || die 'Unable to create temporary folder'
version='v20.0.0'
version_path="${NVM_DIR}/versions/node/${version}"
# No version directory at all: not valid.
nvm_validate_install "${version}" 2>/dev/null && die 'a missing version dir should not validate'
# A healthy install: a non-empty node plus an npm symlink that resolves.
# (validation checks layout, not execution, so bin/node need not be a real
# runnable binary here - just non-empty and executable.)
mkdir -p "${version_path}/bin" "${version_path}/lib/node_modules/npm/bin" || die 'setup mkdir failed'
printf '#!/bin/sh\necho "%s"\n' "${version}" > "${version_path}/bin/node"
chmod +x "${version_path}/bin/node"
echo 'npm-cli' > "${version_path}/lib/node_modules/npm/bin/npm-cli.js"
( cd "${version_path}/bin" && ln -s ../lib/node_modules/npm/bin/npm-cli.js npm )
nvm_validate_install "${version}" 2>/dev/null || die 'a healthy install should validate'
# A binary that is correctly installed but cannot run on this host (e.g. a
# newer node on an older glibc) must STILL validate: whether it runs is the
# host's concern, not a broken install.
printf '#!/bin/sh\nexit 1\n' > "${version_path}/bin/node"
chmod +x "${version_path}/bin/node"
nvm_validate_install "${version}" 2>/dev/null || die 'a non-runnable but present binary should still validate'
# A zero-byte but executable node: passes `[ -x ]` but must not validate.
: > "${version_path}/bin/node"
chmod +x "${version_path}/bin/node"
nvm_validate_install "${version}" 2>/dev/null && die 'a zero-byte node should not validate'
# A non-empty node again, but with a dangling npm symlink: must not validate.
printf '#!/bin/sh\necho "%s"\n' "${version}" > "${version_path}/bin/node"
chmod +x "${version_path}/bin/node"
rm -f "${version_path}/lib/node_modules/npm/bin/npm-cli.js"
nvm_validate_install "${version}" 2>/dev/null && die 'a dangling npm symlink should not validate'
cleanup