Running locally installed npm executables

[2016-01-07] dev, javascript, nodejs
(Ad, please don’t block)

One nice npm feature is that you can install packages with executables locally. This blog post explains how to run locally installed executables.

Running executables from a nearby node_modules  

(An aside, on the topic of packages versus modules: npm packages may or may not contain Node.js modules.)

If you require a module, Node.js looks for it by going through all node_modules/ directories in ancestor directories (./node_modules/, ../node_modules/, ../../node_modules/, etc.). The first appropriate module that is found is used.

Whenever you are somewhere in the file system, npm root tells you where it would install packages if you used npm install. That directory node_modules/ may or may not exist, already; in the following example, directory /tmp/ is empty.

$ cd /tmp/
$ npm root
/tmp/node_modules

When executables are installed via npm packages, npm links to them:

  • In local installs, they are linked to from a node_modules/.bin/ directory.
  • In global installs, they are linked to from a global bin/ directory (e.g. /usr/local/bin).

The command npm bin lets you find out where the closest executables are:

$ npm bin
/tmp/node_modules/.bin

If your shell is bash then you can define the following command for running executables from that directory:

function npm-do { (PATH=$(npm bin):$PATH; eval $@;) }

Let’s try out that shell command: We install package figlet-cli that comes with an executable. npm puts multiple packages into the closest node_modules/ and links to the executable figlet from node_modules/.bin/:

$ npm install figlet-cli
$ ls -1 /tmp/node_modules/
figlet
figlet-cli
minimist
optimist
wordwrap
$ ls -1 /tmp/node_modules/.bin
figlet

If we run figlet as a normal shell command, it fails, because we haven’t installed the package (and thus the executable) globally. However, npm-do allows us to run figlet.

$ figlet hi
-bash: figlet: command not found
$ npm-do figlet hi
  _     _
 | |__ (_)
 | '_ \| |
 | | | | |
 |_| |_|_|

Inside an npm package  

I’m using the repo npm-bin-demo to demonstrate running executables from inside an npm package. This repo is installed as follows (feel free to read on without doing that):

git clone https://github.com/rauschma/npm-bin-demo.git
cd npm-bin-demo/
npm install

That package has the following package.json:

{
  "bin": {
    "hello": "./hello.js"
  },
  "scripts": {
    "fig": "figlet",
    "hello": "./hello.js"
  },
  "dependencies": {
    "figlet-cli": "^0.1.0"
  }
}
  • bin: lists the executables provided by this package. It only matters if this package is installed via npm and then affects the node_modules/ of an ancestor directory.

  • scripts: defines commands that you can execute via npm run if the current package.json is the one that is closest to your current working directory. Note that we can use figlet as if it were a globally installed shell command. That’s because npm adds local .bin/ directories to the shell path before it executes scripts.

  • dependencies: lists packages that are installed by npm install, into npm-bin-demo/node_modules/. As you can see, we have installed figlet-cli.

Let’s examine our surroundings (remember that we are still inside the directory npm-bin-demo/):

$ npm root
/tmp/npm-bin-demo/node_modules
$ ls -1 node_modules/
figlet
figlet-cli
minimist
optimist
wordwrap

$ npm bin
/tmp/npm-bin-demo/node_modules/.bin
$ ls -1 node_modules/.bin/
figlet

As expected, there is no shell command figlet, but we can run figlet via npm-do:

$ figlet hi
-bash: figlet: command not found
$ npm-do figlet hi
  _     _
 | |__ (_)
 | '_ \| |
 | | | | |
 |_| |_|_|

We can also execute figlet via npm run:

$ npm run fig hi

> @ fig /Users/rauschma/tmp/npm-bin-demo
> figlet "hi"

  _     _
 | |__ (_)
 | '_ \| |
 | | | | |
 |_| |_|_|

As explained previously, the entries in bin have no effect inside a package, which is why we can’t run hello via npm-do:

$ npm-do hello
-bash: hello: command not found

We can, however, run the script whose name is hello:

$ npm run hello

> @ hello /tmp/npm-bin-demo
> ./hello.js

Hello everyone!

Further reading  

For more information on the topic of local npm installs, consult Sect. “npm and local installs” in “Setting up ES6”.