I use nvm to manage my version of node which makes it easy to use the right version of node for specific projects. What has annoyed me with nvm is opening up a directory in terminal and running npm
only to get:
-bash: npm: command not found
Code language: Shell Session (shell)
I’d have to remember to run nvm install
after navigating to a project directory (where an .nvmrc
is located). My annoyance finally spurred me to action today and I looked up if there was some way to do it automatically. Naturally, someone asked this exact question on Stackoverflow: run nvm use
automatically every time there’s a .nvmrc file on the directory. The fifth answer points over to the nvm official docs on how to automatically call nvm use
. The problem with that official answer is that it requires that you use its aliased cd
command to navigate to a directory; it doesn’t work if you use something like autojump. So I modified it to incorporate the PROMPT_COMMAND
injection approach from the higher-ranked answers. I’ve also improved performance by persisting the default version and local version in environment variables which avoid unnecessary subshells for nvm
. Here’s what I’ve got in my .bashrc
now:
autonvm() {
nvm_path=$(nvm_find_up .nvmrc | tr -d '\n')
# If there are no .nvmrc file, use the default nvm version
if [[ ! $nvm_path = *[^[:space:]]* ]]; then
if [[ -z $nvm_default_version ]]; then
nvm_default_version=$(nvm version default);
fi
# If there is no default version, set it to `node`
# This will use the latest version on your machine
if [[ $nvm_default_version == "N/A" ]]; then
nvm alias default node;
nvm_default_version=$(nvm version default);
fi
# If the current version is not the default version, set it to use the default version
if [[ $(nvm current) != $nvm_default_version ]]; then
nvm use default;
fi
elif [[ -s $nvm_path/.nvmrc && -r $nvm_path/.nvmrc ]]; then
nvm_version=$(<"$nvm_path"/.nvmrc)
# `nvm ls` will check all locally-available versions
# If there are multiple matching versions, take the latest one
# Remove the `->` and `*` characters and spaces
# `nvm_local_version` will be `N/A` if no local versions are found
if [[ -z $nvm_local_version ]]; then
nvm_local_version=$(nvm ls --no-colors "$nvm_version" | tail -1 | tr -d '\->*' | tr -d '[:space:]')
fi
# If it is not already installed, install it
# `nvm install` will implicitly use the newly-installed version
if [[ "$nvm_local_version" == "N/A" ]]; then
nvm install "$nvm_version";
elif [[ $(nvm current) != "$nvm_local_version" ]]; then
nvm use "$nvm_version";
fi
fi
}
export PROMPT_COMMAND="$PROMPT_COMMAND; autonvm"
Code language: Bash (bash)
Now when I open a new tab in iTerm and I’m in my home directory, I see first:
Now using node v16.14.2 (npm v8.5.0)
Code language: Shell Session (shell)
And if I then cd
into a directory with an .nvmrc
containing 14
(or a subdirectory of such a directory), I see:
Found '/Users/westonruter/foo/.nvmrc' with version <14>
Now using node v14.19.3 (npm v6.14.17)
Code language: Shell Session (shell)
Much better!
Prior to seeing that official answer, I had improved upon the third answer as follows:
- Prevent clobbering any existing
COMMAND_PROMPT
(e.g. from autojump). - Run when first starting a terminal session (when
$PREV_PWD
is empty).
Here’s what I previously added to my .bash_profile
:
run_nvm_use_when_directory_changed() {
if [[ $PWD == $NVM_PREV_PWD ]]; then
return
fi
# Do nothing if a previously-seen parent directory has an .nvmrc.
if [[ $NVM_DIRTY = true ]] && [[ ! -z $NVM_PREV_PWD ]] && [[ $PWD =~ $NVM_PREV_PWD ]]; then
return
fi
if [[ -f ".nvmrc" ]]; then
nvm use
NVM_DIRTY=true
elif [[ $NVM_DIRTY = true ]] || [[ -z $NVM_PREV_PWD ]]; then
nvm use stable
NVM_DIRTY=false
fi
NVM_PREV_PWD=$PWD
}
export PROMPT_COMMAND="$PROMPT_COMMAND; run_nvm_use_when_directory_changed"
Code language: Bash (bash)
The problem with this version is that it doesn’t recognize the .nvmrc
from an ancestor directory if you started a session in a subdirectory. The official answer from nvm addresses that problem by use of nvm_find_up
to recursively search for the file in ancestor directories.
4 replies on “Auto nvm use”
Thanks for sharing this, great idea. Haven’t tried it yet but found the same issue, even though I only ever run
nvm use --lts
. Would be nice to not have to run that every time I open a new terminal.Try out fnm. Based on rust, so it’s relatively quick and the auto-use feature works right out of the box.
Not sure if I have something else that’s wonky in my
.bashrc
but mynvm_local_version
keeps the value in between directory changes, thus it’ll never trigger thenvm use
.I change it as:
# If it is not already installed, install it
# `nvm install` will implicitly use the newly-installed version
if [[ "$nvm_local_version" == "N/A" ]]; then
nvm install "$nvm_version";
fi
if [[ $(nvm current) != "$nvm_version" ]]; then
nvm use "$nvm_version";
fi
This is so helpful. I no longer need to keep track of ‘nvm use’ when I enter project directories that are still tied to old versions.
I am using oh-my-zsh with Powerline10k on MacOS Sonoma, and had to make the following minor tweaks to use the function without needing to alter my prompt:
“`
function autonvm() {
nvm_path=$(nvm_find_up .nvmrc | tr -d ‘\n’)
…..
}
autoload -Uz autonvm
function precmd() {
autonvm
}
“`
This adds an invocation of the function ‘autonvm’ into the zsh ‘precmd’ hook, which is evaluated before every prompt redraw.
I also added the ‘–silent’ argument to all of the nvm use commands to avoid warnings from Powerline10k about output during prompt rendering.
This is great, thank you!