1
0
mirror of https://github.com/nvm-sh/nvm.git synced 2026-10-23 14:53:45 +08:00

[Fix] nvm_alias: unset zsh's extendedglob while reading an alias file

`#` is a repetition operator under `extendedglob`,
so `${VAR%%#*}` is an invalid pattern:
zsh aborts the expansion and `nvm_alias` silently prints nothing,
leaving the `default` alias unresolved when nvm.sh is sourced.

The read loop moves into `nvm_print_alias_file` so that `local_options`
scopes the option change to that section alone,
rather than to everything `nvm_alias` does,
and restores whatever the caller had set.

Fixes #3885

Co-authored-by: debaditya <debaditya2005hait@gmail.com>
Co-authored-by: Jordan Harband <ljharb@gmail.com>
This commit is contained in:
debaditya
2026-09-03 20:54:28 -07:00
committed by Jordan Harband
parent 25b378323c
commit 80e700ec21
3 changed files with 105 additions and 11 deletions

30
nvm.sh
View File

@@ -1427,6 +1427,23 @@ nvm_list_aliases() {
return
}
nvm_print_alias_file() {
# a `#` pattern is a repetition operator under zsh's `extendedglob`, so it is
# unset for the length of this function and restored by `local_options`
nvm_is_zsh && setopt local_options noextendedglob
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 < "$1"
}
nvm_alias() {
local ALIAS
ALIAS="${1-}"
@@ -1457,16 +1474,7 @@ nvm_alias() {
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}"
nvm_print_alias_file "${NVM_ALIAS_PATH}"
}
nvm_ls_current() {
@@ -4961,7 +4969,7 @@ nvm() {
nvm_has_solaris_binary nvm_is_merged_node_version \
nvm_is_natural_num 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_path \
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_node_version_has_solaris_binary nvm_iojs_version_has_solaris_binary \

View File

@@ -0,0 +1,53 @@
#!/bin/sh
die () { echo "$@" ; cleanup ; exit 1; }
cleanup () {
rm -rf "${TEST_DIR-}"
unset TEST_DIR OUTPUT EXPECTED
}
: nvm.sh
\. ../../../nvm.sh
TEST_DIR="${PWD}/nvm_print_alias_file_tmp"
mkdir -p "${TEST_DIR}" || die 'failed to create test dir'
printf 'v0.10 # inline comment\n' > "${TEST_DIR}/inline"
OUTPUT="$(nvm_print_alias_file "${TEST_DIR}/inline")"
[ "_${OUTPUT}" = '_v0.10' ] || die "inline comment not stripped; got '${OUTPUT}'"
printf '# leading comment\nv0.11\n' > "${TEST_DIR}/comment-first"
OUTPUT="$(nvm_print_alias_file "${TEST_DIR}/comment-first")"
[ "_${OUTPUT}" = '_v0.11' ] || die "comment-only line not skipped; got '${OUTPUT}'"
printf 'v0.12\t \nv0.13\n' > "${TEST_DIR}/trailing-space"
OUTPUT="$(nvm_print_alias_file "${TEST_DIR}/trailing-space")"
EXPECTED="$(printf 'v0.12\nv0.13')"
[ "_${OUTPUT}" = "_${EXPECTED}" ] || die "trailing whitespace not stripped, or a line was dropped; got '${OUTPUT}'"
printf '\n \n\t\n# only a comment\n' > "${TEST_DIR}/nothing-usable"
OUTPUT="$(nvm_print_alias_file "${TEST_DIR}/nothing-usable")"
[ "_${OUTPUT}" = '_' ] || die "blank and comment-only lines should produce no output; got '${OUTPUT}'"
nvm_print_alias_file "${TEST_DIR}/nothing-usable" >/dev/null 2>&1 \
|| die 'a file with no usable lines should still exit zero'
printf 'v0.14' > "${TEST_DIR}/no-trailing-newline"
OUTPUT="$(nvm_print_alias_file "${TEST_DIR}/no-trailing-newline")"
[ "_${OUTPUT}" = '_v0.14' ] || die "a final line with no trailing newline should be read; got '${OUTPUT}'"
# a `#` pattern is a repetition operator under zsh's `extendedglob`, which is what
# made `nvm_alias` print nothing at all: https://github.com/nvm-sh/nvm/issues/3885
if [ -n "${ZSH_VERSION-}" ]; then
setopt extendedglob || die 'enabling extendedglob failed'
OUTPUT="$(nvm_print_alias_file "${TEST_DIR}/inline")"
[ "_${OUTPUT}" = '_v0.10' ] || die "with extendedglob set, expected 'v0.10', got '${OUTPUT}'"
[[ -o extendedglob ]] || die 'nvm_print_alias_file left extendedglob unset'
unsetopt extendedglob
nvm_print_alias_file "${TEST_DIR}/inline" > /dev/null
[[ -o extendedglob ]] && die 'nvm_print_alias_file left extendedglob set'
fi
cleanup

View File

@@ -0,0 +1,33 @@
#!/bin/sh
die () { echo "$@" ; exit 1; }
\. ../common.sh
# `#` is a pattern operator only under zsh's `extendedglob`
[ -n "${ZSH_VERSION-}" ] || exit 0
# We need to unload nvm again first, as by the time this test is run,
# despite being unloaded in setup, the inherited PATH still contains
# an nvm-installed node version. `nvm unload` unsets NVM_DIR, so cache it.
\. ../../nvm.sh || die 'sourcing returned nonzero exit code'
NVM_DIR_CACHED="${NVM_DIR}"
nvm unload || die 'unloading returned nonzero exit code'
NVM_DIR="${NVM_DIR_CACHED}"
# the comment is the point: stripping it is what needs a `#` pattern
echo '0.10.1 # comment' > "${NVM_DIR}/alias/default" || die 'creation of default alias failed'
setopt extendedglob || die 'enabling extendedglob failed'
\. ../../nvm.sh || die 'sourcing returned nonzero exit code'
[[ -o extendedglob ]] || die 'sourcing nvm.sh disabled extendedglob'
NVM_LS_CURRENT_NOT_GREPPED="$(nvm ls current | strip_colors)"
NVM_LS_CURRENT="$(nvm ls current | strip_colors | \grep -o 'v0\.10\.1')"
[ "_${NVM_LS_CURRENT}" = '_v0.10.1' ] || die "'nvm ls current' did not return '-> v0.10.1', got '${NVM_LS_CURRENT_NOT_GREPPED}'"
NVM_ALIAS_DEFAULT="$(nvm alias default | strip_colors)"
[ "_${NVM_ALIAS_DEFAULT}" = '_default -> 0.10.1 (-> v0.10.1 *)' ] \
|| die "'nvm alias default' did not return 'default -> 0.10.1 (-> v0.10.1 *)', got '${NVM_ALIAS_DEFAULT}'"