Skip to content

Instantly share code, notes, and snippets.

@callumlocke
Last active October 18, 2024 14:13
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
}

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