Read more

NVM: How to automatically switch version when changing directories

Arne Hartherz
June 04, 2021Software engineer at makandra GmbH

The Node Version Manager allows installing multiple NodeJS versions and switching between them.
By default, it does not automatically switch versions when entering a directory that holds a .nvmrc file.

Illustration UI/UX Design

UI/UX Design by makandra brand

We make sure that your target audience has the best possible experience with your digital product. You get:

  • Design tailored to your audience
  • Proven processes customized to your needs
  • An expert team of experienced designers
Read more Show archive.org snapshot

The project's readme document Show archive.org snapshot offers a bash function which calls nvm use after each cd. In fact, it replaces cd in your bash.

I did not want to do that, but instead use the $PROMPT_COMMAND feature. So here is my take on it.
Note that it is much shorter, it probably does a few less smart things, but has been working great for me for a long while.
Also note that it compares .nvmrc file paths instead of comparing nvm current, so cding around subdirectories of your project should work without any noticable performance impact.

Bash

Put the following at the end of your ~/.bashrc (the first three lines might already be in there):

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"

find-up() {
  path=$(pwd)
  while [[ "$path" != "" && ! -e "$path/$1" ]]; do
    path=${path%/*}
  done
  echo "$path"
}

automatic-nvm-use() {
  NVM_PATH=$(find-up .nvmrc | tr -d '[:space:]')

  if [[ $NVM_PATH == $NVM_PATH_WAS ]]; then
    return
  fi

  NVM_PATH_WAS=$NVM_PATH

  if [[ -f "$NVM_PATH/.nvmrc" ]]; then
    nvm use $(<"$NVM_PATH/.nvmrc")
  else
    nvm use default
  fi
}

if [[ "$PROMPT_COMMAND" ]]; then
  export PROMPT_COMMAND="$PROMPT_COMMAND;automatic-nvm-use"
else
  export PROMPT_COMMAND=automatic-nvm-use
fi

Then re-open your terminals, or reinitialize them via source ~/.bashrc.

Note

If your ~/.bashrc contains other code that changes $PROMPT_COMMAND (like making your terminal's title reflect the current directory), make sure it does not simply set a new $PROMPT_COMMAND, but prepends or appends itself. Alternatively, put the above snippet after it.

ZSH

For zsh this script Show archive.org snapshot from the following stackoverflow post Show archive.org snapshot seems to work the most stable and covers additional features such as

  • guarantees you are always on the right version by searching up the directory tree to find the closest .nvmrc (just like nvm use);
  • can handle any valid .nvmrc format;
  • clearly warns you if no installed version satisfies the .nvmrc,
  • assumes you want default if there is no .nvmrc anywhere up the tree;
  • is completely silent and fast if you are already on the correct Node version.
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
Arne Hartherz
June 04, 2021Software engineer at makandra GmbH
Posted by Arne Hartherz to makandra dev (2021-06-04 10:32)