Compare commits

...
11 Commits
Author SHA1 Message Date
Jordan Harband b6cf55f6ad v0.40.6 2026-07-15 14:09:54 -07:00
Jordan Harband 9275c5badd [Robustness] reject unsafe LTS alias names from a mirror's index.tab
A mirror-supplied LTS codename (field 10 of index.tab) was used verbatim as an alias filename,
so a hostile codename such as `../../../.bashrc` could make nvm_make_alias write outside $NVM_DIR/alias
- with the default layout, into shell startup files.
Constrain remote codenames to safe filename characters at ingestion in nvm_ls_remote_index_tab,
and reject any `..` path component in nvm_make_alias as a backstop for every caller.
2026-07-15 13:46:52 -07:00
Jordan Harband 882ed79ece [Docs] threat model: document the mirror payload/metadata trust boundary
Payloads
(binaries, plus source tarballs that nvm compiles and runs at install time) are trusted by construction,
since nvm exists to build and run them;
parsed metadata (index.tab, SHASUMS, LTS codenames) is not,
and must never reach a command evaluator,
an awk/sed program body,
or an unvalidated filesystem path.
This records why the existing version-string and checksum hardening exists,
and scopes a malicious payload from a configured mirror as out of scope:
no privilege boundary is crossed.
2026-07-15 13:44:04 -07:00
Jordan Harband d83da5d75b [actions] add tests-alpine: a green fast suite and a musl-binary regression matrix
The full suite cannot run on Alpine: the install-based suites pin ancient Node (0.10.x, io.js) that has no musl binary and cannot source-compile on musl.
Run the fast unit suite instead (mirroring the ubuntu runner: non-root via su-exec, passwordless sudo, a PTY, no system node), plus a binary-only regression matrix that installs every (Alpine, Node) pair with a real unofficial musl binary via `nvm install -b`: x64 back to node 8.17.0 on old Alpine, arm64 at the v20.20.1/v22.21.1/v24.9.0 floors on modern Alpine.
2026-07-14 23:18:07 -07:00
Jordan Harband 438009f54e [Tests] make the fast suite pass on Alpine
`nvm_get_arch`: skip the smartos arm64/armv8l cases, which have no uname mock and fall through to the real host (adding a musl suffix on Alpine).
`nvm_get_arch_unofficial`: skip the glibc chroot (fixed /lib64 loader, sudo); the musl mapping is already covered by the `nvm_get_arch alpine` test.
`nvm_install_no_progress_bar`: skip below the musl floor, since v0.12.18 has no musl binary and hardcodes the glibc tarball URL.
2026-07-14 23:17:01 -07:00
Jordan Harband f1e7a84cb5 [Robustness] avoid a set -u error when $_ is unset
Sourcing nvm.sh under `set -u` on a POSIX shell that leaves `$_` unset (e.g. busybox ash or dash on Alpine, where the parent shell does not export it) aborted at `NVM_SCRIPT_SOURCE="$_"`.
Default to empty; the only consumer already falls back to `$0`.
2026-07-14 23:16:39 -07:00
Jordan Harband 86c937436d [Robustness] nvm_get_arch: only apply the Alpine musl suffix when the OS is linux
The `/etc/alpine-release` check applied the -musl suffix regardless of the resolved OS.
In practice Alpine is always linux, so this is behavior-neutral, but it also makes the `nvm_get_arch` unit test hermetic: its mocked smartos/osx cases no longer pick up a real host's Alpine marker.
2026-07-14 23:10:33 -07:00
Jesse ZhuandJordan Harband ce157c1171 [Fix] support arm64-musl on Alpine Linux
Node.js unofficial-builds publishes linux-arm64-musl binaries since v20.20.1 / v22.21.1 / v24+.
Map NVM_ARCH=arm64 to arm64-musl on Alpine so `nvm install`
resolves to the correct tarball instead of the glibc-linked arm64 build that won't run against musl.

[Tests] `nvm_get_arch`: expect arm64-musl on Alpine

Co-authored-by: Jesse Zhu <jesse@itjesse.com>
Co-authored-by: Jordan Harband <ljharb@gmail.com>
2026-05-23 01:35:12 +09:00
Jordan Harband c7d5d7748b [Tests] run every remaining test file: exec bits, fixes; add check-exec to CI
Code scanning - action / CodeQL-Build (push) Has been cancelled
Tests: `nvm install-latest-npm` / matrix (push) Has been cancelled
Tests on Windows: `nvm install` / Cygwin nvm install (push) Has been cancelled
Tests: linting / eclint (push) Has been cancelled
Tests: linting / dockerfile_lint (push) Has been cancelled
Tests: linting / doctoc (push) Has been cancelled
Tests: linting / test_naming (push) Has been cancelled
Tests: linting / check_exec (push) Has been cancelled
Tests: release process / release (push) Has been cancelled
Tests: shellcheck / shellcheck_matrix (bash_completion, bash) (push) Has been cancelled
Tests: shellcheck / shellcheck_matrix (install.sh, bash) (push) Has been cancelled
Tests: shellcheck / shellcheck_matrix (nvm-exec, bash) (push) Has been cancelled
Tests: shellcheck / shellcheck_matrix (nvm.sh, bash) (push) Has been cancelled
Tests: shellcheck / shellcheck_matrix (nvm.sh, dash) (push) Has been cancelled
Tests: shellcheck / shellcheck_matrix (nvm.sh, ksh) (push) Has been cancelled
Tests: shellcheck / shellcheck_matrix (nvm.sh, sh) (push) Has been cancelled
Tests: fast / fast (bash, gawk) (push) Has been cancelled
Tests: fast / fast (dash, gawk) (push) Has been cancelled
Tests: fast / fast (sh, gawk) (push) Has been cancelled
Tests: fast / fast (zsh, gawk) (push) Has been cancelled
Tests: fast / fast (bash, mawk) (push) Has been cancelled
Tests: fast / fast (dash, mawk) (push) Has been cancelled
Tests: fast / fast (sh, mawk) (push) Has been cancelled
Tests: fast / fast (zsh, mawk) (push) Has been cancelled
Tests: installation_iojs / installation_iojs without curl (dash) (push) Has been cancelled
Tests: installation_iojs / installation_iojs without curl (sh) (push) Has been cancelled
Tests: installation_iojs / installation_iojs without curl (zsh) (push) Has been cancelled
Tests: installation_iojs / actually compile io.js from source (gcc 4.9 container) (push) Has been cancelled
Tests: installation_node / installation_node (bash) (push) Has been cancelled
Tests: installation_node / installation_node (bash, without curl) (push) Has been cancelled
Tests: installation_node / installation_node (dash) (push) Has been cancelled
Tests: installation_node / installation_node (dash, without curl) (push) Has been cancelled
Tests: installation_node / installation_node (sh) (push) Has been cancelled
Tests: installation_node / installation_node (zsh) (push) Has been cancelled
Tests: installation_node / installation_node (zsh, without curl) (push) Has been cancelled
Tests: xenial / xenial (bash) (push) Has been cancelled
Tests: xenial / xenial (dash) (push) Has been cancelled
Tests: xenial / xenial (sh) (push) Has been cancelled
Tests: xenial / xenial (zsh) (push) Has been cancelled
urchin tests / tests (bash, install_script) (push) Has been cancelled
urchin tests / tests (bash, installation_iojs) (push) Has been cancelled
urchin tests / tests (bash, slow) (push) Has been cancelled
urchin tests / tests (bash, sourcing) (push) Has been cancelled
urchin tests / tests (dash, installation_iojs) (push) Has been cancelled
urchin tests / tests (dash, slow) (push) Has been cancelled
urchin tests / tests (dash, sourcing) (push) Has been cancelled
urchin tests / tests (sh, installation_iojs) (push) Has been cancelled
urchin tests / tests (sh, slow) (push) Has been cancelled
urchin tests / tests (sh, sourcing) (push) Has been cancelled
urchin tests / tests (zsh, installation_iojs) (push) Has been cancelled
urchin tests / tests (zsh, slow) (push) Has been cancelled
update readme TOC / update readme TOC (push) Has been cancelled
Tests on Windows: `nvm install` / MSYS fail prefix nvm install (push) Has been cancelled
Tests on Windows: `nvm install` / MSYS nvm install (--default 12) (push) Has been cancelled
Tests on Windows: `nvm install` / MSYS nvm install (--lts) (push) Has been cancelled
Tests on Windows: `nvm install` / MSYS nvm install (--no-progress 10) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 10, Debian) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 10, Ubuntu-18.04) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 10, Ubuntu-20.04) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 12, Debian) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 12, Ubuntu-18.04) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 14, Debian) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 14, Ubuntu-18.04) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 14, Ubuntu-20.04) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 16, Debian) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 16, Ubuntu-18.04) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 16, Ubuntu-20.04) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 18, Debian) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 18, Ubuntu-20.04) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 21, Debian) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 21, Ubuntu-20.04) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 10, Debian) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 10, Ubuntu-18.04) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 10, Ubuntu-20.04) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 12, Debian) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 12, Ubuntu-18.04) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 14, Debian) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 14, Ubuntu-20.04) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 16, Debian) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 16, Ubuntu-18.04) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 16, Ubuntu-20.04) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 18, Debian) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 18, Ubuntu-20.04) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 21, Debian) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 21, Ubuntu-20.04) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, --lts, Alpine) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 10, Alpine) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 11, Alpine) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 12, Alpine) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 14, Alpine) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 16, Alpine) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 18, Alpine) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 21, Alpine) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, --lts, Alpine) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 10, Alpine) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 11, Alpine) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 12, Alpine) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 14, Alpine) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 16, Alpine) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 18, Alpine) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 21, Alpine) (push) Has been cancelled
Tests: `nvm install-latest-npm` / nvm install-latest-npm (push) Has been cancelled
Tests: linting / all linting (push) Has been cancelled
Tests: shellcheck / shellcheck (push) Has been cancelled
Tests: fast / all fast tests (push) Has been cancelled
Tests: installation_iojs / all installation_iojs tests (push) Has been cancelled
Tests: installation_node / all installation_node tests (push) Has been cancelled
Tests: xenial / all xenial tests (push) Has been cancelled
urchin tests / all test suites, all shells (push) Has been cancelled
Tests on Windows: `nvm install` / tests, on windows (push) Has been cancelled
Tests: installation_iojs / installation_iojs without curl (bash) (push) Has been cancelled
Tests: installation_node / installation_node (sh, without curl) (push) Has been cancelled
urchin tests / tests (zsh, sourcing) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (, 12, Ubuntu-20.04) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 12, Ubuntu-20.04) (push) Has been cancelled
Tests on Windows: `nvm install` / WSL nvm install (script, 14, Ubuntu-18.04) (push) Has been cancelled
urchin only executes test files that have the executable bit, silently skipping the rest,
and `npm run test:check-exec` would catch non-executable ones, but no workflow ran it -
so several test files have never actually run.
Make all of them executable, and fix the ones that were broken:
- the io.js source-install tests, skipped since 2016: io.js requires python 2 and gcc <= 5,
so they now skip on toolchains that can not compile it,
and a new gcc:4.9-container CI job runs them unmodified -
resolving, downloading, checksumming, and compiling io.js for real.
New `fake source` variants exercise nvm's source pipeline everywhere -
cache lookup, offline resolution, extraction, configure, make, make install, activation -
via a tiny fake source tree seeded into the download cache and installed with `--offline`.
Also strengthen the `nvm run | grep` assertions with `--silent`,
since the version banner used to satisfy the grep even when the built binary could not run,
and give the final assertions the `die` they always meant to call.
- `nvm uninstall ... incorrect file permissions fails nicely`, parked in 2025 as failing:
`sudo npm` resets PATH, so node 5's npm crashed under the system node.
The permissions check only trips on files that are neither writable nor self-owned,
and that precondition only needs `sudo touch` - not npm, jspm, or the network -
so use that, against a fake installed version,
and skip only where passwordless sudo does not exist.
- `nvm install --offline`: `nvm ls | tail -1` always grabbed an alias line, never a version;
use `nvm_ls` and skip its trailing `system <version>` line.
- `nvm exec ... help should not parse`: in a BRE, `[options]` is a bracket expression,
so the grep could never match node's literal `Usage: node [options]` output.
- `nvm use ... nvmrc containing not installed version`: current output once again matches its expectations verbatim; no changes needed.

