Avoiding NPM global install using .bash_profile an

2019-04-02 09:06发布

问题:

NPM philosophy, for better or worse, is to have all necessary dependencies for a project to be locally installed, in ./node_modules, and for package.json to reflect all necessary deps.

I have a library, that would typically benefit from both global installation and local installation, similar to Gulp, Mocha, etc.

One of the most insiduous problems with the above, is that the global and local versions might differ, causing compatibility issues for a given project.

It occurred to me that we could avoid this problem, by creating an alias in .bash_profile, something like this:

# bash psuedocode 
* alias gulp='using current working directory, find the locally installed gulp and run that'

My library is not Gulp, but you get the idea. I know a lot of devs don't like the idea of 3rd parties writing to their bash_profiles, but I think that is a bit anal-retentive, considering doing something like this has zero outside effects (TMK).

So I have three questions:

(1) Is this a good idea?

(2) Are there any projects that do something similar and how?

(3) I an not great with bash scripting, how do you write a bash script that can recursively look for node_modules/.bin/gulp if you the dev runs the gulp command from a directory that is within the project but not in the root?

My thinking was that as a postinstall script to

npm install --save-dev gulp

we would run a script that

  1. added the above line* to the .bash_profile
  2. in that line, we would point to a script stored under /Users/you/.gulp that would be responsible for locating the locally installed gulp.

This way we save the need for global installations of modules, when they are only used for command line convenience, as well as alleviating the problem of differing package versions.

回答1:

I'll go through your 3 questions one by one:

(1) Is this a good idea?

No. Changing user's private files automatically is never a good idea and I see several things that can go horribly wrong if you do that - from a pure annoyance of users having to clean after your programs, to breaking user's system config, to serious security problems.

(2) Are there any projects that do something similar and how?

I hope not. I would never run any program that messes with my .bash_profile - even though I don't have it. I have .profile and .bashrc. And that's another problem for you - are you sure you even know what to edit? What if I use dash which I use sometimes, or zsh or some other shell?

(3) I an not great with bash scripting, how do you write a bash script that can recursively look for node_modules/.bin/gulp if you the dev runs the gulp command from a directory that is within the project but not in the root?

If you are not great with bash scripting then your definitely should not edit user's private files automatically.

What you can do is to provide a certain alias or bash function in a separate file for user's convenience and explain how users can source your file inside of their .bashrc if they want to.

But never make that decision for them. If anything goes wrong this can even have legal consequences for you.

Examples of functions

Answering your questions posted in the comments, I'll add few words on how functions work in Bash.

In my collection of script on GitHub I have few functions with instructions on how to install them and how to use them. Their source code is available here.

An example function in that collection is ok - this is its source code:

ok() {
  # prints OK or ERROR and exit status of previous command
  s=$?
  if [[ $s = 0 ]]; then
    echo OK
  else
    echo ERROR: $s
  fi
}

You can download it (with some other related functions) from:

  • https://raw.githubusercontent.com/rsp/scripts/master/ok-functions

If you save it as ~/ok-functions then you can either run:

source ~/ok-functions

to have them available in that session, or you can put that line in .profile or .bashrc to have it available after every login. You can use it as if it was a normal command or program like this:

ls /bin; ok
ls /binn; ok

Working example of preventing global install

I wrote a Bash function that does what you are trying to do - it stops global installation of a certain module - called modname in this example. Save it to some file - e.g. to ~/no-global-restricted-module - of course you can change the restricted-module name in n and the warning message in w to suit your needs:

npm() {
  n=restricted-module
  w="Please don't install $n globally - see: http://example.com/"
  i=0
  g=0
  m=0
  for a in "$@"; do
    case $a in
      i|install)
        i=1;;
      -g|--global)
        g=1;;
      $n)
        m=1;;
    esac
  done
  if (( $i == 1 && $g == 1 && $m == 1 )); then
    echo $w >&2
    return 1
  else
    `which npm` "$@"
  fi
}

And then add this line to your ~/.profile or ~/.bashrc:

source ~/no-global-restricted-module

Now when you log in again, or when you open a new terminal window, you can try running this in your command line:

npm install --global restricted-module

You should see a warning and no installation will be started. If you enter some other module name or you omit the --global switch then the installation should proceed as usual. This also works with the shortcuts of i instead of install and -g instead of --global.

This is pretty much what you are trying to do. You can include that function in your documentation with instructions on how to use it. I am hereby releasing it under the terms of the MIT license so you can use it freely. I published it on GitHub with some more options for configuration and information on how to use it, see:

  • https://github.com/rsp/npm-no-global-install

See also

See also this answer for a much better explanation of writing, installing and using Bash functions on the example of playing various sounds in the terminal.