Skip to content

Instantly share code, notes, and snippets.

@callumlocke
Last active April 8, 2025 04:21
Show Gist options
  • Save callumlocke/30990e247e52ab6ac1aa98e5f0e5bbf5 to your computer and use it in GitHub Desktop.
Save callumlocke/30990e247e52ab6ac1aa98e5f0e5bbf5 to your computer and use it in GitHub Desktop.
ZSH function to auto-switch to correct Node version
####
# ZSH function to auto-switch to correct Node version
# https://gist.github.com/callumlocke/30990e247e52ab6ac1aa98e5f0e5bbf5
#
# - Searches up your directory tree for the closest .nvmrc, just like `nvm use` does.
#
# - If you are already on the right Node version, IT DOES NOTHING, AND PRINTS NOTHING.
#
# - Works correctly if your .nvmrc file contains something relaxed/generic,
# like "4" or "v12.0" or "stable".
#
# - If an .nvmrc is found but you have no installed version that satisfies it, it
# prints a clear warning, so you can decide whether you want to run `nvm install`.
#
# - If no .nvmrc is found, it does `nvm use default`.
#
# Recommended: leave your default as something generic,
# e.g. do `nvm alias default stable`
####
auto-switch-node-version() {
NVMRC_PATH=$(nvm_find_nvmrc)
CURRENT_NODE_VERSION=$(nvm version)
if [[ ! -z "$NVMRC_PATH" ]]; then
# .nvmrc file found!
# Read the file
REQUESTED_NODE_VERSION=$(cat $NVMRC_PATH)
# Find an installed Node version that satisfies the .nvmrc
MATCHED_NODE_VERSION=$(nvm_match_version $REQUESTED_NODE_VERSION)
if [[ ! -z "$MATCHED_NODE_VERSION" && $MATCHED_NODE_VERSION != "N/A" ]]; then
# A suitable version is already installed.
# Clear any warning suppression
unset AUTOSWITCH_NODE_SUPPRESS_WARNING
# Switch to the matched version ONLY if necessary
if [[ $CURRENT_NODE_VERSION != $MATCHED_NODE_VERSION ]]; then
nvm use $REQUESTED_NODE_VERSION
fi
else
# No installed Node version satisfies the .nvmrc.
# Quit silently if we already just warned about this exact .nvmrc file, so you
# only get spammed once while navigating around within a single project.
if [[ $AUTOSWITCH_NODE_SUPPRESS_WARNING == $NVMRC_PATH ]]; then
return
fi
# Convert the .nvmrc path to a relative one (if possible) for readability
RELATIVE_NVMRC_PATH="$(realpath --relative-to=$(pwd) $NVMRC_PATH 2> /dev/null || echo $NVMRC_PATH)"
# Print a clear warning message
echo ""
echo "WARNING"
echo " Found file: $RELATIVE_NVMRC_PATH"
echo " specifying: $REQUESTED_NODE_VERSION"
echo " ...but no installed Node version satisfies this."
echo " "
echo " Current node version: $CURRENT_NODE_VERSION"
echo " "
echo " You might want to run \"nvm install\""
# Record that we already warned about this unsatisfiable .nvmrc file
export AUTOSWITCH_NODE_SUPPRESS_WARNING=$NVMRC_PATH
fi
else
# No .nvmrc file found.
# Clear any warning suppression
unset AUTOSWITCH_NODE_SUPPRESS_WARNING
# Revert to default version, unless that's already the current version.
if [[ $CURRENT_NODE_VERSION != $(nvm version default) ]]; then
nvm use default
fi
fi
}
# Run the above function in ZSH whenever you change directory
autoload -U add-zsh-hook
add-zsh-hook chpwd auto-switch-node-version
auto-switch-node-version
@robertg042
Copy link

@callumlocke Works like a charm! Thanks!

@kjwierenga
Copy link

kjwierenga commented Feb 1, 2021

Is anyone using the avn + avn-nvm packages? How does this compare? I think it supports .nvmrc too.

@joshuarule
Copy link

Thank you for this!

@y0n3r
Copy link

y0n3r commented Apr 5, 2021

Very nicely done, but this function unfortunately creates a very noticeable lag in my shell when I switch directories...

@agjs
Copy link

agjs commented Mar 16, 2022

Thanks a bunch for taking the time and making this. Cheers

@bmasias1
Copy link

Thanks u! this is dope!

@y-nk
Copy link

y-nk commented Dec 27, 2022

went to use fnm last year. i must say it's faster and less manual ๐Ÿ™ still thanks a lot for the all the time i used this

@jfschaff
Copy link

Great, thanks!

For others who might have the same issue, on macOS, I had nvm installed, but not configured in zsh, so I had to first add

export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

to my ~/.zshrc otherwiwe I had an error on nvm_find_nvmrc ("command not found: nvm_find_nvmrc")

@Query42
Copy link

Query42 commented Jan 11, 2024

I ran into an issue with this on MacOS, using an .nvmrc file that was formatted in a Windows env, which appended ^M to the end of the version number and caused it to not work (it always told me to try nvm install). The workaround I found was to brew install dos2unix and replace $(cat $NVMRC_PATH) with $(dos2unix < $NVMRC_PATH) to handle any bad special characters from Windows.

Now it's working great, thank you for this!

@Jonathan-Kris
Copy link

This works super fine with me! Thanks for your code it's lit!

@ponnex
Copy link

ponnex commented Mar 10, 2024

Hi! Thanks for this, this is very helpful!
I've added a few code that will show a prompt asking user to install the node version specified in .nvmrc and switch to it automatically.

