Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 27 additions & 10 deletions nvm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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

Expand Down
17 changes: 17 additions & 0 deletions test/fast/Aliases/nvm_alias handles alias with spaces in name
Original file line number Diff line number Diff line change
@@ -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"
20 changes: 20 additions & 0 deletions test/fast/Aliases/nvm_alias handles binary data after version
Original file line number Diff line number Diff line change
@@ -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
22 changes: 22 additions & 0 deletions test/fast/Aliases/nvm_alias handles carriage return
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions test/fast/Aliases/nvm_alias handles comment-only alias file
Original file line number Diff line number Diff line change
@@ -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
22 changes: 22 additions & 0 deletions test/fast/Aliases/nvm_alias handles embedded NUL
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions test/fast/Aliases/nvm_alias handles empty alias file
Original file line number Diff line number Diff line change
@@ -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
22 changes: 22 additions & 0 deletions test/fast/Aliases/nvm_alias handles form feed and vertical tab
Original file line number Diff line number Diff line change
@@ -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
16 changes: 16 additions & 0 deletions test/fast/Aliases/nvm_alias handles no trailing newline
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions test/fast/Aliases/nvm_alias handles very long line
Original file line number Diff line number Diff line change
@@ -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
16 changes: 16 additions & 0 deletions test/fast/Aliases/nvm_alias strips trailing whitespace
Original file line number Diff line number Diff line change
@@ -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
52 changes: 52 additions & 0 deletions test/fast/Aliases/nvm_resolve_alias handles cycles
Original file line number Diff line number Diff line change
@@ -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'
20 changes: 20 additions & 0 deletions test/fast/Aliases/nvm_resolve_alias handles deep chain
Original file line number Diff line number Diff line change
@@ -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)"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth adding a sibling test for cycles (1-hop self-reference and a multi-hop loop). The existing test/fast/Aliases/circular/ fixtures already cover this - making sure they keep passing would have flagged the cycle-detection regression on the nvm.sh side.

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
19 changes: 19 additions & 0 deletions test/fast/Aliases/nvm_resolve_alias with nonexistent target
Original file line number Diff line number Diff line change
@@ -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
Loading