diff --git a/nvm.sh b/nvm.sh index 7268de2570..b4883dfc8d 100755 --- a/nvm.sh +++ b/nvm.sh @@ -1323,7 +1323,16 @@ nvm_alias() { return 2 fi - command sed 's/#.*//; s/[[:space:]]*$//' "${NVM_ALIAS_PATH}" | command awk 'NF' + 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}" } nvm_ls_current() { @@ -1356,24 +1365,32 @@ nvm_resolve_alias() { local ALIAS ALIAS="${PATTERN}" local ALIAS_TEMP + local ALIAS_OUTPUT local SEEN_ALIASES - SEEN_ALIASES="${ALIAS}" - local NVM_ALIAS_INDEX - NVM_ALIAS_INDEX=1 + SEEN_ALIASES=" +${ALIAS} +" while true; do - ALIAS_TEMP="$( (nvm_alias "${ALIAS}" 2>/dev/null | command head -n "${NVM_ALIAS_INDEX}" | command tail -n 1) || nvm_echo)" + ALIAS_OUTPUT="$(nvm_alias "${ALIAS}" 2>/dev/null)" || ALIAS_OUTPUT='' + ALIAS_TEMP="${ALIAS_OUTPUT%% +*}" if [ -z "${ALIAS_TEMP}" ]; then break fi - if command printf '%b' "${SEEN_ALIASES}" | nvm_grep -q -e "^${ALIAS_TEMP}$"; then - ALIAS="∞" - break - fi + case "${SEEN_ALIASES}" in + *" +${ALIAS_TEMP} +"*) + ALIAS="∞" + break + ;; + esac - SEEN_ALIASES="${SEEN_ALIASES}\\n${ALIAS_TEMP}" + SEEN_ALIASES="${SEEN_ALIASES}${ALIAS_TEMP} +" ALIAS="${ALIAS_TEMP}" done diff --git a/test/fast/Aliases/nvm_alias handles alias with spaces in name b/test/fast/Aliases/nvm_alias handles alias with spaces in name new file mode 100755 index 0000000000..645461a833 --- /dev/null +++ b/test/fast/Aliases/nvm_alias handles alias with spaces in name @@ -0,0 +1,17 @@ +#!/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" diff --git a/test/fast/Aliases/nvm_alias handles binary data after version b/test/fast/Aliases/nvm_alias handles binary data after version new file mode 100755 index 0000000000..7c9de414da --- /dev/null +++ b/test/fast/Aliases/nvm_alias handles binary data after version @@ -0,0 +1,20 @@ +#!/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 diff --git a/test/fast/Aliases/nvm_alias handles carriage return b/test/fast/Aliases/nvm_alias handles carriage return new file mode 100755 index 0000000000..ba44f1c855 --- /dev/null +++ b/test/fast/Aliases/nvm_alias handles carriage return @@ -0,0 +1,22 @@ +#!/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 diff --git a/test/fast/Aliases/nvm_alias handles comment-only alias file b/test/fast/Aliases/nvm_alias handles comment-only alias file new file mode 100755 index 0000000000..cb8fa077ff --- /dev/null +++ b/test/fast/Aliases/nvm_alias handles comment-only alias file @@ -0,0 +1,17 @@ +#!/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 diff --git a/test/fast/Aliases/nvm_alias handles embedded NUL b/test/fast/Aliases/nvm_alias handles embedded NUL new file mode 100755 index 0000000000..28b3dbb621 --- /dev/null +++ b/test/fast/Aliases/nvm_alias handles embedded NUL @@ -0,0 +1,22 @@ +#!/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 diff --git a/test/fast/Aliases/nvm_alias handles empty alias file b/test/fast/Aliases/nvm_alias handles empty alias file new file mode 100755 index 0000000000..3f80c73ad4 --- /dev/null +++ b/test/fast/Aliases/nvm_alias handles empty alias file @@ -0,0 +1,17 @@ +#!/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 diff --git a/test/fast/Aliases/nvm_alias handles form feed and vertical tab b/test/fast/Aliases/nvm_alias handles form feed and vertical tab new file mode 100755 index 0000000000..c2612418a7 --- /dev/null +++ b/test/fast/Aliases/nvm_alias handles form feed and vertical tab @@ -0,0 +1,22 @@ +#!/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 diff --git a/test/fast/Aliases/nvm_alias handles no trailing newline b/test/fast/Aliases/nvm_alias handles no trailing newline new file mode 100755 index 0000000000..c87a22ae57 --- /dev/null +++ b/test/fast/Aliases/nvm_alias handles no trailing newline @@ -0,0 +1,16 @@ +#!/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 diff --git a/test/fast/Aliases/nvm_alias handles very long line b/test/fast/Aliases/nvm_alias handles very long line new file mode 100755 index 0000000000..face2e510d --- /dev/null +++ b/test/fast/Aliases/nvm_alias handles very long line @@ -0,0 +1,17 @@ +#!/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 diff --git a/test/fast/Aliases/nvm_alias strips trailing whitespace b/test/fast/Aliases/nvm_alias strips trailing whitespace new file mode 100755 index 0000000000..caf53bb4a9 --- /dev/null +++ b/test/fast/Aliases/nvm_alias strips trailing whitespace @@ -0,0 +1,16 @@ +#!/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 diff --git a/test/fast/Aliases/nvm_resolve_alias handles cycles b/test/fast/Aliases/nvm_resolve_alias handles cycles new file mode 100755 index 0000000000..3856742646 --- /dev/null +++ b/test/fast/Aliases/nvm_resolve_alias handles cycles @@ -0,0 +1,52 @@ +#!/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. +# Token-based cycle detection would false-positive on this chain because +# 'bar' appears as a substring of the multi-token alias name 'foo 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' diff --git a/test/fast/Aliases/nvm_resolve_alias handles deep chain b/test/fast/Aliases/nvm_resolve_alias handles deep chain new file mode 100755 index 0000000000..435d35ed67 --- /dev/null +++ b/test/fast/Aliases/nvm_resolve_alias handles deep chain @@ -0,0 +1,20 @@ +#!/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 diff --git a/test/fast/Aliases/nvm_resolve_alias with nonexistent target b/test/fast/Aliases/nvm_resolve_alias with nonexistent target new file mode 100755 index 0000000000..48eb9596a7 --- /dev/null +++ b/test/fast/Aliases/nvm_resolve_alias with nonexistent target @@ -0,0 +1,19 @@ +#!/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