Executing A Project-Specific Node/NPM Package A-la “bundle exec”

It’s no secret that I love NodeJS, though I really haven’t blogged about it much (if at all). Frankly, I think Node and NPM are going to be eating Ruby, RubyGems and Bundler’s $25,000 lunch – at least on OSX. But there’s at least one piece of bundler that I miss from NPM: the ability to say ‘bundle exec’ and have it run a specific gem’s bin file (binary / executable) for the current project gem configuration and version.

If I run “npm install express” from within a folder, the “express” package will be installed in to my ./node_modules folder.

Screen Shot 2012 04 17 at 6 28 41 PM

But I can’t say “express foo” on the command line at this point, to run express. And I can’t say “npm express foo” or “npm exec express foo” or anything like that. This ability is sadly missing from NPM. There are a few ways to get around this, though.

Global Modules

When you install a Node module with the `-g` option, it installs in to the global module list. Global modules can have their bin files run from the command line just like any other command. So if you install the “express” module, for example, with the global flag then you’ll be able to say “express foo” from the command line and watch it build a fresh new project for you in the “foo” folder.

Screen Shot 2012 04 17 at 6 30 23 PM

This is nice for packages that you want to have available for any application you’re building. I use global packages a lot because some of the tools that you can find in node packages are quite useful, even outside the context of a node project. But not every package should be installed in the global package repository for your computer.

Run Bin From Relative Path

You can also run bin files from node modules by using the relative path of ./node_modules/.bin/ to find the command.

Screen Shot 2012 04 17 at 6 32 44 PM

This is a bit tedious, though it is functional. I’ve build a few simple command / shell scripts to execute some of my installed modules this way, for some of my projects. That makes it a bit easier overall, but you have to set up a shell script for each package you want.

Run Bin From Generic Shell Script

Since all of the bin files for all of the node packages are installed in to ./node_modules/.bin/ for a given project, why not build a generic “npm_exec” shell script and just forward all the parameters?

Easy enough:

./node_modules/.bin/$@

Just “chmod +x npm_exec” to make this file executable, and away we go.

Now I can run any arbitrary package binary I want. I could even put this little shell script in my /usr/local/bin folder and it would be available for all of my node projects, anywhere on my box.

Screen Shot 2012 04 17 at 6 35 03 PM

This is a nice little solution, even if it’s a work around for something that should be built in to npm.

NPM Scripts In package.json

This last option is one that I’m using more and more often for project specific, recurring needs.

When you create an express app, it builds a package.json file for you. One of the pieces that it sticks in there is the “scripts” setting which contains a “start” setting, by default.

{
  "name": "application-name",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "start app"
  },
  "dependencies": {
    "express": "3.0.0alpha1",
    "jade": "*"
  }
}

It turns out you can use these “scripts” from the npm command line. When you call “npm start”, npm will execute the “scripts”/”start” configuration for you. By default, this is just a call to “node app” which runs the app.js file in node and gets your express.js app up and running.

You can do more with “scripts”, too. You can add your own named script, in fact.

{
  ...

  "scripts": {
    "start": "node app",
    "foo": "node -e 'console.log(1+2)'"
  }

  ...
}

But you can’t just “npm foo”. The “start” script is recognized by npm explicitly. For other non-standard script names, you have to use the “run-script” command from node: npm run-script foo

Screen Shot 2012 04 17 at 6 38 18 PM

The other thing that this does for us, is give us direct access to all of the bin files that our node packages have installed. So within a “script” configuration, we can call any arbitrary package’s bin file.

For example, if I want to use the node-supervisor package to restart my app whenever any files change, I can set up my package.json file to look like this:

{
  "name": "application-name",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "supervisor app"
  },
  "dependencies": {
    "express": "3.0.0alpha1",
    "jade": "*"
  },
  "devDependencies": {
    "supervisor": "*"
  }
}

This will install the “supervisor” package for development only, and set up the “start” script to run “supervisor app”. Now from the command line, I can’t run “supervisor app” directly:

Screen Shot 2012 04 17 at 6 40 56 PM

But I can run “node start” and node will pick up the ./node_modules/.bin/ folder for me, allowing supervisor to be executed:

Screen Shot 2012 04 17 at 6 41 49 PM

This works well for recurring / repetitive tasks within a project. But if you want ad-hoc package commands, you’re going to be in for a little more work and will likely have to fall back to one of the other solutions I’ve mentioned.

Other Options?

I really do miss the “bundle exec” feature of bundler. I honestly don’t know why npm doesn’t have an equivalent built in to it. Maybe it just needs someone to come along and contribute this feature. Maybe this feature was rejected. I don’t know. I haven’t looked in to it. But I wanted to get something in place now, so that I don’t have to install all of my project specific packages in the global package list.

There might be better option than the ones I’ve listed, as well. So, what am I missing? Is there something built in to npm to make this easier? Are there other ways that you’ve worked around this? Or am I going to stick with my “npm_exec” script and using npm “scripts” in my package.json fil?

 


Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs SignalLeaf.com - the amazingly awesome podcast audio hosting service that everyone should be using, and WatchMeCode.net where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in Command Line, Javascript, NodeJS, NPM. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://twitter.com/EsaMatti Esa-Matti Suuronen

    Here’s a fun hack. Put 

    export PATH=./node_modules/.bin/:$PATH

    to your ~/.bashrc and tada! You can now run your locally installed npm binaries without any prefixes. You just have to be in your project directory where the node_modules subdirectory is.

    • jeskeca

      Since node searches parents for node_modules, why doesn’t it just let us do something like “npm run ” ?

  • Guest

    gist-240904 has been turned into spam.

    • http://mutedsolutions.com Derick Bailey

      DOH! i accidentally got the gist # wrong on that one. fixed. thanks for letting me know!

  • James Smith

    Here’s my hack I use for coffee.

    Create a new bash script under /usr/local/bin/coffee with the following:
    `npm bin`/coffee

    npm bin will resolve to the node_modules/.bin path even if you’re in a sub directory of a project. Best of all is allows multiple version of coffee across different projects. 

    This could be used for any bin package you use like express, just make the necessary changes. I imagine someone well versed in bash could also use the bash script’s name instead so you can just copy the script as a new name for any npm binary

  • Rick Jones

    Might be better to use something like:

    alias npm-exec=’PATH=$(npm bin):$PATH’

    That way you execute commands in your project’s bin directory from anywhere in the tree rather than just the root.

  • http://jquery4u.com/ jQuery4u

    Thanks bro

  • Thomas Deutsch

    I think this solution might be deprecated. Take a look at: https://github.com/npm/npm/issues/3738

    isaacs: “That is, you should never have to do ./node_modules/.bin/anything, for any reason. If you’re running your script from npm, then that’s already in the PATH environment variable.”