2 <a href="http://liftoffjs.com">
3 <img height="100" width="297" src="https://cdn.rawgit.com/tkellen/js-liftoff/master/artwork/liftoff.svg"/>
7 # liftoff [![Build Status](https://secure.travis-ci.org/js-cli/js-liftoff.svg)](http://travis-ci.org/js-cli/js-liftoff) [![Build status](https://ci.appveyor.com/api/projects/status/5a6w8xuq8ed1ilc4/branch/master?svg=true)](https://ci.appveyor.com/project/js-cli/js-liftoff/branch/master)
9 > Launch your command line tool with ease.
11 [![NPM](https://nodei.co/npm/liftoff.png)](https://nodei.co/npm/liftoff/)
14 [See this blog post](http://weblog.bocoup.com/building-command-line-tools-in-node-with-liftoff/), [check out this proof of concept](https://github.com/js-cli/js-hacker), or read on.
16 Say you're writing a CLI tool. Let's call it [hacker](https://github.com/js-cli/js-hacker). You want to configure it using a `Hackerfile`. This is node, so you install `hacker` locally for each project you use it in. But, in order to get the `hacker` command in your PATH, you also install it globally.
18 Now, when you run `hacker`, you want to configure what it does using the `Hackerfile` in your current directory, and you want it to execute using the local installation of your tool. Also, it'd be nice if the `hacker` command was smart enough to traverse up your folders until it finds a `Hackerfile`—for those times when you're not in the root directory of your project. Heck, you might even want to launch `hacker` from a folder outside of your project by manually specifying a working directory. Liftoff manages this for you.
20 So, everything is working great. Now you can find your local `hacker` and `Hackerfile` with ease. Unfortunately, it turns out you've authored your `Hackerfile` in coffee-script, or some other JS variant. In order to support *that*, you have to load the compiler for it, and then register the extension for it with node. Good news, Liftoff can do that, and a whole lot more, too.
26 Create an instance of Liftoff to invoke your application.
28 An example utilizing all options:
30 const Hacker = new Liftoff({
32 processTitle: 'hacker',
34 configName: 'hackerfile',
38 '.coffee': 'coffee-script/register'
40 v8flags: ['--harmony'] // or v8flags: require('v8flags')
46 Sugar for setting `processTitle`, `moduleName`, `configName` automatically.
53 const Hacker = Liftoff({
54 processTitle: 'hacker',
56 configName: 'hackerfile'
60 const Hacker = Liftoff({name:'hacker'});
65 Sets which module your application expects to find locally when being run.
72 Sets the name of the configuration file Liftoff will attempt to find. Case-insensitive.
79 Set extensions to include when searching for a configuration file. If an external module is needed to load a given extension (e.g. `.coffee`), the module name should be specified as the value for the key.
82 Default: `{".js":null,".json":null}`
86 In this example Liftoff will look for `myappfile{.js,.json,.coffee}`. If a config with the extension `.coffee` is found, Liftoff will try to require `coffee-script/require` from the current working directory.
88 const MyApp = new Liftoff({
93 '.coffee': 'coffee-script/register'
98 In this example, Liftoff will look for `.myapp{rc}`.
100 const MyApp = new Liftoff({
102 configName: '.myapp',
109 In this example, Liftoff will automatically attempt to load the correct module for any javascript variant supported by [node-interpret](https://github.com/tkellen/node-interpret) (as long as it does not require a register method).
112 const MyApp = new Liftoff({
114 extensions: require('interpret').jsVariants
119 Any flag specified here will be applied to node, not your program. Useful for supporting invocations like `myapp --harmony command`, where `--harmony` should be passed to node, not your program. This functionality is implemented using [flagged-respawn](http://github.com/tkellen/node-flagged-respawn). To support all v8flags, see [node-v8flags](https://github.com/tkellen/node-v8flags).
121 Type: `Array|Function`
124 If this method is a function, it should take a node-style callback that yields an array of flags.
126 #### opts.processTitle
128 Sets what the [process title](http://nodejs.org/api/process.html#process_process_title) will be.
133 #### opts.completions(type)
135 A method to handle bash/zsh/whatever completions.
140 #### opts.configFiles
142 An object of configuration files to find. Each property is keyed by the default basename of the file being found, and the value is an object of [path arguments](#path-arguments) keyed by unique names.
144 __Note:__ This option is useful if, for example, you want to support an `.apprc` file in addition to an `appfile.js`. If you only need a single configuration file, you probably don't need this. In addition to letting you find multiple files, this option allows more fine-grained control over how configuration files are located.
151 The [`fined`](https://github.com/js-cli/fined) module accepts a string representing the path to search or an object with the following keys:
153 * `path` __(required)__
155 The path to search. Using only a string expands to this property.
162 The basename of the file to find. Extensions are appended during lookup.
165 Default: Top-level key in `configFiles`
169 The extensions to append to `name` during lookup. See also: [`opts.extensions`](#optsextensions).
171 Type: `String|Array|Object`
172 Default: The value of [`opts.extensions`](#optsextensions)
176 The base directory of `path` (if relative).
179 Default: The value of [`opts.cwd`](#optscwd)
183 Whether the `path` should be traversed up to find the file.
190 In this example Liftoff will look for the `.hacker.js` file relative to the `cwd` as declared in `configFiles`.
192 const MyApp = new Liftoff({
202 In this example, Liftoff will look for `.hackerrc` in the home directory.
204 const MyApp = new Liftoff({
219 In this example, Liftoff will look in the `cwd` and then lookup the tree for the `.hacker.js` file.
221 const MyApp = new Liftoff({
234 In this example, the `name` is overridden and the key is ignored so Liftoff looks for `.override.js`.
236 const MyApp = new Liftoff({
249 In this example, Liftoff will use the home directory as the `cwd` and looks for `~/.hacker.js`.
251 const MyApp = new Liftoff({
264 ## launch(opts, callback(env))
265 Launches your application with provided options, builds an environment, and invokes your callback, passing the calculated environment as the first argument.
267 ##### Example Configuration w/ Options Parsing:
269 const Liftoff = require('liftoff');
270 const MyApp = new Liftoff({name:'myapp'});
271 const argv = require('minimist')(process.argv.slice(2));
272 const invoke = function (env) {
273 console.log('my environment is:', env);
274 console.log('my cli options are:', argv);
275 console.log('my liftoff config is:', this);
279 configPath: argv.myappfile,
280 require: argv.require,
281 completion: argv.completion
287 Change the current working directory for this launch. Relative paths are calculated against `process.cwd()`.
290 Default: `process.cwd()`
292 **Example Configuration:**
294 const argv = require('minimist')(process.argv.slice(2));
300 **Matching CLI Invocation:**
307 Don't search for a config, use the one provided. **Note:** Liftoff will assume the current working directory is the directory containing the config file unless an alternate location is explicitly specified using `cwd`.
312 **Example Configuration:**
314 var argv = require('minimist')(process.argv.slice(2));
316 configPath: argv.myappfile
320 **Matching CLI Invocation:**
322 myapp --myappfile /var/www/project/Myappfile.js
325 **Examples using `cwd` and `configPath` together:**
327 These are functionally identical:
329 myapp --myappfile /var/www/project/Myappfile.js
330 myapp --cwd /var/www/project
333 These can run myapp from a shared directory as though it were located in another project:
335 myapp --myappfile /Users/name/Myappfile.js --cwd /var/www/project1
336 myapp --myappfile /Users/name/Myappfile.js --cwd /var/www/project2
341 A string or array of modules to attempt requiring from the local working directory before invoking the launch callback.
346 **Example Configuration:**
348 var argv = require('minimist')(process.argv.slice(2));
350 require: argv.require
354 **Matching CLI Invocation:**
356 myapp --require coffee-script/register
361 A function to start your application. When invoked, `this` will be your instance of Liftoff. The `env` param will contain the following keys:
363 - `cwd`: the current working directory
364 - `require`: an array of modules that liftoff tried to pre-load
365 - `configNameSearch`: the config files searched for
366 - `configPath`: the full path to your configuration file (if found)
367 - `configBase`: the base directory of your configuration file (if found)
368 - `modulePath`: the full path to the local module your project relies on (if found)
369 - `modulePackage`: the contents of the local module's package.json (if found)
370 - `configFiles`: an object of filepaths for each found config file (filepath values will be null if not found)
374 #### require(name, module)
376 Emitted when a module is pre-loaded.
379 var Hacker = new Liftoff({name:'hacker'});
380 Hacker.on('require', function (name, module) {
381 console.log('Requiring external module: '+name+'...');
382 // automatically register coffee-script extensions
383 if (name === 'coffee-script') {
389 #### requireFail(name, err)
391 Emitted when a requested module cannot be preloaded.
394 var Hacker = new Liftoff({name:'hacker'});
395 Hacker.on('requireFail', function (name, err) {
396 console.log('Unable to load:', name, err);
400 #### respawn(flags, child)
402 Emitted when Liftoff re-spawns your process (when a [`v8flags`](#optsv8flags) is detected).
405 var Hacker = new Liftoff({
407 v8flags: ['--harmony']
409 Hacker.on('respawn', function (flags, child) {
410 console.log('Detected node flags:', flags);
411 console.log('Respawned to PID:', child.pid);
415 Event will be triggered for this command:
416 `hacker --harmony commmand`
420 Check out how [gulp](https://github.com/gulpjs/gulp/blob/master/bin/gulp.js) uses Liftoff.
422 For a bare-bones example, try [the hacker project](https://github.com/js-cli/js-hacker/blob/master/bin/hacker.js).
424 To try the example, do the following:
426 1. Install the sample project `hacker` with `npm install -g hacker`.
427 2. Make a `Hackerfile.js` with some arbitrary javascript it.
428 3. Install hacker next to it with `npm install hacker`.
429 3. Run `hacker` while in the same parent folder.