From c28f2b70db8e592ca3d2fc86379346f379af2980 Mon Sep 17 00:00:00 2001 From: Cassidy Marble Date: Tue, 6 Oct 2026 14:01:11 -0300 Subject: [PATCH] [Perf] `nvm_npmrc_bad_news_bears`: scan npmrc files without forking `grep` --- nvm.sh | 23 +++++++++++- test/fast/Unit tests/nvm_die_on_prefix | 51 ++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/nvm.sh b/nvm.sh index ed24ed1c..180d349c 100755 --- a/nvm.sh +++ b/nvm.sh @@ -3329,9 +3329,28 @@ nvm_npm_global_modules() { nvm_npmrc_bad_news_bears() { local NVM_NPMRC + local NVM_NPMRC_LINE + local NVM_NPMRC_KEY NVM_NPMRC="${1-}" - if [ -n "${NVM_NPMRC}" ] && [ -f "${NVM_NPMRC}" ] && nvm_grep -Ee '^(prefix|globalconfig) *=' <"${NVM_NPMRC}" >/dev/null; then - return 0 + if [ -n "${NVM_NPMRC}" ] && [ -f "${NVM_NPMRC}" ]; then + # this is fork-free on purpose: it runs up to four times on every `nvm use`/`nvm install` + # avoid grepping or sub-shells as much as possible here. + while IFS= read -r NVM_NPMRC_LINE || [ -n "${NVM_NPMRC_LINE}" ]; do + case "${NVM_NPMRC_LINE}" in + *=*) + NVM_NPMRC_KEY="${NVM_NPMRC_LINE%%=*}" + ;; + *) + continue + ;; + esac + NVM_NPMRC_KEY="${NVM_NPMRC_KEY%"${NVM_NPMRC_KEY##*[! ]}"}" + case "${NVM_NPMRC_KEY}" in + prefix | globalconfig) + return 0 + ;; + esac + done < "${NVM_NPMRC}" fi return 1 } diff --git a/test/fast/Unit tests/nvm_die_on_prefix b/test/fast/Unit tests/nvm_die_on_prefix index b16b0dc4..8c1f3884 100755 --- a/test/fast/Unit tests/nvm_die_on_prefix +++ b/test/fast/Unit tests/nvm_die_on_prefix @@ -201,4 +201,55 @@ Run \`foo\` to unset it." [ "_$CAPTURED_EXIT_CODE" = "_10" ] || die "'nvm_die_on_prefix 0 foo' with user .npmrc that has globalconfig did not exit with 10; got '$CAPTURED_EXIT_CODE'" ) +# nvm_npmrc_bad_news_bears matches `^(prefix|globalconfig) *=` +NPMRC="${TEST_DIR}/bad-news.npmrc" +printf 'prefix=x\n' > "${NPMRC}" +nvm_npmrc_bad_news_bears "${NPMRC}" || die 'prefix=x should be bad news' +printf 'prefix =x\n' > "${NPMRC}" +nvm_npmrc_bad_news_bears "${NPMRC}" || die 'prefix =x should be bad news' +printf 'prefix =x\n' > "${NPMRC}" +nvm_npmrc_bad_news_bears "${NPMRC}" || die 'prefix =x should be bad news' +printf 'prefix =\n' > "${NPMRC}" +nvm_npmrc_bad_news_bears "${NPMRC}" || die 'prefix = should be bad news' +printf 'foo=1\nprefix = y\n' > "${NPMRC}" +nvm_npmrc_bad_news_bears "${NPMRC}" || die 'a matching line after a non-matching line should be bad news' +printf 'prefix=x' > "${NPMRC}" +nvm_npmrc_bad_news_bears "${NPMRC}" || die 'prefix=x without a trailing newline should be bad news' +printf 'globalconfig=x\n' > "${NPMRC}" +nvm_npmrc_bad_news_bears "${NPMRC}" || die 'globalconfig=x should be bad news' +printf 'globalconfig = x\n' > "${NPMRC}" +nvm_npmrc_bad_news_bears "${NPMRC}" || die 'globalconfig = x should be bad news' + +printf 'prefixfoo=x\n' > "${NPMRC}" +! nvm_npmrc_bad_news_bears "${NPMRC}" || die 'prefixfoo=x should not be bad news' +printf 'globalconfigfoo=x\n' > "${NPMRC}" +! nvm_npmrc_bad_news_bears "${NPMRC}" || die 'globalconfigfoo=x should not be bad news' +printf 'prefix x=x\n' > "${NPMRC}" +! nvm_npmrc_bad_news_bears "${NPMRC}" || die 'prefix x=x should not be bad news' +printf 'prefix\t=x\n' > "${NPMRC}" +! nvm_npmrc_bad_news_bears "${NPMRC}" || die 'a tab before = should not be bad news' +printf ' prefix=x\n' > "${NPMRC}" +! nvm_npmrc_bad_news_bears "${NPMRC}" || die 'a leading space should not be bad news' +printf 'Prefix=x\n' > "${NPMRC}" +! nvm_npmrc_bad_news_bears "${NPMRC}" || die 'an uppercase key should not be bad news' +printf 'prefix\n' > "${NPMRC}" +! nvm_npmrc_bad_news_bears "${NPMRC}" || die 'a bare prefix should not be bad news' +printf 'prefix \n' > "${NPMRC}" +! nvm_npmrc_bad_news_bears "${NPMRC}" || die 'trailing spaces without = should not be bad news' +: > "${NPMRC}" +! nvm_npmrc_bad_news_bears "${NPMRC}" || die 'an empty npmrc should not be bad news' +! nvm_npmrc_bad_news_bears || die 'a missing argument should not be bad news' +! nvm_npmrc_bad_news_bears "${TEST_DIR}/nonexistent.npmrc" || die 'a missing file should not be bad news' + +# the npmrc scan must not fork `grep` +( + rm -f "${TEST_DIR}/grep-was-called" + nvm_grep() { : > "${TEST_DIR}/grep-was-called"; return 1; } + printf 'globalconfig = nope\n' > "${TEST_DIR}/probe.npmrc" + NVM_PROBE_EXIT=0 + nvm_npmrc_bad_news_bears "${TEST_DIR}/probe.npmrc" || NVM_PROBE_EXIT=$? + [ ! -e "${TEST_DIR}/grep-was-called" ] || exit 10 + [ "${NVM_PROBE_EXIT}" = 0 ] || exit 11 +) || die 'nvm_npmrc_bad_news_bears must read npmrc files without calling nvm_grep' + cleanup