From 16df5aa69aeda3814703e14f9d35a4f60220dd37 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 2 Nov 2026 12:24:48 -0600 Subject: [PATCH] [Fix] `nvm_filter_installed_versions`, `nvm_filter_lts_versions`, `nvm_record_lts_status`, `nvm_get_default_packages`: keep backslashes in `NVM_DIR` paths These passed a path under `$NVM_DIR` to awk with `-v`, which processes backslash escapes, so with a backslash in `NVM_DIR` awk opened the wrong file: the LTS status record read as empty, so `nvm_filter_installed_versions` treated every version as non-LTS and `nvm_filter_lts_versions` matched none, `nvm_record_lts_status` could not merge an index without LTS versions into the record, and `nvm_get_default_packages` named the wrong file in its error. The LTS functions now pass the path through the environment, like `nvm_strip_path` and `nvm_replace_literal`; `nvm_get_default_packages` uses awk's `FILENAME`, which is taken literally. --- nvm.sh | 16 +++--- ...ult packages handle backslashes in NVM_DIR | 51 +++++++++++++++++++ 2 files changed, 60 insertions(+), 7 deletions(-) create mode 100755 test/fast/Unit tests/LTS status and default packages handle backslashes in NVM_DIR diff --git a/nvm.sh b/nvm.sh index 69da5a8f..990506ed 100755 --- a/nvm.sh +++ b/nvm.sh @@ -2252,12 +2252,12 @@ nvm_filter_installed_versions() { return 3 fi fi - command awk \ + NVM_LTS_STATUS_PATH="$(nvm_lts_status_path)" command awk \ -v only=",${NVM_ONLY_SELECTED:-${NVM_ONLY:-lts,non-lts,iojs}}," \ - -v needs_lts_status="${NVM_NEEDS_LTS_STATUS}" \ - -v record="$(nvm_lts_status_path)" ' + -v needs_lts_status="${NVM_NEEDS_LTS_STATUS}" ' BEGIN { if (needs_lts_status) { + record = ENVIRON["NVM_LTS_STATUS_PATH"]; while ((getline line < record) > 0) { split(line, fields, " "); lts[fields[1]] = fields[2]; @@ -2589,7 +2589,8 @@ nvm_record_lts_status() { local NVM_LTS_STATUS_TMP NVM_LTS_STATUS_TMP="$(command mktemp "${NVM_LTS_STATUS_PATH}.XXXXXX" 2>/dev/null)" || return 1 # anything without a full row, like an error page, is not an index - if command awk -v previous="${NVM_LTS_STATUS_PATH}" ' + if NVM_LTS_STATUS_PATH="${NVM_LTS_STATUS_PATH}" command awk ' + BEGIN { previous = ENVIRON["NVM_LTS_STATUS_PATH"] } NF >= 10 && $1 ~ /^v[0-9]+\.[0-9]+\.[0-9]+/ { if (!($1 in lts)) { versions[++rows] = $1 } lts[$1] = ($10 ~ /^-?$/ ? "-" : tolower($10)); @@ -2630,8 +2631,9 @@ nvm_filter_lts_versions() { nvm_err 'The LTS status of installed versions is not recorded yet; run `nvm ls-remote` to record it.' return 3 fi - command awk -v lts="${1-}" -v record="$(nvm_lts_status_path)" ' + NVM_LTS_STATUS_PATH="$(nvm_lts_status_path)" command awk -v lts="${1-}" ' BEGIN { + record = ENVIRON["NVM_LTS_STATUS_PATH"]; while ((getline line < record) > 0) { split(line, fields, " "); status[fields[1]] = fields[2]; @@ -6548,11 +6550,11 @@ nvm_get_default_packages() { local NVM_DEFAULT_PACKAGE_FILE NVM_DEFAULT_PACKAGE_FILE="${NVM_DIR}/default-packages" if [ -f "${NVM_DEFAULT_PACKAGE_FILE}" ]; then - command awk -v filename="${NVM_DEFAULT_PACKAGE_FILE}" ' + command awk ' /^[ \t]*#/ { next } # Skip lines that begin with # /^[ \t]*$/ { next } # Skip empty lines /[ \t]/ && !/^[ \t]*#/ { - print "Only one package per line is allowed in `" filename "`. Please remove any lines with multiple space-separated values." > "/dev/stderr" + print "Only one package per line is allowed in `" FILENAME "`. Please remove any lines with multiple space-separated values." > "/dev/stderr" err = 1 exit 1 } diff --git a/test/fast/Unit tests/LTS status and default packages handle backslashes in NVM_DIR b/test/fast/Unit tests/LTS status and default packages handle backslashes in NVM_DIR new file mode 100755 index 00000000..4acff3a8 --- /dev/null +++ b/test/fast/Unit tests/LTS status and default packages handle backslashes in NVM_DIR @@ -0,0 +1,51 @@ +#!/bin/sh + +ORIG_NVM_DIR="${NVM_DIR}" +TMP_DIR='' + +cleanup () { + case "${TMP_DIR}" in + /*) rm -rf "${TMP_DIR}" ;; + esac + NVM_DIR="${ORIG_NVM_DIR}" + export NVM_DIR +} + +die () { echo "$@" ; cleanup ; exit 1; } + +: nvm.sh +\. ../../../nvm.sh + +TMP_DIR="$(mktemp -d)" + +row () { + printf '%s\t2024-01-01\tsrc\t-\t-\t-\t-\t-\t-\t%s\t-\n' "$1" "$2" +} + +# awk's `-v` would turn these into a tab, a newline, and a single backslash +for DIR in 'nvm\test' 'nvm\new' 'nvm\\dir'; do + NVM_DIR="${TMP_DIR}/${DIR}" + export NVM_DIR + mkdir -p "${NVM_DIR}" || die "could not create NVM_DIR ${NVM_DIR}" + + { row v24.11.0 Krypton; row v24.0.0 -; } | nvm_record_lts_status || die "nvm_record_lts_status should record LTS status under ${NVM_DIR}" + # an index without LTS versions only merges into a record it can read + row v26.0.0 - | nvm_record_lts_status || die "nvm_record_lts_status should merge into the record under ${NVM_DIR}" + + OUTPUT="$(printf 'v24.11.0\nv24.0.0\nv26.0.0\n' | nvm_filter_lts_versions '*')" + [ "${OUTPUT}" = 'v24.11.0' ] || die "nvm_filter_lts_versions should read the record under ${NVM_DIR}; got >${OUTPUT}<" + OUTPUT="$(printf 'v24.11.0\nv24.0.0\n' | nvm_filter_lts_versions krypton)" + [ "${OUTPUT}" = 'v24.11.0' ] || die "nvm_filter_lts_versions krypton should read the record under ${NVM_DIR}; got >${OUTPUT}<" + + OUTPUT="$(printf 'v24.11.0\nv24.0.0\nv26.0.0\n' | NVM_ONLY=lts nvm_filter_installed_versions | tr '\n' ' ')" + [ "${OUTPUT}" = 'v24.11.0 ' ] || die "NVM_ONLY=lts nvm_filter_installed_versions should read the record under ${NVM_DIR}; got >${OUTPUT}<" + OUTPUT="$(printf 'v24.11.0\nv24.0.0\nv26.0.0\n' | NVM_ONLY=non-lts nvm_filter_installed_versions | tr '\n' ' ')" + [ "${OUTPUT}" = 'v24.0.0 v26.0.0 ' ] || die "NVM_ONLY=non-lts nvm_filter_installed_versions should read the record under ${NVM_DIR}; got >${OUTPUT}<" + + printf 'yarn pnpm\n' > "${NVM_DIR}/default-packages" + OUTPUT="$(nvm_get_default_packages 2>&1)" + EXPECTED="Only one package per line is allowed in \`${NVM_DIR}/default-packages\`. Please remove any lines with multiple space-separated values." + nvm_echo "${OUTPUT}" | nvm_grep -qxF -e "${EXPECTED}" || die "nvm_get_default_packages should name the file under ${NVM_DIR}; got >${OUTPUT}<" +done + +cleanup