[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
This commit is contained in:
Jordan Harband
2026-05-05 16:23:37 -07:00
parent ed4dbdfdd5
commit 718e880890
2 changed files with 93 additions and 9 deletions

34
nvm.sh
View File

@@ -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 [<version>]'
nvm_err ' Provide a <version>, 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 <version>'
nvm_err ' nvm uninstall --lts'
nvm_err ' nvm uninstall --lts=<LTS name>'
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 [<version>]'
nvm_err ' Provide a <version>, 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 [<version>] [<args>]'
nvm_err ' Provide a <version>, 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 | <version>]'
nvm_err ' Provide a <version>, 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 <name>'
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} <version>"
nvm_err ' Run `nvm --help` for full help.'
return 127
fi

View File

@@ -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 [<version>]' nvm install
assert_usage 'Usage: nvm run [<version>] [<args>]' nvm run
assert_usage 'Usage: nvm which [current | <version>]' nvm which
assert_usage 'Usage: nvm uninstall <version>' nvm uninstall
assert_usage 'Usage: nvm uninstall <version>' nvm uninstall a b
assert_usage 'Usage: nvm unalias <name>' nvm unalias
assert_usage 'Usage: nvm unalias <name>' nvm unalias a b
assert_usage 'Usage: nvm install-latest-npm' nvm install-latest-npm extra
assert_usage 'Usage: nvm reinstall-packages <version>' nvm reinstall-packages
assert_usage 'Usage: nvm copy-packages <version>' 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 [<version>]' nvm use foo
cleanup