# Convert the .nvmrc path to a relative one (if possible) for readability
RELATIVE_NVMRC_PATH="$(realpath --relative-to=$(pwd) $NVMRC_PATH 2> /dev/null || echo $NVMRC_PATH)"

# Print a clear warning message
echo "No installed Node version found that satisfies .nvmrc."
echo "  Do you want to install and use the specified Node version($REQUESTED_NODE_VERSION)? (Y/n)"
read -r INSTALL_NODE_VERSION
if [[ $INSTALL_NODE_VERSION =~ ^[Yy]$ ]]; then
  # Install the requested Node version and switch to it
  echo "Installing Node version $REQUESTED_NODE_VERSION..."
  nvm install $REQUESTED_NODE_VERSION
  echo "Node version $REQUESTED_NODE_VERSION installed."
  echo "Switching to Node version $REQUESTED_NODE_VERSION..."
  nvm use $REQUESTED_NODE_VERSION
  echo "Node version switched to $REQUESTED_NODE_VERSION."
else
  echo "Aborted installation. You can manually install the required Node version later."
fi

# Record that we already warned about this unsatisfiable .nvmrc file
export AUTOSWITCH_NODE_SUPPRESS_WARNING=$NVMRC_PATH

@Nooshu
Copy link

Nooshu commented Sep 20, 2024

Awesome script! Thank you!

Is anyone using the avn + avn-nvm packages? How does this compare? I think it supports .nvmrc too.

FYI avn is no longer maintained.

It's also worth mentioning to that add-zsh-hook chpwd auto-switch-node-version should be added to your .zshrc file. I was under the assumption that a โ€œhookโ€ was persistent between terminal sessions, and I couldn't work out why it stopped working after quitting my terminal. So make sure to run it every time your terminal window opens.

@equiman
Copy link

equiman commented Oct 7, 2024

@callumlocke terrific job. Thank! ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘

I want to add some notes:


Adding the source causes lazy loading of NVM did not to work and can cause performance issues on terminal startup.

DON'T

export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

Instead of that include the minimal code from https://github.dev/nvm-sh/nvm#zsh to avoid the error message.

DO

nvm_echo() {
    command printf %s\\n "$*" 2>/dev/null
}

# Traverse up in directory tree to find containing folder
nvm_find_up() {
    local path_
    path_="${PWD}"
    while [ "${path_}" != "" ] && [ "${path_}" != '.' ] && [ ! -f "${path_}/${1-}" ]; do
        path_=${path_%/*}
    done
    nvm_echo "${path_}"
}

nvm_find_nvmrc() {
    local dir
    dir="$(nvm_find_up '.nvmrc')"
    if [ -e "${dir}/.nvmrc" ]; then
        nvm_echo "${dir}/.nvmrc"
    fi
}

I'm also forked it with a version who install the required version instead of showing a warning message:

auto-switch-node-version() {
    local nvmrc_path=$(nvm_find_nvmrc)
    local current_node_version=$(nvm version)

    if [[ ! -z "$nvmrc_path" ]]; then
        # .nvmrc file found!

        # Read the file
        local requested_node_version=$(cat $nvmrc_path)

        # Find an installed Node version that satisfies the .nvmrc
        local matched_node_version=$(nvm_match_version $requested_node_version)

        if [[ ! -z "$matched_node_version" && $matched_node_version != "N/A" ]]; then
            # A suitable version is already installed.

            # Switch to the matched version ONLY if necessary
            if [[ $current_node_version != $matched_node_version ]]; then
                nvm use $requested_node_version
            fi
        else
            # No installed Node version satisfies the .nvmrc.

            if [[-z "$current_node_version"]]; then
                # Install the requested version on the .nvmrc file
                nvm install
            else
                # Install the requested version on the .nvmrc file with the current packages installed
                nvm install --reinstall-packages-from=${current_node_version}
            fi
        fi
    else
        # No .nvmrc file found.

        # Revert to default version, unless that's already the current version.
        if [[ $current_node_version != $(nvm version default)  ]]; then
            nvm use default
        fi
    fi
}

@reorx
Copy link

reorx commented Apr 8, 2025

Sharing my approach here:

# NVM
export NVM_DIR="$HOME/.nvm"

# https://github.com/nvm-sh/nvm#zsh
use-nvmrc() {
  local nvmrc_path
  nvmrc_path="$(nvm_find_nvmrc)"

  if [ -n "$nvmrc_path" ]; then
    local nvmrc_node_version
    nvmrc_node_version=$(nvm version "$(cat "${nvmrc_path}")")

    if [ "$nvmrc_node_version" = "N/A" ]; then
      nvm install
    elif [ "$nvmrc_node_version" != "$(nvm version)" ]; then
      nvm use
    fi
  elif [ -n "$(PWD=$OLDPWD nvm_find_nvmrc)" ] && [ "$(nvm version)" != "$(nvm version default)" ]; then
    echo "Reverting to nvm default version"
    nvm use default
  fi
}
function auto-load-and-use-nvm() {
    if ! typeset -f nvm_find_nvmrc &> /dev/null; then
        # load nvm if nvm_find_nvmrc is not defined
        [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
    fi
    use-nvmrc
}
# add hook for change directory commands
add-zsh-hook chpwd auto-load-and-use-nvm
# if current directory has .nvmrc, call auto-load-and-use-nvm immediately
[[ -f .nvmrc ]] && auto-load-and-use-nvm

This config has two advantages:

  1. it won't load nvm to zsh environment unless .nvmrc is found
  2. it automatically switches node version when changing directory

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment