Making Nvm And Volta Co-exist With Zsh
When some projects use nvm and others use volta, then we need both to live side by side.

I recently learned about Volta, which is just a better tool than nvm. It is noticably faster on my 4 year old MacBook. It auto-downloads node versions when needed.
How I previously set up nvm
I have been using nvm and have been rather happy with it. I set up a hook to automatically use the right version if a folder contains a .nvmrc
file:
export NVM_DIR="$HOME/.nvm"
export PATH="$PATH:$HOME/.local/bin"
[ -s "/usr/local/opt/nvm/nvm.sh" ] && . "/usr/local/opt/nvm/nvm.sh"
autoload -U add-zsh-hook
load-nvmrc() {
local nvmrc_path=`pwd`/.nvmrc
if [ -e "$nvmrc_path" ] ; then
local nvmrc_node_version=$(nvm version "$(cat "${nvmrc_path}")")
if [[ "$nvmrc_node_version" == "N/A" ]]; then
nvm install >/dev/null
elif [[ "$nvmrc_node_version" != `nvm version` ]]; then
nvm use >/dev/null
fi
fi
}
add-zsh-hook chpwd load-nvmrc
load-nvmrc
This actually does exactly the same as Volta, just with a little zsh magic sprinkled on top.
With this, I actually don't really need Volta at all.
But, I do work with other people. Some of them like nvm. Some of them like Volta. So I need both in my life.
Setup
If we pretended that I could just stop using nvm from one day to the next, it could be as simple as removing the bit above from my .zshrc
file and running the following:
brew install volta
volta setup
Co-existing
It does get a little tricky when you have some projects using nvm and others using volta.
Now I need to extend my nvm hook to initialise nvm when appropriate, and I also need a hook to uninitialize nvm and initialise volta when that is appropriate.
So, we extend the script above a little:
export NVM_DIR="$HOME/.nvm"
export PATH="$PATH:$HOME/.local/bin"
autoload -U bashcompinit
bashcompinit
autoload -U add-zsh-hook
load-nvmrc() {
local nvmrc_path=`pwd`/.nvmrc
if [ -e "$nvmrc_path" ] ; then
if [[ "$__npm_initialized" != "1" ]] ; then
# explicitly do not try to co-exist with volta
clear-volta
__npm_initialized=1
[ -s "/usr/local/opt/nvm/nvm.sh" ] && . "/usr/local/opt/nvm/nvm.sh"
fi
local nvmrc_node_version=$(nvm version "$(cat "${nvmrc_path}")")
if [[ "$nvmrc_node_version" == "N/A" ]]; then
nvm install >/dev/null
elif [[ "$nvmrc_node_version" != `nvm version` ]]; then
nvm use >/dev/null
fi
fi
}
add-zsh-hook chpwd load-nvmrc
load-nvmrc
Next, we need a similar hook for volta:
init-volta() {
if [[ "$VOLTA_HOME" == "" ]] ; then
export __npm_initialized=0
export VOLTA_HOME="$HOME/.volta"
export PATH=`echo $PATH | perl -ane 's|:.*?\.nvm/.*?:|:|;s|^.*?\.nvm/.*?:||;print'`
export PATH="$VOLTA_HOME/bin:$PATH"
fi
}
clear-volta() {
export VOLTA_HOME=
export PATH=`echo $PATH | perl -ane 's|:.*?volta/bin:|:|;s|^.*?volta/bin:||;print'`
}
autoload -U add-zsh-hook
load-volta() {
local package_path=`pwd`/package.json
if [ -e "$package_path" ] ; then
if [[ `jq -cM '.volta' $package_path` != "null" ]] ; then
if [[ "$VOLTA_HOME" == "" ]] ; then
init-volta
fi
fi
fi
}
add-zsh-hook chpwd load-volta
load-volta
Conclusion
Some of the magic of not having to do any chpwd
hooks at all are clearly negated by this, but such is the world of working on multiple heterogenous projects.