Initial commit
[yaffs-website] / node_modules / liftoff / README.md
1 <p align="center">
2   <a href="http://liftoffjs.com">
3     <img height="100" width="297" src="https://cdn.rawgit.com/tkellen/js-liftoff/master/artwork/liftoff.svg"/>
4   </a>
5 </p>
6
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)
8
9 > Launch your command line tool with ease.
10
11 [![NPM](https://nodei.co/npm/liftoff.png)](https://nodei.co/npm/liftoff/)
12
13 ## What is it?
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.
15
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.
17
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`&mdash;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.
19
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.
21
22 ## API
23
24 ### constructor(opts)
25
26 Create an instance of Liftoff to invoke your application.
27
28 An example utilizing all options:
29 ```js
30 const Hacker = new Liftoff({
31   name: 'hacker',
32   processTitle: 'hacker',
33   moduleName: 'hacker',
34   configName: 'hackerfile',
35   extensions: {
36     '.js': null,
37     '.json': null,
38     '.coffee': 'coffee-script/register'
39   },
40   v8flags: ['--harmony'] // or v8flags: require('v8flags')
41 });
42 ```
43
44 #### opts.name
45
46 Sugar for setting `processTitle`, `moduleName`, `configName` automatically.
47
48 Type: `String`  
49 Default: `null`
50
51 These are equivalent:
52 ```js
53 const Hacker = Liftoff({
54   processTitle: 'hacker',
55   moduleName: 'hacker',
56   configName: 'hackerfile'
57 });
58 ```
59 ```js
60 const Hacker = Liftoff({name:'hacker'});
61 ```
62
63 #### opts.moduleName
64
65 Sets which module your application expects to find locally when being run.
66
67 Type: `String`  
68 Default: `null`
69
70 #### opts.configName
71
72 Sets the name of the configuration file Liftoff will attempt to find.  Case-insensitive.
73
74 Type: `String`  
75 Default: `null`
76
77 #### opts.extensions
78
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.
80
81 Type: `Object`  
82 Default: `{".js":null,".json":null}`
83
84 **Examples:**
85
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.
87 ```js
88 const MyApp = new Liftoff({
89   name: 'myapp',
90   extensions: {
91     '.js': null,
92     '.json': null,
93     '.coffee': 'coffee-script/register'
94   }
95 });
96 ```
97
98 In this example, Liftoff will look for `.myapp{rc}`.
99 ```js
100 const MyApp = new Liftoff({
101   name: 'myapp',
102   configName: '.myapp',
103   extensions: {
104     'rc': null
105   }
106 });
107 ```
108
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).
110
111 ```js
112 const MyApp = new Liftoff({
113   name: 'myapp',
114   extensions: require('interpret').jsVariants
115 });
116 ```
117 #### opts.v8flags
118
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).
120
121 Type: `Array|Function`  
122 Default: `null`
123
124 If this method is a function, it should take a node-style callback that yields an array of flags.
125
126 #### opts.processTitle
127
128 Sets what the [process title](http://nodejs.org/api/process.html#process_process_title) will be.
129
130 Type: `String`  
131 Default: `null`
132
133 #### opts.completions(type)
134
135 A method to handle bash/zsh/whatever completions.
136
137 Type: `Function`  
138 Default: `null`
139
140 #### opts.configFiles
141
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.
143
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.
145
146 Type: `Object`  
147 Default: `null`
148
149 #### Path arguments
150
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:
152
153 * `path` __(required)__
154
155   The path to search. Using only a string expands to this property.
156
157   Type: `String`  
158   Default: `null`
159
160 * `name`
161
162   The basename of the file to find. Extensions are appended during lookup.
163
164   Type: `String`  
165   Default: Top-level key in `configFiles`
166
167 * `extensions`
168
169   The extensions to append to `name` during lookup. See also: [`opts.extensions`](#optsextensions).
170
171   Type: `String|Array|Object`  
172   Default: The value of [`opts.extensions`](#optsextensions)
173
174 * `cwd` 
175
176   The base directory of `path` (if relative).
177
178   Type: `String`  
179   Default: The value of [`opts.cwd`](#optscwd)
180
181 * `findUp`
182
183   Whether the `path` should be traversed up to find the file.
184
185   Type: `Boolean`  
186   Default: `false`
187
188 **Examples:**
189
190 In this example Liftoff will look for the `.hacker.js` file relative to the `cwd` as declared in `configFiles`.
191 ```js
192 const MyApp = new Liftoff({
193   name: 'hacker',
194   configFiles: {
195     '.hacker': {
196       cwd: '.'
197     }
198   }
199 });
200 ```
201
202 In this example, Liftoff will look for `.hackerrc` in the home directory.
203 ```js
204 const MyApp = new Liftoff({
205   name: 'hacker',
206   configFiles: {
207     '.hacker': {
208       home: {
209         path: '~',
210         extensions: {
211           'rc': null
212         }
213       }
214     }
215   }
216 });
217 ```
218
219 In this example, Liftoff will look in the `cwd` and then lookup the tree for the `.hacker.js` file.
220 ```js
221 const MyApp = new Liftoff({
222   name: 'hacker',
223   configFiles: {
224     '.hacker': {
225       up: {
226         path: '.',
227         findUp: true
228       }
229     }
230   }
231 });
232 ```
233
234 In this example, the `name` is overridden and the key is ignored so Liftoff looks for `.override.js`.
235 ```js
236 const MyApp = new Liftoff({
237   name: 'hacker',
238   configFiles: {
239     hacker: {
240       override: {
241         path: '.',
242         name: '.override'
243       }
244     }
245   }
246 });
247 ```
248
249 In this example, Liftoff will use the home directory as the `cwd` and looks for `~/.hacker.js`.
250 ```js
251 const MyApp = new Liftoff({
252   name: 'hacker',
253   configFiles: {
254     '.hacker': {
255       home: {
256         path: '.',
257         cwd: '~'
258       }
259     }
260   }
261 });
262 ```
263
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.
266
267 ##### Example Configuration w/ Options Parsing:
268 ```js
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);
276 };
277 MyApp.launch({
278   cwd: argv.cwd,
279   configPath: argv.myappfile,
280   require: argv.require,
281   completion: argv.completion
282 }, invoke);
283 ```
284
285 #### opts.cwd
286
287 Change the current working directory for this launch. Relative paths are calculated against `process.cwd()`.
288
289 Type: `String`  
290 Default: `process.cwd()`
291
292 **Example Configuration:**
293 ```js
294 const argv = require('minimist')(process.argv.slice(2));
295 MyApp.launch({
296   cwd: argv.cwd
297 }, invoke);
298 ```
299
300 **Matching CLI Invocation:**
301 ```
302 myapp --cwd ../
303 ```
304
305 #### opts.configPath
306
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`.
308
309 Type: `String`  
310 Default: `null`
311
312 **Example Configuration:**
313 ```js
314 var argv = require('minimist')(process.argv.slice(2));
315 MyApp.launch({
316   configPath: argv.myappfile
317 }, invoke);
318 ```
319
320 **Matching CLI Invocation:**
321 ```
322 myapp --myappfile /var/www/project/Myappfile.js
323 ```
324
325 **Examples using `cwd` and `configPath` together:**
326
327 These are functionally identical:
328 ```
329 myapp --myappfile /var/www/project/Myappfile.js
330 myapp --cwd /var/www/project
331 ```
332
333 These can run myapp from a shared directory as though it were located in another project:
334 ```
335 myapp --myappfile /Users/name/Myappfile.js --cwd /var/www/project1
336 myapp --myappfile /Users/name/Myappfile.js --cwd /var/www/project2
337 ```
338
339 #### opts.require
340
341 A string or array of modules to attempt requiring from the local working directory before invoking the launch callback.
342
343 Type: `String|Array`  
344 Default: `null`
345
346 **Example Configuration:**
347 ```js
348 var argv = require('minimist')(process.argv.slice(2));
349 MyApp.launch({
350   require: argv.require
351 }, invoke);
352 ```
353
354 **Matching CLI Invocation:**
355 ```js
356 myapp --require coffee-script/register
357 ```
358
359 #### callback(env)
360
361 A function to start your application.  When invoked, `this` will be your instance of Liftoff. The `env` param will contain the following keys:
362
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)
371
372 ### events
373
374 #### require(name, module)
375
376 Emitted when a module is pre-loaded.
377
378 ```js
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') {
384     module.register();
385   }
386 });
387 ```
388
389 #### requireFail(name, err)
390
391 Emitted when a requested module cannot be preloaded.
392
393 ```js
394 var Hacker = new Liftoff({name:'hacker'});
395 Hacker.on('requireFail', function (name, err) {
396   console.log('Unable to load:', name, err);
397 });
398 ```
399
400 #### respawn(flags, child)
401
402 Emitted when Liftoff re-spawns your process (when a [`v8flags`](#optsv8flags) is detected).
403
404 ```js
405 var Hacker = new Liftoff({
406   name: 'hacker',
407   v8flags: ['--harmony']
408 });
409 Hacker.on('respawn', function (flags, child) {
410   console.log('Detected node flags:', flags);
411   console.log('Respawned to PID:', child.pid);
412 });
413 ```
414
415 Event will be triggered for this command:
416 `hacker --harmony commmand`
417
418 ## Examples
419
420 Check out how [gulp](https://github.com/gulpjs/gulp/blob/master/bin/gulp.js) uses Liftoff.
421
422 For a bare-bones example, try [the hacker project](https://github.com/js-cli/js-hacker/blob/master/bin/hacker.js).
423
424 To try the example, do the following:
425
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.