From 718e8808907a8b4a9c5a280c50b61f5504b7128a Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 5 May 2026 16:23:37 -0700 Subject: [PATCH] [Fix] focused error on missing/invalid args for several subcommands Previously a number of subcommands dumped the entire `nvm --help` output (~100 lines) when arguments were missing or invalid, drowning the real error. Replace each dump with a short, command-specific usage block that names the expected syntax and points to `nvm --help` for full help. The exit code (127) is unchanged. Affected subcommands: - `nvm install` (no version + no .nvmrc) - `nvm use` (version unresolvable) - `nvm run` (no version + no .nvmrc) - `nvm which` (no version + no .nvmrc) - `nvm cache` (unknown subcommand) - `nvm uninstall` (wrong arg count) - `nvm unalias` (wrong arg count) - `nvm install-latest-npm` (wrong arg count) - `nvm reinstall-packages` / `nvm copy-packages` (wrong arg count) The catch-all unknown-subcommand handler still dumps full help, since in that case the user has no narrower context to be reminded about. Refs #3755 --- nvm.sh | 34 +++++++--- ...or invalid args show a focused usage error | 68 +++++++++++++++++++ 2 files changed, 93 insertions(+), 9 deletions(-) create mode 100755 test/fast/Subcommands with missing or invalid args show a focused usage error diff --git a/nvm.sh b/nvm.sh index d7b124a0..55198f5c 100755 --- a/nvm.sh +++ b/nvm.sh @@ -3338,7 +3338,9 @@ nvm() { fi ;; *) - >&2 nvm --help + nvm_err 'Usage: nvm cache dir' + nvm_err ' nvm cache clear' + nvm_err ' Run `nvm --help` for full help.' return 127 ;; esac @@ -3594,7 +3596,9 @@ nvm() { else { provided_version="$(nvm_rc_version 3>&1 1>&4)"; } 4>&1 if [ $version_not_provided -eq 1 ] && [ -z "${provided_version}" ]; then - >&2 nvm --help + nvm_err 'Usage: nvm install []' + nvm_err ' Provide a , or run from a directory containing an .nvmrc file.' + nvm_err ' Run `nvm --help` for full help.' return 127 fi fi @@ -3852,7 +3856,10 @@ nvm() { ;; "uninstall") if [ $# -ne 1 ]; then - >&2 nvm --help + nvm_err 'Usage: nvm uninstall ' + nvm_err ' nvm uninstall --lts' + nvm_err ' nvm uninstall --lts=' + nvm_err ' Run `nvm --help` for full help.' return 127 fi @@ -4041,7 +4048,9 @@ nvm() { fi if [ -z "${VERSION}" ]; then - >&2 nvm --help + nvm_err 'Usage: nvm use []' + nvm_err ' Provide a , or run from a directory containing an .nvmrc file.' + nvm_err ' Run `nvm --help` for full help.' return 127 fi @@ -4165,7 +4174,9 @@ nvm() { VERSION="$(nvm_version "${NVM_RC_VERSION}")" ||: fi if [ "${VERSION:-N/A}" = 'N/A' ]; then - >&2 nvm --help + nvm_err 'Usage: nvm run [] []' + nvm_err ' Provide a , or run from a directory containing an .nvmrc file.' + nvm_err ' Run `nvm --help` for full help.' return 127 fi fi @@ -4408,7 +4419,9 @@ nvm() { VERSION="${provided_version-}" fi if [ -z "${VERSION}" ]; then - >&2 nvm --help + nvm_err 'Usage: nvm which [current | ]' + nvm_err ' Provide a , or run from a directory containing an .nvmrc file.' + nvm_err ' Run `nvm --help` for full help.' return 127 fi @@ -4505,7 +4518,8 @@ nvm() { NVM_ALIAS_DIR="$(nvm_alias_path)" command mkdir -p "${NVM_ALIAS_DIR}" if [ $# -ne 1 ]; then - >&2 nvm --help + nvm_err 'Usage: nvm unalias ' + nvm_err ' Run `nvm --help` for full help.' return 127 fi if [ "${1#*\/}" != "${1-}" ]; then @@ -4542,7 +4556,8 @@ nvm() { ;; "install-latest-npm") if [ $# -ne 0 ]; then - >&2 nvm --help + nvm_err 'Usage: nvm install-latest-npm' + nvm_err ' Run `nvm --help` for full help.' return 127 fi @@ -4550,7 +4565,8 @@ nvm() { ;; "reinstall-packages" | "copy-packages") if [ $# -ne 1 ]; then - >&2 nvm --help + nvm_err "Usage: nvm ${COMMAND} " + nvm_err ' Run `nvm --help` for full help.' return 127 fi diff --git a/test/fast/Subcommands with missing or invalid args show a focused usage error b/test/fast/Subcommands with missing or invalid args show a focused usage error new file mode 100755 index 00000000..d9e4ab94 --- /dev/null +++ b/test/fast/Subcommands with missing or invalid args show a focused usage error @@ -0,0 +1,68 @@ +#!/bin/sh + +set -ex + +die () { echo "$@" ; cleanup ; exit 1; } + +cleanup() { + cd "${ORIG_PWD}" 2>/dev/null || true + [ -n "${TMP_DIR-}" ] && rm -rf "${TMP_DIR}" +} + +export NVM_DIR="$(cd ../.. && pwd)" + +: nvm.sh +\. ../../nvm.sh + +\. ../common.sh + +ORIG_PWD="$(pwd)" + +# Run from a fresh, empty directory so the "no version + no .nvmrc" cases +# (install/run/which) are not masked by an ambient .nvmrc above the test dir. +TMP_DIR="$(mktemp -d)" +cd "${TMP_DIR}" || die "could not cd to temp dir" + +# Asserts a subcommand emits the given focused usage line (not the full help +# dump) on stderr, and exits 127. +assert_usage() { + local EXPECTED_LINE + EXPECTED_LINE="$1" + shift + + try_err "$@" + + case "${CAPTURED_STDERR}" in + *"${EXPECTED_LINE}"*) ;; + *) die "\`$*\` did not show focused usage >${EXPECTED_LINE}<; got >${CAPTURED_STDERR}<" ;; + esac + # the focused usage should NOT be the full help dump + case "${CAPTURED_STDERR}" in + *'Show this message'*) die "\`$*\` dumped full help instead of a focused usage" ;; + esac + [ "_${CAPTURED_EXIT_CODE}" = "_127" ] \ + || die "\`$*\` expected exit code 127; got ${CAPTURED_EXIT_CODE}" +} + +assert_usage 'Usage: nvm cache dir' nvm cache bogus +assert_usage 'Usage: nvm install []' nvm install +assert_usage 'Usage: nvm run [] []' nvm run +assert_usage 'Usage: nvm which [current | ]' nvm which +assert_usage 'Usage: nvm uninstall ' nvm uninstall +assert_usage 'Usage: nvm uninstall ' nvm uninstall a b +assert_usage 'Usage: nvm unalias ' nvm unalias +assert_usage 'Usage: nvm unalias ' nvm unalias a b +assert_usage 'Usage: nvm install-latest-npm' nvm install-latest-npm extra +assert_usage 'Usage: nvm reinstall-packages ' nvm reinstall-packages +assert_usage 'Usage: nvm copy-packages ' nvm copy-packages a b + +# `nvm use` reaches its focused-usage guard only when version resolution returns +# an empty string. From the CLI that cannot happen: an omitted version is caught +# earlier (the `Please see ... nvmrc` branch), and an unresolvable non-empty +# version yields the "N/A" sentinel, never "". The branch is a defensive guard, +# so drive it directly by stubbing the resolver to return empty. Keep this last: +# the stub stays in effect for the rest of the shell. +nvm_match_version() { nvm_echo ''; } +assert_usage 'Usage: nvm use []' nvm use foo + +cleanup