Defining global scripts in NPM

I've never quite known the proper way to declare a global script within an npm module. The docs aren't exactly hazy, but I conducted a little survey, below, to grok the overall practice. A brief wrapup follows.

Express

The meat of the app exists in ./bin/express, which freely requires scripts with paths relative to itself.

package.json

"bin": {
  "express": "./bin/express"
}

./bin/express"

#!/usr/bin/env node
# [...]

Grunt

Uses precisely the same pattern as express

package.json

"bin": {
    "grunt": "bin/grunt"
}

bin/grunt

#!/usr/bin/env node
# [...]

mocha

Mocha mixes things up a bit. "mocha" does little but execute "node ./_mocha", except when certain commands would modify node instead of mocha, i.e. "node debug _mocha". It wasn't always that way.

package.json

  "bin": {
    "mocha": "./bin/mocha",
      "_mocha": "./bin/_mocha"
  }

./bin/mocha

#!/usr/bin/env node
/**
 * This tiny wrapper file checks for known node flags and appends them
 * when found, before invoking the "real" _mocha(1) executable.
 */
// [...]
args = [ __dirname + '/_mocha' ]
/* [building some arguments] */

./bin/_mocha

#!/usr/bin/env node

/** [familiar..]
 * Module dependencies.
 */

UglifyJS

Standard.

package.json

"bin"     : {
  "uglifyjs" : "./bin/uglifyjs"
}

./bin/uglifyjs

#!/usr/bin/env node

[...]

browserify

Similar invocation, with an interesting twist: help files are stored in bin/usage.txt and bin/advanced.txt, offering a pretty neat way to manage usage if not using commanderjs.

package.json

"bin": {
  "browserify": "bin/cmd.js"
}

bin/cmd.js

#!/usr/bin/env node
var fs = require('fs');
[...]

jshint

jshint is the first on this list that has a one-liner bash script. Still, it invokes ../src/cli.js.

package.json

"bin": {
  "jshint": "./bin/jshint"
}

./bin/jshint

#!/usr/bin/env node

require("../src/cli.js").interpret(process.argv);

../src/cli.js

// [...]
var OPTIONS = {
    "config": ["c", "Custom configuration file", "string", false ],
    //[...]

Wrapup

  1. The common method of creating a global command is to define it in package.json like so:

    package.json

     "bin": {
       "{commandName}": "bin/{fileName}"
     }

    The npm docs don't disagree.

  2. That script then does the CLI-oriented stuff

    bin/{fileName}

     #!/usr/bin/env node
     var program = require('commander');
     program.parse(process.argv);
  3. The script can require files from its package using paths relative to itself

    bin/{fileName}

     #!/usr/bin/env node
     var tool = require('../lib/tool');
  4. To easily surface usage/help messages, simply write then as .txt files à la browserify:

    bin/{fileName}

     #!/usr/bin/env node
       return fs.createReadStream(__dirname + '/advanced.txt')
         .pipe(process.stdout)
         .on('close', function () { process.exit(1) })
       ;