Rewrite `test:check-exec` in POSIX sh - npm runs scripts with /bin/sh, which is dash on Ubuntu,
where the previous `[[`/`$'\n'` bashisms would break;
match by extension, so that filenames containing dots can not dodge the check;
and disable git's `core.quotePath` mangling of the non-ASCII test filenames.
Then wire it into the lint workflow.
2026-07-09 22:40:07 -07:00
Jordan Harband 4e48294bd6 [Fix] nvm_ls_cached: include source-cached and io.js artifacts
`--offline` version resolution could only see cached node binaries:
`nvm_ls_cached` listed `.cache/bin` with a `node-` prefix filter,
so cached source tarballs, and everything io.js, could never resolve offline.
List both cache kinds, for both flavors,
and update the pinned test expectations to match.
2026-07-09 22:35:15 -07:00
Jordan Harband 1a39786c8a [Tests] nvm_offline_version: make it executable; pin nvm_ls_cached cache visibility
The test was born without its executable bit,
and urchin silently skips non-executable files, so it has never run.
Also pin what `nvm_ls_cached` can currently see:
only bin-cached node artifacts - bin-cached io.js, and src-cached anything,
are invisible to it, and thus to `--offline` version resolution.
2026-07-09 22:35:15 -07:00
25 changed files with 646 additions and 70 deletions
+32
View File
@@ -107,3 +107,35 @@ Additionally, the maintainer of a third-party dependency might introduce a vulne
**Recommendation**: Third-party libraries should be kept up-to-date, applying patches to address publicly known vulnerabilities in a timely fashion. **Recommendation**: Third-party libraries should be kept up-to-date, applying patches to address publicly known vulnerabilities in a timely fashion.
Monitoring and logging capabilities should also be in place to detect and respond to potential attacks. Monitoring and logging capabilities should also be in place to detect and respond to potential attacks.
SLSA compliance may also be considered for further supply chain security hardening. SLSA compliance may also be considered for further supply chain security hardening.
## Trust boundary: mirror payloads vs. mirror metadata
`nvm` fetches two very different kinds of data from a Node.js/io.js mirror (`nodejs.org`/`iojs.org` by default, or whatever `$NVM_NODEJS_ORG_MIRROR` and `$NVM_IOJS_ORG_MIRROR` point at), and they sit on opposite sides of a trust boundary:
- **Payloads** — the Node.js/io.js binaries and source tarballs that `nvm install` downloads, unpacks, compiles (for source installs), and runs.
- **Metadata** — everything `nvm` parses *about* those payloads rather than executing: the `index.tab` version list (including each release's LTS codename), and the `SHASUMS`/`SHASUMS256` checksum files.
Conflating the two leads to mis-scoped reports, so the project draws the line explicitly.
### Payloads are trusted, by construction
The entire purpose of `nvm` is to download a mirror's build of Node.js and run it.
A mirror that serves a backdoored binary has arbitrary code execution the moment you `nvm install` and invoke `node`, and no validation inside `nvm` can prevent that - you have chosen to execute that code.
Installing from source (`nvm install -s`) is if anything more direct: the mirror-supplied source tarball is unpacked and its `configure`/`make` build runs arbitrary code on your machine *at install time*, before `node` is ever invoked.
Checksum verification protects **integrity** (a corrupted or truncated download, or a network intermediary that cannot also forge the same-origin `SHASUMS`), not **authenticity** against the mirror itself, since the checksums come from the same origin as the payload.
Selecting a mirror is therefore equivalent to selecting whom you trust for arbitrary code execution in your account.
A malicious payload from the configured mirror is consequently **out of scope**: no privilege boundary is crossed, so there is no privilege to escalate.
### Metadata is not trusted
Parsing a version list is a pure data operation.
A user who runs `nvm ls-remote` to browse available versions - and installs nothing - has not opted into running any code from the mirror.
Metadata can also be attacker-controlled with no mirror misconfiguration at all, via a compromised mirror/CDN or a man-in-the-middle of the channel (see *Threat ID 3*), so it is treated as hostile input.
The invariant `nvm` maintains is:
> Mirror-supplied metadata must never reach a shell/command evaluator, an `awk`/`sed` program body, or an unvalidated filesystem path (see *Threat ID 2*).
This is why version strings from `index.tab` are passed to the downloader as literal `argv` elements rather than re-parsed by the shell ([CVE-2026-10796](https://github.com/advisories/GHSA-3c52-35h2-gfmm), [CVE-2026-1665](https://github.com/advisories/GHSA-4fc5-r4vr-8rp7)); why checksum comparisons pass the mirror's values as `awk -v` **data** and never as program text; and why LTS codenames are constrained to safe alias filenames before naming a file under `$NVM_DIR/alias/lts`, so a hostile codename such as `../../../.bashrc` cannot traverse out of the alias directory.
The point is not that metadata is "more dangerous" than a payload — a trusted payload can obviously do anything.
It is that metadata carries **no** implied grant of code execution, so any code-execution or arbitrary-write primitive reachable purely by parsing it is a defect worth removing on its own merits, independent of how much the payload channel is trusted.
+19 -1
View File
@@ -71,11 +71,29 @@ jobs:
- name: check tests filenames - name: check tests filenames
run: ./rename_test.sh --check run: ./rename_test.sh --check
check_exec:
runs-on: ubuntu-latest
steps:
- uses: step-security/harden-runner@v2
with:
allowed-endpoints:
github.com:443
raw.githubusercontent.com:443
nodejs.org:443
registry.npmjs.org:443
- uses: actions/checkout@v6
- uses: ljharb/actions/node/install@main
name: 'nvm install lts/* && npm install'
with:
node-version: 'lts/*'
- name: check that test files are executable
run: npm run test:check-exec
all: all:
permissions: permissions:
contents: none contents: none
name: 'all linting' name: 'all linting'
needs: [eclint, dockerfile_lint, doctoc, test_naming] needs: [eclint, dockerfile_lint, doctoc, test_naming, check_exec]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- run: true - run: true
+193
View File
@@ -0,0 +1,193 @@
name: 'Tests: alpine'
on: [push, pull_request]
permissions:
contents: read
jobs:
fast:
permissions:
contents: read
# Native runners per arch (no QEMU): arm64 uses the ubuntu-24.04-arm image.
name: 'fast ${{ matrix.arch }} (alpine ${{ matrix.alpine }}, ${{ matrix.shell }})'
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
strategy:
fail-fast: false
matrix:
alpine:
- '3.15'
- '3.19'
- '3'
arch:
- x64
- arm64
shell:
- sh
- bash
- dash
- zsh
# - ksh (#574)
steps:
- name: Harden Runner
uses: step-security/harden-runner@v2
with:
allowed-endpoints:
github.com:443
registry.npmjs.org:443
raw.githubusercontent.com:443
nodejs.org:443
iojs.org:443
unofficial-builds.nodejs.org:443
dl-cdn.alpinelinux.org:443
dl-cdn.alpinelinux.org:80
registry-1.docker.io:443
auth.docker.io:443
production.cloudflare.docker.com:443
production.cloudfront.docker.com:443
- uses: actions/checkout@v6
id: checkout
continue-on-error: true
with:
submodules: true
- name: 'nvmrc submodule fallback (forks without their own nvmrc)'
if: steps.checkout.outcome == 'failure'
shell: bash
run: |
git submodule set-url test/fixtures/nvmrc https://github.com/nvm-sh/nvmrc.git
git submodule sync --recursive
git submodule update --init --recursive
- uses: ljharb/actions/node/install@main
name: 'npm install (on host; node_modules is mounted into the container)'
with:
node-version: 'lts/*'
skip-ls-check: true
- run: npm ls urchin
- name: 'Run fast tests on Alpine ${{ matrix.alpine }} (${{ matrix.arch }})'
run: |
for i in 1 2 3 4 5; do
docker pull alpine:${{ matrix.alpine }} && break
echo "docker pull failed, attempt $i/5"; sleep $((i * 5))
done
docker run --rm \
-v "${{ github.workspace }}:/workspace" \
-w /workspace \
-e "TEST_SHELL=${{ matrix.shell }}" \
-e "TERM=xterm-256color" \
-e "GITHUB_ACTIONS=true" \
alpine:${{ matrix.alpine }} \
sh -c '
set -ex
cat /etc/alpine-release; uname -m
for i in 1 2 3 4 5; do
apk add --no-cache \
make bash zsh dash \
grep sed gawk coreutils util-linux findutils ncurses \
curl wget ca-certificates openssl tar xz gzip git \
sudo su-exec libstdc++ libgcc && break
echo "apk add failed, attempt $i/5"; sleep $((i * 5))
done
# Mirror the ubuntu runner: run the suite as a non-root user with
# passwordless sudo and a PTY so the permission/terminal tests
# behave the same. The user takes the mounted checkout uid so
# files stay host-owned (no chown; post-checkout cleanup works).
# No node is installed, so no active version skews the output.
HOST_UID="$(stat -c %u /workspace)"
adduser -D -u "$HOST_UID" tester 2>/dev/null || adduser -D tester
echo "tester ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/tester
chmod 0440 /etc/sudoers.d/tester
export NVM_DIR=/workspace HOME=/home/tester
unset NVM_BIN NVM_INC NVM_CD_FLAGS
su-exec tester script -q -e -c "make TEST_SUITE=fast SHELL=$TEST_SHELL URCHIN=/workspace/node_modules/.bin/urchin test-$TEST_SHELL" /dev/null
'
musl-binary:
permissions:
contents: read
# Regression lock, same-era diagonal only: modern musl node needs a newer
# libstdc++ than old Alpine ships, and arm64-musl exists only from
# v20.20.1/v22.21.1/v24.9.0 (built on modern Alpine), so arm64 pins modern Alpine.
name: 'musl-binary ${{ matrix.arch }} (alpine ${{ matrix.alpine }}, node ${{ matrix.node }})'
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
strategy:
fail-fast: false
matrix:
include:
- { arch: x64, alpine: '3.10', node: v8.17.0 }
- { arch: x64, alpine: '3.10', node: v10.24.1 }
- { arch: x64, alpine: '3.12', node: v12.22.12 }
- { arch: x64, alpine: '3.15', node: v14.21.3 }
- { arch: x64, alpine: '3.16', node: v16.20.2 }
- { arch: x64, alpine: '3.18', node: v18.20.4 }
- { arch: x64, alpine: '3.20', node: v20.18.1 }
- { arch: x64, alpine: '3', node: v22.12.0 }
- { arch: arm64, alpine: '3.20', node: v20.20.1 }
- { arch: arm64, alpine: '3', node: v22.21.1 }
- { arch: arm64, alpine: '3', node: v24.9.0 }
steps:
- name: Harden Runner
uses: step-security/harden-runner@v2
with:
allowed-endpoints:
github.com:443
registry.npmjs.org:443
unofficial-builds.nodejs.org:443
dl-cdn.alpinelinux.org:443
dl-cdn.alpinelinux.org:80
registry-1.docker.io:443
auth.docker.io:443
production.cloudflare.docker.com:443
production.cloudfront.docker.com:443
- uses: actions/checkout@v6
with:
submodules: false
- name: 'Install node ${{ matrix.node }} from a musl binary on Alpine ${{ matrix.alpine }}'
run: |
for i in 1 2 3 4 5; do
docker pull alpine:${{ matrix.alpine }} && break
echo "docker pull failed, attempt $i/5"; sleep $((i * 5))
done
docker run --rm \
-v "${{ github.workspace }}:/workspace" \
-w /workspace \
-e "NODE_VERSION=${{ matrix.node }}" \
-e "TERM=xterm-256color" \
alpine:${{ matrix.alpine }} \
sh -c '
set -ex
cat /etc/alpine-release; uname -m
# libstdc++/libgcc are load-bearing: the unofficial musl node
# binary dynamically links them. No build toolchain: -b forbids
# the source fallback, so a missing binary is a hard failure.
for i in 1 2 3 4 5; do
apk add --no-cache \
bash ca-certificates curl wget tar xz gzip \
grep sed coreutils libstdc++ libgcc && break
echo "apk add failed, attempt $i/5"; sleep $((i * 5))
done
export NVM_DIR=/workspace
unset NVM_BIN NVM_INC NVM_CD_FLAGS
# The default nodejs.org mirror serves no -musl artifacts; this one does.
export NVM_NODEJS_ORG_MIRROR=https://unofficial-builds.nodejs.org/download/release
. /workspace/nvm.sh --no-use
nvm install -b --skip-default-packages "$NODE_VERSION"
nvm use "$NODE_VERSION"
[ "$(node -v)" = "$NODE_VERSION" ] || { echo "version mismatch: $(node -v)"; exit 1; }
node -e "process.exit(0)"
ldd "$(command -v node)" | grep -qi musl
'
all:
permissions:
contents: none
name: 'all alpine tests'
needs: [fast, musl-binary]
runs-on: ubuntu-latest
steps:
- run: true
+35 -1
View File
@@ -98,11 +98,45 @@ jobs:
run: sudo apt-get install curl -y run: sudo apt-get install curl -y
shell: bash shell: bash
installation_iojs_source_compile:
permissions:
contents: read
name: 'actually compile io.js from source (gcc 4.9 container)'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Harden Runner
uses: step-security/harden-runner@v2
with:
allowed-endpoints:
github.com:443
mirror.gcr.io:443
storage.googleapis.com:443
iojs.org:443
- uses: actions/checkout@v6
# io.js requires python 2 and gcc <= 5 to compile; the gcc:4.9 image
# (debian jessie) has both, plus curl/wget/xz and CA certificates that
# still validate iojs.org - so the source-install tests run unmodified,
# resolving, downloading, checksumming, and compiling for real.
# The image is pulled through Google's Docker Hub mirror: it serves
# manifests and blobs from a single stable hostname (Docker Hub's blob
# CDN hostnames rotate, which the blocked-egress policy can not allow),
# and it is not subject to Docker Hub's anonymous pull rate limits.
- name: compile io.js from source, for real
run: |
docker run --rm -v "${PWD}:/nvm-under-test" -e NVM_DIR=/nvm-under-test mirror.gcr.io/library/gcc:4.9 bash -ec '
cd "${NVM_DIR}/test/installation_iojs"
sh "./install from source"
sh "./install version specified in .nvmrc from source"
'
all: all:
permissions: permissions:
contents: none contents: none
name: 'all installation_iojs tests' name: 'all installation_iojs tests'
needs: [installation_iojs_without_curl] needs: [installation_iojs_without_curl, installation_iojs_source_compile]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- run: true - run: true
+16 -16
View File
@@ -6,7 +6,7 @@
</a> </a>
# Node Version Manager [![Tests](https://github.com/nvm-sh/nvm/actions/workflows/tests-fast.yml/badge.svg?branch=master)][3] [![nvm version](https://img.shields.io/badge/version-v0.40.5-yellow.svg)][4] [![CII Best Practices](https://bestpractices.dev/projects/684/badge)](https://bestpractices.dev/projects/684) # Node Version Manager [![Tests](https://github.com/nvm-sh/nvm/actions/workflows/tests-fast.yml/badge.svg?branch=master)][3] [![nvm version](https://img.shields.io/badge/version-v0.40.6-yellow.svg)][4] [![CII Best Practices](https://bestpractices.dev/projects/684/badge)](https://bestpractices.dev/projects/684)
<!-- To update this table of contents, ensure you have run `npm install` then `npm run doctoc` --> <!-- 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 --> <!-- START doctoc generated TOC please keep comment here to allow auto update -->
@@ -106,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: 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:
```sh ```sh
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.5/install.sh | bash curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.6/install.sh | bash
``` ```
```sh ```sh
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.5/install.sh | bash wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.6/install.sh | bash
``` ```
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 files path, and then rerun the installation script. 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 files path, and then rerun the installation script.
@@ -136,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. - 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.5/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.6/install.sh | bash'`
#### Installing in Docker #### Installing in Docker
@@ -152,7 +152,7 @@ RUN touch "${BASH_ENV}"
RUN echo '. "${BASH_ENV}"' >> ~/.bashrc RUN echo '. "${BASH_ENV}"' >> ~/.bashrc
# Download and install nvm # Download and install nvm
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.5/install.sh | PROFILE="${BASH_ENV}" bash RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.6/install.sh | PROFILE="${BASH_ENV}" bash
RUN echo node > .nvmrc RUN echo node > .nvmrc
RUN nvm install RUN nvm install
``` ```
@@ -170,7 +170,7 @@ ARG NODE_VERSION=20
RUN apt update && apt install curl -y RUN apt update && apt install curl -y
# install nvm # install nvm
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.5/install.sh | bash RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.6/install.sh | bash
# set env # set env
ENV NVM_DIR=/root/.nvm ENV NVM_DIR=/root/.nvm
@@ -196,7 +196,7 @@ After creation of the image you can start container interactively and run comman
docker run --rm -it nvmimage docker run --rm -it nvmimage
root@0a6b5a237c14:/# nvm -v root@0a6b5a237c14:/# nvm -v
0.40.5 0.40.6
root@0a6b5a237c14:/# node -v root@0a6b5a237c14:/# node -v
v19.9.0 v19.9.0
@@ -259,7 +259,7 @@ You can use a task:
```yaml ```yaml
- name: Install nvm - name: Install nvm
ansible.builtin.shell: > ansible.builtin.shell: >
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.5/install.sh | bash curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.6/install.sh | bash
args: args:
creates: "{{ ansible_env.HOME }}/.nvm/nvm.sh" creates: "{{ ansible_env.HOME }}/.nvm/nvm.sh"
``` ```
@@ -321,7 +321,7 @@ If you have `git` installed (requires git v1.7.10+):
1. clone this repo in the root of your user profile 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` - `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.5` 1. `cd ~/.nvm` and check out the latest version with `git checkout v0.40.6`
1. activate `nvm` by sourcing it from your shell: `. ./nvm.sh` 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: Now add these lines to your `~/.bashrc`, `~/.profile`, or `~/.zshrc` file to have it automatically sourced upon login:
@@ -958,13 +958,13 @@ If installing nvm on Alpine Linux *is* still what you want or need to do, you sh
### Alpine Linux 3.13+ ### Alpine Linux 3.13+
```sh ```sh
apk add -U curl bash ca-certificates openssl ncurses coreutils python3 make gcc g++ libgcc linux-headers grep util-linux binutils findutils apk add -U curl bash ca-certificates openssl ncurses coreutils python3 make gcc g++ libgcc linux-headers grep util-linux binutils findutils
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.5/install.sh | bash curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.6/install.sh | bash
``` ```
### Alpine Linux 3.5 - 3.12 ### Alpine Linux 3.5 - 3.12
```sh ```sh
apk add -U curl bash ca-certificates openssl ncurses coreutils python2 make gcc g++ libgcc linux-headers grep util-linux binutils findutils apk add -U curl bash ca-certificates openssl ncurses coreutils python2 make gcc g++ libgcc linux-headers grep util-linux binutils findutils
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.5/install.sh | bash 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` 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._
@@ -1064,9 +1064,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) To change the user directory and/or account name follow the instructions [here](https://support.apple.com/en-us/HT201548)
[1]: https://github.com/nvm-sh/nvm.git [1]: https://github.com/nvm-sh/nvm.git
[2]: https://github.com/nvm-sh/nvm/blob/v0.40.5/install.sh [2]: https://github.com/nvm-sh/nvm/blob/v0.40.6/install.sh
[3]: https://github.com/nvm-sh/nvm/actions/workflows/tests-fast.yml [3]: https://github.com/nvm-sh/nvm/actions/workflows/tests-fast.yml
[4]: https://github.com/nvm-sh/nvm/releases/tag/v0.40.5 [4]: https://github.com/nvm-sh/nvm/releases/tag/v0.40.6
[Urchin]: https://git.sdf.org/tlevine/urchin [Urchin]: https://git.sdf.org/tlevine/urchin
[Fish]: https://fishshell.com [Fish]: https://fishshell.com
@@ -1124,7 +1124,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. 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. 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. 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.576) 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.676)
- Exit back to your native shell. - Exit back to your native shell.
@@ -1151,7 +1151,7 @@ Now you should be able to use node as usual.
If you've encountered this error on WSL-2: If you've encountered this error on WSL-2:
```sh ```sh
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.5/install.sh | bash curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.6/install.sh | bash
% Total % Received % Xferd Average Speed Time Time Time Current % Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- 0:00:09 --:--:-- 0curl: (6) Could not resolve host: raw.githubusercontent.com 0 0 0 0 0 0 0 0 --:--:-- 0:00:09 --:--:-- 0curl: (6) Could not resolve host: raw.githubusercontent.com
@@ -1186,7 +1186,7 @@ Currently, the sole maintainer is [@ljharb](https://github.com/ljharb) - more ma
## Project Support ## Project Support
Only the latest version (v0.40.5 at this time) is supported. Only the latest version (v0.40.6 at this time) is supported.
## Enterprise Support ## Enterprise Support
+1 -1
View File
@@ -45,7 +45,7 @@ nvm_install_dir() {
} }
nvm_latest_version() { nvm_latest_version() {
nvm_echo "v0.40.5" nvm_echo "v0.40.6"
} }
nvm_profile_is_bash_or_zsh() { nvm_profile_is_bash_or_zsh() {
+30 -13
View File
@@ -11,7 +11,7 @@
{ # this ensures the entire script is downloaded # { # this ensures the entire script is downloaded #
# shellcheck disable=SC3028 # shellcheck disable=SC3028
NVM_SCRIPT_SOURCE="$_" NVM_SCRIPT_SOURCE="${_:-}"
nvm_is_zsh() { nvm_is_zsh() {
[ -n "${ZSH_VERSION-}" ] [ -n "${ZSH_VERSION-}" ]
@@ -1283,6 +1283,13 @@ nvm_make_alias() {
nvm_err "an alias target version is required" nvm_err "an alias target version is required"
return 2 return 2
fi fi
# slashes are legal (eg `lts/iron`), but a `..` component would escape the alias dir
case "/${ALIAS}/" in
*/../*)
nvm_err "invalid alias name: ${ALIAS}"
return 3
;;
esac
nvm_echo "${VERSION}" | tee "$(nvm_alias_path)/${ALIAS}" >/dev/null nvm_echo "${VERSION}" | tee "$(nvm_alias_path)/${ALIAS}" >/dev/null
} }
@@ -1797,6 +1804,7 @@ nvm_ls_remote_index_tab() {
command mkdir -p "$(nvm_alias_path)/lts" command mkdir -p "$(nvm_alias_path)/lts"
{ command awk '{ { command awk '{
if ($10 ~ /^\-?$/) { next } if ($10 ~ /^\-?$/) { next }
if (tolower($10) !~ /^[a-z0-9][a-z0-9._-]*$/) { next }
if ($10 && !a[tolower($10)]++) { if ($10 && !a[tolower($10)]++) {
if (alias) { print alias, version } if (alias) { print alias, version }
alias_name = "lts/" tolower($10) alias_name = "lts/" tolower($10)
@@ -2251,10 +2259,12 @@ nvm_get_arch() {
HOST_ARCH=armv7l HOST_ARCH=armv7l
fi fi
if [ -f "/etc/alpine-release" ]; then if [ -f "/etc/alpine-release" ] && [ "_${NVM_OS}" = "_linux" ]; then
# Alpine Linux uses musl libc; only x64-musl binaries are available # Alpine Linux uses musl libc; map to musl variants where available
# See https://unofficial-builds.nodejs.org/download/release/
case "${NVM_ARCH}" in case "${NVM_ARCH}" in
x64) NVM_ARCH=x64-musl ;; x64) NVM_ARCH=x64-musl ;;
arm64) NVM_ARCH=arm64-musl ;;
esac esac
fi fi
@@ -3163,21 +3173,28 @@ nvm_cache_dir() {
nvm_ls_cached() { nvm_ls_cached() {
local PATTERN local PATTERN
PATTERN="${1-}" PATTERN="${1-}"
local CACHE_BIN_DIR local NVM_CACHE_DIR
CACHE_BIN_DIR="$(nvm_cache_dir)/bin" NVM_CACHE_DIR="$(nvm_cache_dir)"
if [ ! -d "${CACHE_BIN_DIR}" ]; then
return
fi
local NVM_OS local NVM_OS
NVM_OS="$(nvm_get_os)" NVM_OS="$(nvm_get_os)"
local NVM_ARCH local NVM_ARCH
NVM_ARCH="$(nvm_get_arch)" NVM_ARCH="$(nvm_get_arch)"
local SUFFIX local SUFFIX
SUFFIX="${NVM_OS}-${NVM_ARCH}" SUFFIX="${NVM_OS}-${NVM_ARCH}"
# shellcheck disable=SC2010 {
command ls -1 "${CACHE_BIN_DIR}" \ if [ -d "${NVM_CACHE_DIR}/bin" ]; then
| nvm_grep "^node-v.*-${SUFFIX}\$" \ # shellcheck disable=SC2010
| command sed "s/^node-\\(v[0-9][0-9.]*\\)-${SUFFIX}\$/\\1/" \ command ls -1 "${NVM_CACHE_DIR}/bin" \
| nvm_grep "^\\(node\\|iojs\\)-v[0-9][0-9.]*-${SUFFIX}\$" \
| command sed "s/-${SUFFIX}\$//"
fi
if [ -d "${NVM_CACHE_DIR}/src" ]; then
# shellcheck disable=SC2010
command ls -1 "${NVM_CACHE_DIR}/src" \
| nvm_grep "^\\(node\\|iojs\\)-v[0-9][0-9.]*\$"
fi
} \
| command sed 's/^node-//' \
| nvm_grep "$(nvm_ensure_version_prefix "${PATTERN}")" \ | nvm_grep "$(nvm_ensure_version_prefix "${PATTERN}")" \
| command sort -t. -u -k 1.2,1n -k 2,2n -k 3,3n | command sort -t. -u -k 1.2,1n -k 2,2n -k 3,3n
} }
@@ -4720,7 +4737,7 @@ nvm() {
NVM_VERSION_ONLY=true NVM_LTS="${NVM_LTS-}" nvm_remote_version "${PATTERN:-node}" NVM_VERSION_ONLY=true NVM_LTS="${NVM_LTS-}" nvm_remote_version "${PATTERN:-node}"
;; ;;
"--version" | "-v") "--version" | "-v")
nvm_echo '0.40.5' nvm_echo '0.40.6'
;; ;;
"unload") "unload")
nvm deactivate >/dev/null 2>&1 nvm deactivate >/dev/null 2>&1
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "nvm", "name": "nvm",
"version": "0.40.5", "version": "0.40.6",
"description": "Node Version Manager - Simple bash script to manage multiple active node.js versions", "description": "Node Version Manager - Simple bash script to manage multiple active node.js versions",
"directories": { "directories": {
"test": "test" "test": "test"
@@ -14,7 +14,7 @@
"test/installation/node": "shell=$(basename -- $(ps -o comm= $(ps -o ppid= -p $PPID)) | sed 's/^-//'); make TEST_SUITE=installation_node test-$shell", "test/installation/node": "shell=$(basename -- $(ps -o comm= $(ps -o ppid= -p $PPID)) | sed 's/^-//'); make TEST_SUITE=installation_node test-$shell",
"test/installation/iojs": "shell=$(basename -- $(ps -o comm= $(ps -o ppid= -p $PPID)) | sed 's/^-//'); make TEST_SUITE=installation_iojs test-$shell", "test/installation/iojs": "shell=$(basename -- $(ps -o comm= $(ps -o ppid= -p $PPID)) | sed 's/^-//'); make TEST_SUITE=installation_iojs test-$shell",
"test/sourcing": "shell=$(basename -- $(ps -o comm= $(ps -o ppid= -p $PPID)) | sed 's/^-//'); make TEST_SUITE=sourcing test-$shell", "test/sourcing": "shell=$(basename -- $(ps -o comm= $(ps -o ppid= -p $PPID)) | sed 's/^-//'); make TEST_SUITE=sourcing test-$shell",
"test:check-exec": "(IFS=$'\\n'; for file in $(git ls-files test); do if [ ! -x \"$file\" ] && [[ \"$file\" != *.* ]] && [[ \"$file\" != test/fixtures/* ]]; then echo \"$file\"; fi; done) | tee /dev/stderr | awk 'END {if (NR > 0) exit 1}'", "test:check-exec": "git -c core.quotePath=false ls-files test | while IFS= read -r file; do if [ ! -x \"$file\" ]; then case \"$file\" in test/fixtures/*|*.json|*.txt|*.sh|*.js|*.log|*.tab|*.gitkeep) ;; *) echo \"$file\" ;; esac; fi; done | tee /dev/stderr | awk 'END {if (NR > 0) exit 1}'",
"test:check-nonexec": "(IFS=$'\\n'; for file in $(git ls-files test); do if [ -x \"$file\" ] && [ ! -d \"$file\" ] && { [[ \"$file\" =~ '\\.(json|txt|sh|js|log)$' ]] || [[ \"$file\" =~ '^test/(mocks|fixtures)/.*' ]]; }; then echo \"$file\"; fi; done) | tee /dev/stderr | awk 'END {if (NR > 0) exit 1}'", "test:check-nonexec": "(IFS=$'\\n'; for file in $(git ls-files test); do if [ -x \"$file\" ] && [ ! -d \"$file\" ] && { [[ \"$file\" =~ '\\.(json|txt|sh|js|log)$' ]] || [[ \"$file\" =~ '^test/(mocks|fixtures)/.*' ]]; }; then echo \"$file\"; fi; done) | tee /dev/stderr | awk 'END {if (NR > 0) exit 1}'",
"doctoc": "doctoc --title='## Table of Contents' --github README.md", "doctoc": "doctoc --title='## Table of Contents' --github README.md",
"predoctoc:check": "cp README.md v-README.md.orig && npm run doctoc", "predoctoc:check": "cp README.md v-README.md.orig && npm run doctoc",
+4 -2
View File
@@ -12,8 +12,10 @@ nvm_download() {
} }
# --offline with an already-installed version should succeed # --offline with an already-installed version should succeed
INSTALLED_VERSION="$(nvm ls | command tail -1 | command awk '{print $1}' | command sed 's/\x1b\[[0-9;]*m//g')" # (`nvm ls` ends with the alias listing, so use `nvm_ls`, which emits bare
if [ -n "${INSTALLED_VERSION}" ] && [ "_${INSTALLED_VERSION}" != '_N/A' ] && [ "_${INSTALLED_VERSION}" != '_system' ]; then # versions, and skip its trailing `system <version>` line)
INSTALLED_VERSION="$(nvm_ls | command grep -v '^system' | command tail -n1)"
if [ -n "${INSTALLED_VERSION}" ] && [ "_${INSTALLED_VERSION}" != '_N/A' ]; then
try nvm install --offline "${INSTALLED_VERSION}" try nvm install --offline "${INSTALLED_VERSION}"
[ "_$CAPTURED_EXIT_CODE" = "_0" ] \ [ "_$CAPTURED_EXIT_CODE" = "_0" ] \
|| die "nvm install --offline with installed version '${INSTALLED_VERSION}' should succeed, got exit code $CAPTURED_EXIT_CODE" || die "nvm install --offline with installed version '${INSTALLED_VERSION}' should succeed, got exit code $CAPTURED_EXIT_CODE"
+7 -2
View File
@@ -90,8 +90,13 @@ run_test amd64 smartos x64 no_pkg_info
run_test x86 osx x86 run_test x86 osx x86
run_test amd64 osx x64 run_test amd64 osx x64
run_test arm64 smartos x64 # These smartos cases have no arch-specific uname mock, so nvm_get_os falls
run_test armv8l smartos x64 # through to the real host; on Alpine that host is linux and picks up the musl
# suffix, which these non-musl assertions do not expect. Skip them there.
if [ ! -f "/etc/alpine-release" ]; then
run_test arm64 smartos x64
run_test armv8l smartos x64
fi
run_test loongarch64 linux loong64 run_test loongarch64 linux loong64
+4 -4
View File
@@ -15,8 +15,8 @@ die () { cleanup; echo "$@" ; exit 1; }
MOCKS_DIR="$(pwd)/../../mocks" MOCKS_DIR="$(pwd)/../../mocks"
export PATH=".:${PATH}" export PATH=".:${PATH}"
# On Alpine (where /etc/alpine-release exists), x64 should get -musl suffix # On Alpine (where /etc/alpine-release exists), both x64 and arm64 should get
# and arm64 should NOT get -musl suffix. # the -musl suffix, since unofficial-builds publishes musl binaries for both.
# On non-Alpine, neither should get -musl. # On non-Alpine, neither should get -musl.
if [ -f "/etc/alpine-release" ]; then if [ -f "/etc/alpine-release" ]; then
@@ -26,11 +26,11 @@ if [ -f "/etc/alpine-release" ]; then
rm -f ./uname rm -f ./uname
[ "_${OUTPUT}" = "_x64-musl" ] || die "x64 on Alpine should be x64-musl, got ${OUTPUT}" [ "_${OUTPUT}" = "_x64-musl" ] || die "x64 on Alpine should be x64-musl, got ${OUTPUT}"
# aarch64 on Alpine should produce arm64, NOT arm64-musl # aarch64 on Alpine should produce arm64-musl
ln -sf "${MOCKS_DIR}/uname_linux_aarch64" ./uname ln -sf "${MOCKS_DIR}/uname_linux_aarch64" ./uname
OUTPUT="$(nvm_get_arch)" OUTPUT="$(nvm_get_arch)"
rm -f ./uname rm -f ./uname
[ "_${OUTPUT}" = "_arm64" ] || die "aarch64 on Alpine should be arm64 (no musl suffix), got ${OUTPUT}" [ "_${OUTPUT}" = "_arm64-musl" ] || die "aarch64 on Alpine should be arm64-musl, got ${OUTPUT}"
else else
# x64 on non-Alpine should produce x64 (no musl suffix) # x64 on non-Alpine should produce x64 (no musl suffix)
ln -sf "${MOCKS_DIR}/uname_linux_x86_64" ./uname ln -sf "${MOCKS_DIR}/uname_linux_x86_64" ./uname
+15 -7
View File
@@ -57,15 +57,23 @@ setup_chroot() {
sudo mknod "${chroot_dir}/dev/null" c 1 3 sudo mknod "${chroot_dir}/dev/null" c 1 3
} }
setup_chroot "${CHROOT_WITH_ALPINE}" # The chroot fixtures assume a glibc layout (fixed dynamic-linker path under
setup_chroot "${CHROOT_WITHOUT_ALPINE}" # /lib64, coreutils binaries). On musl Alpine that setup does not apply, and
# nvm_get_arch's musl mapping is already covered by the "nvm_get_arch alpine"
# test, so skip the chroot checks there and still run the ls-remote checks.
if [ -f "/etc/alpine-release" ]; then
echo "on Alpine; skipping chroot arch checks (covered by 'nvm_get_arch alpine')"
else
setup_chroot "${CHROOT_WITH_ALPINE}"
setup_chroot "${CHROOT_WITHOUT_ALPINE}"
# Run tests in chroot environments # Run tests in chroot environments
ARCH_WITH_ALPINE=$(sudo chroot "${CHROOT_WITH_ALPINE}" /bin/sh -c ". ./nvm.sh && nvm_get_arch") ARCH_WITH_ALPINE=$(sudo chroot "${CHROOT_WITH_ALPINE}" /bin/sh -c ". ./nvm.sh && nvm_get_arch")
[ "${ARCH_WITH_ALPINE}" = "x64-musl" ] || die "Expected x64-musl for alpine environment but got ${ARCH_WITH_ALPINE}" [ "${ARCH_WITH_ALPINE}" = "x64-musl" ] || die "Expected x64-musl for alpine environment but got ${ARCH_WITH_ALPINE}"
ARCH_WITHOUT_ALPINE=$(sudo chroot "${CHROOT_WITHOUT_ALPINE}" /bin/sh -c ". ./nvm.sh && nvm_get_arch") ARCH_WITHOUT_ALPINE=$(sudo chroot "${CHROOT_WITHOUT_ALPINE}" /bin/sh -c ". ./nvm.sh && nvm_get_arch")
[ "${ARCH_WITHOUT_ALPINE}" != "x64-musl" ] || die "Did not expect x64-musl for non-alpine environment" [ "${ARCH_WITHOUT_ALPINE}" != "x64-musl" ] || die "Did not expect x64-musl for non-alpine environment"
fi
# Run tests for nvm ls-remote # Run tests for nvm ls-remote
test_default_ls_remote() { test_default_ls_remote() {
@@ -14,6 +14,14 @@ die () { >&2 echo "$@" ; cleanup ; exit 1; }
: nvm.sh : nvm.sh
\. ../../../nvm.sh \. ../../../nvm.sh
# v0.12.18 predates musl binaries and the expected output hardcodes the glibc
# x64 tarball URL, so on Alpine nvm would request a nonexistent -musl build.
# The progress-bar behavior is not OS-specific, so skip below the musl floor.
if [ -f "/etc/alpine-release" ]; then
echo 'on Alpine; skipping (v0.12.18 has no musl binary)'
exit 0
fi
cleanup cleanup
OUTPUT="$(TERM=dumb 2>&1 nvm install --no-progress v0.12.18)" OUTPUT="$(TERM=dumb 2>&1 nvm install --no-progress v0.12.18)"
+40
View File
@@ -0,0 +1,40 @@
#!/bin/sh
WORK="$PWD/ls_remote-traversal-work.$$"
export HOME="$WORK/home"
export NVM_DIR="$HOME/.nvm"
BASHRC="$HOME/.bashrc"
cleanup() {
unset -f die cleanup nvm_download
rm -rf "$WORK"
}
die () { echo "$@" ; cleanup ; exit 1; }
mkdir -p "$NVM_DIR/alias"
: nvm.sh
\. ../../../nvm.sh
# a malicious/compromised mirror: the LTS codename field ($10) carries path
# traversal, and the version field ($1) carries a command-substitution payload;
# a valid codename (Iron) is included to prove real aliases still get written
nvm_download() {
printf 'version\tdate\tfiles\tnpm\tv8\tuv\tzlib\topenssl\tmodules\tlts\tsecurity\n'
printf '$(>%s/pwned)\t2026-01-01\tlinux-x64\t-\t-\t-\t-\t-\t-\t../../../.bashrc\t-\n' "$WORK"
printf 'v20.0.0\t2026-01-01\tlinux-x64\t-\t-\t-\t-\t-\t-\tIron\t-\n'
}
nvm_ls_remote >/dev/null 2>&1 || true
[ ! -e "$BASHRC" ] || die "path traversal wrote outside the alias dir: $BASHRC was created"
[ ! -e "$WORK/pwned" ] || die "mirror-supplied payload landed: $WORK/pwned was created"
[ -f "$NVM_DIR/alias/lts/iron" ] || die "valid LTS alias lts/iron was not created"
nvm_make_alias 'lts/../../../escape' 'v1.0.0' 2>/dev/null && die 'nvm_make_alias accepted a traversing alias name'
[ ! -e "$NVM_DIR/../escape" ] || die 'nvm_make_alias wrote outside the alias dir'
nvm_make_alias 'lts/carbon' 'v8.0.0' >/dev/null 2>&1 || die 'nvm_make_alias rejected a valid alias name'
[ -f "$NVM_DIR/alias/lts/carbon" ] || die 'nvm_make_alias did not create a valid alias'
cleanup
echo "nvm_ls_remote LTS codename traversal: passed"
+33
View File
@@ -10,6 +10,10 @@ TEST_DIR="$(pwd)/nvm_offline_version_tmp"
cleanup() { cleanup() {
rm -rf "${TEST_DIR}" rm -rf "${TEST_DIR}"
CACHE_DIR="$(nvm_cache_dir)"
SUFFIX="$(nvm_get_os)-$(nvm_get_arch)"
rm -rf "${CACHE_DIR}/bin/node-v0.99.98-${SUFFIX}" "${CACHE_DIR}/bin/iojs-v0.99.97-${SUFFIX}" \
"${CACHE_DIR}/src/node-v0.99.96" "${CACHE_DIR}/src/iojs-v0.99.95"
} }
[ ! -e "${TEST_DIR}" ] && mkdir -p "${TEST_DIR}" [ ! -e "${TEST_DIR}" ] && mkdir -p "${TEST_DIR}"
@@ -36,4 +40,33 @@ try nvm_ls_cached "999.999"
[ -z "$CAPTURED_STDOUT" ] \ [ -z "$CAPTURED_STDOUT" ] \
|| die "nvm_ls_cached '999.999' should return empty; got '$CAPTURED_STDOUT'" || die "nvm_ls_cached '999.999' should return empty; got '$CAPTURED_STDOUT'"
# nvm_ls_cached should list bin- and src-cached artifacts, for node and io.js
CACHE_DIR="$(nvm_cache_dir)"
SUFFIX="$(nvm_get_os)-$(nvm_get_arch)"
mkdir -p "${CACHE_DIR}/bin/node-v0.99.98-${SUFFIX}" "${CACHE_DIR}/bin/iojs-v0.99.97-${SUFFIX}" \
"${CACHE_DIR}/src/node-v0.99.96" "${CACHE_DIR}/src/iojs-v0.99.95"
try nvm_ls_cached "v0.99.98"
[ "_$CAPTURED_STDOUT" = "_v0.99.98" ] \
|| die "nvm_ls_cached 'v0.99.98' should find the bin-cached node version; got '$CAPTURED_STDOUT'"
try nvm_ls_cached "iojs-v0.99.97"
[ "_$CAPTURED_STDOUT" = "_iojs-v0.99.97" ] \
|| die "nvm_ls_cached 'iojs-v0.99.97' should find the bin-cached io.js version; got '$CAPTURED_STDOUT'"
try nvm_ls_cached "v0.99.96"
[ "_$CAPTURED_STDOUT" = "_v0.99.96" ] \
|| die "nvm_ls_cached 'v0.99.96' should find the src-cached node version; got '$CAPTURED_STDOUT'"
try nvm_ls_cached "iojs-v0.99.95"
[ "_$CAPTURED_STDOUT" = "_iojs-v0.99.95" ] \
|| die "nvm_ls_cached 'iojs-v0.99.95' should find the src-cached io.js version; got '$CAPTURED_STDOUT'"
# ... and nvm_offline_version should resolve them
try nvm_offline_version "iojs-v0.99.95"
[ "_$CAPTURED_STDOUT" = "_iojs-v0.99.95" ] \
|| die "nvm_offline_version 'iojs-v0.99.95' should resolve the src-cached io.js version; got '$CAPTURED_STDOUT'"
[ "_$CAPTURED_EXIT_CODE" = "_0" ] \
|| die "nvm_offline_version 'iojs-v0.99.95' should exit 0; got '$CAPTURED_EXIT_CODE'"
cleanup cleanup
+15
View File
@@ -0,0 +1,15 @@
include config.mk
all: out/fake-bin
out/fake-bin: VERSION
mkdir -p out
printf '#!/bin/sh\necho %s\n' "$$(cat VERSION)" > out/fake-bin
chmod +x out/fake-bin
install: all
mkdir -p "$(PREFIX)/bin"
cp out/fake-bin "$(PREFIX)/bin/node"
cp out/fake-bin "$(PREFIX)/bin/iojs"
.PHONY: all install
+22
View File
@@ -0,0 +1,22 @@
#!/bin/sh
# a fake node/io.js `configure`, for source-install tests: real io.js (and
# old node) can no longer be compiled on modern toolchains (python 2
# `configure`, ancient V8), so source-install tests exercise nvm's entire
# source pipeline against this tiny stand-in instead.
PREFIX=''
while [ $# -gt 0 ]; do
case "${1}" in
--prefix=*) PREFIX="${1#--prefix=}" ;;
esac
shift
done
if [ -z "${PREFIX}" ]; then
echo 'configure: a --prefix is required' >&2
exit 1
fi
printf 'PREFIX = %s\n' "${PREFIX}" > config.mk
echo "configured with PREFIX=${PREFIX}"
+44
View File
@@ -0,0 +1,44 @@
#!/bin/sh
\. ../common.sh
die () { echo "$@" ; exit 1; }
\. ../../nvm.sh
set -ex
NVM_TEST_VERSION='v3.99.9'
NVM_PREFIXED_TEST_VERSION="iojs-${NVM_TEST_VERSION}"
# Remove the stuff we're clobbering.
nvm uninstall "${NVM_TEST_VERSION}" || echo 'not installed'
# Exercise nvm's entire source-install pipeline - cache, extract, configure,
# make, make install, activation - against a tiny fake source tree, seeded
# into the download cache and installed with --offline. This runs everywhere,
# including on toolchains where real io.js can not compile; `install from
# source` covers the real compile, in CI's gcc:4.9 container job.
SLUG="$(nvm_get_download_slug iojs source "${NVM_TEST_VERSION}")"
COMPRESSION="$(nvm_get_artifact_compression "${NVM_TEST_VERSION}")"
CACHE_DIR="$(nvm_cache_dir)/src/${SLUG}"
STAGING="$(mktemp -d)"
mkdir -p "${STAGING}/${SLUG}" "${CACHE_DIR}"
cp ../fixtures/fake-source-tarball/configure ../fixtures/fake-source-tarball/Makefile "${STAGING}/${SLUG}/"
chmod +x "${STAGING}/${SLUG}/configure"
echo "${NVM_TEST_VERSION}" > "${STAGING}/${SLUG}/VERSION"
if [ "${COMPRESSION}" = 'tar.xz' ]; then
(cd "${STAGING}" && tar -cJf "${CACHE_DIR}/${SLUG}.${COMPRESSION}" "${SLUG}")
else
(cd "${STAGING}" && tar -czf "${CACHE_DIR}/${SLUG}.${COMPRESSION}" "${SLUG}")
fi
rm -rf "${STAGING}"
# Install from source
nvm install -s --offline "${NVM_PREFIXED_TEST_VERSION}" || die "'nvm install -s --offline ${NVM_PREFIXED_TEST_VERSION}' failed"
# Check
nvm_is_version_installed "${NVM_PREFIXED_TEST_VERSION}" || die 'version not installed'
nvm run --silent "${NVM_PREFIXED_TEST_VERSION}" --version | grep "${NVM_TEST_VERSION}" || die "'nvm run ${NVM_PREFIXED_TEST_VERSION} --version | grep ${NVM_TEST_VERSION}' failed"
rm -rf "${CACHE_DIR}"
+18 -1
View File
@@ -8,6 +8,23 @@ die () { echo "$@" ; exit 1; }
set -ex set -ex
# Compiling io.js requires a period toolchain: python 2 (its `configure`),
# and gcc <= 5 (its bundled V8). CI compiles it for real in a gcc:4.9
# container job; on modern toolchains, this can only be skipped
# (`install from fake source` covers nvm's source pipeline everywhere).
GCC_MAJOR="$(gcc -dumpversion 2>/dev/null | command cut -d. -f1)"
if ! command -v python2 >/dev/null 2>&1 && ! python -V 2>&1 | command grep -q '^Python 2'; then
echo 'python 2 is not available; skipping the io.js source install'
exit 0
fi
case "${GCC_MAJOR}" in
'' | *[!0-9]*) echo 'gcc is not available; skipping the io.js source install'; exit 0 ;;
esac
if [ "${GCC_MAJOR}" -gt 5 ]; then
echo "gcc <= 5 is required to compile io.js, got ${GCC_MAJOR}; skipping the io.js source install"
exit 0
fi
NVM_TEST_VERSION='v3.3.1' NVM_TEST_VERSION='v3.3.1'
NVM_PREFIXED_TEST_VERSION="iojs-${NVM_TEST_VERSION}" NVM_PREFIXED_TEST_VERSION="iojs-${NVM_TEST_VERSION}"
@@ -19,4 +36,4 @@ nvm uninstall "${NVM_TEST_VERSION}" || echo 'not installed'
# Check # Check
nvm_is_version_installed "${NVM_PREFIXED_TEST_VERSION}" || die 'version not installed' nvm_is_version_installed "${NVM_PREFIXED_TEST_VERSION}" || die 'version not installed'
nvm run "${NVM_PREFIXED_TEST_VERSION}" --version | grep "${NVM_TEST_VERSION}" || "'nvm run ${NVM_PREFIXED_TEST_VERSION} --version | grep ${NVM_TEST_VERSION}' failed" nvm run --silent "${NVM_PREFIXED_TEST_VERSION}" --version | grep "${NVM_TEST_VERSION}" || die "'nvm run ${NVM_PREFIXED_TEST_VERSION} --version | grep ${NVM_TEST_VERSION}' failed"
@@ -0,0 +1,45 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
\. ../../nvm.sh
set -ex
NVM_TEST_VERSION='v3.99.8'
NVM_PREFIXED_TEST_VERSION="iojs-${NVM_TEST_VERSION}"
# Remove the stuff we're clobbering.
nvm uninstall "${NVM_TEST_VERSION}" || echo 'not installed'
# Exercise nvm's source-install pipeline against a tiny fake source tree,
# seeded into the download cache and installed with --offline (see `install
# from fake source`). This runs everywhere, including on toolchains where
# real io.js can not compile; `install version specified in .nvmrc from
# source` covers the real compile, in CI's gcc:4.9 container job.
SLUG="$(nvm_get_download_slug iojs source "${NVM_TEST_VERSION}")"
COMPRESSION="$(nvm_get_artifact_compression "${NVM_TEST_VERSION}")"
CACHE_DIR="$(nvm_cache_dir)/src/${SLUG}"
STAGING="$(mktemp -d)"
mkdir -p "${STAGING}/${SLUG}" "${CACHE_DIR}"
cp ../fixtures/fake-source-tarball/configure ../fixtures/fake-source-tarball/Makefile "${STAGING}/${SLUG}/"
chmod +x "${STAGING}/${SLUG}/configure"
echo "${NVM_TEST_VERSION}" > "${STAGING}/${SLUG}/VERSION"
if [ "${COMPRESSION}" = 'tar.xz' ]; then
(cd "${STAGING}" && tar -cJf "${CACHE_DIR}/${SLUG}.${COMPRESSION}" "${SLUG}")
else
(cd "${STAGING}" && tar -czf "${CACHE_DIR}/${SLUG}.${COMPRESSION}" "${SLUG}")
fi
rm -rf "${STAGING}"
# Install from source, with the version specified in .nvmrc
echo "${NVM_PREFIXED_TEST_VERSION}" > .nvmrc
nvm install -s --offline || die "'nvm install -s --offline' failed"
# Check
nvm_is_version_installed "${NVM_PREFIXED_TEST_VERSION}" || die 'version is not installed'
nvm run --silent "${NVM_PREFIXED_TEST_VERSION}" --version | grep "${NVM_TEST_VERSION}" \
|| die "'nvm run ${NVM_PREFIXED_TEST_VERSION} --version | grep ${NVM_TEST_VERSION}' failed"
rm -rf "${CACHE_DIR}"
+21 -3
View File
@@ -6,18 +6,36 @@ die () { echo "$@" ; exit 1; }
set -ex set -ex
# Compiling io.js requires a period toolchain: python 2 (its `configure`),
# and gcc <= 5 (its bundled V8). CI compiles it for real in a gcc:4.9
# container job; on modern toolchains, this can only be skipped
# (`install version specified in .nvmrc from fake source` covers nvm's
# source pipeline everywhere).
GCC_MAJOR="$(gcc -dumpversion 2>/dev/null | command cut -d. -f1)"
if ! command -v python2 >/dev/null 2>&1 && ! python -V 2>&1 | command grep -q '^Python 2'; then
echo 'python 2 is not available; skipping the io.js source install'
exit 0
fi
case "${GCC_MAJOR}" in
'' | *[!0-9]*) echo 'gcc is not available; skipping the io.js source install'; exit 0 ;;
esac
if [ "${GCC_MAJOR}" -gt 5 ]; then
echo "gcc <= 5 is required to compile io.js, got ${GCC_MAJOR}; skipping the io.js source install"
exit 0
fi
NVM_TEST_VERSION='v3.3.0' NVM_TEST_VERSION='v3.3.0'
NVM_PREFIXED_TEST_VERSION="iojs-${NVM_TEST_VERSION}" NVM_PREFIXED_TEST_VERSION="iojs-${NVM_TEST_VERSION}"
# Remove the stuff we're clobbering. # Remove the stuff we're clobbering.
nvm uninstall "${NVM_TEST_VERSION}" || echo 'not installed' nvm uninstall "${NVM_TEST_VERSION}" || echo 'not installed'
# Install from binary # Install from source, with the version specified in .nvmrc
echo "${NVM_PREFIXED_TEST_VERSION}" > .nvmrc echo "${NVM_PREFIXED_TEST_VERSION}" > .nvmrc
nvm install -s || "'nvm install -s' failed" nvm install -s || die "'nvm install -s' failed"
# Check # Check
nvm_is_version_installed "${NVM_PREFIXED_TEST_VERSION}" || die 'version is not installed' nvm_is_version_installed "${NVM_PREFIXED_TEST_VERSION}" || die 'version is not installed'
nvm run "${NVM_PREFIXED_TEST_VERSION}" --version | grep "${NVM_TEST_VERSION}" \ nvm run --silent "${NVM_PREFIXED_TEST_VERSION}" --version | grep "${NVM_TEST_VERSION}" \
|| die "'nvm run ${NVM_PREFIXED_TEST_VERSION} --version | grep ${NVM_TEST_VERSION}' failed" || die "'nvm run ${NVM_PREFIXED_TEST_VERSION} --version | grep ${NVM_TEST_VERSION}' failed"
+2
View File
@@ -6,6 +6,8 @@ nvm uninstall iojs-v1.0.0
nvm uninstall iojs-v1.0.3 nvm uninstall iojs-v1.0.3
nvm uninstall iojs-v3.3.0 nvm uninstall iojs-v3.3.0
nvm uninstall iojs-v3.3.1 nvm uninstall iojs-v3.3.1
nvm uninstall iojs-v3.99.8
nvm uninstall iojs-v3.99.9
if [ -f ".nvmrc" ]; then if [ -f ".nvmrc" ]; then
rm .nvmrc rm .nvmrc
+3 -1
View File
@@ -6,4 +6,6 @@ die () { echo "$@" ; exit 1; }
nvm use 0.10 nvm use 0.10
nvm exec stable -- node --help | grep 'Usage: node [options]' || die "Help menu should have been displayed for node and not nvm" # note: in a BRE, `[options]` is a bracket expression, so the literal `[options]`
# in node's help output could never match; match the unbracketed prefix instead
nvm exec stable -- node --help | grep 'Usage: node' || die "Help menu should have been displayed for node and not nvm"
@@ -1,30 +1,51 @@
#!/bin/sh #!/bin/sh
die () { echo "$@" ; exit 1; } die () { echo "$@" ; cleanup ; exit 1; }
# Source nvm # Source nvm
\. ../../../nvm.sh \. ../../../nvm.sh
# Version to install/uninstall \. ../../common.sh
NVM_TEST_VERSION=5.10.1
# Make sure it's not already here NVM_TEST_VERSION=v5.99.99
[ -e ../../../$NVM_TEST_VERSION ] && rm -R ../../../$NVM_TEST_VERSION
# Install it cleanup() {
nvm install $NVM_TEST_VERSION VERSION_PATH="${VERSION_PATH-}"
if [ -n "${VERSION_PATH}" ] && [ -d "${VERSION_PATH}" ]; then
sudo -n rm -f "${VERSION_PATH}/lib/root-owned-file" 2>/dev/null
rm -rf "${VERSION_PATH}"
fi
}
# Make sure it installed # The permissions check only trips on files that are neither writable nor
nvm ls | grep "$NVM_TEST_VERSION" || die "Failed to install node" # self-owned, and creating one requires another user: skip where passwordless
# sudo is unavailable; CI always has it.
if ! sudo -n true 2>/dev/null; then
echo 'passwordless sudo is not available, skipping'
exit 0
fi
# Install global module as root # Install a fake version to uninstall
npm_path=$(which npm) make_fake_node "${NVM_TEST_VERSION}" || die 'unable to make fake node'
sudo -n "$npm_path" install jspm@'<2' -g || die 'either sudo failed, or `npm install jspm -g` failed`' VERSION_PATH="$(nvm_version_path "${NVM_TEST_VERSION}")"
# Switch to another version so we can uninstall # Make part of the installation folder unwritable, and not self-owned
nvm use 0.12.7 mkdir -p "${VERSION_PATH}/lib"
sudo -n touch "${VERSION_PATH}/lib/root-owned-file" || die 'sudo touch failed'
sudo -n chmod 444 "${VERSION_PATH}/lib/root-owned-file" || die 'sudo chmod failed'
# Make sure the version to uninstall is not the active one
nvm deactivate >/dev/null 2>&1 || true
# Attempt to uninstall it # Attempt to uninstall it
RETURN_MESSAGE="$(nvm uninstall $NVM_TEST_VERSION 2>&1)" RETURN_MESSAGE="$(nvm uninstall "${NVM_TEST_VERSION}" 2>&1)"
CHECK_FOR="Cannot uninstall, incorrect permissions on installation folder" CHECK_FOR="Cannot uninstall, incorrect permissions on installation folder"
test "${RETURN_MESSAGE#*$CHECK_FOR}" != "$RETURN_MESSAGE" || die "Failed to show error message" case "${RETURN_MESSAGE}" in
*"${CHECK_FOR}"*) ;;
*) die "Failed to show error message; got: ${RETURN_MESSAGE}" ;;
esac
# ... and the version must not have been removed
nvm_is_version_installed "${NVM_TEST_VERSION}" || die 'version was uninstalled despite the permissions error'
cleanup