From 84b176d46d254e68aee8fbf50f5bb0ce72d3f990 Mon Sep 17 00:00:00 2001 From: Wei-Lun Li Date: Mon, 2 Nov 2026 01:47:37 +0900 Subject: [PATCH] [Fix] `nvm_get_default_packages`: ignore CRLF line endings A `default-packages` file saved with Windows line endings left a carriage return on every package name, and turned each blank line into a lone `\r` entry, so `npm install -g` was given `rimraf\r` and failed. Strip a trailing `\r` from each line before parsing it, like `.nvmrc` parsing already tolerates. Assisted-By: Claude Opus 5.5 Signed-off-by: Wei-Lun Li --- nvm.sh | 1 + test/fast/Unit tests/nvm_get_default_packages | 10 ++++++++++ .../Unit tests/nvm_get_default_packages mawk compat | 10 ++++++++++ 3 files changed, 21 insertions(+) diff --git a/nvm.sh b/nvm.sh index 58f2704d..54a0d376 100755 --- a/nvm.sh +++ b/nvm.sh @@ -6600,6 +6600,7 @@ nvm_get_default_packages() { NVM_DEFAULT_PACKAGE_FILE="${NVM_DIR}/default-packages" if [ -f "${NVM_DEFAULT_PACKAGE_FILE}" ]; then command awk ' + { sub(/\r$/, "") } # Strip CRLF line endings /^[ \t]*#/ { next } # Skip lines that begin with # /^[ \t]*$/ { next } # Skip empty lines /[ \t]/ && !/^[ \t]*#/ { diff --git a/test/fast/Unit tests/nvm_get_default_packages b/test/fast/Unit tests/nvm_get_default_packages index 9e10214f..aa91ed17 100755 --- a/test/fast/Unit tests/nvm_get_default_packages +++ b/test/fast/Unit tests/nvm_get_default_packages @@ -93,3 +93,13 @@ EXPECTED_PKGS='' touch "${FILE}" cleanup + +setup + +printf 'rimraf\r\nobject-inspect@1.0.2\r\n\r\n# commented-package\r\n\r\nstevemao/left-pad\r\n' > "${FILE}" + +DEFAULT_PKGS="$(nvm_get_default_packages)" +EXPECTED_PKGS='rimraf object-inspect@1.0.2 stevemao/left-pad' +[ "${DEFAULT_PKGS}" = "${EXPECTED_PKGS}" ] || die "6: expected default packages from a CRLF file >${EXPECTED_PKGS}<; got >${DEFAULT_PKGS}<" + +cleanup diff --git a/test/fast/Unit tests/nvm_get_default_packages mawk compat b/test/fast/Unit tests/nvm_get_default_packages mawk compat index 35c8d4f0..bfbdbcab 100755 --- a/test/fast/Unit tests/nvm_get_default_packages mawk compat +++ b/test/fast/Unit tests/nvm_get_default_packages mawk compat @@ -11,6 +11,7 @@ die () { echo "$@" ; cleanup ; exit 1; } # The awk command from nvm_get_default_packages AWK_SCRIPT=' + { sub(/\r$/, "") } /^[ \t]*#/ { next } /^[ \t]*$/ { next } /[ \t]/ && !/^[ \t]*#/ { @@ -33,14 +34,21 @@ stevemao/left-pad" EXPECTED_OUTPUT="rimraf object-inspect@1.0.2 stevemao/left-pad" +# The same input, with CRLF line endings +CRLF_INPUT='rimraf\r\nobject-inspect@1.0.2\r\n\r\n# commented-package\r\n\r\nstevemao/left-pad\r\n' + # Test with system awk OUTPUT="$(printf '%s\n' "${TEST_INPUT}" | awk "${AWK_SCRIPT}")" [ "${OUTPUT}" = "${EXPECTED_OUTPUT}" ] || die "system awk: expected >${EXPECTED_OUTPUT}<, got >${OUTPUT}<" +OUTPUT="$(printf %b "${CRLF_INPUT}" | awk "${AWK_SCRIPT}")" +[ "${OUTPUT}" = "${EXPECTED_OUTPUT}" ] || die "system awk, CRLF: expected >${EXPECTED_OUTPUT}<, got >${OUTPUT}<" # Test with mawk explicitly if available if command -v mawk > /dev/null 2>&1; then OUTPUT="$(printf '%s\n' "${TEST_INPUT}" | mawk "${AWK_SCRIPT}")" [ "${OUTPUT}" = "${EXPECTED_OUTPUT}" ] || die "mawk: expected >${EXPECTED_OUTPUT}<, got >${OUTPUT}<" + OUTPUT="$(printf %b "${CRLF_INPUT}" | mawk "${AWK_SCRIPT}")" + [ "${OUTPUT}" = "${EXPECTED_OUTPUT}" ] || die "mawk, CRLF: expected >${EXPECTED_OUTPUT}<, got >${OUTPUT}<" echo "mawk test passed" else echo "mawk not available, skipping mawk-specific test" @@ -50,6 +58,8 @@ fi if command -v gawk > /dev/null 2>&1; then OUTPUT="$(printf '%s\n' "${TEST_INPUT}" | gawk "${AWK_SCRIPT}")" [ "${OUTPUT}" = "${EXPECTED_OUTPUT}" ] || die "gawk: expected >${EXPECTED_OUTPUT}<, got >${OUTPUT}<" + OUTPUT="$(printf %b "${CRLF_INPUT}" | gawk "${AWK_SCRIPT}")" + [ "${OUTPUT}" = "${EXPECTED_OUTPUT}" ] || die "gawk, CRLF: expected >${EXPECTED_OUTPUT}<, got >${OUTPUT}<" echo "gawk test passed" else echo "gawk not available, skipping gawk-specific test"