Compare commits

..
Author SHA1 Message Date
copilot-swe-agent[bot] 42bb45f289 Initial plan 2026-07-02 17:33:13 +00:00
36 changed files with 129 additions and 810 deletions
+1 -23
View File
@@ -49,6 +49,7 @@ jobs:
contents: read
needs: [matrix]
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.ref == 'v0.40.0' }} # https://github.com/nvm-sh/nvm/issues/3405
strategy:
fail-fast: false
matrix:
@@ -80,19 +81,12 @@ jobs:
set -e
export NVM_INSTALL_VERSION="${ref}"
curl -o- "https://raw.githubusercontent.com/nvm-sh/nvm/${ref}/install.sh" | bash
# v0.40.0 with an .nvmrc present is expected to fail (https://github.com/nvm-sh/nvm/issues/3405):
# the steps below tolerate failure for that combination, and the final step
# turns the job red only if the expected failure did NOT happen.
- name: nvm --version
id: nvm-version
continue-on-error: ${{ matrix.ref == 'v0.40.0' && matrix.has-nvmrc == 'nvmrc' }}
run: |
set +e
. $NVM_DIR/nvm.sh && nvm --version
- name: nvm install in 1 shell level, ${{ matrix.has-nvmrc }}
id: install-1
if: ${{ matrix.shell-level == '1 shlvl' }}
continue-on-error: ${{ matrix.ref == 'v0.40.0' && matrix.has-nvmrc == 'nvmrc' }}
run: |
set -ex
. $NVM_DIR/nvm.sh
@@ -102,29 +96,13 @@ jobs:
nvm install
fi
- name: nvm install in 2 shell levels, ${{ matrix.has-nvmrc }}
id: install-2
if: ${{ matrix.shell-level == '2 shlvls' }}
continue-on-error: ${{ matrix.ref == 'v0.40.0' && matrix.has-nvmrc == 'nvmrc' }}
run: |
if [ '${{ matrix.has-nvmrc }}' == 'nvmrc' ]; then
bash -c "set -ex && . $NVM_DIR/nvm.sh && echo nvm.sh sourced && nvm --version && nvm install"
else
bash -c "set -ex && . $NVM_DIR/nvm.sh && echo nvm.sh sourced && nvm --version"
fi
- name: 'confirm the expected failure happened (v0.40.0 + nvmrc)'
if: ${{ matrix.ref == 'v0.40.0' && matrix.has-nvmrc == 'nvmrc' }}
env:
SOURCE_OUTCOME: ${{ steps.nvm-version.outcome }}
INSTALL_1_OUTCOME: ${{ steps.install-1.outcome }}
INSTALL_2_OUTCOME: ${{ steps.install-2.outcome }}
run: |
echo "nvm --version: ${SOURCE_OUTCOME}; install (1 shlvl): ${INSTALL_1_OUTCOME}; install (2 shlvls): ${INSTALL_2_OUTCOME}"
if [ "${SOURCE_OUTCOME}" = 'failure' ] || [ "${INSTALL_1_OUTCOME}" = 'failure' ] || [ "${INSTALL_2_OUTCOME}" = 'failure' ]; then
echo 'v0.40.0 failed as expected: https://github.com/nvm-sh/nvm/issues/3405'
else
echo '::error::v0.40.0 with an .nvmrc was expected to fail (https://github.com/nvm-sh/nvm/issues/3405), but every step succeeded. If v0.40.0 somehow works now, remove the expected-failure handling from this workflow.'
exit 1
fi
finisher:
permissions:
+8 -9
View File
@@ -998,13 +998,13 @@ export NVM_DIR="$HOME/.nvm"
## Docker For Development Environment
To make development and testing work easier we supply a Dockerfile for development usage. It's based on an Ubuntu base image prepared with essential and useful tools for `nvm` development. To build the docker image of the environment, do a Docker build at the root of `nvm` repository:
To make the development and testing work easier, we have a Dockerfile for development usage, which is based on Ubuntu 18.04 base image, prepared with essential and useful tools for `nvm` development, to build the docker image of the environment, run the docker command at the root of `nvm` repository:
```sh
$ docker build -t nvm-dev .
```
This will package your current nvm working copy with our pre-defined development environment into a Docker image named `nvm-dev`. After the build you should see it appear in the list of images:
This will package your current nvm repository with our pre-defined development environment into a docker image named `nvm-dev`, once it's built with success, validate your image via `docker images`:
```sh
$ docker images
@@ -1013,7 +1013,7 @@ REPOSITORY TAG IMAGE ID CREATED S
nvm-dev latest 9ca4c57a97d8 7 days ago 650 MB
```
To start and enter a container based on this image:
If you got no error message, now you can easily involve in:
```sh
$ docker run -h nvm-dev -it nvm-dev
@@ -1021,9 +1021,12 @@ $ docker run -h nvm-dev -it nvm-dev
nvm@nvm-dev:~/.nvm$
```
It takes several minutes to build the image and the image size is about 650MB, so it's not suitable for production usage.
Please note that it'll take about 8 minutes to build the image and the image size would be about 650MB, so it's not suitable for production usage.
For more information and documentation about Docker, please refer to its [official website][docker-www] and [documentation][docker-docs]:
For more information and documentation about docker, please refer to its official website:
- https://www.docker.com/
- https://docs.docker.com/
## Problems
@@ -1202,7 +1205,3 @@ See [LICENSE.md](./LICENSE.md).
Copyright [OpenJS Foundation](https://openjsf.org) and `nvm` contributors. All rights reserved. The [OpenJS Foundation](https://openjsf.org) has registered trademarks and uses trademarks. For a list of trademarks of the [OpenJS Foundation](https://openjsf.org), please see our [Trademark Policy](https://trademark-policy.openjsf.org/) and [Trademark List](https://trademark-list.openjsf.org/). Trademarks and logos not indicated on the [list of OpenJS Foundation trademarks](https://trademark-list.openjsf.org) are trademarks™ or registered® trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.
[The OpenJS Foundation](https://openjsf.org/) | [Terms of Use](https://terms-of-use.openjsf.org/) | [Privacy Policy](https://privacy-policy.openjsf.org/) | [Bylaws](https://bylaws.openjsf.org/) | [Code of Conduct](https://code-of-conduct.openjsf.org) | [Trademark Policy](https://trademark-policy.openjsf.org/) | [Trademark List](https://trademark-list.openjsf.org/) | [Cookie Policy](https://www.linuxfoundation.org/cookies/)
<!-------------------------------------------------------------------->
[docker-docs]: https://docs.docker.com/
[docker-www]: https://www.docker.com/
+7 -19
View File
@@ -6,18 +6,6 @@ nvm_has() {
type "$1" > /dev/null 2>&1
}
# resolves like `command "${1}"` does: only executables on the PATH,
# ignoring shell functions and aliases
nvm_has_executable() {
(
# `|| true` so that shells with errexit-style options (eg, zsh's ERR_RETURN)
# do not abort the subshell when the name is not an alias or a function
unalias "${1-}" 2>/dev/null || true
unset -f "${1-}" 2>/dev/null || true
command -v "${1-}" > /dev/null 2>&1
)
}
nvm_echo() {
command printf %s\\n "$*" 2>/dev/null
}
@@ -117,9 +105,9 @@ nvm_node_version() {
}
nvm_download() {
if nvm_has_executable "curl"; then
command curl --fail --compressed -q "$@"
elif nvm_has_executable "wget"; then
if nvm_has "curl"; then
curl --fail --compressed -q "$@"
elif nvm_has "wget"; then
# Emulate curl with wget
ARGS=$(nvm_echo "$@" | command sed -e 's/--progress-bar /--progress=bar /' \
-e 's/--compressed //' \
@@ -131,7 +119,7 @@ nvm_download() {
-e 's/-o /-O /' \
-e 's/-C - /-c /')
# shellcheck disable=SC2086
eval command wget $ARGS
eval wget $ARGS
fi
}
@@ -413,7 +401,7 @@ nvm_do_install() {
# Autodetect install method
if nvm_has git; then
install_nvm_from_git
elif nvm_has_executable curl || nvm_has_executable wget; then
elif nvm_has curl || nvm_has wget; then
install_nvm_as_script
else
nvm_echo >&2 'You need git, curl, or wget to install nvm'
@@ -426,7 +414,7 @@ nvm_do_install() {
fi
install_nvm_from_git
elif [ "${METHOD}" = 'script' ]; then
if ! nvm_has_executable curl && ! nvm_has_executable wget; then
if ! nvm_has curl && ! nvm_has wget; then
nvm_echo >&2 "You need curl or wget to install nvm"
exit 1
fi
@@ -508,7 +496,7 @@ nvm_do_install() {
# during the execution of the install script
#
nvm_reset() {
unset -f nvm_has nvm_has_executable nvm_install_dir nvm_latest_version nvm_profile_is_bash_or_zsh \
unset -f nvm_has nvm_install_dir nvm_latest_version nvm_profile_is_bash_or_zsh \
nvm_source nvm_node_version nvm_download install_nvm_from_git nvm_install_node \
install_nvm_as_script nvm_try_profile nvm_detect_profile nvm_check_global_modules \
nvm_do_install nvm_reset nvm_default_install_dir nvm_grep
+37 -93
View File
@@ -49,18 +49,6 @@ nvm_has() {
type "${1-}" >/dev/null 2>&1
}
# resolves like `command "${1}"` does: only executables on the PATH,
# ignoring shell functions and aliases
nvm_has_executable() {
(
# `|| true` so that shells with errexit-style options (eg, zsh's ERR_RETURN)
# do not abort the subshell when the name is not an alias or a function
unalias "${1-}" 2>/dev/null || true
unset -f "${1-}" 2>/dev/null || true
command -v "${1-}" >/dev/null 2>&1
)
}
nvm_has_non_aliased() {
nvm_has "${1-}" && ! nvm_is_alias "${1-}"
}
@@ -99,7 +87,7 @@ nvm_has_colors() {
}
nvm_curl_libz_support() {
command curl -V 2>/dev/null | nvm_grep "^Features:" | nvm_grep -q "libz"
curl -V 2>/dev/null | nvm_grep "^Features:" | nvm_grep -q "libz"
}
nvm_curl_use_compression() {
@@ -109,13 +97,13 @@ nvm_curl_use_compression() {
nvm_get_latest() {
local NVM_LATEST_URL
local CURL_COMPRESSED_FLAG
if nvm_has_executable "curl"; then
if nvm_has "curl"; then
if nvm_curl_use_compression; then
CURL_COMPRESSED_FLAG="--compressed"
fi
NVM_LATEST_URL="$(command curl ${CURL_COMPRESSED_FLAG:-} -q -w "%{url_effective}\\n" -L -s -S https://latest.nvm.sh -o /dev/null)"
elif nvm_has_executable "wget"; then
NVM_LATEST_URL="$(command wget -q https://latest.nvm.sh --server-response -O /dev/null 2>&1 | command awk '/^ Location: /{DEST=$2} END{ print DEST }')"
NVM_LATEST_URL="$(curl ${CURL_COMPRESSED_FLAG:-} -q -w "%{url_effective}\\n" -L -s -S https://latest.nvm.sh -o /dev/null)"
elif nvm_has "wget"; then
NVM_LATEST_URL="$(wget -q https://latest.nvm.sh --server-response -O /dev/null 2>&1 | command awk '/^ Location: /{DEST=$2} END{ print DEST }')"
else
nvm_err 'nvm needs curl or wget to proceed.'
return 1
@@ -139,13 +127,13 @@ nvm_download() {
local NVM_DOWNLOADER
NVM_DOWNLOADER=''
if nvm_has_executable "curl"; then
if nvm_has "curl"; then
NVM_DOWNLOADER='curl'
set -- -q --fail "$@"
if nvm_curl_use_compression; then
set -- --compressed "$@"
fi
elif nvm_has_executable "wget"; then
elif nvm_has "wget"; then
NVM_DOWNLOADER='wget'
# Emulate curl with wget
local NVM_DOWNLOAD_WGET_COUNT
@@ -182,17 +170,12 @@ nvm_download() {
set -- "$@" --header "Authorization: ${sanitized_header}"
fi
command "${NVM_DOWNLOADER}" "$@"
"${NVM_DOWNLOADER}" "$@"
}
nvm_sanitize_auth_header() {
# Remove potentially dangerous characters; allow the full base64 (A-Za-z0-9+/=)
# and base64url (A-Za-z0-9-_=) charsets, plus the space, colon, dot, and
# underscore the previous allowlist already permitted, so that values like
# `Basic <base64>` and `Bearer <token>` survive intact.
# token68's '~' is still stripped.
# Note: '-' must be at the end of the bracket expression to be treated as a literal.
nvm_echo "$1" | command sed 's/[^a-zA-Z0-9 :_.+/=-]//g'
# Remove potentially dangerous characters
nvm_echo "$1" | command sed 's/[^a-zA-Z0-9:;_. -]//g'
}
nvm_has_system_node() {
@@ -565,12 +548,12 @@ ${1}"
$(nvm_wrap_with_color_code 'y' "${warn_text}")"
}
nvm_process_nvmrc_content() {
local NVMRC_CONTENT
NVMRC_CONTENT="${1-}"
nvm_process_nvmrc() {
local NVMRC_PATH
NVMRC_PATH="$1"
local lines
lines=$(nvm_echo "${NVMRC_CONTENT}" | command sed 's/#.*//' | command sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | nvm_grep -v '^$')
lines=$(command sed 's/#.*//' "$NVMRC_PATH" | command sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | nvm_grep -v '^$')
if [ -z "$lines" ]; then
nvm_nvmrc_invalid_msg "${lines}"
@@ -634,13 +617,6 @@ EOF
nvm_echo "${unpaired_line}"
}
nvm_process_nvmrc() {
local NVMRC_PATH
NVMRC_PATH="$1"
nvm_process_nvmrc_content "$(command cat "${NVMRC_PATH}")"
}
nvm_rc_version() {
local NVMRC_PATH
NVMRC_PATH="$(nvm_find_nvmrc)"
@@ -673,7 +649,7 @@ nvm_clang_version() {
}
nvm_curl_version() {
command curl -V | command awk '{ if ($1 == "curl") print $2 }' | command sed 's/-.*$//g'
curl -V | command awk '{ if ($1 == "curl") print $2 }' | command sed 's/-.*$//g'
}
nvm_version_greater() {
@@ -1367,24 +1343,7 @@ nvm_alias() {
return 2
fi
if [ ! -r "${NVM_ALIAS_PATH}" ]; then
# an existing-but-unreadable alias file yields empty output with a success
# status - a nonzero status here would make `nvm_ensure_default_set`
# overwrite an existing default alias it merely could not read
nvm_err "Alias file is not readable: ${NVM_ALIAS_PATH}"
return 0
fi
local NVM_ALIAS_LINE
while IFS= read -r NVM_ALIAS_LINE || [ -n "${NVM_ALIAS_LINE}" ]; do
NVM_ALIAS_LINE="${NVM_ALIAS_LINE%%#*}"
case "${NVM_ALIAS_LINE}" in
*[![:space:]]*) ;;
*) continue ;;
esac
NVM_ALIAS_LINE="${NVM_ALIAS_LINE%"${NVM_ALIAS_LINE##*[![:space:]]}"}"
nvm_echo "${NVM_ALIAS_LINE}"
done < "${NVM_ALIAS_PATH}"
command sed 's/#.*//; s/[[:space:]]*$//' "${NVM_ALIAS_PATH}" | command awk 'NF'
}
nvm_ls_current() {
@@ -1417,32 +1376,24 @@ nvm_resolve_alias() {
local ALIAS
ALIAS="${PATTERN}"
local ALIAS_TEMP
local ALIAS_OUTPUT
local SEEN_ALIASES
SEEN_ALIASES="
${ALIAS}
"
SEEN_ALIASES="${ALIAS}"
local NVM_ALIAS_INDEX
NVM_ALIAS_INDEX=1
while true; do
ALIAS_OUTPUT="$(nvm_alias "${ALIAS}" 2>/dev/null)" || ALIAS_OUTPUT=''
ALIAS_TEMP="${ALIAS_OUTPUT%%
*}"
ALIAS_TEMP="$( (nvm_alias "${ALIAS}" 2>/dev/null | command head -n "${NVM_ALIAS_INDEX}" | command tail -n 1) || nvm_echo)"
if [ -z "${ALIAS_TEMP}" ]; then
break
fi
case "${SEEN_ALIASES}" in
*"
${ALIAS_TEMP}
"*)
ALIAS="∞"
break
;;
esac
if command printf '%b' "${SEEN_ALIASES}" | nvm_grep -q -e "^${ALIAS_TEMP}$"; then
ALIAS="∞"
break
fi
SEEN_ALIASES="${SEEN_ALIASES}${ALIAS_TEMP}
"
SEEN_ALIASES="${SEEN_ALIASES}\\n${ALIAS_TEMP}"
ALIAS="${ALIAS_TEMP}"
done
@@ -1523,17 +1474,6 @@ nvm_strip_iojs_prefix() {
nvm_ls() {
local PATTERN
PATTERN="${1-}"
case "${PATTERN}" in
*'#'* | *'
'*)
local NVMRC_PATTERN
if ! NVMRC_PATTERN="$(nvm_process_nvmrc_content "${PATTERN}" 2>/dev/null)"; then
nvm_echo 'N/A'
return 3
fi
PATTERN="${NVMRC_PATTERN}"
;;
esac
local VERSIONS
VERSIONS=''
if [ "${PATTERN}" = 'current' ]; then
@@ -2231,7 +2171,6 @@ nvm_get_arch() {
x86_64 | amd64) NVM_ARCH="x64" ;;
i*86) NVM_ARCH="x86" ;;
aarch64 | armv8l) NVM_ARCH="arm64" ;;
loongarch64) NVM_ARCH="loong64" ;;
*) NVM_ARCH="${HOST_ARCH}" ;;
esac
@@ -2499,10 +2438,15 @@ nvm_get_download_slug() {
fi
fi
# If running MAC M1 :: ARM64 binaries are not available for Node < 16.0.0
# https://github.com/nodejs/node/issues/40126 (binary distributions aren't available until v16)
if nvm_version_greater '16.0.0' "${VERSION}" && [ "_${NVM_OS}" = '_darwin' ] && [ "${NVM_ARCH}" = 'arm64' ]; then
NVM_ARCH=x64
# If running MAC M1 :: Node v14.17.0 was the first version to offer official experimental support:
# https://github.com/nodejs/node/issues/40126 (although binary distributions aren't available until v16)
if \
nvm_version_greater '14.17.0' "${VERSION}" \
|| (nvm_version_greater_than_or_equal_to "${VERSION}" '15.0.0' && nvm_version_greater '16.0.0' "${VERSION}") \
; then
if [ "_${NVM_OS}" = '_darwin' ] && [ "${NVM_ARCH}" = 'arm64' ]; then
NVM_ARCH=x64
fi
fi
if [ "${KIND}" = 'binary' ]; then
@@ -3630,7 +3574,7 @@ nvm() {
esac
done
if [ "${NVM_OFFLINE}" != 1 ] && ! nvm_has_executable "curl" && ! nvm_has_executable "wget"; then
if [ "${NVM_OFFLINE}" != 1 ] && ! nvm_has "curl" && ! nvm_has "wget"; then
nvm_err 'nvm needs curl or wget to proceed.'
return 1
fi
@@ -4749,7 +4693,7 @@ nvm() {
nvm_version_greater nvm_version_greater_than_or_equal_to \
nvm_print_npm_version nvm_install_latest_npm nvm_npm_global_modules \
nvm_has_system_node nvm_has_system_iojs \
nvm_download nvm_get_latest nvm_has nvm_has_executable nvm_install_default_packages nvm_get_default_packages \
nvm_download nvm_get_latest nvm_has nvm_install_default_packages nvm_get_default_packages \
nvm_curl_use_compression nvm_curl_version \
nvm_auto nvm_supports_xz \
nvm_echo nvm_err nvm_grep nvm_cd \
@@ -4765,7 +4709,7 @@ nvm() {
nvm_get_colors nvm_set_colors nvm_print_color_code nvm_wrap_with_color_code nvm_format_help_message_colors \
nvm_echo_with_colors nvm_err_with_colors \
nvm_get_artifact_compression nvm_install_binary_extract nvm_extract_tarball \
nvm_process_nvmrc nvm_process_nvmrc_content nvm_nvmrc_invalid_msg \
nvm_process_nvmrc nvm_nvmrc_invalid_msg \
nvm_write_nvmrc \
>/dev/null 2>&1
unset NVM_NODEJS_ORG_MIRROR NVM_IOJS_ORG_MIRROR NVM_DIR \
-33
View File
@@ -50,39 +50,6 @@ make_echo() {
chmod a+x "$1"
}
# `command curl` bypasses shell functions and aliases, so tests that mock curl
# must provide an actual executable on the PATH: this creates one in the given
# directory. Invoked as `curl -V`, it prints ${VERSION_MESSAGE} (exported here
# on the mock's behalf); any other invocation runs the body given as the
# second argument, or fails.
make_fake_curl() {
local FAKE_BIN_DIR
FAKE_BIN_DIR="${1-}"
[ -n "${FAKE_BIN_DIR}" ] || return 1
local FAKE_CURL_BODY
FAKE_CURL_BODY="${2-}"
if [ -z "${FAKE_CURL_BODY}" ]; then
FAKE_CURL_BODY='echo >&2 "This fake curl only takes one parameter, -V"
exit 1'
fi
mkdir -p "${FAKE_BIN_DIR}" || return 2
{
echo '#!/bin/sh'
echo 'if [ "$#" -eq 1 ] && [ "$1" = "-V" ]; then'
echo ' echo "${VERSION_MESSAGE}"'
echo ' exit 0'
echo 'fi'
printf '%s\n' "${FAKE_CURL_BODY}"
} > "${FAKE_BIN_DIR}/curl"
chmod +x "${FAKE_BIN_DIR}/curl"
# the fake curl reads VERSION_MESSAGE from the environment
export VERSION_MESSAGE
}
make_fake_node() {
local VERSION
VERSION="${1-}"
@@ -1,17 +0,0 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
export NVM_DIR="$(cd ../../.. && pwd)"
: nvm.sh
\. "${NVM_DIR}/nvm.sh"
# Create an alias file whose name contains spaces
mkdir -p ../../../alias
printf 'v22.1.0\n' > "../../../alias/test edge spaces"
ACTUAL="$(nvm_alias "test edge spaces")"
EXPECTED='v22.1.0'
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}<, got >${ACTUAL}<"
rm -f "../../../alias/test edge spaces"
@@ -1,20 +0,0 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
export NVM_DIR="$(cd ../../.. && pwd)"
: nvm.sh
\. "${NVM_DIR}/nvm.sh"
# Create an alias file where the first line has a valid version
# followed by a second line of binary-like data
printf 'v22.1.0\n' > ../../../alias/test-edge-binary
printf '\001\002\003\377\n' >> ../../../alias/test-edge-binary
ACTUAL="$(nvm_alias test-edge-binary)"
# nvm_alias emits every non-blank, non-comment line — first line should be the version
FIRST_LINE="$(nvm_echo "${ACTUAL}" | command head -n 1)"
EXPECTED='v22.1.0'
[ "${FIRST_LINE}" = "${EXPECTED}" ] || die "expected first line >${EXPECTED}<, got >${FIRST_LINE}<"
rm -f ../../../alias/test-edge-binary
@@ -1,22 +0,0 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
export NVM_DIR="$(cd ../../.. && pwd)"
: nvm.sh
\. "${NVM_DIR}/nvm.sh"
# Create an alias file with Windows-style line endings (CRLF)
printf 'v22.1.0\r\n' > ../../../alias/test-edge-cr
ACTUAL="$(nvm_alias test-edge-cr)"
EXPECTED='v22.1.0'
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}<, got >${ACTUAL}<"
# Create an alias file with bare carriage return (no newline)
printf 'v22.2.0\r' > ../../../alias/test-edge-cr-bare
ACTUAL="$(nvm_alias test-edge-cr-bare)"
EXPECTED='v22.2.0'
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}< for bare CR, got >${ACTUAL}<"
rm -f ../../../alias/test-edge-cr ../../../alias/test-edge-cr-bare
@@ -1,17 +0,0 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
export NVM_DIR="$(cd ../../.. && pwd)"
: nvm.sh
\. "${NVM_DIR}/nvm.sh"
# Create an alias file containing only comments
printf '# this is a comment\n# another comment\n' > ../../../alias/test-edge-comments
ACTUAL="$(nvm_alias test-edge-comments 2>/dev/null)"
EXIT_CODE="$(nvm_alias test-edge-comments 2>/dev/null; echo $?)"
[ -z "${ACTUAL}" ] || die "expected empty output for comment-only alias file, got >${ACTUAL}<"
[ "${EXIT_CODE}" = '0' ] || die "expected exit code 0, got ${EXIT_CODE}"
rm -f ../../../alias/test-edge-comments
@@ -1,22 +0,0 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
export NVM_DIR="$(cd ../../.. && pwd)"
: nvm.sh
\. "${NVM_DIR}/nvm.sh"
# Create an alias file with an embedded NUL byte after the version.
# NUL handling varies by shell: some stop at NUL, others read through it.
# The function must not crash, and must emit output starting with the version.
printf 'v22.1.0\000garbage\n' > ../../../alias/test-edge-nul
ACTUAL="$(nvm_alias test-edge-nul)"
EXIT_CODE=$?
[ "${EXIT_CODE}" = '0' ] || die "expected exit code 0, got ${EXIT_CODE}"
case "${ACTUAL}" in
v22.1.0*) ;; # OK — starts with the version regardless of NUL handling
*) die "expected output starting with >v22.1.0<, got >${ACTUAL}<" ;;
esac
rm -f ../../../alias/test-edge-nul
@@ -1,17 +0,0 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
export NVM_DIR="$(cd ../../.. && pwd)"
: nvm.sh
\. "${NVM_DIR}/nvm.sh"
# Create an empty alias file
printf '' > ../../../alias/test-edge-empty
ACTUAL="$(nvm_alias test-edge-empty 2>/dev/null)"
EXIT_CODE="$(nvm_alias test-edge-empty 2>/dev/null; echo $?)"
[ -z "${ACTUAL}" ] || die "expected empty output for empty alias file, got >${ACTUAL}<"
[ "${EXIT_CODE}" = '0' ] || die "expected exit code 0, got ${EXIT_CODE}"
rm -f ../../../alias/test-edge-empty
@@ -1,22 +0,0 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
export NVM_DIR="$(cd ../../.. && pwd)"
: nvm.sh
\. "${NVM_DIR}/nvm.sh"
# Create an alias file with trailing form feed
printf 'v22.1.0\f\n' > ../../../alias/test-edge-ff
ACTUAL="$(nvm_alias test-edge-ff)"
EXPECTED='v22.1.0'
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}< for form feed, got >${ACTUAL}<"
# Create an alias file with trailing vertical tab
printf 'v22.2.0\v\n' > ../../../alias/test-edge-vt
ACTUAL="$(nvm_alias test-edge-vt)"
EXPECTED='v22.2.0'
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}< for vertical tab, got >${ACTUAL}<"
rm -f ../../../alias/test-edge-ff ../../../alias/test-edge-vt
@@ -1,16 +0,0 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
export NVM_DIR="$(cd ../../.. && pwd)"
: nvm.sh
\. "${NVM_DIR}/nvm.sh"
# Create an alias file with no trailing newline
printf 'v22.1.0' > ../../../alias/test-edge-no-newline
ACTUAL="$(nvm_alias test-edge-no-newline)"
EXPECTED='v22.1.0'
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}<, got >${ACTUAL}<"
rm -f ../../../alias/test-edge-no-newline
@@ -1,38 +0,0 @@
#!/bin/sh
die () { echo "$@" ; cleanup ; exit 1; }
cleanup() {
chmod 644 ../../../alias/test-edge-unreadable 2>/dev/null
rm -f ../../../alias/test-edge-unreadable
}
export NVM_DIR="$(cd ../../.. && pwd)"
: nvm.sh
\. "${NVM_DIR}/nvm.sh"
# Create an alias file that exists but can not be read
echo 'v0.0.1' > ../../../alias/test-edge-unreadable
chmod 000 ../../../alias/test-edge-unreadable
# root (and some containers) can read mode-000 files; nothing to assert there
if [ -r ../../../alias/test-edge-unreadable ]; then
cleanup
exit 0
fi
# like the sed/awk pipeline this replaced: empty output, success status, so
# that `nvm_ensure_default_set` does not overwrite an unreadable default alias
OUTPUT="$(nvm_alias test-edge-unreadable 2>/dev/null)"
EXIT_CODE=$?
[ "${EXIT_CODE}" = '0' ] || die "expected exit code 0 for unreadable alias file, got ${EXIT_CODE}"
[ -z "${OUTPUT}" ] || die "expected empty output for unreadable alias file, got >${OUTPUT}<"
STDERR="$(nvm_alias test-edge-unreadable 2>&1 >/dev/null)"
case "${STDERR}" in
*'not readable'*) ;;
*) die "expected a not-readable warning on stderr, got >${STDERR}<" ;;
esac
cleanup
@@ -1,17 +0,0 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
export NVM_DIR="$(cd ../../.. && pwd)"
: nvm.sh
\. "${NVM_DIR}/nvm.sh"
# Create an alias file with a very long line (version followed by a long comment)
LONG_COMMENT="$(printf '%0*d' 10000 0 | command tr '0' 'x')"
printf 'v22.1.0 #%s\n' "${LONG_COMMENT}" > ../../../alias/test-edge-long
ACTUAL="$(nvm_alias test-edge-long)"
EXPECTED='v22.1.0'
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}<, got >${ACTUAL}<"
rm -f ../../../alias/test-edge-long
@@ -1,16 +0,0 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
export NVM_DIR="$(cd ../../.. && pwd)"
: nvm.sh
\. "${NVM_DIR}/nvm.sh"
# Create an alias file with trailing spaces and tabs
printf '22.1.0 \t \n' > ../../../alias/test-edge-trailing-ws
ACTUAL="$(nvm_alias test-edge-trailing-ws)"
EXPECTED='22.1.0'
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}<, got >${ACTUAL}<"
rm -f ../../../alias/test-edge-trailing-ws
@@ -1,74 +0,0 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
export NVM_DIR="$(cd ../../.. && pwd)"
: nvm.sh
\. "${NVM_DIR}/nvm.sh"
# 1-hop self-reference: alias points to itself
echo 'self' > '../../../alias/self'
ACTUAL="$(nvm_resolve_alias self)"
EXPECTED='∞'
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}< for self-reference, got >${ACTUAL}<"
rm -f '../../../alias/self'
# Multi-hop loop: link1 -> link2 -> link3 -> link1
echo 'link2' > '../../../alias/link1'
echo 'link3' > '../../../alias/link2'
echo 'link1' > '../../../alias/link3'
ACTUAL="$(nvm_resolve_alias link1)"
EXPECTED='∞'
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}< for 3-hop cycle, got >${ACTUAL}<"
rm -f '../../../alias/link1' '../../../alias/link2' '../../../alias/link3'
# Cycle through an alias name containing a space
echo 'midway' > '../../../alias/foo bar'
echo 'foo bar' > '../../../alias/midway'
ACTUAL="$(nvm_resolve_alias 'foo bar')"
EXPECTED='∞'
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}< for space-name cycle, got >${ACTUAL}<"
rm -f '../../../alias/foo bar' '../../../alias/midway'
# Non-cycle through an alias name containing a space.
# Guards the newline-delimited SEEN_ALIASES storage: with space- or
# token-delimited storage, seen name 'foo bar' would falsely match 'bar'.
# Resolves: 'foo bar' -> 'midway' -> 'bar' -> 0.0.99
echo 'midway' > '../../../alias/foo bar'
echo 'bar' > '../../../alias/midway'
echo '0.0.99' > '../../../alias/bar'
ACTUAL="$(nvm_resolve_alias 'foo bar')"
EXPECTED='v0.0.99'
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}< for space-name chain, got >${ACTUAL}<"
rm -f '../../../alias/foo bar' '../../../alias/midway' '../../../alias/bar'
# Non-cycle through an alias name containing a regex metacharacter.
# Guards the literal `case` matching: the previous grep-based detection
# interpolated the resolved name into an anchored regex, so resolving 'axb'
# matched the seen name 'axb' against the pattern 'a.b' and falsely
# reported a cycle.
# Resolves: 'axb' -> 'a.b' -> 0.0.99
echo 'a.b' > '../../../alias/axb'
echo '0.0.99' > '../../../alias/a.b'
ACTUAL="$(nvm_resolve_alias axb)"
EXPECTED='v0.0.99'
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}< for metachar-name chain, got >${ACTUAL}<"
# ... while a genuine cycle through a metachar name is still detected
echo 'axb' > '../../../alias/a.b'
ACTUAL="$(nvm_resolve_alias axb)"
EXPECTED='∞'
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}< for metachar-name cycle, got >${ACTUAL}<"
rm -f '../../../alias/axb' '../../../alias/a.b'
@@ -1,20 +0,0 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
export NVM_DIR="$(cd ../../.. && pwd)"
: nvm.sh
\. "${NVM_DIR}/nvm.sh"
# Create a 4-deep alias chain: hop1 -> hop2 -> hop3 -> hop4 -> 0.0.99
echo 'hop2' > ../../../alias/hop1
echo 'hop3' > ../../../alias/hop2
echo 'hop4' > ../../../alias/hop3
echo '0.0.99' > ../../../alias/hop4
ACTUAL="$(nvm_resolve_alias hop1)"
EXPECTED='v0.0.99'
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}< for 4-deep chain, got >${ACTUAL}<"
rm -f ../../../alias/hop1 ../../../alias/hop2 ../../../alias/hop3 ../../../alias/hop4
@@ -1,19 +0,0 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
export NVM_DIR="$(cd ../../.. && pwd)"
: nvm.sh
\. "${NVM_DIR}/nvm.sh"
# Create an alias pointing to a version that does not exist as an alias
echo '99.99.99' > ../../../alias/test-edge-noexist
ACTUAL="$(nvm_resolve_alias test-edge-noexist)"
EXPECTED='v99.99.99'
EXIT_CODE="$(nvm_resolve_alias test-edge-noexist >/dev/null 2>&1; echo $?)"
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}<, got >${ACTUAL}<"
[ "${EXIT_CODE}" = '0' ] || die "expected exit code 0, got ${EXIT_CODE}"
rm -f ../../../alias/test-edge-noexist
-5
View File
@@ -17,8 +17,3 @@ rm -f "../../../alias/iojs"
rm -f "../../../alias/default"
rm -f "../../../alias/test-blank-lines"
rm -f "../../../alias/test-multi-lines"
rm -f "../../../alias/test-edge-"* "../../../alias/test edge spaces"
rm -f "../../../alias/self" "../../../alias/link1" "../../../alias/link2" "../../../alias/link3"
rm -f "../../../alias/foo bar" "../../../alias/midway" "../../../alias/bar"
rm -f "../../../alias/hop1" "../../../alias/hop2" "../../../alias/hop3" "../../../alias/hop4"
rm -f "../../../alias/axb" "../../../alias/a.b"
+1 -2
View File
@@ -30,10 +30,9 @@ nvm_echo "$CAPTURED_STDERR" | nvm_grep -q "${EXPECTED_ERR}" \
# --offline should not require curl or wget
nvm_has() { return 1; }
nvm_has_executable() { return 1; }
try_err nvm install --offline 999.999.999
# Should fail with "not found" not "nvm needs curl or wget"
nvm_echo "$CAPTURED_STDERR" | nvm_grep -q "curl or wget" \
&& die "nvm install --offline should not require curl or wget"
alias nvm_has='\nvm_has'
unset -f nvm_has nvm_has_executable
unset -f nvm_has
+18 -18
View File
@@ -1,11 +1,7 @@
#!/bin/sh
WORK="$PWD/nvm_curl_libz_support-work.$$"
TEST_BIN="$WORK/bin"
cleanup() {
rm -rf "$WORK"
export PATH="$OLDPATH"
unset -f curl
}
die() { cleanup; echo "$@" ; exit 1; }
@@ -13,28 +9,32 @@ die() { cleanup; echo "$@" ; exit 1; }
: nvm.sh
\. ../../../nvm.sh
\. ../../common.sh
OLDPATH="$PATH"
make_fake_curl "$TEST_BIN"
export PATH="$TEST_BIN:$OLDPATH"
# curl with libz feature
VERSION_MESSAGE="
curl() {
# curl with libz feature
if [ $# -ne 1 ] || [ "$1" != "-V" ]; then
die "This fake curl only takes one parameter -V"
fi
echo "
curl 7.47.0 (x86_64-pc-linux-gnu) libcurl/7.47.0 GnuTLS/3.4.10 zlib/1.2.8 libidn/1.32 librtmp/2.3
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP UnixSockets"
}
nvm_curl_libz_support || die "nvm_curl_libz_support should return 0"
# curl without libz feature
VERSION_MESSAGE="
unset -f curl
curl() {
# curl without libz feature
if [ "$#" -ne 1 ] || [ "$1" != "-V" ]; then
die "This fake curl only takes one parameter -V"
fi
echo "
curl 7.47.0 (x86_64-pc-linux-gnu) libcurl/7.47.0 GnuTLS/3.4.10 zlib/1.2.8 libidn/1.32 librtmp/2.32
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL TLS-SRP UnixSockets"
}
! nvm_curl_libz_support || die "nvm_curl_libz_support should return 1"
cleanup
unset -f curl
+5 -12
View File
@@ -1,25 +1,18 @@
#!/bin/sh
WORK="$PWD/nvm_curl_use_compression-work.$$"
TEST_BIN="$WORK/bin"
cleanup () {
unset -f die
rm -rf "$WORK"
export PATH="$OLDPATH"
}
die () { echo -e "$@" ; cleanup ; exit 1; }
NVM_ENV=testing \. ../../../nvm.sh
\. ../../common.sh
OLDPATH="$PATH"
make_fake_curl "$TEST_BIN"
export PATH="$TEST_BIN:$OLDPATH"
curl() {
if [ "$1" = "-V" ]; then
echo "${VERSION_MESSAGE}"
fi
}
CURL_VERSION_ON_ARCHLINUX_WITH_LIBZ="curl 7.54.0 (x86_64-pc-linux-gnu) libcurl/7.54.0 OpenSSL/1.1.0f zlib/1.2.11 libpsl/0.17.0 (+libicu/59.1) libssh2/1.8.0 nghttp2/1.22.0
Protocols: dict file ftp ftps gopher http https imap imaps pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp
+7 -13
View File
@@ -1,25 +1,19 @@
#!/bin/sh
WORK="$PWD/nvm_curl_version-work.$$"
TEST_BIN="$WORK/bin"
cleanup () {
unset -f die cleanup assert_version_is
rm -rf "$WORK"
export PATH="$OLDPATH"
unset -f die
unset -f curl
}
die () { echo -e "$@" ; cleanup ; exit 1; }
NVM_ENV=testing \. ../../../nvm.sh
\. ../../common.sh
OLDPATH="$PATH"
make_fake_curl "$TEST_BIN"
export PATH="$TEST_BIN:$OLDPATH"
curl() {
if [ "$1" = "-V" ]; then
echo "${VERSION_MESSAGE}"
fi
}
assert_version_is() {
if [ "${1}" != "${2}" ]; then
@@ -1,66 +0,0 @@
#!/bin/sh
WORK="$PWD/shadowed-curl-work.$$"
TEST_BIN="$WORK/bin"
ARGV_LOG="$WORK/argv.log"
cleanup () {
unset -f die cleanup curl wget
rm -rf "$WORK"
export PATH="$OLDPATH"
}
die () { echo "$@" ; cleanup ; exit 1; }
NVM_ENV=testing \. ../../../nvm.sh
\. ../../common.sh
OLDPATH="$PATH"
# real-looking curl on PATH; calling the shadowing shell function below instead is a failure
make_fake_curl "$TEST_BIN" ': > "$ARGV_LOG"
for a in "$@"; do printf "%s\n" "$a" >> "$ARGV_LOG"; done'
VERSION_MESSAGE="curl 7.99.0 (fake)
Features: libz"
export ARGV_LOG
export PATH="$TEST_BIN:$OLDPATH"
# shadowing shell functions, like aliases baked into a user's shell, must be
# bypassed in favor of the executables on PATH (https://github.com/nvm-sh/nvm/issues/2923)
curl() {
echo 'curl 0.0.0-shadowed'
return 1
}
wget() {
return 1
}
[ "$(nvm_curl_version)" = '7.99.0' ] || die "nvm_curl_version used the shadowing curl function; got $(nvm_curl_version)"
nvm_curl_libz_support || die 'nvm_curl_libz_support used the shadowing curl function'
nvm_download -s 'http://example.com/' -o /dev/null || die 'nvm_download used the shadowing curl function and failed'
grep -Fxq 'http://example.com/' "$ARGV_LOG" || die "nvm_download did not invoke the curl executable; got: $(cat "$ARGV_LOG")"
# when no curl executable exists, a shadowing curl shell function must not fool
# downloader selection: nvm_download must fall back to the wget executable
WGET_ONLY_BIN="$WORK/wget-only-bin"
mkdir -p "$WGET_ONLY_BIN"
{
echo '#!/bin/sh'
echo ': > "$ARGV_LOG"'
echo 'for a in "$@"; do printf "%s\n" "$a" >> "$ARGV_LOG"; done'
} > "$WGET_ONLY_BIN/wget"
chmod +x "$WGET_ONLY_BIN/wget"
(
PATH="$WGET_ONLY_BIN"
export PATH
nvm_download -s 'http://wget-fallback.example.com/' -o /dev/null
) || die 'nvm_download did not fall back to wget when curl is only a shell function'
grep -Fxq 'http://wget-fallback.example.com/' "$ARGV_LOG" || die "nvm_download did not invoke the wget executable; got: $(cat "$ARGV_LOG")"
cleanup
@@ -6,7 +6,7 @@ TEST_BIN="$WORK/bin"
ARGV_LOG="$WORK/argv.log"
cleanup() {
unset -f die cleanup nvm_has_executable
unset -f die cleanup nvm_has
rm -rf "$WORK"
export PATH="$OLDPATH"
}
@@ -30,7 +30,7 @@ chmod +x "$TEST_BIN/wget"
export ARGV_LOG
export PATH="$TEST_BIN:$OLDPATH"
# force the wget path while keeping system tools (sed) available for sanitization
nvm_has_executable() { [ "$1" != curl ] && command -v "$1" >/dev/null 2>&1; }
nvm_has() { [ "$1" != curl ] && command -v "$1" >/dev/null 2>&1; }
# given an Authorization credential in NVM_AUTH_HEADER
# when nvm_download uses the wget path
-2
View File
@@ -93,6 +93,4 @@ run_test amd64 osx x64
run_test arm64 smartos x64
run_test armv8l smartos x64
run_test loongarch64 linux loong64
cleanup
@@ -110,26 +110,3 @@ ACTUAL="$(nvm_get_download_slug iojs source 15.99.99)"
EXPECTED="iojs-15.99.99"
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}<, got >${ACTUAL}<"
REAL_OS="$(command uname -s 2>/dev/null || echo '')"
REAL_ARCH="$(command uname -m 2>/dev/null || echo '')"
if [ "${REAL_OS}" = "Darwin" ] && [ "${REAL_ARCH}" = "arm64" ]; then
# Node < 16 uses x64 on darwin-arm64
ACTUAL="$(nvm_get_download_slug node binary 14.21.3)"
EXPECTED='node-14.21.3-darwin-x64'
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}<, got >${ACTUAL}<"
ACTUAL="$(nvm_get_download_slug node binary 15.99.99)"
EXPECTED='node-15.99.99-darwin-x64'
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}<, got >${ACTUAL}<"
ACTUAL="$(nvm_get_download_slug iojs binary 15.99.99)"
EXPECTED='iojs-15.99.99-darwin-x64'
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}<, got >${ACTUAL}<"
# Test Node >= 16 uses arm64 on darwin-arm64
ACTUAL="$(nvm_get_download_slug node binary 16.0.0)"
EXPECTED='node-16.0.0-darwin-arm64'
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}<, got >${ACTUAL}<"
ACTUAL="$(nvm_get_download_slug node binary 18.0.0)"
EXPECTED='node-18.0.0-darwin-arm64'
[ "${ACTUAL}" = "${EXPECTED}" ] || die "expected >${EXPECTED}<, got >${ACTUAL}<"
fi
@@ -3,7 +3,7 @@
die () { echo "$@" ; cleanup ; exit 1; }
cleanup() {
unset -f nvm_has nvm_has_executable
unset -f nvm_has
}
: nvm.sh
@@ -12,7 +12,6 @@ cleanup() {
\. ../../common.sh
nvm_has() { return 1 ; }
nvm_has_executable() { return 1 ; }
try_err nvm_get_latest
[ "_$CAPTURED_STDERR" = "_nvm needs curl or wget to proceed." ] \
@@ -1,24 +1,10 @@
#!/bin/sh
die () { echo "$@" ; cleanup ; exit 1; }
cleanup() {
rm -rf "${TEST_DIR-}"
unset NVMRC_CONTENT OUTPUT EXIT_CODE TEST_DIR NVM_DIR
}
die () { echo "$@" ; exit 1; }
: nvm.sh
\. ../../../nvm.sh
\. ../../common.sh
# an isolated NVM_DIR with a single known version, so that ambient versions
# and aliases can not affect any assertions
TEST_DIR="${PWD}/nvm_ls_hash_pattern_tmp"
mkdir -p "${TEST_DIR}/versions/node/v24.13.0/bin" || die 'failed to create test version dir'
make_echo "${TEST_DIR}/versions/node/v24.13.0/bin/node" 'v24.13.0' || die 'failed to create test node binary'
NVM_DIR="${TEST_DIR}"
# Test: nvm_ls with pattern containing # should not cause sed error
# This is a regression test for https://github.com/nvm-sh/nvm/issues/3761
@@ -36,33 +22,3 @@ echo "$OUTPUT" | grep -q "invalid command code" && \
# Should return N/A with exit code 3 (not found)
[ "$EXIT_CODE" = "3" ] || die "nvm_ls 'foo#bar' should exit with code 3, got $EXIT_CODE"
echo "$OUTPUT" | grep -q "N/A" || die "nvm_ls 'foo#bar' should output N/A, got: $OUTPUT"
NVMRC_CONTENT='v24.13.0
# krypton is the codename for Node.js v24.x'
OUTPUT="$(nvm_ls "${NVMRC_CONTENT}" 2>&1)"
EXIT_CODE=$?
[ "$EXIT_CODE" = "0" ] || die "nvm_ls with .nvmrc comments should exit with code 0, got $EXIT_CODE"
[ "$OUTPUT" = "v24.13.0" ] || die "nvm_ls with .nvmrc comments should output v24.13.0, got: $OUTPUT"
OUTPUT="$(nvm_version "${NVMRC_CONTENT}" 2>&1)"
[ "$OUTPUT" = "v24.13.0" ] || die "nvm_version with .nvmrc comments should output v24.13.0, got: $OUTPUT"
# a partial version reaches nvm_ls's find/sed pipeline, which is where multiline
# patterns used to break sed ("unterminated regular expression" on BSD sed,
# "unterminated address regex" on GNU sed); a full x.y.z version takes the
# explicit-version fast path and never reaches it
NVMRC_CONTENT='24
# krypton is the codename for Node.js v24.x'
OUTPUT="$(nvm_ls "${NVMRC_CONTENT}" 2>&1)"
EXIT_CODE=$?
echo "$OUTPUT" | grep -q "unterminated" && \
die "nvm_ls with partial version + comments caused a sed error: $OUTPUT"
[ "$EXIT_CODE" = "0" ] || die "nvm_ls with partial version + comments should exit with code 0, got $EXIT_CODE"
[ "$OUTPUT" = "v24.13.0" ] || die "nvm_ls with partial version + comments should output v24.13.0, got: $OUTPUT"
cleanup
@@ -1,38 +0,0 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
\. ../../../nvm.sh
set -ex
# Test 1: all standard base64 characters (RFC 4648) are preserved
STANDARD_B64="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
RESULT=$(nvm_sanitize_auth_header "${STANDARD_B64}")
[ "${RESULT}" = "${STANDARD_B64}" ] || die "FAIL: standard base64 chars were stripped. Got: '${RESULT}'"
# Test 2: all base64url characters (RFC 4648 §5) are preserved
B64URL="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_="
RESULT=$(nvm_sanitize_auth_header "${B64URL}")
[ "${RESULT}" = "${B64URL}" ] || die "FAIL: base64url chars were stripped. Got: '${RESULT}'"
# Test 3: a real JWT Bearer token (base64url-encoded header.payload.signature) is preserved
JWT_TOKEN="Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
RESULT=$(nvm_sanitize_auth_header "${JWT_TOKEN}")
[ "${RESULT}" = "${JWT_TOKEN}" ] || die "FAIL: JWT Bearer token chars were stripped. Got: '${RESULT}'"
# Test 4: Basic auth (base64-encoded user:pass) is preserved
BASIC_TOKEN="Basic dXNlcm5hbWU6cGFzc3dvcmQ="
RESULT=$(nvm_sanitize_auth_header "${BASIC_TOKEN}")
[ "${RESULT}" = "${BASIC_TOKEN}" ] || die "FAIL: Basic auth base64 token chars were stripped. Got: '${RESULT}'"
# Test 5: dangerous shell metacharacters are removed
DANGEROUS="Bearer token;\`evil\`-cmd \$(inject)"
RESULT=$(nvm_sanitize_auth_header "${DANGEROUS}")
case "${RESULT}" in
*";"*|*"\`"*|*'$'*|*"("*|*")"*)
die "FAIL: dangerous shell metacharacters survived sanitization. Got: '${RESULT}'"
;;
esac
echo "All nvm_sanitize_auth_header tests passed"
+2 -2
View File
@@ -32,9 +32,9 @@ fi
# Test 2: Verify that sanitized header still works for legitimate requests
# The sanitized header should only contain safe characters
SANITIZED=$(nvm_sanitize_auth_header "${MALICIOUS_HEADER}")
# Verify that dangerous shell metacharacters were removed
# Verify that dangerous characters were removed
case "${SANITIZED}" in
*";"*|*'$'*|*"\`"*)
*";"*|*"touch"*|*"/tmp"*)
die "SECURITY FAILURE: Sanitization did not remove dangerous characters properly"
;;
esac
@@ -72,19 +72,19 @@ npm list --global | grep object-is > /dev/null || die "object-is isn't installed
# install a fresh global package on the source (v9.7.0) only
nvm use 9.7.0
npm install -g is-nan@1.0.1 || die "npm install -g is-nan failed"
npm list --global --depth=0 | grep is-nan > /dev/null || die "is-nan isn't installed on v9.7.0"
npm install -g is-nan@1.0.0 || die "npm install -g is-nan failed"
npm list --global | grep is-nan > /dev/null || die "is-nan isn't installed on v9.7.0"
# precondition: the already-installed target (v9.10.0) must not have it yet
nvm use 9.10.0
npm list --global --depth=0 | grep is-nan > /dev/null && die "is-nan should not be installed on v9.10.0 before reinstall"
npm list --global | grep is-nan > /dev/null && die "is-nan should not be installed on v9.10.0 before reinstall"
# target is already installed, so this should report so AND still migrate packages
OUTPUT="$(nvm install --reinstall-packages-from=9.7.0 9.10.0 2>&1)" || die "nvm install --reinstall-packages-from=9.7.0 9.10.0 failed: ${OUTPUT}"
nvm_echo "${OUTPUT}" | grep "is already installed" > /dev/null || die "expected 'already installed' message, got: ${OUTPUT}"
nvm use 9.10.0
npm list --global --depth=0 | grep is-nan > /dev/null || die "is-nan was not migrated to already-installed v9.10.0"
npm list --global | grep is-nan > /dev/null || die "is-nan was not migrated to already-installed v9.10.0"
# ...the same is true for default packages: they must install on the already-installed path too.
@@ -99,11 +99,11 @@ nvm_echo 'object-inspect@1.0.2' > "${DEFAULT_PACKAGES_FILE}"
# precondition: the already-installed target must not have the default package yet
nvm use 9.10.0
npm list --global --depth=0 | grep object-inspect > /dev/null && die "object-inspect should not be installed on v9.10.0 before default-packages test"
npm list --global | grep object-inspect > /dev/null && die "object-inspect should not be installed on v9.10.0 before default-packages test"
nvm install 9.10.0 > /dev/null 2>&1 || die "nvm install 9.10.0 (already installed, default-packages) failed"
nvm use 9.10.0
npm list --global --depth=0 | grep object-inspect > /dev/null || die "default packages were not installed on already-installed v9.10.0"
npm list --global | grep object-inspect > /dev/null || die "default packages were not installed on already-installed v9.10.0"
# restore the default-packages file before the next assertion
cleanup
-5
View File
@@ -1,5 +0,0 @@
if [ "_$1" = "_-m" ]; then
echo "loongarch64"
else
echo "Linux foo 6.12.54-16k #0 SMP PREEMPT_DYNAMIC Fri Dec 5 12:35:43 UTC loongarch64 GNU/Linux"
fi
+26 -42
View File
@@ -2,46 +2,37 @@
die () { echo "$@" ; cleanup ; exit 1; }
WORK="$PWD/nvm_get_latest-work.$$"
TEST_BIN="$WORK/bin"
cleanup() {
unset -f curl wget
rm -rf "$WORK"
export PATH="$OLDPATH"
unset -f curl wget nvm_has
}
\. ../../../nvm.sh
\. ../../common.sh
OLDPATH="$PATH"
EXPECTED_VERSION="v12.3.456"
URL="https://github.com/nvm-sh/nvm/releases/tag/$EXPECTED_VERSION"
EXPECTED_CURL_ARGS="--compressed -q -w %{url_effective}\n -L -s -S https://latest.nvm.sh -o /dev/null"
EXPECTED_WGET_ARGS="-q https://latest.nvm.sh --server-response -O /dev/null"
export URL EXPECTED_CURL_ARGS EXPECTED_WGET_ARGS
VERSION_MESSAGE="
curl() {
if [ $# -eq 1 ] && [ "$1" = "-V" ]; then
echo "
curl 7.47.0 (x86_64-pc-linux-gnu) libcurl/7.47.0 GnuTLS/3.4.10 zlib/1.2.8 libidn/1.32 librtmp/2.3
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP UnixSockets"
# fake curl/wget: `command curl` bypasses shell functions, so the mocks must be executables on PATH
make_fake_curl "$TEST_BIN" 'if [ "_$*" != "_$EXPECTED_CURL_ARGS" ]; then
echo >&2 "expected args ($EXPECTED_CURL_ARGS), got ($*)"
exit 1
fi
echo "$URL"'
cat > "$TEST_BIN/wget" <<'OUTER'
#!/bin/sh
if [ "_$*" != "_$EXPECTED_WGET_ARGS" ]; then
echo >&2 "expected args ($EXPECTED_WGET_ARGS), got ($*)"
exit 1
fi
cat >&2 <<CONTENTS
elif [ "_$*" != "_$EXPECTED_CURL_ARGS" ]; then
echo >&2 "expected args ($EXPECTED_CURL_ARGS), got ($*)"
return 1
else
echo $URL
fi
}
wget() {
if [ "_$*" != "_$EXPECTED_WGET_ARGS" ]; then
echo >&2 "expected args ($EXPECTED_WGET_ARGS), got ($*)"
return 1
else
local WGET_CONTENTS
WGET_CONTENTS="
HTTP/1.1 301 Moved Permanently
Location: https://github.com/nvm-sh/nvm/releases/latest
Content-Type: text/html; charset=utf-8
@@ -91,23 +82,16 @@ cat >&2 <<CONTENTS
X-Content-Type-Options: nosniff
Vary: Accept-Encoding
X-Served-By: 926b734ea1992f8ee1f88ab967a93dac
CONTENTS
OUTER
chmod +x "$TEST_BIN/wget"
export PATH="$TEST_BIN:$OLDPATH"
# shadowing shell functions, like aliases baked into a user's shell, must be
# bypassed in favor of the executables on PATH (https://github.com/nvm-sh/nvm/issues/2923)
curl() {
echo >&2 'the shadowing curl function was called'
return 1
}
wget() {
echo >&2 'the shadowing wget function was called'
return 1
"
"$WGET_CONTENTS" | while read line
do
>&2 echo "$line"
done
fi
}
\. ../../common.sh
try nvm_get_latest
[ "_$CAPTURED_STDOUT" = "_$EXPECTED_VERSION" ] \
|| die "success path did not return version '$EXPECTED_VERSION', got '$CAPTURED_STDOUT'"
@@ -2,26 +2,20 @@
die () { echo "$@" ; cleanup ; exit 1; }
WORK="$PWD/nvm_get_latest_failed_redirect-work.$$"
TEST_BIN="$WORK/bin"
cleanup() {
rm -rf "$WORK"
export PATH="$OLDPATH"
unset -f curl wget
}
\. ../../../nvm.sh
\. ../../common.sh
OLDPATH="$PATH"
# fake curl/wget: `command curl` bypasses shell functions, so the mocks must be executables on PATH
make_fake_curl "$TEST_BIN" 'exit 1'
printf '#!/bin/sh\nexit 1\n' > "$TEST_BIN/wget"
chmod +x "$TEST_BIN/wget"
export PATH="$TEST_BIN:$OLDPATH"
curl() {
return 1
}
wget() {
return 1
}
try_err nvm_get_latest
[ "_$CAPTURED_STDERR" = "_https://latest.nvm.sh did not redirect to the latest release on GitHub" ] \