Thursday, January 25, 2018

VS Code and the Node Version Manager

nvm is not compatible with the npm config "prefix" option: currently set to "/usr/local"

We can fix it! Jump to the end if you just want to know what to do, the middle explains why you need to do it.

This error was especially difficult to run down, which is why it warrants documenting it here. Ultimately it was a combination of how Node.js is installed, the Node Version Manager (nvm), a feature of the bash shell (3.6.2) in MacOS High Sierra, and that Visual Studio Code starts an integrated terminal with a login shell that triggered it. I have not explored any other Mac systems, and Linux systems could be affected as well. Similar behavior likely means the same problem is occurring.

The system that exhibited the problem had Node.js installed in two different ways. First through HomeBrew into /usr/local/bin, and then via nvm in user directories. If /usr/local/bin/node exists in the path in front of the user nvm directory ~/.nvm, then nvm breaks because it uses the wrong version of node. That is the direct source of the error message above. One way to fix it is simply to remove /usr/local/bin/node, but that may not be a viable option on a shared computer.

It is important to note that the nvm startup script /usr/local/opt/nvm/nvm.sh needs to be placed in ~/.bashrc, not ~/.bash_profile. The script creates functions. nvm is a shell function, not a command. If the script is run from ~/.bash_profile, then the function only exists in login shells.

Fixing the PATH variable in ~/.bash_profile, or ~/.bashrc, works well. Except when an interactive terminal is launched from inside of VS Code.

The problem is triggered by VS Code launching the bash shell in an interactive terminal using the -l option (--login). Of course one option is to reconfigure VS Code to not use -l, but that can break other things. Three things --login does become very important:
  1. --login keeps the existing environment, but re-sources the ~/.bash_profile. If this file appends to the PATH variable, the PATH gets doubled-up. Changes in the ~/.bash_profile have to be carefully set up so that if the PATH already contains what is needed, it is not blindly appended to the PATH again.
  2. --login does not source ~/.bashrc, so ~/.bash_profile needs to source ~/.bashrc if aliases and other functions are defined there. In this case, the function nvm.
  3. --login in some versions of bash rearranges the PATH variable! If anything was placed in the PATH in front of the system defined /usr/bin:/usr/sbin:/bin:/sbin:/usr/local/bin, then that is moved to the end of the PATH. And, in this case that means that /usr/local/bin/node gets found before the node executable that nvm expects in its own directories. And that means the error up above gets found.

So the configuration needs to follow these steps:
  1. Place any normal changes to the PATH in ~/.bash_profile, but use if statements to make sure the PATH is not doubled up in every login shell:
    if echo $PATH | grep -v -q something
    then
            export PATH=$PATH:something
    fi
    
  2. Have ~/.bash_profile source ~/.bashrc.
  3. Put the nvm configuration in ~/.bashrc, so that the nvm function is created in every shell.
    export NVM_DIR=${HOME}/.nvm
    source /usr/local/opt/nvm/nvm.sh
    
  4. In ~/.bashrc, before running the nvm configuration, fix the PATH variable. This pipeline of three sed commands will make sure that the nvm path to node is always at the front of the PATH (the line wraps):
    export PATH=$(echo $PATH | sed -E "s|(.*):?($NVM_DIR/versions/node/v[0-9]+\.
    [0-9]+\.[0-9]+/bin):?(.*$)|\2:\1:\3|" | sed -E "s|::|:|g" | sed -E "s|:$||")
    
This will provide a stable configuration, not just for nvm inside and outside of VS Code, but for other applications as well.

No comments:

Post a Comment