Categories
Coding

Auto nvm use

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 foundCode 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.

Not sure if I have something else that’s wonky in my .bashrc but my nvm_local_version keeps the value in between directory changes, thus it’ll never trigger the nvm 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!

Leave a Reply

Your email address will not be published. Required fields are marked *