Compare commits

..
31 Commits
Author SHA1 Message Date
Jordan HarbandandElan Ruusamäe b1b3a0038c [Fix] nvm_cd: bypass a user-defined cd function
Code scanning - action / CodeQL-Build (push) Waiting to run
Tests: `nvm install-latest-npm` / matrix (push) Waiting to run
latest-npm.yml / nvm install-latest-npm (${{ fromJson(needs.matrix.outputs.latest) }}) (push) Blocked by required conditions
Tests: `nvm install-latest-npm` / nvm install-latest-npm (0.10) (push) Blocked by required conditions
Tests: `nvm install-latest-npm` / nvm install-latest-npm (0.12) (push) Blocked by required conditions
Tests: `nvm install-latest-npm` / nvm install-latest-npm (14.16) (push) Blocked by required conditions
Tests: `nvm install-latest-npm` / nvm install-latest-npm (14.17) (push) Blocked by required conditions
Tests: `nvm install-latest-npm` / nvm install-latest-npm (20.4) (push) Blocked by required conditions
Tests: `nvm install-latest-npm` / nvm install-latest-npm (20.5) (push) Blocked by required conditions
Tests: `nvm install-latest-npm` / nvm install-latest-npm (21) (push) Blocked by required conditions
Tests: `nvm install-latest-npm` / nvm install-latest-npm (4.4) (push) Blocked by required conditions
Tests: `nvm install-latest-npm` / nvm install-latest-npm (4.5) (push) Blocked by required conditions
Tests: `nvm install-latest-npm` / nvm install-latest-npm (4.6) (push) Blocked by required conditions
Tests: `nvm install-latest-npm` / nvm install-latest-npm (5.9) (push) Blocked by required conditions
Tests: `nvm install-latest-npm` / nvm install-latest-npm (6.1) (push) Blocked by required conditions
Tests: `nvm install-latest-npm` / nvm install-latest-npm (9.0) (push) Blocked by required conditions
Tests: `nvm install-latest-npm` / nvm install-latest-npm (9.1) (push) Blocked by required conditions
Tests: `nvm install-latest-npm` / nvm install-latest-npm (9.2) (push) Blocked by required conditions
Tests: `nvm install-latest-npm` / nvm install-latest-npm (push) Blocked by required conditions
Tests: linting / eclint (push) Waiting to run
Tests: linting / dockerfile_lint (push) Waiting to run
Tests: linting / doctoc (push) Waiting to run
Tests: linting / test_naming (push) Waiting to run
Tests: linting / check_exec (push) Waiting to run
Tests: linting / all linting (push) Blocked by required conditions
Tests: nvm install with set -e / matrix (push) Waiting to run
nvm-install-test.yml / test (no nvmrc, ${{ fromJson(needs.matrix.outputs.matrix) }}, 1 shlvl) (push) Blocked by required conditions
nvm-install-test.yml / test (no nvmrc, ${{ fromJson(needs.matrix.outputs.matrix) }}, 2 shlvls) (push) Blocked by required conditions
nvm-install-test.yml / test (nvmrc, ${{ fromJson(needs.matrix.outputs.matrix) }}, 1 shlvl) (push) Blocked by required conditions
nvm-install-test.yml / test (nvmrc, ${{ fromJson(needs.matrix.outputs.matrix) }}, 2 shlvls) (push) Blocked by required conditions
Tests: nvm install with set -e / finisher (push) Blocked by required conditions
Tests: release process / release (push) Waiting to run
Tests: shellcheck / shellcheck_matrix (bash_completion, bash) (push) Waiting to run
Tests: shellcheck / shellcheck_matrix (install.sh, bash) (push) Waiting to run
Tests: shellcheck / shellcheck_matrix (nvm-exec, bash) (push) Waiting to run
Tests: shellcheck / shellcheck_matrix (nvm.sh, bash) (push) Waiting to run
Tests: shellcheck / shellcheck_matrix (nvm.sh, dash) (push) Waiting to run
Tests: shellcheck / shellcheck_matrix (nvm.sh, ksh) (push) Waiting to run
Tests: shellcheck / shellcheck_matrix (nvm.sh, sh) (push) Waiting to run
Tests: shellcheck / shellcheck (push) Blocked by required conditions
Tests: alpine / fast arm64 (alpine 3.19, bash) (push) Waiting to run
Tests: alpine / fast arm64 (alpine 3.19, dash) (push) Waiting to run
Tests: alpine / fast arm64 (alpine 3.19, sh) (push) Waiting to run
Tests: alpine / fast arm64 (alpine 3.19, zsh) (push) Waiting to run
Tests: alpine / fast x64 (alpine 3.19, bash) (push) Waiting to run
Tests: alpine / fast x64 (alpine 3.19, dash) (push) Waiting to run
Tests: alpine / fast x64 (alpine 3.19, sh) (push) Waiting to run
Tests: alpine / fast x64 (alpine 3.19, zsh) (push) Waiting to run
Tests: alpine / musl-binary arm64 (alpine 3, node v22.21.1) (push) Waiting to run
Tests: alpine / musl-binary arm64 (alpine 3, node v24.9.0) (push) Waiting to run
Tests: alpine / musl-binary x64 (alpine 3, node v22.12.0) (push) Waiting to run
Tests: alpine / musl-binary x64 (alpine 3.10, node v10.24.1) (push) Waiting to run
Tests: alpine / musl-binary x64 (alpine 3.10, node v8.17.0) (push) Waiting to run
Tests: alpine / musl-binary x64 (alpine 3.12, node v12.22.12) (push) Waiting to run
Tests: alpine / musl-binary x64 (alpine 3.15, node v14.21.3) (push) Waiting to run
Tests: alpine / musl-binary x64 (alpine 3.16, node v16.20.2) (push) Waiting to run
Tests: alpine / musl-binary x64 (alpine 3.18, node v18.20.4) (push) Waiting to run
Tests: alpine / musl-binary arm64 (alpine 3.20, node v20.20.1) (push) Waiting to run
Tests: alpine / musl-binary x64 (alpine 3.20, node v20.18.1) (push) Waiting to run
Tests: alpine / all alpine tests (push) Blocked by required conditions
Tests: alpine / fast arm64 (alpine 3.15, zsh) (push) Waiting to run
Tests: alpine / fast x64 (alpine 3.15, bash) (push) Waiting to run
Tests: alpine / fast x64 (alpine 3.15, dash) (push) Waiting to run
Tests: alpine / fast x64 (alpine 3.15, sh) (push) Waiting to run
Tests: alpine / fast x64 (alpine 3.15, zsh) (push) Waiting to run
Tests: alpine / fast arm64 (alpine 3, bash) (push) Waiting to run
Tests: alpine / fast arm64 (alpine 3, dash) (push) Waiting to run
Tests: alpine / fast arm64 (alpine 3, sh) (push) Waiting to run
Tests: alpine / fast arm64 (alpine 3, zsh) (push) Waiting to run
Tests: alpine / fast x64 (alpine 3, bash) (push) Waiting to run
Tests: alpine / fast x64 (alpine 3, dash) (push) Waiting to run
Tests: alpine / fast x64 (alpine 3, sh) (push) Waiting to run
Tests: alpine / fast x64 (alpine 3, zsh) (push) Waiting to run
Tests: alpine / fast arm64 (alpine 3.15, bash) (push) Waiting to run
Tests: alpine / fast arm64 (alpine 3.15, dash) (push) Waiting to run
Tests: alpine / fast arm64 (alpine 3.15, sh) (push) Waiting to run
Tests: fast / fast (bash, gawk) (push) Waiting to run
Tests: fast / fast (dash, gawk) (push) Waiting to run
Tests: fast / fast (sh, gawk) (push) Waiting to run
Tests: fast / fast (zsh, gawk) (push) Waiting to run
Tests: fast / fast (bash, mawk) (push) Waiting to run
Tests: fast / fast (dash, mawk) (push) Waiting to run
Tests: fast / fast (sh, mawk) (push) Waiting to run
Tests: fast / fast (zsh, mawk) (push) Waiting to run
Tests: fast / fast (macOS, bash) (push) Waiting to run
Tests: fast / fast (macOS, dash) (push) Waiting to run
Tests: fast / fast (macOS, sh) (push) Waiting to run
Tests: fast / fast (macOS, zsh) (push) Waiting to run
Tests: fast / fast (WSL Ubuntu-24.04, bash) (push) Waiting to run
Tests: fast / fast (WSL Ubuntu-24.04, dash) (push) Waiting to run
Tests: fast / fast (WSL Ubuntu-24.04, sh) (push) Waiting to run
Tests: fast / fast (WSL Ubuntu-24.04, zsh) (push) Waiting to run
Tests: fast / all fast tests (push) Blocked by required conditions
Tests: installation_iojs / installation_iojs without curl (bash) (push) Waiting to run
Tests: installation_iojs / installation_iojs without curl (dash) (push) Waiting to run
Tests: installation_iojs / installation_iojs without curl (sh) (push) Waiting to run
Tests: installation_iojs / installation_iojs without curl (zsh) (push) Waiting to run
Tests: installation_iojs / actually compile io.js from source (gcc 4.9 container) (push) Waiting to run
Tests: installation_iojs / all installation_iojs tests (push) Blocked by required conditions
Tests: installation_node / installation_node (bash) (push) Waiting to run
Tests: installation_node / installation_node (bash, without curl) (push) Waiting to run
Tests: installation_node / installation_node (dash) (push) Waiting to run
Tests: installation_node / installation_node (dash, without curl) (push) Waiting to run
Tests: installation_node / installation_node (sh) (push) Waiting to run
Tests: installation_node / installation_node (sh, without curl) (push) Waiting to run
Tests: installation_node / installation_node (zsh) (push) Waiting to run
Tests: installation_node / installation_node (zsh, without curl) (push) Waiting to run
Tests: installation_node / all installation_node tests (push) Blocked by required conditions
Tests: xenial / xenial (bash) (push) Waiting to run
Tests: xenial / xenial (dash) (push) Waiting to run
Tests: xenial / xenial (sh) (push) Waiting to run
Tests: xenial / xenial (zsh) (push) Waiting to run
Tests: xenial / all xenial tests (push) Blocked by required conditions
urchin tests / tests (bash, install_script) (push) Waiting to run
urchin tests / tests (bash, installation_iojs) (push) Waiting to run
urchin tests / tests (bash, slow) (push) Waiting to run
urchin tests / tests (bash, sourcing) (push) Waiting to run
urchin tests / tests (dash, installation_iojs) (push) Waiting to run
urchin tests / tests (dash, slow) (push) Waiting to run
urchin tests / tests (dash, sourcing) (push) Waiting to run
urchin tests / tests (sh, installation_iojs) (push) Waiting to run
urchin tests / tests (sh, slow) (push) Waiting to run
urchin tests / tests (sh, sourcing) (push) Waiting to run
urchin tests / tests (zsh, installation_iojs) (push) Waiting to run
urchin tests / tests (zsh, slow) (push) Waiting to run
urchin tests / tests (zsh, sourcing) (push) Waiting to run
urchin tests / all test suites, all shells (push) Blocked by required conditions
update readme TOC / update readme TOC (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 12, Debian-12) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 12, Debian-13) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 12, Ubuntu-18.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 12, Ubuntu-20.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 12, Ubuntu-24.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 14, Debian-12) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 14, Debian-13) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 14, Ubuntu-18.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 14, Ubuntu-20.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 14, Ubuntu-24.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 16, Debian-12) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 16, Debian-13) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 16, Ubuntu-18.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 16, Ubuntu-20.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 16, Ubuntu-24.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 18, Debian-12) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 18, Debian-13) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 18, Ubuntu-20.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 18, Ubuntu-24.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 21, Debian-12) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 21, Debian-13) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 21, Ubuntu-20.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 21, Ubuntu-24.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 10, Debian-12) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 10, Debian-13) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 10, Ubuntu-18.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 10, Ubuntu-20.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 10, Ubuntu-24.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 12, Debian-12) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 12, Debian-13) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 12, Ubuntu-18.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 12, Ubuntu-20.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 12, Ubuntu-24.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 14, Debian-12) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 14, Debian-13) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 14, Ubuntu-18.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 14, Ubuntu-20.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 14, Ubuntu-24.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 16, Debian-12) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 16, Debian-13) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 16, Ubuntu-18.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 16, Ubuntu-20.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 16, Ubuntu-24.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 18, Debian-12) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 18, Debian-13) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 18, Ubuntu-20.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 18, Ubuntu-24.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 21, Debian-12) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 21, Debian-13) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 21, Ubuntu-20.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 21, Ubuntu-24.04) (push) Waiting to run
Tests on Windows: `nvm install` / tests, on windows (push) Blocked by required conditions
Tests on Windows: `nvm install` / MSYS fail prefix nvm install (push) Waiting to run
Tests on Windows: `nvm install` / MSYS nvm install (--default 12) (push) Waiting to run
Tests on Windows: `nvm install` / MSYS nvm install (--lts) (push) Waiting to run
Tests on Windows: `nvm install` / MSYS nvm install (--no-progress 10) (push) Waiting to run
Tests on Windows: `nvm install` / Cygwin nvm install (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 10, Debian-12) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 10, Debian-13) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 10, Ubuntu-18.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 10, Ubuntu-20.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 10, Ubuntu-24.04) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, --lts, Alpine-3.17) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, --lts, Alpine-3.23) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 10, Alpine-3.17) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 10, Alpine-3.23) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 11, Alpine-3.17) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 11, Alpine-3.23) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 12, Alpine-3.17) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 12, Alpine-3.23) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 14, Alpine-3.17) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 14, Alpine-3.23) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 16, Alpine-3.17) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 16, Alpine-3.23) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 18, Alpine-3.17) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 18, Alpine-3.23) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 21, Alpine-3.17) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (, 21, Alpine-3.23) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, --lts, Alpine-3.17) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, --lts, Alpine-3.23) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 10, Alpine-3.17) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 10, Alpine-3.23) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 11, Alpine-3.17) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 11, Alpine-3.23) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 12, Alpine-3.17) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 12, Alpine-3.23) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 14, Alpine-3.17) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 14, Alpine-3.23) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 16, Alpine-3.17) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 16, Alpine-3.23) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 18, Alpine-3.17) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 18, Alpine-3.23) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 21, Alpine-3.17) (push) Waiting to run
Tests on Windows: `nvm install` / WSL nvm install (script, 21, Alpine-3.23) (push) Waiting to run
`\cd` only bypasses aliases, so a user-defined `cd` function (eg, cdhist's) still ran,
and broke `NVM_DIR` auto-detection when it rejected zsh's `-q`.

Use `command cd`, which skips functions in POSIX shells.
zsh's `command` runs `/usr/bin/cd` instead of the builtin,
so use `builtin cd` there, as `nvm_echo` does for `printf`.
`nvm_die_on_prefix` also now uses `nvm_cd` instead of a bare `cd`.

Fixes #3835. See #3836.

Co-authored-by: Elan Ruusamäe <glen@delfi.ee>
2026-10-09 21:16:17 -07:00
clifennelandJordan Harband 6dc3765f6b [readme] Fix a Docker warning from using Docker installation instructions
Fix a LegacyKeyValueFormat warning when building a Docker image using the 'Installing In Docker' instructions.

The warning fixed is: `LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format`

See: [LegacyKeyValueFormat](https://docs.docker.com/reference/build-checks/legacy-key-value-format/)

Co-authored-by: clifennel <li.chr+github@northeastern.edu>
Co-authored-by: Jordan Harband <ljharb@gmail.com>
2026-09-15 21:28:51 -07:00
Jordan Harband 5c05fb659c [actions] cache container images, verified against digests looked up live
Each container job now looks up the image's index, manifest,
and config digests from the registry,
keys an `actions/cache` entry on the config digest,
and only uses a cached image if its ID matches one of those digests;
otherwise it is discarded and the image is pulled (with retries) instead.
A poisoned or stale cache entry can therefore never be used,
and a cache hit needs only a couple of small manifest requests,
so this scales regardless of pull rate limits.
2026-10-09 14:14:20 -07:00
Jordan Harband 37be73f11d [actions] pull container images through Google's Docker Hub mirror
DockerHub's anonymous pull rate limit applies per IP for hours,
so retrying cannot ride it out on shared runners;
`mirror.gcr.io` serves the same official images without that limit,
as the io.js source-install job already does.
2026-10-09 14:04:54 -07:00
Saadanjum0 906fb605bc [Fix] nvm use: handle regex-special characters in $NVM_DIR
nvm_change_path interpolated `$NVM_DIR` and the new version dir unescaped into grep and sed expressions,
so a path containing `&`, `#`, `[`, `*`, etc. corrupted `$PATH` (or made sed fail) when switching versions.
Escape them before use.
2026-10-01 16:35:00 +05:00
lazerg e45c2df7c0 [Fix] install.sh: start a new line before appending to a profile that does not end with one
Fixes #3951
2026-10-08 18:50:50 +05:00
Cassidy Marble d55a55c387 [Perf] nvm_version_greater, nvm_version_greater_than_or_equal_to: avoid awk where possible 2026-10-07 15:30:01 -03:00
Cassidy Marble 9c8cac4a2e [Perf] nvm_is_valid_version, nvm_validate_implicit_alias: avoid forks 2026-10-06 18:01:38 -03:00
Cassidy Marble 66640e6000 [Perf] nvm_echo: use the printf shell builtin under zsh
In zsh, `command printf` skips the builtin and forks `/usr/bin/printf`.
2026-10-06 17:54:48 -03:00
Cassidy Marble 1dd1d8c82e [Perf] nvm_get_os: read /etc/os-release instead of forking uname -a
The file is read, not sourced. An `ID` with no OS of its own is only taken as Linux when the kernel's `ostype` says so; otherwise `uname -a` decides.
2026-10-06 14:23:02 -03:00
Cassidy Marble c28f2b70db [Perf] nvm_npmrc_bad_news_bears: scan npmrc files without forking grep 2026-10-06 14:01:11 -03:00
Cassidy Marble f296b8d210 [Perf] nvm_die_on_prefix: detect the OS only when it is needed 2026-10-06 14:00:46 -03:00
Cassidy Marble c7ba8d2106 [Perf] nvm_version: resolve an exact, installed version without nvm_ls
An alias of the same name still takes precedence, as it does in `nvm_ls`.
2026-10-06 12:43:22 -03:00
Cassidy Marble d211d5fdb8 [Perf] nvm_is_version_installed: check the modern layout's path before the full lookup 2026-10-06 12:43:22 -03:00
Hridhin Varkey Joseph 913b8cb625 [Perf] nvm_resolve_local_alias: skip nvm_version for an exact, installed version
See #3416
2026-10-04 02:22:58 +05:30
Hridhin Varkey Joseph f1f3d463ab [Perf] nvm use: skip re-resolving an exact, installed version
When `nvm use` is given an exact `vX.Y.Z` that is already installed,
and no alias with that exact name exists,
use it directly instead of re-resolving it through
`nvm_match_version` and `nvm_ensure_version_installed`,
both of which call `nvm_version`.

`nvm_auto` takes this path on every shell startup with an already-resolved version,
so this removes ~95ms from each new shell
(~330ms -> ~235ms in a clean environment on macOS/zsh).

Fixes #3930
2026-10-01 09:51:16 +05:30
Jordan Harband 077156e88f [actions] never let an apt install wait on a prompt
Moving WSL package installs out of `setup-wsl`'s `additional-packages` lost its non-interactive setup:
on Ubuntu 18.04, upgrading `libssl1.1` asks whether to restart services,
and the job waited on that dialog until it was cancelled.
Set `DEBIAN_FRONTEND=noninteractive`,
keep existing config files without asking, and give apt no stdin.
2026-10-08 11:10:27 -07:00
Jordan Harband 50e73c4cd5 [actions] retry apt package installs through Ubuntu mirror outages
Mirror outages have outlasted the existing retries,
which covered only `apt-get update` (and not the WSL `additional-packages` install).
Installs now go through `.github/scripts/apt-install.sh`,
which retries each request within apt and retries the whole update+install with backoff for about 10 minutes.
2026-10-07 15:10:36 -07:00
Hridhin Varkey Joseph ed1b96f5db [readme] explain the misleading npm install error from npm run test/fast 2026-10-04 00:49:18 +05:30
Shrikant 5326eaab9d [Tests] add unit coverage for nvm_version_greater_than_or_equal_to 2026-10-03 12:49:00 +05:30
Hridhin Varkey Joseph a27fb0bd78 [Tests] add unit tests for nvm_version_greater_than_or_equal_to 2026-10-03 11:51:31 +05:30
Mingyang Wu cb96d2a56a [Fix] Handle filesystem identity when checking path containment
Fixes #1049.

Assisted-by: OpenAI GPT-6 <noreply@openai.com>
2026-09-11 16:49:46 +08:00
Jordan Harband 46ca42f939 [Tests] nvm install --reinstall-packages-from: mock the remote version instead of fetching it 2026-10-05 10:52:19 -07:00
Jordan Harband 52a1e55574 [actions] run the fast suite on macOS and WSL too
macOS covers a case-insensitive filesystem and bash 3.2
(`/bin/sh`, `/bin/bash`);
WSL covers Windows.
Both run it as the Linux jobs do:
without a system node, with a PTY,
and on WSL as a non-root user with passwordless sudo.
2026-09-30 16:12:14 +09:00
Jordan Harband 79bc9b90dd [Tests] make the fast suite pass on macOS
- `nvm_get_arch`: the unmocked smartos cases fall through to the host, so only run them on x86_64
- `nvm_get_arch_unofficial`: the chroot checks need x86_64 glibc Linux
- `nvm_install_no_progress_bar`: derive the tarball slug and checksum tool instead of assuming linux-x64 and `sha256sum`
2026-09-30 16:09:09 +09:00
Jordan Harband e2f57b733f [Fix] nvm alias: do not drop output from background jobs on bash 3.2
bash 3.2 (macOS `/bin/bash` and `/bin/sh`) exec-optimizes a trailing `command` call inside a function run as a background job,
which silently drops the output of nested command substitutions;
in `nvm alias` that lost installed-alias colors and some default aliases,
Running each job in an explicit subshell avoids it.
2026-09-30 15:56:49 +09:00
Jordan Harband 0add6b8555 [Fix] nvm: do not change or export the caller's IFS
`IFS=... nvm "$@"` is a prefix assignment to a function call, which bash's POSIX mode (macOS `/bin/sh`) keeps after the call returns, and exports.
2026-09-30 15:50:38 +09:00
Jordan Harband 3bfc2b4a9a [actions] rename windows-npm workflow to windows-nvm, since it tests nvm install 2026-09-30 15:25:38 +09:00
Jordan Harband 04ac3f2a83 [Fix] nvm install: do not remove the caller's TMPDIR after a failed download
`nvm_install_binary` and `nvm_install_source` declared `local TMPDIR` without initializing it, and remove it with `rm -rf` on failure. dash's `local` keeps the caller's value, as does bash's when `TMPDIR` is passed as a prefix assignment (`TMPDIR=… nvm install`), so a failed download removed the caller's temp directory; on macOS, `tar` instead blocked reading the archive from the terminal.
2026-10-05 10:02:48 -07:00
Ben YounesandJordan Harband a8bb497400 [New] nvm ls-remote: annotate versions with the node alias and local aliases
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: 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, 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
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 on Windows: `nvm install` / MSYS fail prefix nvm install (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 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, 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: installation_node / installation_node (zsh) (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.19, sh) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 12, Debian-12) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 16, Debian-13) (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 (, 16, Ubuntu-24.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 10, Debian-13) (push) Canceled after 0s
Tests: alpine / fast x64 (alpine 3.19, zsh) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 12, Debian-13) (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 (, 12, Ubuntu-24.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 14, Debian-12) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 14, Debian-13) (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 (, 14, Ubuntu-24.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 16, Debian-12) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 18, Debian-12) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 18, Debian-13) (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 (, 18, Ubuntu-24.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 21, Debian-12) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 21, Debian-13) (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 (, 21, Ubuntu-24.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 10, Debian-12) (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, 10, Ubuntu-24.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 12, Debian-12) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 12, Debian-13) (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, 12, Ubuntu-24.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 14, Debian-12) (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, 14, Ubuntu-24.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 16, Debian-13) (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, 16, Ubuntu-24.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, --lts, Alpine-3.17) (push) Canceled after 0s
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: nvm install with set -e / matrix (push) Canceled after 0s
Tests: release process / release (push) Canceled after 0s
Tests: alpine / fast arm64 (alpine 3, 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: 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.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 on Windows: `nvm install` / WSL nvm install (script, 14, Debian-13) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 16, Debian-12) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 18, Debian-12) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 18, Debian-13) (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, 18, Ubuntu-24.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 21, Debian-12) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 21, Debian-13) (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 (script, 21, Ubuntu-24.04) (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: alpine / fast arm64 (alpine 3, dash) (push) Canceled after 0s
Tests: alpine / fast arm64 (alpine 3, sh) (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, sh) (push) Canceled after 0s
Tests: alpine / musl-binary x64 (alpine 3.15, node v14.21.3) (push) Canceled after 0s
Tests: fast / fast (bash, 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: 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_iojs / actually compile io.js from source (gcc 4.9 container) (push) Canceled after 0s
Tests: fast / fast (dash, gawk) (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 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-12) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 10, Debian-13) (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 (, 10, Ubuntu-24.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, --lts, Alpine-3.23) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 10, Alpine-3.17) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 10, Alpine-3.23) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 11, Alpine-3.17) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 11, Alpine-3.23) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 12, Alpine-3.17) (push) Canceled after 0s
Tests: installation_iojs / installation_iojs without curl (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
Tests on Windows: `nvm install` / WSL nvm install (, 10, Ubuntu-18.04) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, --lts, Alpine-3.23) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 10, Alpine-3.17) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 10, Alpine-3.23) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 11, Alpine-3.17) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 11, Alpine-3.23) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 12, Alpine-3.17) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 12, Alpine-3.23) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 14, Alpine-3.17) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 14, Alpine-3.23) (push) Canceled after 0s
Tests: fast / all fast tests (push) Canceled after 0s
latest-npm.yml / nvm install-latest-npm (${{ fromJson(needs.matrix.outputs.latest) }}) (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 (9.0) (push) Canceled after 0s
nvm-install-test.yml / test (nvmrc, ${{ fromJson(needs.matrix.outputs.matrix) }}, 2 shlvls) (push) Canceled after 0s
Tests: nvm install with set -e / finisher (push) Canceled after 0s
Tests: shellcheck / shellcheck (push) Canceled after 0s
Tests: alpine / all alpine 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
Tests on Windows: `nvm install` / WSL nvm install (, 12, Alpine-3.23) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 14, Alpine-3.17) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 14, Alpine-3.23) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 16, Alpine-3.17) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 16, Alpine-3.23) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 18, Alpine-3.17) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 18, Alpine-3.23) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 21, Alpine-3.17) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (, 21, Alpine-3.23) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, --lts, Alpine-3.17) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 16, Alpine-3.17) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 16, Alpine-3.23) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 18, Alpine-3.17) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 18, Alpine-3.23) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 21, Alpine-3.17) (push) Canceled after 0s
Tests on Windows: `nvm install` / WSL nvm install (script, 21, Alpine-3.23) (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
Tests: linting / all linting (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 (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.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
nvm-install-test.yml / test (no nvmrc, ${{ fromJson(needs.matrix.outputs.matrix) }}, 1 shlvl) (push) Canceled after 0s
nvm-install-test.yml / test (no nvmrc, ${{ fromJson(needs.matrix.outputs.matrix) }}, 2 shlvls) (push) Canceled after 0s
nvm-install-test.yml / test (nvmrc, ${{ fromJson(needs.matrix.outputs.matrix) }}, 1 shlvl) (push) Canceled after 0s
Fixes #2786.

Ben Younes added the alias annotations to bare `nvm ls-remote`, with their regression tests;
Jordan Harband moved `(Latest: node)` onto the newest node release, gave each kind of annotation a fixed column so the output can be sliced with `cut`, matched aliases to versions in one pass, and kept local aliases out of the mocks and tests.

Co-authored-by: Ben Younes <2910651+ousamabenyounes@users.noreply.github.com>
Co-authored-by: Jordan Harband <ljharb@gmail.com>
2026-09-23 21:35:43 +00:00
Jordan Harband 04fbd9570c [New] add nvm_has_italics, to detect terminal italics support via terminfo 2026-09-29 17:08:36 +09:00
41 changed files with 1917 additions and 182 deletions
+52
View File
@@ -0,0 +1,52 @@
#!/bin/sh
# Install apt packages, riding out Ubuntu/Debian mirror outages: apt itself
# retries each request, and the whole update+install is retried with backoff
# for about 10 minutes, since outages have lasted longer than apt's own retries.
#
# usage: sh .github/scripts/apt-install.sh <package>...
# Runs apt-get through sudo when not root.
set -u
if [ "$#" -eq 0 ]; then
echo 'usage: apt-install.sh <package>...' >&2
exit 2
fi
SUDO=''
if [ "$(id -u)" != '0' ]; then
SUDO='sudo'
fi
apt_get() {
# never prompt: an upgrade (e.g. libssl asking to restart services) would wait forever
$SUDO env DEBIAN_FRONTEND=noninteractive apt-get \
-o Acquire::Retries=5 \
-o Acquire::http::Timeout=30 \
-o Acquire::https::Timeout=30 \
-o Dpkg::Options::=--force-confdef \
-o Dpkg::Options::=--force-confold \
"$@" < /dev/null
}
ATTEMPT=1
MAX_ATTEMPTS=8
DELAY=15
while :; do
apt_get update || echo "apt-get update failed (attempt ${ATTEMPT}/${MAX_ATTEMPTS})" >&2
if apt_get install -y "$@"; then
exit 0
fi
if [ "${ATTEMPT}" -ge "${MAX_ATTEMPTS}" ]; then
echo "apt-get install failed after ${MAX_ATTEMPTS} attempts: $*" >&2
exit 1
fi
echo "apt-get install failed (attempt ${ATTEMPT}/${MAX_ATTEMPTS}); retrying in ${DELAY}s" >&2
sleep "${DELAY}"
ATTEMPT=$((ATTEMPT + 1))
DELAY=$((DELAY * 2))
if [ "${DELAY}" -gt 120 ]; then
DELAY=120
fi
done
+114
View File
@@ -0,0 +1,114 @@
#!/bin/sh
# Cache a container image as a tarball, keyed by digests looked up live from the registry: a cached
# image is only used if it is exactly what the registry serves now, so a poisoned cache entry is never used.
#
# usage:
# sh .github/scripts/docker-image.sh resolve <image>
# prints `key=<config digest>` and `digests=<index, manifest, and config digests>`, for $GITHUB_OUTPUT
# DIGESTS='<digests>' sh .github/scripts/docker-image.sh ensure <image> <tarball>
# loads <tarball> if it holds one of $DIGESTS; otherwise pulls <image>, checks it, and saves it to <tarball>
set -eu
ACCEPT='application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.oci.image.manifest.v1+json,application/vnd.docker.distribution.manifest.v2+json'
sha256_of() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$1"
else
shasum -a 256 "$1"
fi | cut -d ' ' -f 1
}
# fetches a manifest into a file; digests are computed from its exact bytes
registry_get() {
curl -fsSL --retry 5 --retry-all-errors -H "Accept: ${ACCEPT}" -o "$2" "$1"
}
resolve() {
IMAGE="$1"
HOST="${IMAGE%%/*}"
REST="${IMAGE#*/}"
REPO="${REST%:*}"
TAG="${REST##*:}"
OS="$(docker version --format '{{.Server.Os}}')"
ARCH="$(docker version --format '{{.Server.Arch}}')"
WORK="$(mktemp -d)"
trap 'rm -rf "${WORK}"' EXIT
registry_get "https://${HOST}/v2/${REPO}/manifests/${TAG}" "${WORK}/index.json"
INDEX_DIGEST="sha256:$(sha256_of "${WORK}/index.json")"
if jq -e '.manifests' "${WORK}/index.json" >/dev/null; then
MANIFEST_DIGEST="$(jq -r --arg os "${OS}" --arg arch "${ARCH}" '[.manifests[] | select(.platform.os == $os and .platform.architecture == $arch)][0].digest // empty' "${WORK}/index.json")"
if [ -z "${MANIFEST_DIGEST}" ]; then
echo "${IMAGE} has no ${OS}/${ARCH} image" >&2
exit 1
fi
registry_get "https://${HOST}/v2/${REPO}/manifests/${MANIFEST_DIGEST}" "${WORK}/manifest.json"
if [ "sha256:$(sha256_of "${WORK}/manifest.json")" != "${MANIFEST_DIGEST}" ]; then
echo "the ${OS}/${ARCH} manifest of ${IMAGE} does not match its digest" >&2
exit 1
fi
else
MANIFEST_DIGEST="${INDEX_DIGEST}"
cp "${WORK}/index.json" "${WORK}/manifest.json"
fi
CONFIG_DIGEST="$(jq -r '.config.digest' "${WORK}/manifest.json")"
echo "key=${CONFIG_DIGEST}"
echo "digests=${INDEX_DIGEST} ${MANIFEST_DIGEST} ${CONFIG_DIGEST}"
}
# an image's ID is its config digest (or, with the containerd image store, its manifest or index digest)
image_matches() {
ID="$(docker image inspect --format '{{.Id}}' "$1" 2>/dev/null)" || return 1
case " ${DIGESTS} " in
*" ${ID} "*) return 0 ;;
esac
return 1
}
ensure() {
IMAGE="$1"
TARBALL="$2"
if [ -f "${TARBALL}" ]; then
if docker load -i "${TARBALL}" && image_matches "${IMAGE}"; then
echo "loaded ${IMAGE} from the cache"
return 0
fi
echo "::warning::the cached ${IMAGE} does not match the registry's digests; pulling it instead"
docker image rm -f "${IMAGE}" >/dev/null 2>&1 || true
rm -f "${TARBALL}"
fi
ATTEMPT=1
until docker pull "${IMAGE}"; do
if [ "${ATTEMPT}" -ge 5 ]; then
echo "docker pull ${IMAGE} failed after ${ATTEMPT} attempts" >&2
return 1
fi
echo "docker pull failed, attempt ${ATTEMPT}/5"
sleep $((ATTEMPT * 5))
ATTEMPT=$((ATTEMPT + 1))
done
# the tag can move between `resolve` and the pull; then this run uses the image, but does not cache it
if image_matches "${IMAGE}"; then
docker save -o "${TARBALL}" "${IMAGE}"
else
echo "::warning::the pulled ${IMAGE} no longer matches the digests looked up earlier; not caching it"
fi
}
case "${1-}" in
resolve) resolve "${2-}" ;;
ensure) ensure "${2-}" "${3-}" ;;
*)
echo 'usage: docker-image.sh resolve <image> | ensure <image> <tarball>' >&2
exit 2
;;
esac
+32 -10
View File
@@ -46,6 +46,7 @@ jobs:
dl-cdn.alpinelinux.org:80
registry-1.docker.io:443
auth.docker.io:443
mirror.gcr.io:443
production.cloudflare.docker.com:443
production.cloudfront.docker.com:443
- uses: actions/checkout@v6
@@ -66,19 +67,29 @@ jobs:
node-version: 'lts/*'
skip-ls-check: true
- run: npm ls urchin
# Pull through Google's Docker Hub mirror, which is not subject to Docker Hub's
# anonymous pull rate limits, and cache the image; a cached image is only used
# if it matches the digests the registry serves now
- name: 'Resolve the alpine:${{ matrix.alpine }} image digests'
id: image
run: sh .github/scripts/docker-image.sh resolve mirror.gcr.io/library/alpine:${{ matrix.alpine }} >> "$GITHUB_OUTPUT"
- uses: actions/cache@v6
with:
path: ${{ runner.temp }}/docker-image.tar
key: docker-image-${{ runner.arch }}-${{ steps.image.outputs.key }}
- name: 'Load or pull the alpine:${{ matrix.alpine }} image'
env:
DIGESTS: ${{ steps.image.outputs.digests }}
run: sh .github/scripts/docker-image.sh ensure mirror.gcr.io/library/alpine:${{ matrix.alpine }} "${RUNNER_TEMP}/docker-image.tar"
- 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 }} \
mirror.gcr.io/library/alpine:${{ matrix.alpine }} \
sh -c '
set -ex
cat /etc/alpine-release; uname -m
@@ -142,23 +153,34 @@ jobs:
dl-cdn.alpinelinux.org:80
registry-1.docker.io:443
auth.docker.io:443
mirror.gcr.io:443
production.cloudflare.docker.com:443
production.cloudfront.docker.com:443
- uses: actions/checkout@v6
with:
submodules: false
# Pull through Google's Docker Hub mirror, which is not subject to Docker Hub's
# anonymous pull rate limits, and cache the image; a cached image is only used
# if it matches the digests the registry serves now
- name: 'Resolve the alpine:${{ matrix.alpine }} image digests'
id: image
run: sh .github/scripts/docker-image.sh resolve mirror.gcr.io/library/alpine:${{ matrix.alpine }} >> "$GITHUB_OUTPUT"
- uses: actions/cache@v6
with:
path: ${{ runner.temp }}/docker-image.tar
key: docker-image-${{ runner.arch }}-${{ steps.image.outputs.key }}
- name: 'Load or pull the alpine:${{ matrix.alpine }} image'
env:
DIGESTS: ${{ steps.image.outputs.digests }}
run: sh .github/scripts/docker-image.sh ensure mirror.gcr.io/library/alpine:${{ matrix.alpine }} "${RUNNER_TEMP}/docker-image.tar"
- 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 }} \
mirror.gcr.io/library/alpine:${{ matrix.alpine }} \
sh -c '
set -ex
cat /etc/alpine-release; uname -m
+134 -5
View File
@@ -6,7 +6,7 @@ permissions:
contents: read
jobs:
fast:
fast-linux:
permissions:
contents: read
@@ -60,11 +60,11 @@ jobs:
git submodule update --init --recursive
- name: Install zsh, additional shells, and awk variant
run: |
sudo apt-get update
sudo apt-get install -y zsh ${{ matrix.awk }}
PACKAGES="zsh ${{ matrix.awk }}"
if [ "${{ matrix.shell }}" != "sh" ] && [ "${{ matrix.shell }}" != "bash" ] && [ "${{ matrix.shell }}" != "zsh" ]; then
sudo apt-get install -y ${{ matrix.shell }}
PACKAGES="${PACKAGES} ${{ matrix.shell }}"
fi
sh .github/scripts/apt-install.sh ${PACKAGES}
# Set the selected awk as the default
sudo update-alternatives --set awk /usr/bin/${{ matrix.awk }}
shell: bash
@@ -101,11 +101,140 @@ jobs:
if [ -f /usr/local/bin/npx.bak ]; then sudo mv /usr/local/bin/npx.bak /usr/local/bin/npx; fi
shell: bash
fast-macos:
permissions:
contents: read
# APFS is case-insensitive, and /bin/sh and /bin/bash are bash 3.2
name: 'fast (macOS, ${{ matrix.shell }})'
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
shell:
- sh
- bash
- dash
- zsh
steps:
- 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
- run: sw_vers; uname -m; which ${{ matrix.shell }}
- name: Ensure wget is installed
run: command -v wget || brew install wget
- run: wget --version
- uses: ljharb/actions/node/install@main
name: 'npm install && version checks'
with:
node-version: 'lts/*'
skip-ls-check: true
- run: npm ls urchin
- name: Hide system node
shell: bash
run: |
export PATH="$(echo "$PATH" | tr ':' '\n' | grep -v '\.nvm' | grep -v 'toolcache' | tr '\n' ':')"
for bin in node npm npx; do
while found="$(command -v "${bin}")"; do
sudo mv "${found}" "${found}.bak" || break
echo "${found}" >> "${RUNNER_TEMP}/hidden-node"
hash -r
done
done
- name: Run fast tests
shell: bash
run: |
unset NVM_CD_FLAGS NVM_BIN NVM_INC
export NVM_DIR="${{ github.workspace }}"
export PATH="$(echo "$PATH" | tr ':' '\n' | grep -v '\.nvm' | grep -v 'toolcache' | tr '\n' ':')"
script -q /dev/null make TERM=xterm-256color TEST_SUITE="fast" SHELL="${{ matrix.shell }}" URCHIN="${{ github.workspace }}/node_modules/.bin/urchin" test-${{ matrix.shell }} < /dev/null
- name: Restore system node
if: always()
shell: bash
run: |
if [ -f "${RUNNER_TEMP}/hidden-node" ]; then
while IFS= read -r found; do sudo mv "${found}.bak" "${found}"; done < "${RUNNER_TEMP}/hidden-node"
fi
fast-windows:
permissions:
contents: read
# GitHub's Windows runners provide WSL1
name: 'fast (WSL ${{ matrix.wsl-distrib }}, ${{ matrix.shell }})'
runs-on: windows-latest
defaults:
run:
shell: wsl-bash {0}
strategy:
fail-fast: false
matrix:
wsl-distrib:
- Ubuntu-24.04
shell:
- sh
- bash
- dash
- zsh
steps:
- uses: actions/checkout@v6
- uses: Vampire/setup-wsl@v7
with:
distribution: ${{ matrix.wsl-distrib }}
- name: Install packages with retries
# the Windows checkout has CRLF line endings
run: |
tr -d '\r' < "$(wslpath "${{ github.workspace }}")/.github/scripts/apt-install.sh" > /tmp/apt-install.sh
sh /tmp/apt-install.sh git make zsh dash sudo curl wget ca-certificates nodejs npm
- name: 'Clone into the WSL filesystem as a non-root user'
# the Windows checkout shows every file as executable, which urchin would run as tests
run: |
set -ex
useradd -m tester
echo 'tester ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/tester
chmod 0440 /etc/sudoers.d/tester
su tester -c '
set -ex
git config --global --add safe.directory "*"
git clone "$(wslpath "${{ github.workspace }}")" ~/nvm
cd ~/nvm
git submodule set-url test/fixtures/nvmrc https://github.com/nvm-sh/nvmrc.git
git submodule update --init --recursive
npm install
npm ls urchin
'
- name: Hide system node
run: |
for bin in node npm npx; do
if [ -e "/usr/bin/${bin}" ]; then mv "/usr/bin/${bin}" "/usr/bin/${bin}.bak"; fi
done
- name: Run fast tests
run: |
su tester -c '
cd ~/nvm
unset NVM_CD_FLAGS NVM_BIN NVM_INC
export NVM_DIR="${HOME}/nvm" GITHUB_ACTIONS=true TERM=xterm-256color
script -q -e -c "make TEST_SUITE=fast SHELL=${{ matrix.shell }} URCHIN=${HOME}/nvm/node_modules/.bin/urchin test-${{ matrix.shell }}" /dev/null
'
all:
permissions:
contents: none
name: 'all fast tests'
needs: [fast]
needs: [fast-linux, fast-macos, fast-windows]
runs-on: ubuntu-latest
steps:
- run: true
@@ -52,11 +52,11 @@ jobs:
git submodule update --init --recursive
- name: Install zsh and additional shells
run: |
sudo apt-get update
sudo apt-get install -y zsh
PACKAGES='zsh'
if [ "${{ matrix.shell }}" != "sh" ] && [ "${{ matrix.shell }}" != "bash" ] && [ "${{ matrix.shell }}" != "zsh" ]; then
sudo apt-get install -y ${{ matrix.shell }}
PACKAGES="${PACKAGES} ${{ matrix.shell }}"
fi
sh .github/scripts/apt-install.sh ${PACKAGES}
shell: bash
- run: sudo ${{ matrix.shell }} --version 2> /dev/null || dpkg -s ${{ matrix.shell }} 2> /dev/null || which ${{ matrix.shell }}
- run: wget --version
@@ -95,7 +95,7 @@ jobs:
shell: bash
- name: Restore curl
if: always()
run: sudo apt-get install curl -y
run: sh .github/scripts/apt-install.sh curl
shell: bash
installation_iojs_source_compile:
+18 -21
View File
@@ -44,6 +44,7 @@ jobs:
production.cloudfront.docker.com:443
registry-1.docker.io:443
auth.docker.io:443
mirror.gcr.io:443
- uses: actions/checkout@v6
id: checkout
continue-on-error: true
@@ -63,14 +64,22 @@ jobs:
skip-ls-check: true
- run: npm ls urchin
- run: npx which urchin
# Pull through Google's Docker Hub mirror, which is not subject to Docker Hub's
# anonymous pull rate limits, and cache the image; a cached image is only used
# if it matches the digests the registry serves now
- name: Resolve the ubuntu:16.04 image digests
id: image
run: sh .github/scripts/docker-image.sh resolve mirror.gcr.io/library/ubuntu:16.04 >> "$GITHUB_OUTPUT"
- uses: actions/cache@v6
with:
path: ${{ runner.temp }}/docker-image.tar
key: docker-image-${{ runner.arch }}-${{ steps.image.outputs.key }}
- name: Load or pull the ubuntu:16.04 image
env:
DIGESTS: ${{ steps.image.outputs.digests }}
run: sh .github/scripts/docker-image.sh ensure mirror.gcr.io/library/ubuntu:16.04 "${RUNNER_TEMP}/docker-image.tar"
- name: Run installation_node tests in container
run: |
# Retry the image pull to tolerate transient Docker registry failures
for i in 1 2 3 4 5; do
docker pull ubuntu:16.04 && break
echo "docker pull failed, attempt $i/5"
sleep $((i * 5))
done
docker run --rm \
-v "${{ github.workspace }}:/workspace" \
-w /workspace \
@@ -79,25 +88,13 @@ jobs:
-e "DEBIAN_FRONTEND=noninteractive" \
-e "GITHUB_ACTIONS=true" \
-e "WITHOUT_CURL=${{ matrix.without_curl }}" \
ubuntu:16.04 \
mirror.gcr.io/library/ubuntu:16.04 \
bash -c '
set -ex
# Retry apt-get update up to 5 times due to flaky Ubuntu mirrors
# apt-get update can return 0 even with partial failures, so check for warnings
for i in 1 2 3 4 5; do
if apt-get update 2>&1 | tee /tmp/apt-update.log | grep -qE "^(W:|E:|Err:)"; then
echo "apt-get update had warnings/errors, attempt $i/5"
cat /tmp/apt-update.log
sleep $((i * 5))
else
break
fi
done
apt-get install -y git curl wget make build-essential python zsh libssl-dev
sh /workspace/.github/scripts/apt-install.sh git curl wget make build-essential python zsh libssl-dev
if [ "$TEST_SHELL" != "sh" ] && [ "$TEST_SHELL" != "bash" ]; then
apt-get install -y $TEST_SHELL || true
sh /workspace/.github/scripts/apt-install.sh $TEST_SHELL || true
fi
# Use nvm to install Node.js for running urchin
+18 -21
View File
@@ -41,6 +41,7 @@ jobs:
production.cloudfront.docker.com:443
registry-1.docker.io:443
auth.docker.io:443
mirror.gcr.io:443
- uses: actions/checkout@v6
id: checkout
continue-on-error: true
@@ -60,14 +61,22 @@ jobs:
skip-ls-check: true
- run: npm ls urchin
- run: npx which urchin
# Pull through Google's Docker Hub mirror, which is not subject to Docker Hub's
# anonymous pull rate limits, and cache the image; a cached image is only used
# if it matches the digests the registry serves now
- name: Resolve the ubuntu:16.04 image digests
id: image
run: sh .github/scripts/docker-image.sh resolve mirror.gcr.io/library/ubuntu:16.04 >> "$GITHUB_OUTPUT"
- uses: actions/cache@v6
with:
path: ${{ runner.temp }}/docker-image.tar
key: docker-image-${{ runner.arch }}-${{ steps.image.outputs.key }}
- name: Load or pull the ubuntu:16.04 image
env:
DIGESTS: ${{ steps.image.outputs.digests }}
run: sh .github/scripts/docker-image.sh ensure mirror.gcr.io/library/ubuntu:16.04 "${RUNNER_TEMP}/docker-image.tar"
- name: Run xenial tests in container
run: |
# Retry the image pull to tolerate transient Docker registry failures
for i in 1 2 3 4 5; do
docker pull ubuntu:16.04 && break
echo "docker pull failed, attempt $i/5"
sleep $((i * 5))
done
docker run --rm \
-v "${{ github.workspace }}:/workspace" \
-w /workspace \
@@ -75,25 +84,13 @@ jobs:
-e "TERM=xterm-256color" \
-e "DEBIAN_FRONTEND=noninteractive" \
-e "GITHUB_ACTIONS=true" \
ubuntu:16.04 \
mirror.gcr.io/library/ubuntu:16.04 \
bash -c '
set -ex
# Retry apt-get update up to 5 times due to flaky Ubuntu mirrors
# apt-get update can return 0 even with partial failures, so check for warnings
for i in 1 2 3 4 5; do
if apt-get update 2>&1 | tee /tmp/apt-update.log | grep -qE "^(W:|E:|Err:)"; then
echo "apt-get update had warnings/errors, attempt $i/5"
cat /tmp/apt-update.log
sleep $((i * 5))
else
break
fi
done
apt-get install -y git curl wget make build-essential python zsh libssl-dev
sh /workspace/.github/scripts/apt-install.sh git curl wget make build-essential python zsh libssl-dev
if [ "$TEST_SHELL" != "sh" ] && [ "$TEST_SHELL" != "bash" ]; then
apt-get install -y $TEST_SHELL || true
sh /workspace/.github/scripts/apt-install.sh $TEST_SHELL || true
fi
# Use nvm to install Node.js for running urchin
+1 -1
View File
@@ -54,7 +54,7 @@ jobs:
azure.archive.ubuntu.com:80
packages.microsoft.com:443
- uses: actions/checkout@v6
- run: sudo apt-get update; sudo apt-get install ${{ matrix.shell }}
- run: sh .github/scripts/apt-install.sh ${{ matrix.shell }}
if: matrix.shell == 'zsh' || matrix.shell == 'ksh'
# zsh (https://github.com/actions/runner-images/issues/264) and ksh are not in the ubuntu image
shell: bash
@@ -145,12 +145,37 @@ jobs:
- ''
- 'script'
steps:
# For Ubuntu: install with packages directly
# For Ubuntu: `additional-packages` can't retry, so install in a separate step
- if: ${{ !startsWith(matrix.wsl-distrib, 'Debian') }}
uses: Vampire/setup-wsl@v7
with:
distribution: ${{ matrix.wsl-distrib }}
additional-packages: bash git curl ca-certificates wget
# mirror outages can outlast apt's own retries, so retry update+install with
# backoff for about 10 minutes (as .github/scripts/apt-install.sh does; this
# job has no checkout)
- if: ${{ !startsWith(matrix.wsl-distrib, 'Debian') }}
name: 'Install packages with retries'
shell: 'wsl-bash {0}'
run: |
# never prompt: an upgrade (e.g. libssl asking to restart services) would wait forever
export DEBIAN_FRONTEND=noninteractive
attempt=1
delay=15
while true; do
apt-get -o Acquire::Retries=5 update < /dev/null || true
if apt-get -o Acquire::Retries=5 -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold install --yes bash git curl ca-certificates wget < /dev/null; then
break
fi
if [ "${attempt}" -ge 8 ]; then
echo "apt-get install failed after ${attempt} attempts" >&2
exit 1
fi
echo "apt-get install failed (attempt ${attempt}/8); retrying in ${delay}s" >&2
sleep "${delay}"
attempt=$((attempt + 1))
delay=$((delay * 2))
if [ "${delay}" -gt 120 ]; then delay=120; fi
done
# For Debian, install without packages: `additional-packages` would run
# `apt-get update` before we get to fix up sources.list
@@ -204,14 +229,17 @@ jobs:
}
done
}
# never prompt: an upgrade (e.g. libssl asking to restart services) would wait forever
export DEBIAN_FRONTEND=noninteractive
packages=(bash git curl ca-certificates wget)
retry apt-get update || true
if ! retry apt-get install --yes "${packages[@]}"; then
install_options=(-o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold install --yes)
retry apt-get update < /dev/null || true
if ! retry apt-get "${install_options[@]}" "${packages[@]}" < /dev/null; then
# a security index can outlive the pool objects it advertises, which
# no retry resolves; main is complete once a release is archived
sed -i '/debian-security/d' /etc/apt/sources.list
retry apt-get update || true
retry apt-get install --yes "${packages[@]}"
retry apt-get update < /dev/null || true
retry apt-get "${install_options[@]}" "${packages[@]}" < /dev/null
fi
- name: Retrieve nvm on WSL
+3 -3
View File
@@ -15,14 +15,14 @@ LABEL version="latest"
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Prevent dialog during apt install
ENV DEBIAN_FRONTEND noninteractive
ENV DEBIAN_FRONTEND=noninteractive
# ShellCheck version
ENV SHELLCHECK_VERSION=0.7.0
# Pick a Ubuntu apt mirror site for better speed
# ref: https://launchpad.net/ubuntu/+archivemirrors
ENV UBUNTU_APT_SITE ubuntu.cs.utah.edu
ENV UBUNTU_APT_SITE=ubuntu.cs.utah.edu
# Replace origin apt package site with the mirror site
RUN sed -E -i "s/([a-z]+.)?archive.ubuntu.com/$UBUNTU_APT_SITE/g" /etc/apt/sources.list
@@ -92,7 +92,7 @@ RUN echo 'nvm ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers
USER nvm
# Create a script file sourced by both interactive and non-interactive bash shells
ENV BASH_ENV /home/nvm/.bash_env
ENV BASH_ENV=/home/nvm/.bash_env
RUN touch "$BASH_ENV"
RUN echo '. "$BASH_ENV"' >> "$HOME/.bashrc"
+17 -2
View File
@@ -148,7 +148,7 @@ When invoking bash as a non-interactive shell, like in a [Docker](https://www.do
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Create a script file sourced by both interactive and non-interactive bash shells
ENV BASH_ENV "${HOME}/.bash_env"
ENV BASH_ENV="${HOME:-/root}/.bash_env"
RUN touch "${BASH_ENV}"
RUN echo '. "${BASH_ENV}"' >> ~/.bashrc
@@ -174,7 +174,7 @@ RUN apt update && apt install curl -y
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.8/install.sh | bash
# set env
ENV NVM_DIR=/root/.nvm
ENV NVM_DIR="${HOME:-/root}/.nvm"
# install node
RUN bash -c "source $NVM_DIR/nvm.sh && nvm install $NODE_VERSION"
@@ -401,6 +401,15 @@ You can list available versions using `ls-remote`:
nvm ls-remote
```
When no version or `--lts` filter is given, the list also marks the version the `node` alias would install, and every version that one of your own aliases would resolve to. Each kind of annotation always starts in the same column, so the output can be sliced with `cut -c` or similar tools:
```
v20.9.0 (Latest LTS: Iron) (Aliases: work)
v21.0.0
v21.1.0
v21.2.0 (Latest: node) (Aliases: default)
```
And then in any new shell just use the installed version:
```sh
@@ -886,6 +895,12 @@ Run all of the tests like this:
npm test
`npm run test/fast` runs `make` inside a clean environment (`env -i ... bash -lc`), so `node` must be on the `PATH` of a fresh login `bash` shell.
If `node` is only available through `nvm` in your current shell, it fails with "Did you forget to run `npm install` after cloning the repo?", even after a successful `npm install`.
In that case, run `make` directly from the root of the nvm git repository, choosing the test suite and shell:
make TEST_SUITE=fast test-zsh
Nota bene: Avoid running nvm while the tests are running.
## Environment variables
+3
View File
@@ -490,6 +490,9 @@ nvm_do_install() {
# shellcheck disable=SC2016
if ${BASH_OR_ZSH} && ! command grep -qc '$NVM_DIR/bash_completion' "$NVM_PROFILE"; then
nvm_echo "=> Appending bash_completion source string to $NVM_PROFILE"
if [ -n "$(command tail -c 1 "$NVM_PROFILE")" ]; then
nvm_echo >> "$NVM_PROFILE"
fi
command printf '%b' "$COMPLETION_STR" >> "$NVM_PROFILE"
else
nvm_echo "=> bash_completion source string already in ${NVM_PROFILE}"
+493 -98
View File
@@ -22,15 +22,32 @@ nvm_stdout_is_terminal() {
}
nvm_echo() {
command printf %s\\n "$*" 2>/dev/null
# in zsh, `command printf` skips the builtin and runs `/usr/bin/printf`
if [ -n "${ZSH_VERSION-}" ]; then
# shellcheck disable=SC3044
builtin printf %s\\n "$*" 2>/dev/null
else
command printf %s\\n "$*" 2>/dev/null
fi
}
nvm_echo_with_colors() {
command printf %b\\n "$*" 2>/dev/null
if [ -n "${ZSH_VERSION-}" ]; then
# shellcheck disable=SC3044
builtin printf %b\\n "$*" 2>/dev/null
else
command printf %b\\n "$*" 2>/dev/null
fi
}
nvm_cd() {
\cd "$@"
# `\cd` still runs a user-defined `cd` function; in zsh, `command cd` runs `/usr/bin/cd` instead of the builtin
if [ -n "${ZSH_VERSION-}" ]; then
# shellcheck disable=SC3044
builtin cd "$@"
else
command cd "$@"
fi
}
# a caller that closed stderr, rather than redirecting it to /dev/null, makes
@@ -101,6 +118,11 @@ nvm_has_colors() {
[ -t 1 ] && [ "${NVM_NUM_COLORS:--1}" -ge 8 ] && [ "${NVM_NO_COLORS-}" != '--no-colors' ]
}
# terminals without italics may render the italic escape as reverse video instead, so check terminfo's `sitm` capability
nvm_has_italics() {
nvm_has_colors && command tput -T "${TERM:-vt100}" sitm >/dev/null 2>&1
}
nvm_curl_libz_support() {
command curl -V 2>/dev/null | nvm_grep "^Features:" | nvm_grep -q "libz"
}
@@ -207,10 +229,43 @@ nvm_has_system_iojs() {
[ "$(nvm deactivate >/dev/null 2>&1 && command -v iojs)" != '' ]
}
nvm_is_installed_exact_version() {
case "${1-}" in
v*) ;;
*) return 1 ;;
esac
case "${1#v}" in
*[!0-9.]* | .* | *. | *..* | *.*.*.*) return 1 ;;
*.*.*) ;;
*) return 1 ;;
esac
# an alias with this exact name takes precedence, as it does in `nvm_version`
if [ -e "$(nvm_alias_path)/${1}" ]; then
return 1
fi
nvm_is_version_installed "${1}"
}
nvm_is_version_installed() {
if [ -z "${1-}" ]; then
return 1
fi
# Fast path: versions in the modern layout live at a directly-computable
# path. Checking it first avoids forking `nvm_version_path` (and the `awk`
# version comparison inside it) and `nvm_get_os` on shell startup. Versions
# below `v0.12` are skipped, since they must not count from the modern
# layout; those, io.js, and Windows binaries fall through to the full lookup
# below, which also rejects path traversal.
case "/${1}/" in
*/../* | /v0.[0-9].* | /v0.1[01].* | /0.[0-9].* | /0.1[01].*) ;;
*)
if [ -x "${NVM_DIR}/versions/node/${1}/bin/node" ]; then
return 0
fi
;;
esac
local NVM_NODE_BINARY
NVM_NODE_BINARY='node'
if [ "_$(nvm_get_os)" = '_win' ]; then
@@ -547,9 +602,8 @@ else
fi
unset NVM_SCRIPT_SOURCE 2>/dev/null
# Performs pure in-memory POSIX path containment checking without subshell process forks.
# Uses case-guarded ${pathdir%/*} for parent-walk and exact string equality (=) to ensure literal matching
# for directory names containing glob metacharacters (*, ?, []) across all shells including zsh.
# Walks parent paths without subshells, comparing names literally before checking
# filesystem identity to account for case-insensitive paths and directory symlinks.
nvm_tree_contains_path() {
local tree
tree="${1-}"
@@ -572,12 +626,11 @@ nvm_tree_contains_path() {
clean_tree='/'
fi
# Pure in-memory POSIX parent-walk using parameter expansion instead of subshell dirname forks.
# Uses literal string equality [ "${pathdir}" = "${clean_tree}" ] to prevent glob expansion bugs.
# Keep literal matching for paths that do not exist, including glob characters.
local pathdir
pathdir="${node_path}"
while [ "${pathdir}" != '' ] && [ "${pathdir}" != '.' ] && [ "${pathdir}" != '/' ] &&
[ "${pathdir}" != "${clean_tree}" ]; do
[ "${pathdir}" != "${clean_tree}" ] && ! [ "${pathdir}" -ef "${clean_tree}" ]; do
case "${pathdir}" in
*/*)
pathdir="${pathdir%/*}"
@@ -590,7 +643,7 @@ nvm_tree_contains_path() {
;;
esac
done
[ "${pathdir}" = "${clean_tree}" ]
[ "${pathdir}" = "${clean_tree}" ] || [ "${pathdir}" -ef "${clean_tree}" ]
}
nvm_find_project_dir() {
@@ -749,33 +802,81 @@ nvm_curl_version() {
command curl -V | command awk '{ if ($1 == "curl") print $2 }' | command sed 's/-.*$//g'
}
# Try to do some shell magic to do semver greater-than maths.
# Fall through to the expensive test by calling `awk` if we can't be sure.
nvm_version_greater() {
command awk 'BEGIN {
local NVM_LEFT
NVM_LEFT="${1#v}"
local NVM_RIGHT
NVM_RIGHT="${2#v}"
local NVM_PLAIN
# a third, more complex, argument of `1` (a flag) compares equal versions as true; `0` or
# no argument compares them as false.
local NVM_OR_EQUAL
NVM_OR_EQUAL="${3:-0}"
NVM_PLAIN='true'
# `x.y.z` operands are compared in shell arithmetic, which avoids a fork,
# except when a group is long enough to overflow it; anything else - partial
# versions, prereleases, and malformed operands - keeps the comparison this
# function has always made
case "${NVM_LEFT}" in
'' | *[!0-9.]* | *..* | .* | *. | *.*.*.*) NVM_PLAIN='false' ;;
*.*.*) ;;
*) NVM_PLAIN='false' ;;
esac
case "${NVM_RIGHT}" in
'' | *[!0-9.]* | *..* | .* | *. | *.*.*.*) NVM_PLAIN='false' ;;
*.*.*) ;;
*) NVM_PLAIN='false' ;;
esac
if [ "${NVM_PLAIN}" = 'true' ] && [ "${#NVM_LEFT}" -lt 19 ] && [ "${#NVM_RIGHT}" -lt 19 ]; then
local NVM_GROUP
local NVM_LEFT_GROUP
local NVM_RIGHT_GROUP
NVM_GROUP=1
while [ "${NVM_GROUP}" -le 3 ]; do
NVM_LEFT_GROUP="${NVM_LEFT%%.*}"
NVM_LEFT="${NVM_LEFT#*.}"
NVM_RIGHT_GROUP="${NVM_RIGHT%%.*}"
NVM_RIGHT="${NVM_RIGHT#*.}"
if [ "${NVM_LEFT_GROUP}" -lt "${NVM_RIGHT_GROUP}" ]; then
return 1
elif [ "${NVM_LEFT_GROUP}" -gt "${NVM_RIGHT_GROUP}" ]; then
return 0
fi
NVM_GROUP=$((NVM_GROUP + 1))
done
if [ "${NVM_OR_EQUAL}" = 1 ]; then
return 0
fi
return 1
fi
command awk -v or_equal="${NVM_OR_EQUAL}" 'BEGIN {
if (ARGV[1] == "" || ARGV[2] == "") exit(1)
split(ARGV[1], a, /\./);
split(ARGV[2], b, /\./);
for (i=1; i<=3; i++) {
if (a[i] && a[i] !~ /^[0-9]+$/) exit(2);
if (b[i] && b[i] !~ /^[0-9]+$/) { exit(0); }
if (b[i] && b[i] !~ /^[0-9]+$/) {
# a non-numeric right-hand group (a prerelease): the left is greater,
# unless equality counts, in which case the comparison below decides
if (or_equal != 1) { exit(0); }
}
if (a[i] < b[i]) exit(3);
else if (a[i] > b[i]) exit(0);
}
if (or_equal == 1) { exit(0); }
exit(4)
}' "${1#v}" "${2#v}"
}' "${NVM_LEFT}" "${NVM_RIGHT}"
}
# Try to do some shell magic to do semver greater-than or equal-to maths.
# Fall through to the expensive test by calling `awk` if we can't be sure.
# see `nvm_version_greater`
nvm_version_greater_than_or_equal_to() {
command awk 'BEGIN {
if (ARGV[1] == "" || ARGV[2] == "") exit(1)
split(ARGV[1], a, /\./);
split(ARGV[2], b, /\./);
for (i=1; i<=3; i++) {
if (a[i] && a[i] !~ /^[0-9]+$/) exit(2);
if (a[i] < b[i]) exit(3);
else if (a[i] > b[i]) exit(0);
}
exit(0)
}' "${1#v}" "${2#v}"
nvm_version_greater "${1-}" "${2-}" 1
}
nvm_version_dir() {
@@ -877,6 +978,16 @@ nvm_version() {
PATTERN="stable"
;;
esac
# Fast path: an exact, fully-qualified version that is installed resolves to
# itself, unless an alias of the same name takes precedence. Short-circuiting
# it here avoids the `nvm_ls` subprocess pipeline on shell startup; every
# other pattern falls through to `nvm_ls` unchanged.
if nvm_is_installed_exact_version "${PATTERN}"; then
nvm_echo "${PATTERN}"
return 0
fi
VERSION="$(nvm_ls "${PATTERN}" | command tail -1)"
case "${VERSION}" in
system[[:blank:]]*)
@@ -1004,17 +1115,14 @@ ${NVM_LS_REMOTE_POST_MERGED_OUTPUT}" | nvm_grep -v "N/A" | command sed '/^ *$/d'
}
nvm_is_valid_version() {
if nvm_validate_implicit_alias "${1-}" 2>/dev/null; then
return 0
fi
case "${1-}" in
"$(nvm_iojs_prefix)" | \
"$(nvm_node_prefix)")
"stable" | "unstable" | "iojs" | "node")
return 0
;;
*)
local VERSION
VERSION="$(nvm_strip_iojs_prefix "${1-}")"
VERSION="${1-}"
VERSION="${VERSION#iojs-}"
local NVM_VERSION_CORE
NVM_VERSION_CORE="${VERSION#v}"
case "${NVM_VERSION_CORE}" in
@@ -1028,6 +1136,9 @@ nvm_is_valid_version() {
*.*.*) ;;
*) return 1 ;;
esac
# a prerelease's `-` makes it unreachable for the pure-shell checks
# below, so do the full check now.
nvm_version_greater_than_or_equal_to "${VERSION}" 0 || return 1
;;
*.)
NVM_VERSION_CORE="${NVM_VERSION_CORE%.}"
@@ -1036,7 +1147,7 @@ nvm_is_valid_version() {
case "${NVM_VERSION_CORE}" in
'' | .* | *. | *..* | *.*.*.* | *[!0-9.]*) return 1 ;;
esac
nvm_version_greater_than_or_equal_to "${VERSION}" 0
# every version that reaches this point is a number, and so `>= 0`
;;
esac
}
@@ -1149,24 +1260,33 @@ nvm_change_path() {
# if there’s no initial path, just return the supplementary path
if [ -z "${1-}" ]; then
nvm_echo "${3-}${2-}"
return
fi
# `${NVM_DIR}` is matched literally, so escape characters that are special in regexes
local NVM_DIR_RE
NVM_DIR_RE="$(nvm_echo "${NVM_DIR}" | command sed 's/[][\.*^$+?(){}|]/\\&/g')"
# if the initial path doesn’t contain an nvm path, prepend the supplementary
# path
elif ! nvm_echo "${1-}" | nvm_grep -q "${NVM_DIR}/[^/]*${2-}" \
&& ! nvm_echo "${1-}" | nvm_grep -q "${NVM_DIR}/versions/[^/]*/[^/]*${2-}"; then
if ! nvm_echo "${1-}" | nvm_grep -Eq "${NVM_DIR_RE}/[^/]*${2-}" \
&& ! nvm_echo "${1-}" | nvm_grep -Eq "${NVM_DIR_RE}/versions/[^/]*/[^/]*${2-}"; then
nvm_echo "${3-}${2-}:${1-}"
# if the initial path contains BOTH an nvm path (checked for above) and
# that nvm path is preceded by a system binary path, just prepend the
# supplementary path instead of replacing it.
# https://github.com/nvm-sh/nvm/issues/1652#issuecomment-342571223
elif nvm_echo "${1-}" | nvm_grep -Eq "(^|:)(/usr(/local)?)?${2-}:.*${NVM_DIR}/[^/]*${2-}" \
|| nvm_echo "${1-}" | nvm_grep -Eq "(^|:)(/usr(/local)?)?${2-}:.*${NVM_DIR}/versions/[^/]*/[^/]*${2-}"; then
elif nvm_echo "${1-}" | nvm_grep -Eq "(^|:)(/usr(/local)?)?${2-}:.*${NVM_DIR_RE}/[^/]*${2-}" \
|| nvm_echo "${1-}" | nvm_grep -Eq "(^|:)(/usr(/local)?)?${2-}:.*${NVM_DIR_RE}/versions/[^/]*/[^/]*${2-}"; then
nvm_echo "${3-}${2-}:${1-}"
# use sed to replace the existing nvm path with the supplementary path. This
# preserves the order of the path.
else
nvm_echo "${1-}" | command sed \
-e "s#${NVM_DIR}/[^/]*${2-}[^:]*#${3-}${2-}#" \
-e "s#${NVM_DIR}/versions/[^/]*/[^/]*${2-}[^:]*#${3-}${2-}#"
local NVM_DIR_SED
NVM_DIR_SED="$(nvm_echo "${NVM_DIR_RE}" | command sed 's/#/\\#/g')"
local NEW_DIR_SED
NEW_DIR_SED="$(nvm_echo "${3-}" | command sed 's/[\&#]/\\&/g')"
nvm_echo "${1-}" | command sed -E \
-e "s#${NVM_DIR_SED}/[^/]*${2-}[^:]*#${NEW_DIR_SED}${2-}#" \
-e "s#${NVM_DIR_SED}/versions/[^/]*/[^/]*${2-}[^:]*#${NEW_DIR_SED}${2-}#"
fi
}
@@ -1458,8 +1578,9 @@ nvm_list_aliases() {
nvm_is_zsh && setopt local_options nonomatch
(
local ALIAS_PATH
# background jobs are explicit subshells: bash 3.2 otherwise drops output from nested `command` calls in them
for ALIAS_PATH in "${NVM_ALIAS_DIR}/${ALIAS}"*; do
NVM_NO_COLORS="${NVM_NO_COLORS-}" NVM_HAS_COLORS="${NVM_HAS_COLORS}" NVM_CURRENT="${NVM_CURRENT}" nvm_print_alias_path "${NVM_ALIAS_DIR}" "${ALIAS_PATH}" &
(NVM_NO_COLORS="${NVM_NO_COLORS-}" NVM_HAS_COLORS="${NVM_HAS_COLORS}" NVM_CURRENT="${NVM_CURRENT}" nvm_print_alias_path "${NVM_ALIAS_DIR}" "${ALIAS_PATH}") &
done
wait
) | command sort
@@ -1467,12 +1588,12 @@ nvm_list_aliases() {
(
local ALIAS_NAME
for ALIAS_NAME in "$(nvm_node_prefix)" "stable" "unstable" "$(nvm_iojs_prefix)"; do
{
(
# shellcheck disable=SC2030,SC2031 # (https://github.com/koalaman/shellcheck/issues/2217)
if [ ! -f "${NVM_ALIAS_DIR}/${ALIAS_NAME}" ] && { [ -z "${ALIAS}" ] || [ "${ALIAS_NAME}" = "${ALIAS}" ]; }; then
NVM_NO_COLORS="${NVM_NO_COLORS-}" NVM_HAS_COLORS="${NVM_HAS_COLORS}" NVM_CURRENT="${NVM_CURRENT}" nvm_print_default_alias "${ALIAS_NAME}"
fi
} &
) &
done
wait
) | command sort
@@ -1481,12 +1602,12 @@ nvm_list_aliases() {
local LTS_ALIAS
# shellcheck disable=SC2030,SC2031 # (https://github.com/koalaman/shellcheck/issues/2217)
for ALIAS_PATH in "${NVM_ALIAS_DIR}/lts/${ALIAS}"*; do
{
(
LTS_ALIAS="$(NVM_NO_COLORS="${NVM_NO_COLORS-}" NVM_HAS_COLORS="${NVM_HAS_COLORS}" NVM_LTS=true nvm_print_alias_path "${NVM_ALIAS_DIR}" "${ALIAS_PATH}")"
if [ -n "${LTS_ALIAS}" ]; then
nvm_echo "${LTS_ALIAS}"
fi
} &
) &
done
wait
) | command sort
@@ -1652,11 +1773,18 @@ nvm_resolve_local_alias() {
if [ -z "${VERSION}" ]; then
return $EXIT_CODE
fi
if [ "_${VERSION}" != '_∞' ]; then
nvm_version "${VERSION}"
else
if [ "_${VERSION}" = '_∞' ]; then
nvm_echo "${VERSION}"
return
fi
# Fast path: an exact, installed vX.Y.Z needs no further resolution.
if nvm_is_installed_exact_version "${VERSION}"; then
nvm_echo "${VERSION}"
return
fi
nvm_version "${VERSION}"
}
nvm_iojs_prefix() {
@@ -1676,12 +1804,9 @@ nvm_add_iojs_prefix() {
}
nvm_strip_iojs_prefix() {
local NVM_IOJS_PREFIX
NVM_IOJS_PREFIX="$(nvm_iojs_prefix)"
case "${1-}" in
"${NVM_IOJS_PREFIX}") nvm_echo ;;
*) nvm_echo "${1#"${NVM_IOJS_PREFIX}"-}" ;;
"iojs") nvm_echo ;;
*) nvm_echo "${1#iojs-}" ;;
esac
}
@@ -2156,10 +2281,53 @@ nvm_get_checksum() {
nvm_download -L -s "${SHASUMS_URL}" -o - | command awk -v tarball="${4}.${5}" '{ if (tarball == $2) print $1 }'
}
nvm_get_remote_aliases() {
local NVM_ALIAS_DIR
NVM_ALIAS_DIR="$(nvm_alias_path)"
if [ ! -d "${NVM_ALIAS_DIR}" ]; then
return 0
fi
local NVM_NODE_PREFIX
NVM_NODE_PREFIX="$(nvm_node_prefix)"
local NVM_IOJS_PREFIX
NVM_IOJS_PREFIX="$(nvm_iojs_prefix)"
local NVM_ALIAS_PATH
local NVM_ALIAS_NAME
local NVM_ALIAS_TARGET
nvm_is_zsh && setopt local_options nonomatch
# the same glob as `nvm_list_aliases`, so this never shows an alias that `nvm alias` omits
for NVM_ALIAS_PATH in "${NVM_ALIAS_DIR}/"*; do
if [ ! -f "${NVM_ALIAS_PATH}" ]; then
continue
fi
NVM_ALIAS_NAME="${NVM_ALIAS_PATH##*/}"
case "${NVM_ALIAS_NAME}" in
"${NVM_NODE_PREFIX}" | "${NVM_IOJS_PREFIX}" | stable | unstable) continue ;;
# control characters would corrupt the terminal, or the tab-separated output
*[[:cntrl:]]*) continue ;;
esac
NVM_ALIAS_TARGET="$(nvm_resolve_alias "${NVM_ALIAS_NAME}" 2>/dev/null)" || continue
case "${NVM_ALIAS_TARGET}" in
'' | *[[:cntrl:]]*) continue ;;
esac
command printf '%s\t%s\n' "${NVM_ALIAS_TARGET}" "${NVM_ALIAS_NAME}"
done
}
nvm_print_versions() {
local NVM_CURRENT
NVM_CURRENT=$(nvm_ls_current)
local NVM_LATEST_ALIAS
NVM_LATEST_ALIAS="${2-}"
local NVM_NAMED_ALIASES
NVM_NAMED_ALIASES="${3-}"
local INSTALLED_COLOR
local SYSTEM_COLOR
local CURRENT_COLOR
@@ -2180,20 +2348,36 @@ nvm_print_versions() {
NVM_HAS_COLORS=1
fi
local NVM_HAS_ITALICS
NVM_HAS_ITALICS=0
if [ "${NVM_HAS_COLORS}" = 1 ] && [ -n "${NVM_LATEST_ALIAS}${NVM_NAMED_ALIASES}" ] && nvm_has_italics; then
NVM_HAS_ITALICS=1
fi
NVM_NAMED_ALIASES="${NVM_NAMED_ALIASES}" \
command awk \
-v remote_versions="$(printf '%s' "${1-}" | tr '\n' '|')" \
-v latest_alias="${NVM_LATEST_ALIAS}" \
-v node_prefix="$(nvm_node_prefix)" -v iojs_prefix="$(nvm_iojs_prefix)" \
-v installed_versions="$(nvm_ls | tr '\n' '|')" -v current="$NVM_CURRENT" \
-v installed_color="$INSTALLED_COLOR" -v system_color="$SYSTEM_COLOR" \
-v current_color="$CURRENT_COLOR" -v default_color="$DEFAULT_COLOR" \
-v old_lts_color="$DEFAULT_COLOR" -v has_colors="$NVM_HAS_COLORS" '
-v old_lts_color="$DEFAULT_COLOR" -v has_colors="$NVM_HAS_COLORS" \
-v has_italics="$NVM_HAS_ITALICS" '
function alen(arr, i, len) { len=0; for(i in arr) len++; return len; }
function paint(color, text) { return (has_colors && color) ? ("\033[" color text "\033[0m") : text; }
function spaces(count, result) { result = ""; while (count-- > 0) result = result " "; return result; }
function italicize(text) { return has_italics ? ("\033[3m" text "\033[23m") : text; }
BEGIN {
named_alias_list = ENVIRON["NVM_NAMED_ALIASES"];
fmt_installed = has_colors ? (installed_color ? "\033[" installed_color "%15s\033[0m" : "%15s") : "%15s *";
fmt_system = has_colors ? (system_color ? "\033[" system_color "%15s\033[0m" : "%15s") : "%15s *";
fmt_current = has_colors ? (current_color ? "\033[" current_color "->%13s\033[0m" : "%15s") : "->%13s *";
latest_lts_color = current_color;
sub(/0;/, "1;", latest_lts_color);
alias_color = current_color;
sub(/^1;/, "0;", alias_color);
fmt_latest_lts = has_colors && latest_lts_color ? ("\033[" latest_lts_color " (Latest LTS: %s)\033[0m") : " (Latest LTS: %s)";
fmt_old_lts = has_colors && old_lts_color ? ("\033[" old_lts_color " (LTS: %s)\033[0m") : " (LTS: %s)";
@@ -2203,6 +2387,57 @@ BEGIN {
split(installed_versions, installed, "|");
rows = alen(lines);
for (n = 1; n <= rows; n++) {
split(lines[n], fields, "[[:blank:]]+");
if (index(fields[1], iojs_prefix "-") == 1) {
latest_iojs = fields[1];
} else if (fields[1] ~ /^v0\.[0-9]*[13579]\./) {
latest_unstable = fields[1];
} else if (fields[1] ~ /^v[0-9]/) {
latest_stable = fields[1];
}
}
named_alias_rows = split(named_alias_list, named_alias_lines, "\n");
for (a = 1; a <= named_alias_rows; a++) {
tab_position = index(named_alias_lines[a], "\t");
if (tab_position < 2) {
continue;
}
alias_target = substr(named_alias_lines[a], 1, tab_position - 1);
alias_name = substr(named_alias_lines[a], tab_position + 1);
if (alias_name == "") {
continue;
}
alias_version = "";
if (alias_target == node_prefix || alias_target == "stable") {
alias_version = latest_stable;
} else if (alias_target == "unstable") {
alias_version = latest_unstable;
} else if (alias_target == iojs_prefix || alias_target == (iojs_prefix "-")) {
alias_version = latest_iojs;
} else {
for (n = 1; n <= rows; n++) {
split(lines[n], fields, "[[:blank:]]+");
if (fields[1] == alias_target || index(fields[1], alias_target ".") == 1) {
alias_version = fields[1];
}
}
}
if (alias_version != "") {
# mawk creates the assigned element before evaluating the right side, so `in` must be checked first
if (alias_version in named_aliases) {
named_aliases[alias_version] = named_aliases[alias_version] ", " alias_name;
italic_aliases[alias_version] = italic_aliases[alias_version] ", " italicize(alias_name);
} else {
named_aliases[alias_version] = alias_name;
italic_aliases[alias_version] = italicize(alias_name);
}
}
}
for (n = 1; n <= rows; n++) {
split(lines[n], fields, "[[:blank:]]+");
cols = alen(fields);
@@ -2225,23 +2460,69 @@ BEGIN {
fmt_version = fmt_installed;
}
padding = (!has_colors && is_installed) ? "" : " ";
padding[n] = (!has_colors && is_installed) ? "" : " ";
if (cols == 1) {
formatted = sprintf(fmt_version, version);
} else if (version == "system" && cols >= 2) {
formatted = sprintf((fmt_version fmt_system_target), version, fields[2]);
output[n] = sprintf(fmt_version, version);
version_width[n] = output[n] padding[n];
gsub(/\033\[[0-9;]*m/, "", version_width[n]);
version_width[n] = length(version_width[n]);
if (version_width[n] > max_version_width) {
max_version_width = version_width[n];
}
for (c = 1; c <= 3; c++) {
text[n, c] = "";
cell[n, c] = "";
}
if (version == "system" && cols >= 2) {
output[n] = output[n] sprintf(fmt_system_target, fields[2]);
} else if (cols == 2) {
formatted = sprintf((fmt_version padding fmt_old_lts), version, fields[2]);
text[n, 1] = sprintf(" (LTS: %s)", fields[2]);
cell[n, 1] = sprintf(fmt_old_lts, fields[2]);
} else if (cols == 3 && fields[3] == "*") {
formatted = sprintf((fmt_version padding fmt_latest_lts), version, fields[2]);
text[n, 1] = sprintf(" (Latest LTS: %s)", fields[2]);
cell[n, 1] = sprintf(fmt_latest_lts, fields[2]);
}
output[n] = formatted;
if (latest_alias != "" && version == latest_stable) {
text[n, 2] = " (Latest: " latest_alias ")";
cell[n, 2] = paint(alias_color, " (Latest: " italicize(latest_alias) ")");
}
if (version in named_aliases) {
text[n, 3] = " (Aliases: " named_aliases[version] ")";
cell[n, 3] = paint(alias_color, " (Aliases: " italic_aliases[version] ")");
}
for (c = 1; c <= 3; c++) {
if (length(text[n, c]) > width[c]) {
width[c] = length(text[n, c]);
}
}
}
# each kind of annotation starts in the same column on every row, so the output stays `cut`-able
for (n = 1; n <= rows; n++) {
print output[n]
last = 0;
for (c = 1; c <= 3; c++) {
if (text[n, c] != "") {
last = c;
}
}
formatted = output[n];
if (last) {
formatted = formatted padding[n];
if (width[2] || width[3]) {
formatted = formatted spaces(max_version_width - version_width[n]);
}
gap = "";
for (c = 1; c <= last; c++) {
if (width[c]) {
formatted = formatted gap cell[n, c];
gap = spaces(width[c] - length(text[n, c])) " ";
}
}
}
print formatted
}
exit
@@ -2249,17 +2530,12 @@ BEGIN {
}
nvm_validate_implicit_alias() {
local NVM_IOJS_PREFIX
NVM_IOJS_PREFIX="$(nvm_iojs_prefix)"
local NVM_NODE_PREFIX
NVM_NODE_PREFIX="$(nvm_node_prefix)"
case "$1" in
"stable" | "unstable" | "${NVM_IOJS_PREFIX}" | "${NVM_NODE_PREFIX}")
"stable" | "unstable" | "iojs" | "node")
return
;;
*)
nvm_err "Only implicit aliases 'stable', 'unstable', '${NVM_IOJS_PREFIX}', and '${NVM_NODE_PREFIX}' are supported."
nvm_err "Only implicit aliases 'stable', 'unstable', 'iojs', and 'node' are supported."
return 1
;;
esac
@@ -2353,18 +2629,74 @@ nvm_print_implicit_alias() {
}
nvm_get_os() {
local NVM_UNAME
NVM_UNAME="$(command uname -a)"
# dash's `local` keeps the caller's value, so these must start empty
local NVM_OS
case "${NVM_UNAME}" in
Linux\ *) NVM_OS=linux ;;
Darwin\ *) NVM_OS=darwin ;;
SunOS\ *) NVM_OS=sunos ;;
FreeBSD\ *) NVM_OS=freebsd ;;
OpenBSD\ *) NVM_OS=openbsd ;;
AIX\ *) NVM_OS=aix ;;
CYGWIN* | MSYS* | MINGW*) NVM_OS=win ;;
esac
NVM_OS=''
local NVM_UNAME
local NVM_OS_RELEASE_PATH
local NVM_OS_RELEASE_LINE
local NVM_OS_RELEASE_ID
NVM_OS_RELEASE_ID=''
local NVM_OS_KERNEL_PATH
local NVM_OS_KERNEL_TYPE
NVM_OS_KERNEL_TYPE=''
# `/etc/os-release` is available on Linux, FreeBSD 13.2+, and the MSYS2 and
# Cygwin runtimes; where it is readable, its `ID` names the OS without
# spawning `uname`. The file is read, not sourced, so nothing in it runs.
# systems without the file (macOS, OpenBSD, ...) and files without an `ID`
# fall through to `uname -a` below.
NVM_OS_RELEASE_PATH="${NVM_OS_RELEASE:-/etc/os-release}"
if [ -r "${NVM_OS_RELEASE_PATH}" ]; then
while IFS= read -r NVM_OS_RELEASE_LINE || [ -n "${NVM_OS_RELEASE_LINE}" ]; do
case "${NVM_OS_RELEASE_LINE}" in
ID=*)
NVM_OS_RELEASE_ID="${NVM_OS_RELEASE_LINE#ID=}"
break
;;
esac
done < "${NVM_OS_RELEASE_PATH}"
# an `ID` is a single word, which may be quoted and followed by
# whitespace (including a CR) or a comment
NVM_OS_RELEASE_ID="${NVM_OS_RELEASE_ID%%[[:space:]#]*}"
NVM_OS_RELEASE_ID="${NVM_OS_RELEASE_ID#[\"\']}"
NVM_OS_RELEASE_ID="${NVM_OS_RELEASE_ID%[\"\']}"
case "${NVM_OS_RELEASE_ID}" in
freebsd) NVM_OS=freebsd ;;
cygwin* | mingw* | msys*) NVM_OS=win ;;
# illumos distributions and Oracle Solaris report `SunOS` from
# `uname -a`, and GhostBSD reports `FreeBSD`; map these directly instead
# of forking `uname`
solaris | omnios | openindiana | smartos | illumos) NVM_OS=sunos ;;
ghostbsd) NVM_OS=freebsd ;;
# no `ID`, or one with no `nvm` OS of its own (DragonFly BSD): let
# `uname -a` decide
'' | dragonfly) NVM_OS='' ;;
# any other `ID` is a Linux distribution's only if the kernel says so;
# unlisted BSD and illumos derivatives (e.g. HardenedBSD, Helios) let
# `uname -a` decide
*)
NVM_OS_KERNEL_PATH="${NVM_OS_KERNEL_OSTYPE:-/proc/sys/kernel/ostype}"
if [ -r "${NVM_OS_KERNEL_PATH}" ] && IFS= read -r NVM_OS_KERNEL_TYPE < "${NVM_OS_KERNEL_PATH}" && [ "${NVM_OS_KERNEL_TYPE}" = 'Linux' ]; then
NVM_OS=linux
fi
;;
esac
fi
if [ -z "${NVM_OS-}" ]; then
NVM_UNAME="$(command uname -a)"
case "${NVM_UNAME}" in
Linux\ *) NVM_OS=linux ;;
Darwin\ *) NVM_OS=darwin ;;
SunOS\ *) NVM_OS=sunos ;;
FreeBSD\ *) NVM_OS=freebsd ;;
OpenBSD\ *) NVM_OS=openbsd ;;
AIX\ *) NVM_OS=aix ;;
CYGWIN* | MSYS* | MINGW*) NVM_OS=win ;;
esac
fi
nvm_echo "${NVM_OS-}"
}
@@ -2614,8 +2946,12 @@ nvm_install_binary() {
return 2
fi
# dash's `local` keeps the caller's value, and TMPDIR is usually set: it is
# later removed with `rm -rf`, so it must not start as the system temp dir
local TARBALL
TARBALL=''
local TMPDIR
TMPDIR=''
local PROGRESS_BAR
local NODE_OR_IOJS
@@ -3015,9 +3351,13 @@ nvm_install_source() {
fi
fi
# see nvm_install_binary: these must not inherit TMPDIR, which is removed on failure
local TARBALL
TARBALL=''
local TMPDIR
TMPDIR=''
local VERSION_PATH
VERSION_PATH=''
if [ "${NVM_NO_PROGRESS-}" = "1" ]; then
# --silent, --show-error, use short option as @samrocketman mentions the compatibility issue.
@@ -3111,9 +3451,28 @@ nvm_npm_global_modules() {
nvm_npmrc_bad_news_bears() {
local NVM_NPMRC
local NVM_NPMRC_LINE
local NVM_NPMRC_KEY
NVM_NPMRC="${1-}"
if [ -n "${NVM_NPMRC}" ] && [ -f "${NVM_NPMRC}" ] && nvm_grep -Ee '^(prefix|globalconfig) *=' <"${NVM_NPMRC}" >/dev/null; then
return 0
if [ -n "${NVM_NPMRC}" ] && [ -f "${NVM_NPMRC}" ]; then
# this is fork-free on purpose: it runs up to four times on every `nvm use`/`nvm install`
# avoid grepping or sub-shells as much as possible here.
while IFS= read -r NVM_NPMRC_LINE || [ -n "${NVM_NPMRC_LINE}" ]; do
case "${NVM_NPMRC_LINE}" in
*=*)
NVM_NPMRC_KEY="${NVM_NPMRC_LINE%%=*}"
;;
*)
continue
;;
esac
NVM_NPMRC_KEY="${NVM_NPMRC_KEY%"${NVM_NPMRC_KEY##*[! ]}"}"
case "${NVM_NPMRC_KEY}" in
prefix | globalconfig)
return 0
;;
esac
done < "${NVM_NPMRC}"
fi
return 1
}
@@ -3147,9 +3506,6 @@ nvm_die_on_prefix() {
return 3
fi
local NVM_OS
NVM_OS="$(nvm_get_os)"
# npm normalizes NPM_CONFIG_-prefixed env vars
# https://github.com/npm/npmconf/blob/22827e4038d6eebaafeb5c13ed2b92cf97b8fb82/npmconf.js#L331-L348
# https://github.com/npm/npm/blob/5e426a78ca02d0044f8dd26e0c5f881217081cbd/lib/config/core.js#L343-L359
@@ -3159,10 +3515,14 @@ nvm_die_on_prefix() {
local NVM_NPM_CONFIG_x_PREFIX_ENV
NVM_NPM_CONFIG_x_PREFIX_ENV="$(command awk 'BEGIN { for (name in ENVIRON) if (toupper(name) == "NPM_CONFIG_PREFIX") { print name; break } }')"
if [ -n "${NVM_NPM_CONFIG_x_PREFIX_ENV-}" ]; then
# `$NVM_OS` is only used to translate Windows paths, but detecting the OS
# shells out to `uname`, so it is deferred until it is actually needed
local NVM_OS
NVM_OS="$(nvm_get_os)"
local NVM_CONFIG_VALUE
eval "NVM_CONFIG_VALUE=\"\$${NVM_NPM_CONFIG_x_PREFIX_ENV}\""
if [ -n "${NVM_CONFIG_VALUE-}" ] && [ "_${NVM_OS}" = "_win" ]; then
NVM_CONFIG_VALUE="$(cd "$NVM_CONFIG_VALUE" 2>/dev/null && pwd)"
NVM_CONFIG_VALUE="$(nvm_cd "$NVM_CONFIG_VALUE" 2>/dev/null && pwd)"
fi
if [ -n "${NVM_CONFIG_VALUE-}" ] && ! nvm_tree_contains_path "${NVM_DIR}" "${NVM_CONFIG_VALUE}"; then
nvm deactivate >/dev/null 2>&1
@@ -3293,14 +3653,29 @@ nvm_has_solaris_binary() {
fi
}
# replaces every literal occurrence of $2 in $1 with $3; values go through the
# environment so awk does not interpret backslash escapes or regex characters
nvm_replace_literal() {
nvm_echo "${1-}" | NVM_FROM="${2-}" NVM_TO="${3-}" command awk '
BEGIN { from = ENVIRON["NVM_FROM"]; to = ENVIRON["NVM_TO"]; n = length(from) }
{
out = ""; s = $0
while (n > 0 && (i = index(s, from)) > 0) {
out = out substr(s, 1, i - 1) to
s = substr(s, i + n)
}
print out s
}'
}
nvm_sanitize_path() {
local SANITIZED_PATH
SANITIZED_PATH="${1-}"
if [ "_${SANITIZED_PATH}" != "_${NVM_DIR}" ]; then
SANITIZED_PATH="$(nvm_echo "${SANITIZED_PATH}" | command sed -e "s#${NVM_DIR}#\${NVM_DIR}#g")"
SANITIZED_PATH="$(nvm_replace_literal "${SANITIZED_PATH}" "${NVM_DIR}" '${NVM_DIR}')"
fi
if [ "_${SANITIZED_PATH}" != "_${HOME}" ]; then
SANITIZED_PATH="$(nvm_echo "${SANITIZED_PATH}" | command sed -e "s#${HOME}#\${HOME}#g")"
SANITIZED_PATH="$(nvm_replace_literal "${SANITIZED_PATH}" "${HOME}" '${HOME}')"
fi
nvm_echo "${SANITIZED_PATH}"
}
@@ -3503,14 +3878,18 @@ nvm() {
if [ "${-#*e}" != "$-" ]; then
set +e
local EXIT_CODE
IFS="${DEFAULT_IFS}" nvm "$@"
local IFS
IFS="${DEFAULT_IFS}"
nvm "$@"
EXIT_CODE="$?"
set -e
return "$EXIT_CODE"
elif [ "${-#*a}" != "$-" ]; then
set +a
local EXIT_CODE
IFS="${DEFAULT_IFS}" nvm "$@"
local IFS
IFS="${DEFAULT_IFS}"
nvm "$@"
EXIT_CODE="$?"
set -a
return "$EXIT_CODE"
@@ -3518,13 +3897,17 @@ nvm() {
# shellcheck disable=SC3041
set +E
local EXIT_CODE
IFS="${DEFAULT_IFS}" nvm "$@"
local IFS
IFS="${DEFAULT_IFS}"
nvm "$@"
EXIT_CODE="$?"
# shellcheck disable=SC3041
set -E
return "$EXIT_CODE"
elif [ "${IFS}" != "${DEFAULT_IFS}" ]; then
IFS="${DEFAULT_IFS}" nvm "$@"
local IFS
IFS="${DEFAULT_IFS}"
nvm "$@"
return "$?"
fi
@@ -4360,6 +4743,8 @@ nvm() {
IS_VERSION_FROM_NVMRC=0
local NVM_WRITE_TO_NVMRC
NVM_WRITE_TO_NVMRC=0
local NVM_USE_EXACT_INSTALLED
NVM_USE_EXACT_INSTALLED=0
while [ $# -ne 0 ]; do
case "$1" in
@@ -4400,6 +4785,9 @@ nvm() {
nvm_err 'Please see `nvm --help` or https://github.com/nvm-sh/nvm#nvmrc for more information.'
return 127
fi
elif nvm_is_installed_exact_version "${PROVIDED_VERSION}"; then
VERSION="${PROVIDED_VERSION}"
NVM_USE_EXACT_INSTALLED=1
else
VERSION="$(nvm_match_version "${PROVIDED_VERSION}")"
fi
@@ -4441,9 +4829,8 @@ nvm() {
nvm_ensure_version_installed "${PROVIDED_VERSION}" "${IS_VERSION_FROM_NVMRC}"
fi
return 3
# This nvm_ensure_version_installed call can be a performance bottleneck
# on shell startup. Perhaps we can optimize it away or make it faster.
elif ! nvm_ensure_version_installed "${VERSION}" "${IS_VERSION_FROM_NVMRC}"; then
# an exact, already-installed version needs no further resolution or checks
elif [ "${NVM_USE_EXACT_INSTALLED}" -ne 1 ] && ! nvm_ensure_version_installed "${VERSION}" "${IS_VERSION_FROM_NVMRC}"; then
return $?
fi
@@ -4746,7 +5133,15 @@ nvm() {
NVM_OUTPUT="$(NVM_LTS="${NVM_LTS-}" nvm_remote_versions "${PATTERN-}" &&:)"
EXIT_CODE=$?
if [ -n "${NVM_OUTPUT}" ]; then
NVM_NO_COLORS="${NVM_NO_COLORS-}" nvm_print_versions "${NVM_OUTPUT}"
local NVM_REMOTE_LATEST_ALIAS
NVM_REMOTE_LATEST_ALIAS=''
local NVM_REMOTE_NAMED_ALIASES
NVM_REMOTE_NAMED_ALIASES=''
if [ "${EXIT_CODE}" -eq 0 ] && [ -z "${NVM_LTS-}" ] && [ -z "${PATTERN-}" ]; then
NVM_REMOTE_LATEST_ALIAS="$(nvm_node_prefix)"
NVM_REMOTE_NAMED_ALIASES="$(nvm_get_remote_aliases)"
fi
NVM_NO_COLORS="${NVM_NO_COLORS-}" nvm_print_versions "${NVM_OUTPUT}" "${NVM_REMOTE_LATEST_ALIAS}" "${NVM_REMOTE_NAMED_ALIASES}"
return $EXIT_CODE
fi
NVM_NO_COLORS="${NVM_NO_COLORS-}" nvm_print_versions "N/A"
@@ -5030,7 +5425,7 @@ nvm() {
nvm_iojs_prefix nvm_node_prefix \
nvm_add_iojs_prefix nvm_strip_iojs_prefix \
nvm_is_iojs_version nvm_is_alias nvm_has_non_aliased \
nvm_ls_remote nvm_ls_remote_iojs nvm_ls_remote_index_tab \
nvm_ls_remote nvm_ls_remote_iojs nvm_ls_remote_index_tab nvm_get_remote_aliases \
nvm_ls nvm_remote_version nvm_remote_versions \
nvm_install_binary nvm_install_source nvm_clang_version \
nvm_get_mirror nvm_get_download_slug nvm_download_artifact \
@@ -5057,11 +5452,11 @@ 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_validate_install \
nvm_is_natural_num nvm_is_installed_exact_version 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_file 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 \
nvm_replace_literal nvm_sanitize_path nvm_has_colors nvm_has_italics nvm_process_parameters \
nvm_node_version_has_solaris_binary nvm_iojs_version_has_solaris_binary \
nvm_curl_libz_support nvm_command_info nvm_is_zsh nvm_stdout_is_terminal \
nvm_npmrc_bad_news_bears nvm_sanitize_auth_header \
@@ -0,0 +1,20 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
: nvm.sh
\. ../../../nvm.sh
nvm_version() {
echo 'nvm_version was called' >&2
return 42
}
EXACT="$(nvm_resolve_local_alias test-stable-1)"
[ "_$EXACT" = "_v0.0.1" ] || die "'nvm_resolve_local_alias test-stable-1' was not v0.0.1; got $EXACT"
echo v0.0.99 > ../../../alias/test-not-installed
NOT_INSTALLED="$(nvm_resolve_local_alias test-not-installed 2>/dev/null)"
EXIT_CODE=$?
rm -f ../../../alias/test-not-installed
[ "_$EXIT_CODE" = "_42" ] || die "an exact version that is not installed should still go through nvm_version; got exit code $EXIT_CODE"
@@ -5,6 +5,7 @@ set -ex
die () { echo "$@" ; cleanup ; exit 1; }
cleanup () {
rm -rf "${NVM_DIR}/v0.10.4"
unset -f nvm_remote_version
}
export NVM_DIR="$(cd ../.. && pwd)"
@@ -13,6 +14,8 @@ export NVM_DIR="$(cd ../.. && pwd)"
\. ../../nvm.sh
\. ../common.sh
nvm_remote_version() { nvm_echo "${1-}"; }
make_fake_node v0.10.4
nvm deactivate >/dev/null 2>&1
@@ -0,0 +1,19 @@
#!/bin/sh
die () { IFS=' '; echo "$@" ; exit 1; }
: nvm.sh
\. ../../nvm.sh
IFS=':'
nvm --version >/dev/null
[ "${IFS}" = ':' ] || die "nvm changed the caller's IFS; got >${IFS}<"
set -e
nvm --version >/dev/null
set +e
[ "${IFS}" = ':' ] || die "nvm changed the caller's IFS under set -e; got >${IFS}<"
! env | grep -q '^IFS=' || die 'nvm exported IFS'
IFS=' '
+1 -1
View File
@@ -797,4 +797,4 @@
v22.10.0
v22.11.0  (Latest LTS: Jod)
v23.0.0
v23.1.0
v23.1.0  (Latest: node)
+4 -1
View File
@@ -3,7 +3,7 @@
die () { echo "$@" ; cleanup ; exit 1; }
cleanup() {
unset -f nvm_download nvm_ls_remote nvm_ls_remote_iojs
unset -f nvm_download nvm_ls_remote nvm_ls_remote_iojs nvm_get_remote_aliases nvm_has_italics
if [ -n "${TEMP_NVM_COLORS-}" ]; then
export NVM_COLORS=TEMP_NVM_COLORS
fi
@@ -87,6 +87,9 @@ REMOTE_IOJS="${PWD}/mocks/nvm_ls_remote_iojs.txt"
nvm_ls_remote_iojs() {
cat "${REMOTE_IOJS}"
}
# the mock must not depend on whatever local aliases exist in $NVM_DIR
nvm_get_remote_aliases() { :; }
nvm_has_italics() { return 0; }
EXPECTED_OUTPUT_PATH="${MOCKS_DIR}/nvm ls-remote.txt"
OUTPUT="$(nvm ls-remote | sed 's/[ \t]*$//')"
+126
View File
@@ -0,0 +1,126 @@
#!/bin/sh
WORK="${PWD}/nvm-ls-remote-aliases-work.$$"
export HOME="${WORK}/home"
export NVM_DIR="${HOME}/.nvm"
cleanup() {
command rm -rf "${WORK}"
unset -f cleanup die nvm_has_colors nvm_has_italics nvm_remote_versions expected_row
}
die () { echo "$@" ; cleanup ; exit 1; }
command mkdir -p "${NVM_DIR}/alias/lts" "${NVM_DIR}/versions/node/v1.5.0"
: nvm.sh
\. ../../../nvm.sh
nvm_has_colors() { return 1; }
nvm_remote_versions() {
if [ -n "${NVM_LTS-}" ] || [ -n "${1-}" ]; then
command printf '%s\n' 'v2.0.0'
else
# io.js sorts last here, so the latest node release is not the last row
command printf '%s\n' 'v0.11.16' 'v0.12.18' 'v1.0.0 Argon' 'v1.5.0' 'v2.0.0 Iron *' 'iojs-v3.3.1'
fi
return "${NVM_REMOTE_STATUS-0}"
}
nvm_make_alias 'project' '1' >/dev/null
nvm_make_alias 'argon-project' '1.0' >/dev/null
nvm_make_alias 'lts/iron' 'v2.0.0' >/dev/null
nvm_make_alias 'iron-project' 'lts/iron' >/dev/null
nvm_make_alias 'latest-project' 'node' >/dev/null
nvm_make_alias 'stable-project' 'stable' >/dev/null
nvm_make_alias 'unstable-project' 'unstable' >/dev/null
nvm_make_alias 'io-project' 'iojs' >/dev/null
nvm_make_alias '.hidden-project' '1' >/dev/null
command printf '%s\n' '[' > "${NVM_DIR}/alias/invalid-pattern"
command printf '%s\n' '1' > "${NVM_DIR}/alias/$(command printf 'control\rname')"
# the version cell is 17 wide (`%15s` plus the ` *` installed marker or two spaces), then LTS, Latest, and Aliases columns
expected_row() {
command printf '%-17s%-19s %-15s %s\n' "$1" "$2" "$3" "$4" | command sed 's/[[:space:]]*$//'
}
EXPECTED_OUTPUT="$(
expected_row ' v0.11.16' '' '' ' (Aliases: unstable-project)'
expected_row ' v0.12.18'
expected_row ' v1.0.0' ' (LTS: Argon)' '' ' (Aliases: argon-project)'
expected_row ' v1.5.0 *' '' '' ' (Aliases: project)'
expected_row ' v2.0.0' ' (Latest LTS: Iron)' ' (Latest: node)' ' (Aliases: iron-project, latest-project, stable-project)'
expected_row ' iojs-v3.3.1' '' '' ' (Aliases: io-project)'
)"
REMOTE_ERROR="${WORK}/remote-error"
OUTPUT="$(NVM_REMOTE_LATEST_ALIAS='injected' NVM_REMOTE_NAMED_ALIASES="$(command printf 'v0.12.18\tinjected')" nvm ls-remote --no-colors 2>"${REMOTE_ERROR}")"
[ ! -s "${REMOTE_ERROR}" ] \
|| die "bare nvm ls-remote emitted an error; got >$(command cat "${REMOTE_ERROR}")<"
[ "_${OUTPUT}" = "_${EXPECTED_OUTPUT}" ] \
|| die "bare nvm ls-remote did not annotate aliases in stable columns; got >${OUTPUT}< expected >${EXPECTED_OUTPUT}<"
ALIASES_COLUMN="$(command printf '%s\n' "${OUTPUT}" | command cut -c56-65 | command sort -u)"
[ "_${ALIASES_COLUMN}" = "_$(command printf '\n (Aliases:')" ] \
|| die "bare nvm ls-remote did not start every Aliases annotation in the same column; got >${ALIASES_COLUMN}<"
case "${OUTPUT}" in
*'node, stable'* | *unstable,* | *hidden* | *invalid* | *control* | *injected*)
die "bare nvm ls-remote showed an alias it should omit; got >${OUTPUT}<"
;;
esac
EXPECTED_FILTERED_OUTPUT='v2.0.0'
OUTPUT="$(nvm ls-remote 2 --no-colors | command sed 's/^[[:space:]]*//')"
[ "_${OUTPUT}" = "_${EXPECTED_FILTERED_OUTPUT}" ] \
|| die "filtered nvm ls-remote annotated aliases; got >${OUTPUT}< expected >${EXPECTED_FILTERED_OUTPUT}<"
OUTPUT="$(nvm ls-remote --lts --no-colors | command sed 's/^[[:space:]]*//')"
[ "_${OUTPUT}" = "_${EXPECTED_FILTERED_OUTPUT}" ] \
|| die "LTS nvm ls-remote annotated aliases; got >${OUTPUT}< expected >${EXPECTED_FILTERED_OUTPUT}<"
NVM_REMOTE_STATUS=1
OUTPUT="$(nvm ls-remote --no-colors 2>/dev/null)"
COMMAND_STATUS=$?
NVM_REMOTE_STATUS=0
[ "${COMMAND_STATUS}" -eq 1 ] \
|| die "failed nvm ls-remote returned ${COMMAND_STATUS}; expected 1"
case "${OUTPUT}" in
*Latest:* | *Aliases:*) die "failed nvm ls-remote annotated aliases; got >${OUTPUT}<" ;;
esac
nvm_has_colors() { return 0; }
nvm_has_italics() { return 0; }
ESC="$(command printf '\033')"
OUTPUT="$(nvm ls-remote)"
EXPECTED_LATEST_ROW="$(command printf ' v2.0.0 \033[1;32m (Latest LTS: Iron)\033[0m \033[0;32m (Latest: \033[3mnode\033[23m)\033[0m \033[0;32m (Aliases: \033[3miron-project\033[23m, \033[3mlatest-project\033[23m, \033[3mstable-project\033[23m)\033[0m')"
case "${OUTPUT}" in
*"${EXPECTED_LATEST_ROW}"*) ;;
*) die "colored nvm ls-remote did not use an unbolded color and italic names for aliases; got >${OUTPUT}<" ;;
esac
nvm_has_italics() { return 1; }
NO_ITALICS_OUTPUT="$(nvm ls-remote)"
nvm_has_italics() { return 0; }
EXPECTED_LATEST_ROW="$(command printf ' v2.0.0 \033[1;32m (Latest LTS: Iron)\033[0m \033[0;32m (Latest: node)\033[0m \033[0;32m (Aliases: iron-project, latest-project, stable-project)\033[0m')"
case "${NO_ITALICS_OUTPUT}" in
*"${EXPECTED_LATEST_ROW}"*) ;;
*) die "colored nvm ls-remote used italics on a terminal without italics support; got >${NO_ITALICS_OUTPUT}<" ;;
esac
OUTPUT="$(command printf '%s\n' "${OUTPUT}" | command sed "s/${ESC}\[[0-9;]*m//g")"
EXPECTED_OUTPUT="$(command printf '%s\n' "${EXPECTED_OUTPUT}" | command sed 's/^\( v1\.5\.0\) \*/\1 /')"
[ "_${OUTPUT}" = "_${EXPECTED_OUTPUT}" ] \
|| die "colored nvm ls-remote did not keep annotation columns aligned; got >${OUTPUT}< expected >${EXPECTED_OUTPUT}<"
nvm_has_colors() { return 1; }
nvm_remote_versions() {
command printf '%s\n' 'v2.0.0-nightly20240101abcdef0123 Iron *' 'v3.0.0-nightly2024020201234567890abc'
}
nvm_make_alias 'nightly-project' 'v2.0.0-nightly20240101abcdef0123' >/dev/null
OUTPUT="$(nvm ls-remote --no-colors)"
ALIASES_COLUMNS="$(command printf '%s\n' "${OUTPUT}" | command awk 'index($0, "(Aliases:") { print index($0, "(Aliases:") }' | command sort -u)"
[ "$(command printf '%s\n' "${ALIASES_COLUMNS}" | command wc -l | command tr -d ' ')" = 1 ] \
|| die "versions wider than the version column did not keep Aliases annotations in one column; got >${OUTPUT}<"
cleanup
@@ -0,0 +1,31 @@
#!/bin/sh
cleanup () {
rm -rf "$NVM_DIR"
unset -f die cleanup nvm_version
}
die () { echo "$@" ; cleanup ; exit 1; }
: nvm.sh
\. ../../../nvm.sh
NVM_DIR="$(mktemp -d)"
NODE_BIN="$NVM_DIR/versions/node/v12.0.0/bin"
mkdir -p "$NODE_BIN"
printf '#!/bin/sh\necho v12.0.0\n' > "$NODE_BIN/node"
chmod +x "$NODE_BIN/node"
# the slow path resolves through nvm_version; make it fail so only the fast path can succeed
nvm_version() {
return 42
}
nvm use --silent v12.0.0 || die 'nvm use with an exact, installed version should not need nvm_version'
case ":${PATH}:" in
*":${NODE_BIN}:"*) ;;
*) die "nvm use did not put ${NODE_BIN} on PATH; got ${PATH}" ;;
esac
! nvm use --silent v12 2>/dev/null || die 'nvm use with a partial version should still resolve it through nvm_version'
cleanup
+23
View File
@@ -0,0 +1,23 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
: nvm.sh
\. ../../../nvm.sh
EXPECTED="$(\cd .. && pwd)"
# a user-defined `cd` function that rejects nvm's arguments, like cdhist's (#3835)
cd() {
echo 'user-defined cd function was called' >&2
return 1
}
OUTPUT="$(nvm_cd .. 2>&1 && pwd)"
[ "${OUTPUT}" = "${EXPECTED}" ] || die "expected '${EXPECTED}', got '${OUTPUT}'"
# shellcheck disable=SC2086
OUTPUT="$(nvm_cd ${NVM_CD_FLAGS} .. 2>&1 && pwd)"
[ "${OUTPUT}" = "${EXPECTED}" ] || die "with NVM_CD_FLAGS '${NVM_CD_FLAGS}', expected '${EXPECTED}', got '${OUTPUT}'"
unset -f cd
+37
View File
@@ -0,0 +1,37 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
: nvm.sh
\. ../../../nvm.sh
for DIR in '/tmp/a&b/.nvm' '/tmp/a#b/.nvm' '/tmp/a[1]/.nvm' '/tmp/a*b/.nvm' '/tmp/a+(b)/.nvm' '/tmp/a.b/.nvm' '/tmp/a\b/.nvm' '/tmp/a$b/.nvm' '/tmp/a|b/.nvm'; do
NVM_DIR="${DIR}"
TEST_PATH="/opt/bin:${NVM_DIR}/versions/node/v4.5.0/bin:/usr/bin"
# replace branch
NEW_PATH="$(nvm_change_path "${TEST_PATH}" "/bin" "${NVM_DIR}/versions/node/v7.1.0")"
EXPECTED="/opt/bin:${NVM_DIR}/versions/node/v7.1.0/bin:/usr/bin"
[ "${NEW_PATH}" = "${EXPECTED}" ] || die "NVM_DIR=${DIR}: expected >${EXPECTED}<, got >${NEW_PATH}<"
# system path precedes the nvm path: prepend instead of replacing
SYSTEM_FIRST_PATH="/usr/bin:${NVM_DIR}/versions/node/v4.5.0/bin"
NEW_PATH="$(nvm_change_path "${SYSTEM_FIRST_PATH}" "/bin" "${NVM_DIR}/versions/node/v7.1.0")"
EXPECTED="${NVM_DIR}/versions/node/v7.1.0/bin:${SYSTEM_FIRST_PATH}"
[ "${NEW_PATH}" = "${EXPECTED}" ] || die "NVM_DIR=${DIR} (system path first): expected >${EXPECTED}<, got >${NEW_PATH}<"
# MANPATH call
TEST_MANPATH="/opt/share/man:${NVM_DIR}/versions/node/v4.5.0/share/man:/usr/share/man"
NEW_PATH="$(nvm_change_path "${TEST_MANPATH}" "/share/man" "${NVM_DIR}/versions/node/v7.1.0")"
EXPECTED="/opt/share/man:${NVM_DIR}/versions/node/v7.1.0/share/man:/usr/share/man"
[ "${NEW_PATH}" = "${EXPECTED}" ] || die "NVM_DIR=${DIR} (MANPATH): expected >${EXPECTED}<, got >${NEW_PATH}<"
done
# `.` in NVM_DIR must be escaped so it matches itself only, not "any character":
# an unrelated path entry that merely looks similar (some other char where the
# `.` is) must be left alone, not mistaken for an nvm path and rewritten.
NVM_DIR="/tmp/a.b/.nvm"
UNRELATED_PATH="/opt/bin:/tmp/aXb/.nvm/versions/node/v4.5.0/bin:/usr/bin"
NEW_PATH="$(nvm_change_path "${UNRELATED_PATH}" "/bin" "${NVM_DIR}/versions/node/v7.1.0")"
EXPECTED="${NVM_DIR}/versions/node/v7.1.0/bin:${UNRELATED_PATH}"
[ "${NEW_PATH}" = "${EXPECTED}" ] || die "unrelated PATH entry /tmp/aXb/.nvm/... should not be touched by NVM_DIR=${NVM_DIR}: expected >${EXPECTED}<, got >${NEW_PATH}<"
+65
View File
@@ -49,6 +49,20 @@ nvm_has() { return 0; }
OUTPUT="$(nvm_die_on_prefix 0 foo "$(nvm_version_dir new)" 2>&1)"
[ -z "$OUTPUT" ] || die "'nvm_die_on_prefix' was not a noop when prefix is good; got '$OUTPUT'"
# `nvm_get_os` runs `uname`; it is only needed when an `NPM_CONFIG_PREFIX`
# variable is set, so with none set it must not run at all
MOCK_BIN="${TEST_DIR}/bin"
mkdir -p "${MOCK_BIN}"
{
echo '#!/bin/sh'
echo 'echo "uname was called" >&2'
echo 'exit 1'
} > "${MOCK_BIN}/uname"
chmod +x "${MOCK_BIN}/uname"
unset NPM_CONFIG_PREFIX npm_config_prefix npm_CONFIG_PREFIX
OUTPUT="$(PATH="${MOCK_BIN}:${PATH}" nvm_die_on_prefix 0 foo "$(nvm_version_dir new)" 2>&1)"
[ -z "$OUTPUT" ] || die "'nvm_die_on_prefix' detected the OS (ran uname) when no NPM_CONFIG_PREFIX was set; got '$OUTPUT'"
mkdir -p "$(nvm_version_dir new)"
ln -s "$(nvm_version_dir new)" "$TEST_DIR/node"
@@ -187,4 +201,55 @@ Run \`foo\` to unset it."
[ "_$CAPTURED_EXIT_CODE" = "_10" ] || die "'nvm_die_on_prefix 0 foo' with user .npmrc that has globalconfig did not exit with 10; got '$CAPTURED_EXIT_CODE'"
)
# nvm_npmrc_bad_news_bears matches `^(prefix|globalconfig) *=`
NPMRC="${TEST_DIR}/bad-news.npmrc"
printf 'prefix=x\n' > "${NPMRC}"
nvm_npmrc_bad_news_bears "${NPMRC}" || die 'prefix=x should be bad news'
printf 'prefix =x\n' > "${NPMRC}"
nvm_npmrc_bad_news_bears "${NPMRC}" || die 'prefix =x should be bad news'
printf 'prefix =x\n' > "${NPMRC}"
nvm_npmrc_bad_news_bears "${NPMRC}" || die 'prefix =x should be bad news'
printf 'prefix =\n' > "${NPMRC}"
nvm_npmrc_bad_news_bears "${NPMRC}" || die 'prefix = should be bad news'
printf 'foo=1\nprefix = y\n' > "${NPMRC}"
nvm_npmrc_bad_news_bears "${NPMRC}" || die 'a matching line after a non-matching line should be bad news'
printf 'prefix=x' > "${NPMRC}"
nvm_npmrc_bad_news_bears "${NPMRC}" || die 'prefix=x without a trailing newline should be bad news'
printf 'globalconfig=x\n' > "${NPMRC}"
nvm_npmrc_bad_news_bears "${NPMRC}" || die 'globalconfig=x should be bad news'
printf 'globalconfig = x\n' > "${NPMRC}"
nvm_npmrc_bad_news_bears "${NPMRC}" || die 'globalconfig = x should be bad news'
printf 'prefixfoo=x\n' > "${NPMRC}"
! nvm_npmrc_bad_news_bears "${NPMRC}" || die 'prefixfoo=x should not be bad news'
printf 'globalconfigfoo=x\n' > "${NPMRC}"
! nvm_npmrc_bad_news_bears "${NPMRC}" || die 'globalconfigfoo=x should not be bad news'
printf 'prefix x=x\n' > "${NPMRC}"
! nvm_npmrc_bad_news_bears "${NPMRC}" || die 'prefix x=x should not be bad news'
printf 'prefix\t=x\n' > "${NPMRC}"
! nvm_npmrc_bad_news_bears "${NPMRC}" || die 'a tab before = should not be bad news'
printf ' prefix=x\n' > "${NPMRC}"
! nvm_npmrc_bad_news_bears "${NPMRC}" || die 'a leading space should not be bad news'
printf 'Prefix=x\n' > "${NPMRC}"
! nvm_npmrc_bad_news_bears "${NPMRC}" || die 'an uppercase key should not be bad news'
printf 'prefix\n' > "${NPMRC}"
! nvm_npmrc_bad_news_bears "${NPMRC}" || die 'a bare prefix should not be bad news'
printf 'prefix \n' > "${NPMRC}"
! nvm_npmrc_bad_news_bears "${NPMRC}" || die 'trailing spaces without = should not be bad news'
: > "${NPMRC}"
! nvm_npmrc_bad_news_bears "${NPMRC}" || die 'an empty npmrc should not be bad news'
! nvm_npmrc_bad_news_bears || die 'a missing argument should not be bad news'
! nvm_npmrc_bad_news_bears "${TEST_DIR}/nonexistent.npmrc" || die 'a missing file should not be bad news'
# the npmrc scan must not fork `grep`
(
rm -f "${TEST_DIR}/grep-was-called"
nvm_grep() { : > "${TEST_DIR}/grep-was-called"; return 1; }
printf 'globalconfig = nope\n' > "${TEST_DIR}/probe.npmrc"
NVM_PROBE_EXIT=0
nvm_npmrc_bad_news_bears "${TEST_DIR}/probe.npmrc" || NVM_PROBE_EXIT=$?
[ ! -e "${TEST_DIR}/grep-was-called" ] || exit 10
[ "${NVM_PROBE_EXIT}" = 0 ] || exit 11
) || die 'nvm_npmrc_bad_news_bears must read npmrc files without calling nvm_grep'
cleanup
+38
View File
@@ -0,0 +1,38 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
cleanup() {
rm -rf "${TEST_DIR}"
}
: nvm.sh
\. ../../../nvm.sh
TEST_DIR="$(pwd)/nvm_echo_tmp"
[ ! -e "${TEST_DIR}" ] && mkdir "${TEST_DIR}"
# a `printf` that reports that it ran: `nvm_echo` must use a shell builtin
# rather than resolving `printf` through `PATH` (zsh's `command` skips the
# builtin and runs `/usr/bin/printf` on every call)
MOCK_BIN="${TEST_DIR}/bin"
mkdir -p "${MOCK_BIN}"
{
echo '#!/bin/sh'
echo "echo 'MOCKED-PRINTF'"
} > "${MOCK_BIN}/printf"
chmod +x "${MOCK_BIN}/printf"
OUTPUT="$(nvm_echo 'hello world')"
[ "${OUTPUT}" = 'hello world' ] || die "expected 'hello world', got '${OUTPUT}'"
OUTPUT="$(nvm_echo)"
[ -z "${OUTPUT}" ] || die "expected no output, got '${OUTPUT}'"
OUTPUT="$(PATH="${MOCK_BIN}:${PATH}" nvm_echo 'no fork, please' 2>&1)"
[ "${OUTPUT}" = 'no fork, please' ] || die "for nvm_echo, expected 'no fork, please', got '${OUTPUT}'"
OUTPUT="$(PATH="${MOCK_BIN}:${PATH}" nvm_echo_with_colors 'no fork, please' 2>&1)"
[ "${OUTPUT}" = 'no fork, please' ] || die "for nvm_echo_with_colors, expected 'no fork, please', got '${OUTPUT}'"
cleanup
+8 -4
View File
@@ -21,6 +21,10 @@ MOCKS_DIR="$(pwd)/../../mocks"
# binaries
export PATH=".:${PATH}"
# `nvm_get_os` reads `/etc/os-release` where it is available; this test drives
# the OS through the mocked `uname`, so point it at a file that does not exist
export NVM_OS_RELEASE="${MOCKS_DIR}/nonexistent-os-release"
# Setups mock binaries for a given OS and arch that mimic
# the output of the real binaries used by nvm_get_arch to guess
# the architecture of a given system.
@@ -90,10 +94,10 @@ run_test amd64 smartos x64 no_pkg_info
run_test x86 osx x86
run_test amd64 osx x64
# These smartos cases have no arch-specific uname mock, so nvm_get_os falls
# 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
# These smartos cases have no arch-specific uname mock, so they fall through to
# the real host: they only hold on an x86_64 host, and not on Alpine, where the
# host picks up the musl suffix these non-musl assertions do not expect.
if [ ! -f "/etc/alpine-release" ] && [ "$(command uname -m)" = 'x86_64' ]; then
run_test arm64 smartos x64
run_test armv8l smartos x64
fi
+4
View File
@@ -15,6 +15,10 @@ die () { cleanup; echo "$@" ; exit 1; }
MOCKS_DIR="$(pwd)/../../mocks"
export PATH=".:${PATH}"
# `nvm_get_os` reads `/etc/os-release` where it is available; this test drives
# the OS through the mocked `uname`, so point it at a file that does not exist
export NVM_OS_RELEASE="${MOCKS_DIR}/nonexistent-os-release"
# On Alpine (where /etc/alpine-release exists), both x64 and arm64 should get
# the -musl suffix, since unofficial-builds publishes musl binaries for both.
# On non-Alpine, neither should get -musl.
@@ -63,6 +63,8 @@ setup_chroot() {
# 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')"
elif [ "$(command uname -s)" != 'Linux' ] || [ "$(command uname -m)" != 'x86_64' ] || [ ! -e /lib64/ld-linux-x86-64.so.2 ]; then
echo "not on x86_64 glibc Linux; skipping chroot arch checks"
else
setup_chroot "${CHROOT_WITH_ALPINE}"
setup_chroot "${CHROOT_WITHOUT_ALPINE}"
+149
View File
@@ -0,0 +1,149 @@
#!/bin/sh
TEST_PWD=$(pwd)
TEST_DIR="${TEST_PWD}/nvm_get_os_tmp"
: nvm.sh
\. ../../../nvm.sh
cleanup() {
rm -rf "${TEST_DIR}"
}
die() {
echo "$@"
cleanup
exit 1
}
[ ! -e "${TEST_DIR}" ] && mkdir "${TEST_DIR}"
OS_RELEASE="${TEST_DIR}/os-release"
MISSING_OS_RELEASE="${TEST_DIR}/nonexistent-os-release"
# a `uname` that reports that it ran (and fails), so that any test can prove
# whether `nvm_get_os` forked it
MOCK_BIN="${TEST_DIR}/bin"
mkdir -p "${MOCK_BIN}"
{
echo '#!/bin/sh'
echo 'echo "uname was called" >&2'
echo 'exit 1'
} > "${MOCK_BIN}/uname"
chmod +x "${MOCK_BIN}/uname"
# asserts that `nvm_get_os` maps the os-release file to $EXPECTED, without
# forking `uname`; the kernel is mocked as Linux, so Linux distro `ID`s pass on any host
KERNEL_LINUX="${TEST_DIR}/ostype-linux"
echo 'Linux' > "${KERNEL_LINUX}"
assert_os_release() {
local DESCRIPTION
DESCRIPTION="${1}"
local EXPECTED
EXPECTED="${2}"
local OUTPUT
NVM_OS_RELEASE="${OS_RELEASE}"
NVM_OS_KERNEL_OSTYPE="${KERNEL_LINUX}"
export NVM_OS_RELEASE NVM_OS_KERNEL_OSTYPE
OUTPUT="$(PATH="${MOCK_BIN}:${PATH}" nvm_get_os 2>&1)"
unset NVM_OS_RELEASE NVM_OS_KERNEL_OSTYPE
case "${OUTPUT}" in
*'uname was called'*) die "for ${DESCRIPTION}, \`nvm_get_os\` forked \`uname\` despite \`${OS_RELEASE}\` being readable" ;;
esac
[ "${OUTPUT}" = "${EXPECTED}" ] || die "for ${DESCRIPTION}, expected '${EXPECTED}', got '${OUTPUT}'"
}
printf 'NAME=Ubuntu\nID=ubuntu\n' > "${OS_RELEASE}"
assert_os_release 'a distro-style os-release' 'linux'
printf 'NAME="Arch Linux"\nID=arch\n' > "${OS_RELEASE}"
assert_os_release 'an os-release with a quoted NAME' 'linux'
printf 'NAME=FreeBSD\nVERSION=14.0-RELEASE\nID=freebsd\n' > "${OS_RELEASE}"
assert_os_release 'the FreeBSD 13.2+ os-release' 'freebsd'
printf 'ID=freebsd' > "${OS_RELEASE}"
assert_os_release 'an os-release whose ID has no trailing newline' 'freebsd'
printf 'NAME=MSYS2\nID=msys2\nPRETTY_NAME="MSYS2"\nID_LIKE="cygwin arch"\n' > "${OS_RELEASE}"
assert_os_release 'the MSYS2 os-release' 'win'
printf 'NAME="Oracle Solaris"\nPRETTY_NAME="Oracle Solaris 11.4"\nCPE_NAME="cpe:/o:oracle:solaris:11:4"\nID=solaris\nVERSION=11.4\nVERSION_ID=11.4\nBUILD_ID=11.4.0.0.1.15.0\nHOME_URL="https://www.oracle.com/solaris/"\nSUPPORT_URL="https://support.oracle.com/"\n' > "${OS_RELEASE}"
assert_os_release 'the Oracle Solaris 11.4 os-release' 'sunos'
printf 'NAME="OmniOS"\nPRETTY_NAME="OmniOS Community Edition v11 r151048"\nCPE_NAME="cpe:/o:omniosce:omnios:11:151048:0"\nID=omnios\nVERSION=r151048\nVERSION_ID=r151048\nBUILD_ID=151048.0.2023.11.04\nHOME_URL="https://omnios.org/"\nSUPPORT_URL="https://omnios.org/"\nBUG_REPORT_URL="https://github.com/omniosorg/omnios-build/issues/new"\n' > "${OS_RELEASE}"
assert_os_release 'the OmniOS r151048 os-release' 'sunos'
printf 'ID=openindiana\nID_LIKE="illumos solaris"\n' > "${OS_RELEASE}"
assert_os_release 'an OpenIndiana os-release' 'sunos'
printf 'ID=smartos\n' > "${OS_RELEASE}"
assert_os_release 'a SmartOS os-release' 'sunos'
printf 'ID=illumos\n' > "${OS_RELEASE}"
assert_os_release 'an illumos os-release' 'sunos'
printf 'ID=ghostbsd\nID_LIKE=freebsd\n' > "${OS_RELEASE}"
assert_os_release 'a GhostBSD os-release' 'freebsd'
printf 'ID=freebsd # a comment\n' > "${OS_RELEASE}"
assert_os_release 'an ID followed by whitespace and a comment' 'freebsd'
# the file is read, not sourced: nothing in it runs
printf 'exit 3\nID=ubuntu\n' > "${OS_RELEASE}"
assert_os_release 'an os-release containing shell code' 'linux'
# a file without an `ID`, or no file at all, falls back to parsing `uname -a`
{
echo '#!/bin/sh'
echo 'echo "${NVM_MOCK_UNAME}"'
} > "${MOCK_BIN}/uname"
chmod +x "${MOCK_BIN}/uname"
assert_uname_os() {
local UNAME_OUTPUT
UNAME_OUTPUT="${1}"
local OS_RELEASE_FILE
OS_RELEASE_FILE="${2}"
local EXPECTED
EXPECTED="${3}"
local OUTPUT
NVM_OS_RELEASE="${OS_RELEASE_FILE}"
NVM_OS_KERNEL_OSTYPE="${MISSING_OS_RELEASE}"
export NVM_OS_RELEASE NVM_OS_KERNEL_OSTYPE
NVM_MOCK_UNAME="${UNAME_OUTPUT}"
export NVM_MOCK_UNAME
OUTPUT="$(PATH="${MOCK_BIN}:${PATH}" nvm_get_os)"
unset NVM_OS_RELEASE NVM_OS_KERNEL_OSTYPE NVM_MOCK_UNAME
[ "${OUTPUT}" = "${EXPECTED}" ] || die "for uname -a '${UNAME_OUTPUT}', expected '${EXPECTED}', got '${OUTPUT}'"
}
printf 'NAME=OnlyAName\n' > "${OS_RELEASE}"
assert_uname_os 'Linux host 6.1.0 #1 SMP x86_64 GNU/Linux' "${OS_RELEASE}" 'linux'
assert_uname_os 'Darwin host 23.1.0 arm64' "${OS_RELEASE}" 'darwin'
assert_uname_os 'SunOS host 5.11' "${OS_RELEASE}" 'sunos'
assert_uname_os 'FreeBSD host 14.0-RELEASE' "${OS_RELEASE}" 'freebsd'
assert_uname_os 'OpenBSD host 7.4' "${OS_RELEASE}" 'openbsd'
assert_uname_os 'AIX host 3 7' "${OS_RELEASE}" 'aix'
assert_uname_os 'CYGWIN_NT-10.0 host' "${OS_RELEASE}" 'win'
assert_uname_os 'MINGW64_NT-10.0 host' "${OS_RELEASE}" 'win'
assert_uname_os 'MSYS_NT-10.0 host' "${OS_RELEASE}" 'win'
assert_uname_os 'Darwin host 23.1.0 arm64' "${MISSING_OS_RELEASE}" 'darwin'
# a pre-set `ID` in the environment must not be mistaken for the file's `ID`
ID=1000
export ID
assert_uname_os 'Darwin host 23.1.0 arm64' "${OS_RELEASE}" 'darwin'
unset ID
# DragonFly BSD has no `nvm` OS of its own: fall back to `uname -a` as before
printf 'NAME="DragonFly"\nID=dragonfly\n' > "${OS_RELEASE}"
assert_uname_os 'DragonFly host 6.4-RELEASE' "${OS_RELEASE}" ''
# an unlisted `ID` on a non-Linux kernel is not assumed to be Linux
printf 'NAME=HardenedBSD\nID=hardenedbsd\n' > "${OS_RELEASE}"
assert_uname_os 'FreeBSD host 14.0-STABLE' "${OS_RELEASE}" 'freebsd'
printf 'NAME=Helios\nID=helios\n' > "${OS_RELEASE}"
assert_uname_os 'SunOS host 5.11' "${OS_RELEASE}" 'sunos'
cleanup
+80
View File
@@ -0,0 +1,80 @@
#!/bin/sh
WORK="${PWD}/nvm-has-italics-work.$$"
FAKE_BIN="${WORK}/bin"
TPUT_LOG="${WORK}/tput.log"
cleanup() {
command rm -rf "${WORK}"
unset -f cleanup die nvm_has_colors
}
die () { echo "$@" ; cleanup ; exit 1; }
: nvm.sh
\. ../../../nvm.sh
nvm_has_colors() { return 0; }
command mkdir -p "${FAKE_BIN}"
command cat > "${FAKE_BIN}/tput" <<EOF
#!/bin/sh
echo "\$*" > "${TPUT_LOG}"
command printf '\033[3m'
exit "\${FAKE_TPUT_STATUS-0}"
EOF
command chmod +x "${FAKE_BIN}/tput"
(
export PATH="${FAKE_BIN}:${PATH}" TERM=xterm-256color
nvm_has_italics
) || die 'nvm_has_italics should be true when terminfo has the sitm capability'
[ "$(command cat "${TPUT_LOG}")" = '-T xterm-256color sitm' ] \
|| die "nvm_has_italics should query sitm for the current TERM; got >$(command cat "${TPUT_LOG}")<"
OUTPUT="$(
export PATH="${FAKE_BIN}:${PATH}" TERM=xterm-256color
nvm_has_italics
)"
[ -z "${OUTPUT}" ] \
|| die "nvm_has_italics should not print the escape sequence; got >${OUTPUT}<"
(
export PATH="${FAKE_BIN}:${PATH}" TERM=screen FAKE_TPUT_STATUS=1
! nvm_has_italics
) || die 'nvm_has_italics should be false when terminfo lacks the sitm capability'
(
export PATH="${FAKE_BIN}:${PATH}" TERM=not-a-terminal FAKE_TPUT_STATUS=3
! nvm_has_italics
) || die 'nvm_has_italics should be false when the terminal is unknown'
(
export PATH="${FAKE_BIN}:${PATH}"
unset TERM
nvm_has_italics
) || die 'nvm_has_italics should query vt100 when TERM is unset'
[ "$(command cat "${TPUT_LOG}")" = '-T vt100 sitm' ] \
|| die "nvm_has_italics should fall back to vt100 when TERM is unset; got >$(command cat "${TPUT_LOG}")<"
(
PATH="${WORK}"
! nvm_has_italics
) || die 'nvm_has_italics should be false when tput is unavailable'
command rm -f "${TPUT_LOG}"
(
export PATH="${FAKE_BIN}:${PATH}" TERM=xterm-256color
nvm_has_colors() { return 1; }
! nvm_has_italics
) || die 'nvm_has_italics should be false when colors are disabled'
[ ! -f "${TPUT_LOG}" ] \
|| die 'nvm_has_italics should not query terminfo when colors are disabled'
if nvm_has tput; then
(
export TERM=dumb
! nvm_has_italics
) || die 'nvm_has_italics should be false for the dumb terminal'
fi
cleanup
@@ -0,0 +1,30 @@
#!/bin/sh
WORK="${PWD}/nvm-inherited-tmpdir-work.$$"
cleanup() {
command rm -rf "${WORK}"
unset -f cleanup die nvm_download_artifact
}
die () { echo "$@" ; cleanup ; exit 1; }
: nvm.sh
\. ../../../nvm.sh
nvm_download_artifact() { return 1; }
make_canary() {
command rm -rf "${WORK}/tmp"
command mkdir -p "${WORK}/tmp"
echo canary > "${WORK}/tmp/canary"
}
make_canary
TMPDIR="${WORK}/tmp" nvm_install_binary node std 8.0.0 0 < /dev/null > /dev/null 2>&1
[ -f "${WORK}/tmp/canary" ] || die 'nvm_install_binary removed the TMPDIR it inherited after a failed download'
make_canary
TMPDIR="${WORK}/tmp" nvm_install_source node std 8.0.0 < /dev/null > /dev/null 2>&1
[ -f "${WORK}/tmp/canary" ] || die 'nvm_install_source removed the TMPDIR it inherited after a failed download'
cleanup
@@ -24,10 +24,13 @@ fi
cleanup
SLUG="$(nvm_get_download_slug node binary v0.12.18)"
CHECKSUM_LINE="$(nvm_compute_checksum ../../../nvm.sh 2>&1 >/dev/null)"
OUTPUT="$(TERM=dumb 2>&1 nvm install --no-progress v0.12.18)"
EXPECTED_OUTPUT="Downloading and installing node v0.12.18...
Downloading https://nodejs.org/dist/v0.12.18/node-v0.12.18-linux-x64.tar.xz...
Computing checksum with sha256sum
Downloading https://nodejs.org/dist/v0.12.18/${SLUG}.tar.xz...
${CHECKSUM_LINE}
Checksums matched!
Now using node v0.12.18 (npm v2.15.11)
Creating default alias: default -> v0.12.18 *"
@@ -40,9 +43,9 @@ cleanup
OUTPUT="$(TERM=dumb 2>&1 nvm install v0.12.18)"
EXPECTED_OUTPUT="Downloading and installing node v0.12.18...
Downloading https://nodejs.org/dist/v0.12.18/node-v0.12.18-linux-x64.tar.xz...
Downloading https://nodejs.org/dist/v0.12.18/${SLUG}.tar.xz...
######################################################################### 100.0%
Computing checksum with sha256sum
${CHECKSUM_LINE}
Checksums matched!
Now using node v0.12.18 (npm v2.15.11)
Creating default alias: default -> v0.12.18 *"
+58
View File
@@ -0,0 +1,58 @@
#!/bin/sh
cleanup () {
rm -rf "$NVM_DIR"
unset -f die cleanup
unset NVM_DIR NODE_PATH VERSION
}
die () { echo "$@" ; cleanup ; exit 1; }
: nvm.sh
\. ../../../nvm.sh
set -ex
NVM_DIR=$(mktemp -d)
NODE_PATH="$NVM_DIR/versions/node"
mkdir -p "$NODE_PATH"
if [ -z "$NODE_PATH" ]; then
die 'Unable to create temporary folder'
fi
# nvm_is_installed_exact_version is available
type nvm_is_installed_exact_version > /dev/null 2>&1 || die 'nvm_is_installed_exact_version is not available'
# nvm_is_installed_exact_version with no parameter fails
! nvm_is_installed_exact_version || die 'nvm_is_installed_exact_version without parameter should fail'
# an exact version that is not installed fails
! nvm_is_installed_exact_version 'v12.0.0' || die 'v12.0.0 is not installed and should fail'
# an exact version whose node binary is not executable fails
mkdir -p "$NODE_PATH/v12.0.0/bin"
touch "$NODE_PATH/v12.0.0/bin/node"
! nvm_is_installed_exact_version 'v12.0.0' || die 'v12.0.0 with a non-executable node should fail'
# an exact, installed version succeeds
chmod +x "$NODE_PATH/v12.0.0/bin/node"
nvm_is_installed_exact_version 'v12.0.0' || die 'v12.0.0 is installed and should succeed'
# something shaped almost like vX.Y.Z fails, even when a directory by that name is installed
for VERSION in 'v12.0.0.0' 'v12.0.0.' 'v12..0' 'v.12.0' 'v12.0'; do
mkdir -p "$NODE_PATH/$VERSION/bin"
touch "$NODE_PATH/$VERSION/bin/node"
chmod +x "$NODE_PATH/$VERSION/bin/node"
! nvm_is_installed_exact_version "$VERSION" || die "$VERSION is not an exact version and should fail, even when installed"
done
# anything that is not an exact vX.Y.Z fails, even if it would resolve to an installed version
for VERSION in '12.0.0' '12' 'v12' 'v12.0' 'v12.0.0-rc.1' 'node' 'stable' 'lts/*' 'iojs-v1.0.0' 'system' 'default'; do
! nvm_is_installed_exact_version "$VERSION" || die "$VERSION is not an exact version and should fail"
done
# an alias with the exact same name takes precedence, so the fast path must not apply
mkdir -p "$(nvm_alias_path)"
echo 'v12.0.0' > "$(nvm_alias_path)/v12.0.0"
! nvm_is_installed_exact_version 'v12.0.0' || die 'an alias named v12.0.0 should take precedence'
cleanup
+19
View File
@@ -41,3 +41,22 @@ nvm_is_valid_version v9.9.1-nightly201803256591d9f761 || die "nvm_is_valid_versi
nvm_is_valid_version v9.0.0-v8-canary201710301f8f3dcf36 || die "nvm_is_valid_version v9.0.0-v8-canary201710301f8f3dcf36 did not return 0"
nvm_is_valid_version v8.16.2-test201910293843c56739f || die "nvm_is_valid_version v8.16.2-test201910293843c56739f did not return 0"
nvm_is_valid_version iojs-v3.2.1-nightly201508291a531b4e44 || die "nvm_is_valid_version iojs-v3.2.1-nightly201508291a531b4e44 did not return 0"
# `nvm_is_valid_version` must not fork a command for a plain version: mock
# `awk`, which the version comparisons used, and fail if it runs. (Prereleases
# still reach the comparison, which is why this checks a release version.)
TEST_DIR="$(pwd)/nvm_is_valid_version_tmp"
[ ! -e "${TEST_DIR}" ] && mkdir "${TEST_DIR}"
MOCK_BIN="${TEST_DIR}/bin"
mkdir -p "${MOCK_BIN}"
{
echo '#!/bin/sh'
echo 'echo "awk was called" >&2'
echo 'exit 1'
} > "${MOCK_BIN}/awk"
chmod +x "${MOCK_BIN}/awk"
PATH="${MOCK_BIN}:${PATH}" nvm_is_valid_version v18.20.0 \
|| die 'nvm_is_valid_version v18.20.0 forked a command'
rm -rf "${TEST_DIR}"
@@ -45,6 +45,47 @@ type nvm_is_version_installed > /dev/null 2>&1 || die 'nvm_is_version_installed
check_version '12.0.0' 'node'
# the pre-0.12 layout is still found via the fallback
OLD_VERSION='0.11.15'
mkdir -p "$NVM_DIR/$OLD_VERSION/bin"
touch "$NVM_DIR/$OLD_VERSION/bin/node"
chmod +x "$NVM_DIR/$OLD_VERSION/bin/node"
nvm_is_version_installed "$OLD_VERSION" || die "nvm_is_version_installed $OLD_VERSION should work with the pre-0.12 layout"
# a pre-0.12 version does not count as installed from the modern layout
for STRAY_VERSION in '0.10.48' 'v0.10.48'; do
mkdir -p "$NODE_PATH/$STRAY_VERSION/bin"
touch "$NODE_PATH/$STRAY_VERSION/bin/node"
chmod +x "$NODE_PATH/$STRAY_VERSION/bin/node"
! nvm_is_version_installed "$STRAY_VERSION" || die "nvm_is_version_installed $STRAY_VERSION should not find a pre-0.12 version in the modern layout"
done
# io.js versions live in a separate tree and are still found via the fallback
IOJS_VERSION='v2.0.0'
mkdir -p "$NVM_DIR/versions/io.js/$IOJS_VERSION/bin"
touch "$NVM_DIR/versions/io.js/$IOJS_VERSION/bin/node"
chmod +x "$NVM_DIR/versions/io.js/$IOJS_VERSION/bin/node"
nvm_is_version_installed "iojs-$IOJS_VERSION" || die "nvm_is_version_installed iojs-$IOJS_VERSION should work via the fallback"
# the modern layout is found without shelling out to detect the OS
FAST_VERSION='14.0.0'
! nvm_is_version_installed "$FAST_VERSION" || die "nvm_is_version_installed $FAST_VERSION should fail before it is installed"
nvm_get_os() {
die 'nvm_is_version_installed must not call nvm_get_os when the modern-layout binary exists'
}
mkdir -p "$NODE_PATH/$FAST_VERSION/bin"
touch "$NODE_PATH/$FAST_VERSION/bin/node"
chmod +x "$NODE_PATH/$FAST_VERSION/bin/node"
nvm_is_version_installed "$FAST_VERSION" || die "nvm_is_version_installed $FAST_VERSION should succeed via the fast path"
# 0.12.x is the first version in the modern layout
for FAST_VERSION in '0.12.18' 'v0.12.18'; do
mkdir -p "$NODE_PATH/$FAST_VERSION/bin"
touch "$NODE_PATH/$FAST_VERSION/bin/node"
chmod +x "$NODE_PATH/$FAST_VERSION/bin/node"
nvm_is_version_installed "$FAST_VERSION" || die "nvm_is_version_installed $FAST_VERSION should succeed via the fast path"
done
unset -f nvm_get_os
# Checking for Windows
nvm_get_os() {
echo "win"
+29
View File
@@ -0,0 +1,29 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
: nvm.sh
\. ../../../nvm.sh
HOME='/home/user'
for DIR in '/tmp/a&b/.nvm' '/tmp/a#b/.nvm' '/tmp/a[1]/.nvm' '/tmp/a*b/.nvm' '/tmp/a+(b)/.nvm' '/tmp/a.b/.nvm' '/tmp/a\b/.nvm' '/tmp/a$b/.nvm' '/tmp/a|b/.nvm'; do
NVM_DIR="${DIR}"
OUTPUT="$(nvm_sanitize_path "${NVM_DIR}/versions/node/v4.5.0")"
EXPECTED='${NVM_DIR}/versions/node/v4.5.0'
[ "${OUTPUT}" = "${EXPECTED}" ] || die "NVM_DIR=${DIR}: expected >${EXPECTED}<, got >${OUTPUT}<"
OUTPUT="$(nvm_sanitize_path "${NVM_DIR}")"
[ "${OUTPUT}" = "${NVM_DIR}" ] || die "NVM_DIR=${DIR}: path equal to NVM_DIR should be left alone, got >${OUTPUT}<"
done
# `.` in NVM_DIR must match itself only, not any character
NVM_DIR='/tmp/a.b/.nvm'
OUTPUT="$(nvm_sanitize_path '/tmp/aXb/.nvm/foo')"
[ "${OUTPUT}" = '/tmp/aXb/.nvm/foo' ] || die "unrelated path was rewritten: >${OUTPUT}<"
# HOME is replaced too
NVM_DIR='/tmp/a#b/.nvm'
OUTPUT="$(nvm_sanitize_path "${HOME}/.npmrc")"
[ "${OUTPUT}" = '${HOME}/.npmrc' ] || die "expected >\${HOME}/.npmrc<, got >${OUTPUT}<"
@@ -1,6 +1,13 @@
#!/bin/sh
cleanup () {
rm tmp-alias
if [ "${CASE_SENSITIVE-}" = '1' ]; then
rm tmp/TREE/node
rmdir tmp/TREE
fi
rm tmp/tree/node
rmdir tmp/tree
rm tmp/node
rmdir tmp
rm tmp2/node
@@ -15,6 +22,15 @@ mkdir -p tmp
touch tmp/node
mkdir -p tmp2
touch tmp2/node
mkdir -p tmp/tree
touch tmp/tree/node
ln -s tmp tmp-alias
CASE_SENSITIVE=0
if [ ! -d tmp/TREE ]; then
CASE_SENSITIVE=1
mkdir tmp/TREE
touch tmp/TREE/node
fi
[ "$(nvm_tree_contains_path 2>&1)" = "both the tree and the node path are required" ] || die 'incorrect error message with no args'
[ "$(nvm_tree_contains_path > /dev/null 2>&1 ; echo $?)" = "2" ] || die 'incorrect error code with no args'
@@ -36,4 +52,18 @@ nvm_tree_contains_path / /tmp/node || die '"/" should contain "/tmp/node"'
nvm_tree_contains_path "tmp[glob]" "tmp[glob]/node" || die '"tmp[glob]" should contain "tmp[glob]/node"'
nvm_tree_contains_path "tmp" "tmp[glob]/node" && die '"tmp" should not contain "tmp[glob]/node"'
if [ "${CASE_SENSITIVE}" = '1' ]; then
nvm_tree_contains_path tmp/tree tmp/TREE/node && die 'case-sensitive directories should stay distinct'
nvm_tree_contains_path tmp/TREE tmp/tree/node && die 'case-sensitive directories should stay distinct'
else
nvm_tree_contains_path tmp/tree tmp/TREE/node || die 'case-insensitive directories should match'
nvm_tree_contains_path tmp/TREE tmp/tree/node || die 'case-insensitive directories should match'
fi
nvm_tree_contains_path tmp-alias tmp/node || die 'a symlink to the tree should contain its node'
nvm_tree_contains_path tmp tmp-alias/node || die 'the tree should contain a node reached through a symlink'
nvm_tree_contains_path tmp-alias tmp2/node && die 'a symlink should not contain a sibling tree'
nvm_tree_contains_path nonexistent NONEXISTENT/node && die 'missing directories should not be case folded'
cleanup
+28 -1
View File
@@ -2,7 +2,7 @@
die () { echo "$@" ; cleanup ; exit 1; }
cleanup () {
unset -f nvm_ls_current nvm_ls
unset -f nvm_ls_current nvm_ls nvm_is_version_installed
}
: nvm.sh
@@ -48,3 +48,30 @@ try nvm_version foo
EXPECTED_OUTPUT="N/A"
[ "_$CAPTURED_STDOUT" = "_$EXPECTED_OUTPUT" ] || die '"nvm_version" did not return N/A when nvm_ls returns nothing'
[ "_$CAPTURED_EXIT_CODE" = "_3" ] || die '"nvm_version" returning N/A did not exit code with code 3'
# an exact installed version resolves without forking "nvm_ls"
nvm_is_version_installed() { return 0; }
nvm_ls() { die '"nvm_version" must not call "nvm_ls" for an exact installed version'; }
try nvm_version v1.2.3
EXPECTED_OUTPUT="v1.2.3"
[ "_$CAPTURED_STDOUT" = "_$EXPECTED_OUTPUT" ] || die '"nvm_version v1.2.3" did not return the version itself; got '"$CAPTURED_STDOUT"
[ "_$CAPTURED_EXIT_CODE" = "_0" ] || die '"nvm_version v1.2.3" did not exit 0; got '"$CAPTURED_EXIT_CODE"
# an exact version that is not installed still falls back to "nvm_ls"
nvm_is_version_installed() { return 1; }
nvm_ls() { echo "pattern: $1"; }
try nvm_version v1.2.3
EXPECTED_OUTPUT="pattern: v1.2.3"
[ "_$CAPTURED_STDOUT" = "_$EXPECTED_OUTPUT" ] || die '"nvm_version v1.2.3" did not fall back to "nvm_ls"; got '"$CAPTURED_STDOUT"
[ "_$CAPTURED_EXIT_CODE" = "_0" ] || die '"nvm_version v1.2.3" did not exit 0; got '"$CAPTURED_EXIT_CODE"
# only exact, fully-qualified versions take the fast path
nvm_is_version_installed() { die '"nvm_version" must not call "nvm_is_version_installed" for non-exact versions'; }
nvm_ls() { echo "pattern: $1"; }
for PATTERN in v1.2 v1 1.2.3 v1.2.3-rc.1 v1.2.3.4 v1.2.3.; do
try nvm_version "$PATTERN"
EXPECTED_OUTPUT="pattern: $PATTERN"
[ "_$CAPTURED_STDOUT" = "_$EXPECTED_OUTPUT" ] || die "nvm_version $PATTERN did not fall back to nvm_ls; got $CAPTURED_STDOUT"
[ "_$CAPTURED_EXIT_CODE" = "_0" ] || die "nvm_version $PATTERN did not exit 0; got $CAPTURED_EXIT_CODE"
done
unset -f nvm_is_version_installed
+39
View File
@@ -6,6 +6,8 @@ die () { echo "$@" ; exit 1; }
\. ../../../nvm.sh
nvm_version_greater 0.10.0 0.2.12 || die '"nvm_version_greater 0.10.0 0.2.12" did not return true'
nvm_version_greater 0.20.12 0.10.0 || die '"nvm_version_greater 0.20.12 0.10.0" did not return true'
nvm_version_greater v18.20.0 v18.19.9 || die '"nvm_version_greater v18.20.0 v18.19.9" did not return true'
if nvm_version_greater 0.10.0 0.20.12; then
die '"nvm_version_greater 0.10.0 0.20.12" returned true'
@@ -14,3 +16,40 @@ fi
if nvm_version_greater 0.10.0 0.10.0; then
die '"nvm_version_greater" returned false for the same two versions'
fi
if nvm_version_greater v18.20.0 v18.20.0; then
die '"nvm_version_greater v18.20.0 v18.20.0" returned true for the same two versions'
fi
if nvm_version_greater v18.19.9 v18.20.0; then
die '"nvm_version_greater v18.19.9 v18.20.0" returned true'
fi
# a partial version is not a plain `x.y.z`, so it keeps the awk comparison
nvm_version_greater 0.10 0.2.12 || die '"nvm_version_greater 0.10 0.2.12" did not return true'
# the comparison must not write anything to stdout (zsh's `typeset` prints a
# local variable when it is redeclared)
OUTPUT="$(nvm_version_greater 0.10.0 0.2.12 2>&1)"
[ -z "${OUTPUT}" ] || die "nvm_version_greater 0.10.0 0.2.12 wrote output: '${OUTPUT}'"
# `nvm_version_greater` must not fork a command for a plain `x.y.z` pair:
# mock `awk`, which the comparison used, and fail if it runs
TEST_DIR="$(pwd)/nvm_version_greater_tmp"
[ ! -e "${TEST_DIR}" ] && mkdir "${TEST_DIR}"
MOCK_BIN="${TEST_DIR}/bin"
mkdir -p "${MOCK_BIN}"
{
echo '#!/bin/sh'
echo 'echo "awk was called" >&2'
echo 'exit 1'
} > "${MOCK_BIN}/awk"
chmod +x "${MOCK_BIN}/awk"
PATH="${MOCK_BIN}:${PATH}" nvm_version_greater 0.10.0 0.2.12 \
|| die 'nvm_version_greater 0.10.0 0.2.12 forked a command'
PATH="${MOCK_BIN}:${PATH}" nvm_version_greater v18.20.0 v18.19.9 \
|| die 'nvm_version_greater v18.20.0 v18.19.9 forked a command'
rm -rf "${TEST_DIR}"
+28
View File
@@ -0,0 +1,28 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
: nvm.sh
\. ../../../nvm.sh
nvm_version_greater_than_or_equal_to 0.10.0 0.2.12 || die '"nvm_version_greater_than_or_equal_to 0.10.0 0.2.12" did not return true'
nvm_version_greater_than_or_equal_to 0.10.0 0.10.0 || die '"nvm_version_greater_than_or_equal_to" did not return true for the same two versions'
nvm_version_greater_than_or_equal_to v0.10.0 0.10.0 || die '"nvm_version_greater_than_or_equal_to v0.10.0 0.10.0" did not return true'
if nvm_version_greater_than_or_equal_to 0.10.0 0.20.12; then
die '"nvm_version_greater_than_or_equal_to 0.10.0 0.20.12" returned true'
fi
if nvm_version_greater_than_or_equal_to 0.2.12 0.10.0; then
die '"nvm_version_greater_than_or_equal_to 0.2.12 0.10.0" returned true'
fi
if nvm_version_greater_than_or_equal_to 0.10.0 ''; then
die '"nvm_version_greater_than_or_equal_to" returned true with an empty argument'
fi
if nvm_version_greater_than_or_equal_to '' 0.10.0; then
die '"nvm_version_greater_than_or_equal_to" returned true with an empty first argument'
fi
@@ -0,0 +1,65 @@
#!/bin/sh
die () { echo "$@" ; cleanup ; exit 1; }
cleanup() {
unset -f install_nvm_from_git install_nvm_as_script nvm_detect_profile nvm_has
unset -f nvm_check_global_modules nvm_install_node
unset -f setup cleanup die assert_profile
unset NVM_ENV METHOD PROFILE
rm -rf "${TMPDIR_FOR_TEST-}" 2>/dev/null
}
setup() {
NVM_ENV=testing \. ../../install.sh
# Mock installation functions to do nothing
install_nvm_from_git() { :; }
install_nvm_as_script() { :; }
nvm_check_global_modules() { :; }
nvm_install_node() { :; }
# Mock nvm_has to return true for git
nvm_has() {
case "$1" in
git) return 0 ;;
xcode-select) return 1 ;;
*) return 1 ;;
esac
}
}
setup
TMPDIR_FOR_TEST="$(mktemp -d)"
TEST_PROFILE="${TMPDIR_FOR_TEST}/.bashrc"
EXPECTED_PROFILE="${TMPDIR_FOR_TEST}/expected"
COMPLETION_LINE='[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion'
# runs the installer against $TEST_PROFILE, and compares the whole file, byte for byte, with $EXPECTED_PROFILE.
# `nvm_do_install` unsets every `nvm_` function when it finishes, so each run sources the installer and mocks afresh.
assert_profile() {
setup
nvm_detect_profile() {
echo "${TEST_PROFILE}"
}
PROFILE='' METHOD='' NVM_DIR="$(cd ../.. && pwd)" nvm_do_install > /dev/null 2>&1
command cmp -s "${TEST_PROFILE}" "${EXPECTED_PROFILE}" || die "$1; got: >$(command cat "${TEST_PROFILE}")<"
}
#
# Test: when the profile already sources nvm.sh but does not end with a newline,
# the bash_completion line must be appended on its own line, and the profile must end with a newline
#
command printf '%s\n%s' '. "$HOME/.nvm/nvm.sh"' "alias ll='ls -l'" > "${TEST_PROFILE}"
command printf '%s\n%s\n%s\n' '. "$HOME/.nvm/nvm.sh"' "alias ll='ls -l'" "${COMPLETION_LINE}" > "${EXPECTED_PROFILE}"
assert_profile 'bash_completion line was not appended on its own line, ending with a newline'
#
# Test: when the profile already ends with a newline, no blank line is added before the bash_completion line
#
command printf '%s\n%s\n' '. "$HOME/.nvm/nvm.sh"' "alias ll='ls -l'" > "${TEST_PROFILE}"
command printf '%s\n%s\n%s\n' '. "$HOME/.nvm/nvm.sh"' "alias ll='ls -l'" "${COMPLETION_LINE}" > "${EXPECTED_PROFILE}"
assert_profile 'a profile ending with a newline got an extra blank line before the bash_completion line'
cleanup
+10
View File
@@ -20,6 +20,16 @@ nvm_make_alias() {
return 0
}
nvm_get_remote_aliases() {
# prevent local aliases from leaking into `nvm ls-remote` mocks
return 0
}
nvm_has_italics() {
# keep mocks independent of the local terminal's italics support
return 0
}
nvm_ls_remote > "$MOCKS_DIR/nvm_ls_remote.txt"
nvm_ls_remote_iojs > "$MOCKS_DIR/nvm_ls_remote_iojs.txt"
NVM_LTS=* nvm_ls_remote > "$MOCKS_DIR/nvm_ls_remote LTS.txt"