nvm
nvm copied to clipboard
npm is only available in interactive shell
It seems that nvm modifies ~/.bashrc to load npm and node. This means that non interactive shells that don't run bashrc won't have it. For instance I was running a script that calls npm from cron and it was failing because npm was unknown.
What would you suggest?
I'm not so sure because I'm no unix expert, but I've been discussing this over there: http://unix.stackexchange.com/a/321524/198722 and it seems that ~/.profile could be a better place.
The original issue was discussed here: http://unix.stackexchange.com/q/321176/198722
@gsabran I guess you can still load nvm before the command of using npm
Have you tried sourcing nvm.sh at the beginning of your script?
This is the closest that I have come to work around this issue:
export NVM_DIR="/usr/local/nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && NODE_PATH="/usr/local/nvm/versions/node/v6.9.1/lib/node_modules" nvm exec 6.9 node myScript.js
It's no clean version, and you have to specify the correct NODE_PATH, otherwise it doesn't find the modules, but then it works.
I'm still looking for a cleaner way to do this.
You definitely should never set NODE_PATH for any reason. Global modules aren't supposed to be requireable.
I'm not sure why global modules aren't supposed to be requireable, but that might explain why I had to set the NODE_PATH manually for it to work.
@kurteknikk global modules are for runnable binaries. Things you require are dependencies, and as such, should be specified in your project's package.json and installed locally to that project.
@kurteknikk solution worked for me
export NVM_DIR="/home/gaganb/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
@gagan-bansal that's exactly what the install script, and the "manual install" section of the readme, already do.
nvm not being a regular executable can cause problems. I am having problems working with it in my Dockerfile. The problem might be caused by .bashrc exiting early in non-interactive mode, or the default shell being sh and not bash or something else.
Maybe install.sh could accept a parameter which makes it create a proxy executable in /usr/local/bin/ (or wherever appropriate) to solve these issues.
@AlicanC nvm will not work properly as an executable since it needs the ability to modify your current shell session's env vars, which is why it's a sourced function.
any solution for this? the one proposed by @kurteknikk seems more like a patch.
I've extracted the above into convenience gist for easier testing.
@ljharb I also had the original problem (nvm/node is only incorporated with interactive shells) a few times now. My solution was always to put the nvm lines in .profile or above this in .bashrc:
# If not running interactively, don't do anything
case $- in
*i*) ;;
*) return;;
esac
I know, the .bashrc solution is not the best way, because it is thought to be for interactive shells. But also the .profile solution is not the best one, since it is not always read with interactive shells (e.g. if .bash_profile or .bash_login exists).
But nonetheless, it's better than having node not included in non-interactive shells. So my solution proposals for the install script are:
either
- append the NVM lines in both files:
.bashrcand.profile
or
- insert the NVM lines to the top of the
.bashrcfile instead of appending it
Is that possible or do I miss a point?
Often one of those is made to source the other; as for the position in bashrc, I’ve never seen that kind of bail-out before, and it seems like a bad idea to have it - what appears after it that can’t run when not interactive?
yes, they often source each other (that's why I think solution 2 is better).
sorry, not quite sure if I understand your second part correctly: the shown code is already in .bashcrc - I usually put the nvm lines above it.
With the question, do you mean, what happens if a node program needs an interactive shell? I can't answer that, but isn't that a general question for all programs? (Probably the user has to ensure that this situation will never occur.)
I mean the “return”. Why would you ever want your profile file not to execute fully?
Because there are things like color settings you only need to have in interactive shells.
It's not my code, I just quoted it - the shown code is already included in .bashrc after a fresh linux install (I think because .bashrc is only intended for interactive shells).
In my case, this quoted lines are the cause why node and npm are only available in interactive shells, if installed via nvm (because the nvm lines are below the return statement). My suggestion is that the installer puts the nvm lines above it.
I’m not sure how to code the installer to parse a profile file, figure out at which point the file returns early, and then place the lines above it. Looking for that exact pattern would only handle that one use case, which doesn’t seem particularly common. Do you have a suggestion?
You could place the nvm-lines on the first line of .bashrc by replacing line 367 in install.sh with this:
# insert the nvm source string at the first possible line
command sed -i "${FIRST_LINE_IN_PROFILE}"'i\'"${SOURCE_STR}" "${NVM_PROFILE}"
And in line 374 for bash completion:
command sed -i "${LINE_FOR_BASH_COMPLETION}"'i\'"${COMPLETION_STR}" "${NVM_PROFILE}"
You need some tmp variables before that code (probably in line 349):
# find first line not starting with a comment (#)
FIRST_LINE_IN_PROFILE=$( command grep -n -m 1 -v "^[#]" "${NVM_PROFILE}" | command cut -d: -f1)
# number of lines for nvm source string
NVM_SOURCE_STRING_LINES=3 # this number should also be parsed from the string
LINE_FOR_BASH_COMPLETION=$((FIRST_LINE_IN_PROFILE + NVM_SOURCE_STRING_LINES))
There can be some problems with my code:
- I don't know which values
NVM_PROFILEcan become. Maybe this code has some problems with other profile files (I don't think so) - I don't know zsh. not sure if it works there
Users can manually add the nvm lines anywhere in their profile, and that should be supported too.
Yes, it's still possible with my code
I have a nvm-ci script with this inside:
#!/bin/bash
# set -euo pipefail
# IFS=$'\n\t'
NVM_DIR=~/.nvm
[ -f "$NVM_DIR/nvm.sh" ] || wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" # This loads nvm
nvm install
nvm use
And this is executed in my CI with . nvm-ci which is how you import environmental variables in BASH.
@forbesmyester note that the last nvm use is redundant, since install auto-uses.
You can just make bash source it for non-interactive shells
echo '. $NVM_DIR/nvm.sh' > /usr/bin/source_nvm.sh && chmod +x /usr/bin/source_nvm.sh
and set BASH_ENV environment variable as follows
export BASH_ENV=/usr/bin/source_nvm.sh
Bash sources $BASH_ENV even for non-interactive shells
Update: To make it explicit: This is a workaround that I want to share here, since this is the most relevant issue to find when having this problem.
From all the answers here and in this stackoverflow question I created this script (gist) that you can copy or clone (documentation is inlined):
- doesn't rely on bash or any shell specific "profile" files or things for "interactive mode"
- doesn't install nvm, just uses it if available
- supports the recommended linux and mac install paths
# shellcheck shell=sh
# https://gist.github.com/karfau/dcf98c6eefc2f2132c160f5c14d2112f
# needs to be sourced as part of your script that needs nvm support
# 1. tries to configure nvm and run `nvm install`
# 2. checks if the node version is correct based on ./.nvmrc (`v` prefix not supported)
# if both doesn't work, exits with code 1 and some helpful messages
# https://unix.stackexchange.com/a/184512/194420
# https://github.com/nvm-sh/nvm/issues/1290
if [ -f ~/.nvm/nvm.sh ]; then
echo 'sourcing nvm from ~/.nvm'
. ~/.nvm/nvm.sh
elif command -v brew; then
# https://docs.brew.sh/Manpage#--prefix-formula
BREW_PREFIX=$(brew --prefix nvm)
if [ -f "$BREW_PREFIX/nvm.sh" ]; then
echo "sourcing nvm from brew ($BREW_PREFIX)"
. $BREW_PREFIX/nvm.sh
fi
fi
if command -v nvm ; then
nvm i
else
echo "WARN: not able to configure nvm"
fi
NODE_VERSION="v$(cat .nvmrc)"
which node
ACTIVE_VERSION=$(node --version)
GLOBAL_NPM=$(which npm || echo "not found on PATH")
# .nvmrc can contain only major or major.minor or full version
# so we replace active version with node version and anything afterwards
# if something is left, it's not a match
if [ "${ACTIVE_VERSION%%$NODE_VERSION*}" ] || [ ! -e "$GLOBAL_NPM" ]; then
echo "expected node '$NODE_VERSION' and npm on path"
echo "but was '$ACTIVE_VERSION' and npm:'$GLOBAL_NPM'"
exit 1
fi
Hope it helps, feedback on the gist is welcome.
@karfau when installed via brew, nvm is unsupported, so I’d prefer not to see it encouraged. additionally, this script seems like it would still have to be added to the right profile files anyways, or else you’d have to invoke it manually - if so, how is that an improvement over the current situation?
when installed via brew, nvm is unsupported
I was not aware of that, sorry. For the people that still installed it this way when it was supported and don't need/want to change their setup, the above script works for them as well.
additionally, this script seems like it would still have to be added to the right profile files anyways, or else you’d have to invoke it manually
That's correct, it seems like I forgot to mention that this is not a proposal for a solution, but a workaround. I updated my comment to make this clear. And just to be explicit: The intended usage is not for automatic/inclusion into a profile file, but for inclusion into the script that needs/wants nvm support.
if so, how is that an improvement over the current situation?
Since the issue is open since four years, I figured, it's better to share a "solid and helpful" workaround that solves the issue on the side of the user that needs nvm support in their script of choice. I must admit that I'm sure I didn't read the whole issue thread (but most of it). So if you think this is not contributing to the problem, feel free to act accordingly.
What we really need is a symlink. After years of failed attempts at putting node everywhere, now I just run this in a common user's .bashrc, after NVM initialization:
if ! [ "$(which node)" -ef "$(readlink -f /opt/bin/node)" ]; then
echo "Fixing node symlinks."
ln -fs "$NVM_BIN/"* /opt/bin
fi
Then add /opt/bin to /etc/profile.d, the root user and the sercure_path of visudo.
Only downside is symlinks have to be supported and -ef is not posix, but all bash like shells support it, except sh. NVM_DIR also has to be accessible by all users... i moved it to /opt/nvm