[Pkg-javascript-commits] [node-liftoff] 01/09: Import Upstream version 2.3.0

Paolo Greppi paolog-guest at moszumanska.debian.org
Wed Dec 14 09:06:07 UTC 2016


This is an automated email from the git hooks/post-receive script.

paolog-guest pushed a commit to branch master
in repository node-liftoff.

commit 39885149372c19e4b271c6273548c9dc885be86c
Author: Paolo Greppi <paolo.greppi at libpf.com>
Date:   Wed Dec 14 06:23:37 2016 +0000

    Import Upstream version 2.3.0
---
 .gitignore                                    |  15 +
 .jscsrc                                       |  60 ++++
 .jshintrc                                     |  11 +
 .npmignore                                    |   2 +
 .travis.yml                                   |  16 +
 CHANGELOG                                     | 127 ++++++++
 LICENSE                                       |  22 ++
 README.md                                     | 429 ++++++++++++++++++++++++++
 UPGRADING.md                                  |  28 ++
 appveyor.yml                                  |  29 ++
 artwork/liftoff-icon.eps                      | Bin 0 -> 520682 bytes
 artwork/liftoff-icon.png                      | Bin 0 -> 8407 bytes
 artwork/liftoff-icon.svg                      |  77 +++++
 artwork/liftoff.eps                           | Bin 0 -> 551958 bytes
 artwork/liftoff.png                           | Bin 0 -> 10492 bytes
 artwork/liftoff.svg                           |  78 +++++
 index.js                                      | 210 +++++++++++++
 lib/build_config_name.js                      |  17 +
 lib/file_search.js                            |  14 +
 lib/find_config.js                            |  25 ++
 lib/find_cwd.js                               |  18 ++
 lib/parse_options.js                          |  35 +++
 lib/register_loader.js                        |  25 ++
 lib/silent_require.js                         |   5 +
 package.json                                  |  48 +++
 test/fixtures/case/Mochafile.js               |   0
 test/fixtures/coffee/mochafile.coffee         |   0
 test/fixtures/coffee/mochafile.coffee.md      |   0
 test/fixtures/coffee/mochafile.iced           |   0
 test/fixtures/configfiles/README.txt          |   0
 test/fixtures/configfiles/index.json          |   1 +
 test/fixtures/configfiles/require-md.js       |  10 +
 test/fixtures/configfiles/require-txt.js      |  10 +
 test/fixtures/mochafile.js                    |   0
 test/fixtures/register_loader/app.cfg         |   0
 test/fixtures/register_loader/app.rc          |   0
 test/fixtures/register_loader/app.tmp         |   0
 test/fixtures/register_loader/require-cfg.js  |  10 +
 test/fixtures/register_loader/require-fail.js |   1 +
 test/fixtures/register_loader/require-rc.js   |  10 +
 test/fixtures/search_path/mochafile.js        |   0
 test/fixtures/symlink/.gitkeep                |   0
 test/fixtures/v8flags.js                      |  13 +
 test/fixtures/v8flags_function.js             |  17 +
 test/fixtures/v8flags_value.js                |  13 +
 test/index.js                                 | 427 +++++++++++++++++++++++++
 test/lib/build_config_name.js                 |  28 ++
 test/lib/file_search.js                       |  12 +
 test/lib/find_config.js                       |  30 ++
 test/lib/find_cwd.js                          |  31 ++
 test/lib/parse_options.js                     |  32 ++
 test/lib/register_loader.js                   | 218 +++++++++++++
 test/lib/silent_require.js                    |  15 +
 53 files changed, 2169 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ca94494
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+# Logs
+logs
+*.log
+
+# Coverage direcory used by tools like istanbul
+coverage
+
+# Dependency directory
+node_modules
+
+# Test creations
+test/fixtures/symlink/mochafile.js
+
+.DS_Store
+
diff --git a/.jscsrc b/.jscsrc
new file mode 100644
index 0000000..af3c78e
--- /dev/null
+++ b/.jscsrc
@@ -0,0 +1,60 @@
+{
+  "esnext": true,
+  "disallowMixedSpacesAndTabs": true,
+  "disallowSpaceAfterObjectKeys": true,
+  "disallowSpaceBeforeBinaryOperators": [
+    ","
+  ],
+  "disallowSpacesInsideArrayBrackets": true,
+  "disallowSpacesInsideParentheses": true,
+  "disallowTrailingWhitespace": true,
+  "requireCommaBeforeLineBreak": true,
+  "requireLineFeedAtFileEnd": true,
+  "requireSpaceAfterBinaryOperators": [
+    "=",
+    ",",
+    "+",
+    "-",
+    "/",
+    "*",
+    "==",
+    "===",
+    "!=",
+    "!==",
+    ":",
+    "&&",
+    "||"
+  ],
+  "requireSpaceAfterKeywords": [
+    "if",
+    "else",
+    "for",
+    "while",
+    "do",
+    "switch",
+    "return",
+    "try",
+    "catch"
+  ],
+  "requireSpaceBeforeBinaryOperators": [
+    "=",
+    "+",
+    "-",
+    "/",
+    "*",
+    "==",
+    "===",
+    "!=",
+    "!==",
+    "&&",
+    "||"
+  ],
+  "requireSpaceBeforeBlockStatements": true,
+  "requireSpacesInFunctionExpression": {
+    "beforeOpeningCurlyBrace": true
+  },
+  "validateQuoteMarks": {
+    "escape": true,
+    "mark": "'"
+  }
+}
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..6871084
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,11 @@
+{
+  "undef": true,
+  "unused": true,
+  "node": true,
+  "esnext": true,
+  "expr": true,
+  "globals": {
+    "describe": true,
+    "it": true
+  }
+}
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..9c9c73b
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,2 @@
+test
+artwork
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..61ec9ec
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,16 @@
+language: node_js
+os:
+  - linux
+  - osx
+node_js:
+  - "6"
+  - "5"
+  - "4"
+  - "0.12"
+  - "0.10"
+before_install:
+  - npm update -g npm
+matrix:
+  fast_finish: true
+  allow_failures:
+    - node_js: "0.10"
diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644
index 0000000..ee5b84d
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,127 @@
+v2.2.2:
+  date: 2016-05-20
+  changes:
+    - Update dependencies.
+v2.2.1:
+  date: 2016-03-23
+  changes:
+    - Make sure that v8 flags are passed properly through the `respawn` event
+v2.1.0:
+  date: 2015-05-20
+  changes:
+    - Use rechoir to autoload modules.
+v2.0.3:
+  date: 2015-03-31
+  changes:
+    - Internal bugfix, don't wrap callback error in another error, idiot.
+v2.0.2:
+  date: 2015-02-24
+  changes:
+    - Support process.env.NODE_PATH when resolving module.
+v2.0.1:
+  date: 2015-02-01
+  changes:
+    - Find modulePath correctly when devving against yourself.
+v2.0.0:
+  date: 2015-01-15
+  changes:
+    - Rename `nodeFlags` to `v8Flags` and make it async.
+v1.0.4:
+  date: 2015-01-04
+  changes:
+    - Detect config extension using basename, not full path.
+v1.0.0:
+  date: 2014-12-16
+  changes:
+    - Update dependencies
+v0.13.6:
+  date: 2014-11-07
+  changes:
+    - Don't include artwork on npm.
+v0.13.5:
+  date: 2014-10-10
+  changes:
+    - Only attempt to resolve the real path of configFile if it is actually a symlink.
+v0.13.4:
+  date: 2014-10-07
+  changes:
+    - Set configBase to the directory of the symlink, not the directory of its real location.
+v0.13.3:
+  date: 2014-10-06
+  changes:
+    - Return the real location of symlinked config files.
+v0.13.2:
+  date: 2014-09-12
+  changes:
+    - Include flags in respawn event. I really miss `npm publish --force`.
+v0.13.1:
+  date: 2014-09-12
+  changes:
+    - Slight performance tweak.
+v0.13.0:
+  date: 2014-09-12
+  changes:
+    - Support passing flags to node with `nodeFlags` option.
+v0.12.1:
+  date: 2014-06-27
+  changes:
+    - Support preloading modules for compound extensions like `.coffee.md`.
+v0.12.0:
+  date: 2014-06-27
+  changes:
+    - Respect order of extensions when searching for config.
+    - Rename `configNameRegex` environment property to `configNameSearch`.
+v0.11.3:
+  date: 2014-06-09
+  changes:
+    - Make cwd match configBase if cwd isn't explictly provided
+v0.11.2:
+  date: 2014-06-04
+  changes:
+    - Regression fix: coerce preloads into array before attempting to push more
+v0.11.1:
+  date: 2014-06-02
+  changes:
+    - Update dependencies.
+v0.11.0:
+  date: 2014-05-27
+  changes:
+    - Refactor and remove options parsing.
+v0.10.0:
+  date: 2014-05-06
+  changes:
+    - Remove `addExtension` in favor of `extension` option.
+    - Support preloading modules based on extension.
+v0.9.7:
+  date: 2014-04-28
+  changes:
+    - Locate local module in cwd even if config isn't present.
+v0.9.6:
+  date: 2014-04-02
+  changes:
+    - Fix regression where external modules are not properly required.
+    - Ignore configPathFlag / cwdFlag if the value isn't a string
+v0.9.3:
+  date: 2014-02-28
+  changes:
+    - Fix regression where developing against self doesn't correctly set cwd.
+v0.9.0:
+  date: 2014-02-28
+  changes:
+    - Use liftoff instance as context (`this`) for launch callback.
+    - Support split --cwd and --configfile locations.
+    - Rename `configLocationFlag` to `configPathFlag`
+    - Support node 0.8+
+v0.8.7:
+  date: 2014-02-24
+  changes:
+    - Pass environment as first argument to `launch`.
+v0.8.5:
+  date: 2014-02-19
+  changes:
+    - Implement `addExtensions` option.
+    - Default to `index.js` if `modulePackage` has no `main` property.
+v0.8.4:
+  date: 2014-02-05
+  changes:
+    - Initial public release.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..a55f5b7
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2014 Tyler Kellen
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9a5a0ae
--- /dev/null
+++ b/README.md
@@ -0,0 +1,429 @@
+<p align="center">
+  <a href="http://liftoffjs.com">
+    <img height="100" width="297" src="https://cdn.rawgit.com/tkellen/js-liftoff/master/artwork/liftoff.svg"/>
+  </a>
+</p>
+
+# 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)
+
+> Launch your command line tool with ease.
+
+[![NPM](https://nodei.co/npm/liftoff.png)](https://nodei.co/npm/liftoff/)
+
+## What is it?
+[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.
+
+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.
+
+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 work [...]
+
+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.
+
+## API
+
+### constructor(opts)
+
+Create an instance of Liftoff to invoke your application.
+
+An example utilizing all options:
+```js
+const Hacker = new Liftoff({
+  name: 'hacker',
+  processTitle: 'hacker',
+  moduleName: 'hacker',
+  configName: 'hackerfile',
+  extensions: {
+    '.js': null,
+    '.json': null,
+    '.coffee': 'coffee-script/register'
+  },
+  v8flags: ['--harmony'] // or v8flags: require('v8flags')
+});
+```
+
+#### opts.name
+
+Sugar for setting `processTitle`, `moduleName`, `configName` automatically.
+
+Type: `String`  
+Default: `null`
+
+These are equivalent:
+```js
+const Hacker = Liftoff({
+  processTitle: 'hacker',
+  moduleName: 'hacker',
+  configName: 'hackerfile'
+});
+```
+```js
+const Hacker = Liftoff({name:'hacker'});
+```
+
+#### opts.moduleName
+
+Sets which module your application expects to find locally when being run.
+
+Type: `String`  
+Default: `null`
+
+#### opts.configName
+
+Sets the name of the configuration file Liftoff will attempt to find.  Case-insensitive.
+
+Type: `String`  
+Default: `null`
+
+#### opts.extensions
+
+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.
+
+Type: `Object`  
+Default: `{".js":null,".json":null}`
+
+**Examples:**
+
+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.
+```js
+const MyApp = new Liftoff({
+  name: 'myapp',
+  extensions: {
+    '.js': null,
+    '.json': null,
+    '.coffee': 'coffee-script/register'
+  }
+});
+```
+
+In this example, Liftoff will look for `.myapp{rc}`.
+```js
+const MyApp = new Liftoff({
+  name: 'myapp',
+  configName: '.myapp',
+  extensions: {
+    'rc': null
+  }
+});
+```
+
+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).
+
+```js
+const MyApp = new Liftoff({
+  name: 'myapp',
+  extensions: require('interpret').jsVariants
+});
+```
+#### opts.v8flags
+
+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).
+
+Type: `Array|Function`  
+Default: `null`
+
+If this method is a function, it should take a node-style callback that yields an array of flags.
+
+#### opts.processTitle
+
+Sets what the [process title](http://nodejs.org/api/process.html#process_process_title) will be.
+
+Type: `String`  
+Default: `null`
+
+#### opts.completions(type)
+
+A method to handle bash/zsh/whatever completions.
+
+Type: `Function`  
+Default: `null`
+
+#### opts.configFiles
+
+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.
+
+__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.
+
+Type: `Object`  
+Default: `null`
+
+#### Path arguments
+
+The [`fined`](https://github.com/js-cli/fined) module accepts a string representing the path to search or an object with the following keys:
+
+* `path` __(required)__
+
+  The path to search. Using only a string expands to this property.
+
+  Type: `String`  
+  Default: `null`
+
+* `name`
+
+  The basename of the file to find. Extensions are appended during lookup.
+
+  Type: `String`  
+  Default: Top-level key in `configFiles`
+
+* `extensions`
+
+  The extensions to append to `name` during lookup. See also: [`opts.extensions`](#optsextensions).
+
+  Type: `String|Array|Object`  
+  Default: The value of [`opts.extensions`](#optsextensions)
+
+* `cwd` 
+
+  The base directory of `path` (if relative).
+
+  Type: `String`  
+  Default: The value of [`opts.cwd`](#optscwd)
+
+* `findUp`
+
+  Whether the `path` should be traversed up to find the file.
+
+  Type: `Boolean`  
+  Default: `false`
+
+**Examples:**
+
+In this example Liftoff will look for the `.hacker.js` file relative to the `cwd` as declared in `configFiles`.
+```js
+const MyApp = new Liftoff({
+  name: 'hacker',
+  configFiles: {
+    '.hacker': {
+      cwd: '.'
+    }
+  }
+});
+```
+
+In this example, Liftoff will look for `.hackerrc` in the home directory.
+```js
+const MyApp = new Liftoff({
+  name: 'hacker',
+  configFiles: {
+    '.hacker': {
+      home: {
+        path: '~',
+        extensions: {
+          'rc': null
+        }
+      }
+    }
+  }
+});
+```
+
+In this example, Liftoff will look in the `cwd` and then lookup the tree for the `.hacker.js` file.
+```js
+const MyApp = new Liftoff({
+  name: 'hacker',
+  configFiles: {
+    '.hacker': {
+      up: {
+        path: '.',
+        findUp: true
+      }
+    }
+  }
+});
+```
+
+In this example, the `name` is overridden and the key is ignored so Liftoff looks for `.override.js`.
+```js
+const MyApp = new Liftoff({
+  name: 'hacker',
+  configFiles: {
+    hacker: {
+      override: {
+        path: '.',
+        name: '.override'
+      }
+    }
+  }
+});
+```
+
+In this example, Liftoff will use the home directory as the `cwd` and looks for `~/.hacker.js`.
+```js
+const MyApp = new Liftoff({
+  name: 'hacker',
+  configFiles: {
+    '.hacker': {
+      home: {
+        path: '.',
+        cwd: '~'
+      }
+    }
+  }
+});
+```
+
+## launch(opts, callback(env))
+Launches your application with provided options, builds an environment, and invokes your callback, passing the calculated environment as the first argument.
+
+##### Example Configuration w/ Options Parsing:
+```js
+const Liftoff = require('liftoff');
+const MyApp = new Liftoff({name:'myapp'});
+const argv = require('minimist')(process.argv.slice(2));
+const invoke = function (env) {
+  console.log('my environment is:', env);
+  console.log('my cli options are:', argv);
+  console.log('my liftoff config is:', this);
+};
+MyApp.launch({
+  cwd: argv.cwd,
+  configPath: argv.myappfile,
+  require: argv.require,
+  completion: argv.completion
+}, invoke);
+```
+
+#### opts.cwd
+
+Change the current working directory for this launch. Relative paths are calculated against `process.cwd()`.
+
+Type: `String`  
+Default: `process.cwd()`
+
+**Example Configuration:**
+```js
+const argv = require('minimist')(process.argv.slice(2));
+MyApp.launch({
+  cwd: argv.cwd
+}, invoke);
+```
+
+**Matching CLI Invocation:**
+```
+myapp --cwd ../
+```
+
+#### opts.configPath
+
+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`.
+
+Type: `String`  
+Default: `null`
+
+**Example Configuration:**
+```js
+var argv = require('minimist')(process.argv.slice(2));
+MyApp.launch({
+  configPath: argv.myappfile
+}, invoke);
+```
+
+**Matching CLI Invocation:**
+```
+myapp --myappfile /var/www/project/Myappfile.js
+```
+
+**Examples using `cwd` and `configPath` together:**
+
+These are functionally identical:
+```
+myapp --myappfile /var/www/project/Myappfile.js
+myapp --cwd /var/www/project
+```
+
+These can run myapp from a shared directory as though it were located in another project:
+```
+myapp --myappfile /Users/name/Myappfile.js --cwd /var/www/project1
+myapp --myappfile /Users/name/Myappfile.js --cwd /var/www/project2
+```
+
+#### opts.require
+
+A string or array of modules to attempt requiring from the local working directory before invoking the launch callback.
+
+Type: `String|Array`  
+Default: `null`
+
+**Example Configuration:**
+```js
+var argv = require('minimist')(process.argv.slice(2));
+MyApp.launch({
+  require: argv.require
+}, invoke);
+```
+
+**Matching CLI Invocation:**
+```js
+myapp --require coffee-script/register
+```
+
+#### callback(env)
+
+A function to start your application.  When invoked, `this` will be your instance of Liftoff. The `env` param will contain the following keys:
+
+- `cwd`: the current working directory
+- `require`: an array of modules that liftoff tried to pre-load
+- `configNameSearch`: the config files searched for
+- `configPath`: the full path to your configuration file (if found)
+- `configBase`: the base directory of your configuration file (if found)
+- `modulePath`: the full path to the local module your project relies on (if found)
+- `modulePackage`: the contents of the local module's package.json (if found)
+- `configFiles`: an object of filepaths for each found config file (filepath values will be null if not found)
+
+### events
+
+#### require(name, module)
+
+Emitted when a module is pre-loaded.
+
+```js
+var Hacker = new Liftoff({name:'hacker'});
+Hacker.on('require', function (name, module) {
+  console.log('Requiring external module: '+name+'...');
+  // automatically register coffee-script extensions
+  if (name === 'coffee-script') {
+    module.register();
+  }
+});
+```
+
+#### requireFail(name, err)
+
+Emitted when a requested module cannot be preloaded.
+
+```js
+var Hacker = new Liftoff({name:'hacker'});
+Hacker.on('requireFail', function (name, err) {
+  console.log('Unable to load:', name, err);
+});
+```
+
+#### respawn(flags, child)
+
+Emitted when Liftoff re-spawns your process (when a [`v8flags`](#optsv8flags) is detected).
+
+```js
+var Hacker = new Liftoff({
+  name: 'hacker',
+  v8flags: ['--harmony']
+});
+Hacker.on('respawn', function (flags, child) {
+  console.log('Detected node flags:', flags);
+  console.log('Respawned to PID:', child.pid);
+});
+```
+
+Event will be triggered for this command:
+`hacker --harmony commmand`
+
+## Examples
+
+Check out how [gulp](https://github.com/gulpjs/gulp/blob/master/bin/gulp.js) uses Liftoff.
+
+For a bare-bones example, try [the hacker project](https://github.com/js-cli/js-hacker/blob/master/bin/hacker.js).
+
+To try the example, do the following:
+
+1. Install the sample project `hacker` with `npm install -g hacker`.
+2. Make a `Hackerfile.js` with some arbitrary javascript it.
+3. Install hacker next to it with `npm install hacker`.
+3. Run `hacker` while in the same parent folder.
diff --git a/UPGRADING.md b/UPGRADING.md
new file mode 100644
index 0000000..7f95e3e
--- /dev/null
+++ b/UPGRADING.md
@@ -0,0 +1,28 @@
+# 1.0.0 -> 2.0.0
+The option `nodeFlags` was renamed to `v8flags` for accuracy. It can now be a callback taking method that yields an array of flags, **or** an array literal.
+
+# 0.11 -> 0.12
+For the environment passed into the `launch` callback, `configNameRegex` has been renamed to `configNameSearch`.  It now returns an array of valid config names instead of a regular expression.
+
+# 0.10 -> 0.11
+The method signature for `launch` was changed in this version of Liftoff.
+
+You must now provide your own options parser and pass your desired params directly into `launch` as the first argument.  The second argument is now the invocation callback that starts your application.
+
+To replicate the default functionality of 0.10, use the following:
+```js
+const Liftoff = require('liftoff');
+const MyApp = new Liftoff({name:'myapp'});
+const argv = require('minimist')(process.argv.slice(2));
+const invoke = function (env) {
+  console.log('my environment is:', env);
+  console.log('my cli options are:', argv);
+  console.log('my liftoff config is:', this);
+};
+MyApp.launch({
+  cwd: argv.cwd,
+  configPath: argv.myappfile,
+  require: argv.require,
+  completion: argv.completion
+}, invoke);
+```
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..bcb6b74
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,29 @@
+# http://www.appveyor.com/docs/appveyor-yml
+# http://www.appveyor.com/docs/lang/nodejs-iojs
+
+environment:
+  matrix:
+    # node.js
+    - nodejs_version: "0.10"
+    - nodejs_version: "0.12"
+    - nodejs_version: "4"
+    - nodejs_version: "5"
+    - nodejs_version: "6"
+
+install:
+  - IF %nodejs_version% EQU 0.10 npm -g install npm at 2
+  - IF %nodejs_version% EQU 0.10 set PATH=%APPDATA%\npm;%PATH%
+  - ps: Install-Product node $env:nodejs_version
+  - npm install
+
+test_script:
+  - node --version
+  - npm --version
+  # power shell
+  - ps: "npm test"
+  # standard command line
+  - cmd: npm test
+
+build: off
+
+version: "{build}"
diff --git a/artwork/liftoff-icon.eps b/artwork/liftoff-icon.eps
new file mode 100644
index 0000000..8181f00
Binary files /dev/null and b/artwork/liftoff-icon.eps differ
diff --git a/artwork/liftoff-icon.png b/artwork/liftoff-icon.png
new file mode 100644
index 0000000..7865c5d
Binary files /dev/null and b/artwork/liftoff-icon.png differ
diff --git a/artwork/liftoff-icon.svg b/artwork/liftoff-icon.svg
new file mode 100644
index 0000000..bd7359e
--- /dev/null
+++ b/artwork/liftoff-icon.svg
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="98.774px" height="102.996px" viewBox="0 0 98.774 102.996" enable-background="new 0 0 98.774 102.996"
+	 xml:space="preserve">
+<path fill="#BFBFBF" d="M98.454,53.609c0,27.095-21.97,49.069-49.068,49.069c-27.098,0-49.067-21.974-49.067-49.069
+	c0-27.102,21.97-49.071,49.067-49.071C76.484,4.538,98.454,26.507,98.454,53.609z"/>
+<path fill="#A3A2A2" d="M47.701,54.474L58.794,5.463c-3.048-0.594-6.188-0.925-9.408-0.925c-27.098,0-49.067,21.969-49.067,49.071
+	c0,14.721,6.519,27.898,16.79,36.893c0.68,0.571,1.35,1.128,2.014,1.678c5.229,4.107,11.309,7.166,17.935,8.886L47.701,54.474z"/>
+<circle fill="#3A3A3A" cx="49.387" cy="53.609" r="41.971"/>
+<g>
+	<path fill="#FFFFFF" d="M71.184,8.354"/>
+</g>
+<g>
+	<defs>
+		<path id="SVGID_1_" d="M98.774,53.609c0,27.273-22.111,49.387-49.389,49.387S0,80.882,0,53.609
+			C0,26.332,22.108,4.538,49.386,4.538S98.774,26.332,98.774,53.609z"/>
+	</defs>
+	<clipPath id="SVGID_2_">
+		<use xlink:href="#SVGID_1_"  overflow="visible"/>
+	</clipPath>
+	<path opacity="0.2" clip-path="url(#SVGID_2_)" d="M57.708,34.346c0.579-2.007,1.076-4.045,1.415-6.098
+		C61.851,11.952,53.673,3,53.673,3s-12.585,9.579-16.119,23.393c-0.549,2.152-0.956,4.376-1.279,6.601l-7.068,5.722l-3.671,15.563
+		l9.864-4.782c0.113,4.773,0.474,7.978,0.474,7.978h3.902c-1.812,6.528-3.437,10.553-5.023,14.327
+		c-3.684,0.656-6.833,2.798-8.93,5.761c-0.734,0.774-7.394,5.385-6.255,3.952c-0.207-0.141-7.874-3.921-8.596-3.196
+		c0.132,0.223,0.265,0.432,0.418,0.716c2.188,4.019,4.324,7.636,7.838,10.691c0.348,0.303,0.708,0.627,1.074,0.963
+		c0.229,0.062,0.459,0.148,0.693,0.267c3.429,1.742,8.995,3.824,13.578,6.628L45.17,57.475h1.72c0,0,1.933-2.868,4.294-7.338
+		l7.342,4.74l3.356-14.464L57.708,34.346z M51.359,31.06c-0.612,2.7-2.99,4.463-5.308,3.94c-2.319-0.532-3.704-3.144-3.09-5.846
+		c0.613-2.696,2.988-4.456,5.307-3.934C50.59,25.754,51.972,28.366,51.359,31.06z"/>
+</g>
+<g>
+	<defs>
+		<path id="SVGID_3_" d="M93.544,53.609c0,24.382-19.772,44.156-44.158,44.156c-24.385,0-44.154-19.773-44.154-44.156
+			c0-24.387,19.77-44.156,44.154-44.156C73.771,9.453,93.544,29.222,93.544,53.609z"/>
+	</defs>
+	<clipPath id="SVGID_4_">
+		<use xlink:href="#SVGID_3_"  overflow="visible"/>
+	</clipPath>
+	<path clip-path="url(#SVGID_4_)" fill="#848FA3" d="M53.76,39.577c0.431-1.745,0.786-3.156,1.017-4.043h-4.828
+		c-0.231,0.939-0.579,2.349-1.008,4.043H53.76z"/>
+	<g clip-path="url(#SVGID_4_)">
+		<path fill="#848FA3" d="M11.157,90.167c0.224-4.571,2.962-7.526,4.131-8.594c-0.133-0.027-0.261-0.061-0.396-0.061
+			c-3.871,0-7.013,4.805-7.013,8.743h3.277C11.157,90.223,11.153,90.2,11.157,90.167z"/>
+	</g>
+	<path clip-path="url(#SVGID_4_)" fill="#BFBFBF" d="M64.905,100.975c-0.181-0.109-0.39-0.151-0.577-0.234
+		c-0.1,1.766-0.847,3.533-2.407,5.281c-0.124,0.104-0.211,0.046-0.273-0.062c0.456-1.475,0.584-2.987,0.468-4.476l13.252-7.503
+		c0-3.938-3.144-8.743-7.015-8.743c-0.207,0-0.41,0.068-0.612,0.092c0.809,1.572,1.063,3.474,0.606,5.772
+		c-0.057,0.153-0.155,0.147-0.268,0.085c-1.239-5.214-5.831-9.108-11.344-9.108c-0.248,0-0.482,0.063-0.727,0.076
+		c0.255,1.809,0.209,3.772-0.229,5.875c-0.254,1.237-0.749,1.715-0.764,1.237l-0.006-0.009c0.015-0.32,0.021-0.629,0.021-0.877
+		c0-5.779-3.39-10.752-8.256-12.993c0.988-14.644,7.05-37.236,7.05-37.236l-4.79-0.24c0,0-6.485,26.978-10.459,36.432
+		c-3.683,0.656-6.832,2.798-8.931,5.761c-0.732,0.774-1.42,1.606-1.986,2.568c-0.124,0.211-0.275,0.48-0.427,0.762l-0.004,0.004
+		c-0.261,0.405-0.442-0.257-0.033-1.448c0.688-2.038,1.647-3.75,2.785-5.182c-0.207-0.141-0.372-0.305-0.588-0.433
+		c-4.748-2.802-10.679-1.775-14.397,2.086c-0.128-0.002-0.215-0.044-0.187-0.211c0.774-2.208,1.962-3.717,3.456-4.662
+		c-0.166-0.124-0.302-0.286-0.486-0.392c-3.334-1.968-8.482,0.577-10.481,3.972l1.771,10.545c-1.478,1.742-2.429,4.119-2.429,6.268
+		l3.725,1.471l2.798,16.673l53.346-0.059C68.506,108.674,68.238,102.935,64.905,100.975z"/>
+	<g clip-path="url(#SVGID_4_)">
+		<path fill="#848FA3" d="M-1.57,66.519c0.222-4.568,2.961-7.527,4.13-8.593c-0.132-0.025-0.261-0.062-0.395-0.062
+			c-3.874,0-7.016,4.807-7.016,8.745h3.28C-1.57,66.573-1.574,66.552-1.57,66.519z"/>
+	</g>
+</g>
+<path fill="#A3A2A2" d="M47.701,54.474l3.723-16.444l-2.39-0.118c0,0-6.485,26.978-10.459,36.432
+	c-3.683,0.656-6.832,2.798-8.931,5.761c-0.732,0.774-1.42,1.606-1.986,2.568c-0.124,0.211-0.275,0.48-0.427,0.762l-0.004,0.004
+	c-0.261,0.405-0.442-0.257-0.033-1.448c0.688-2.038,1.647-3.75,2.785-5.182c-0.207-0.141-0.372-0.305-0.588-0.433
+	c-4.748-2.802-10.679-1.775-14.397,2.086c-0.128-0.002-0.215-0.044-0.187-0.211c0.774-2.208,1.962-3.717,3.456-4.662
+	c-0.166-0.124-0.302-0.286-0.486-0.392c-3.334-1.968-8.482,0.577-10.481,3.972l0.362,2.147c2.589,4.18,5.768,7.96,9.45,11.187
+	c0.68,0.571,1.35,1.128,2.014,1.678c5.229,4.107,12.029,7.392,18.657,9.111L47.701,54.474z"/>
+<rect x="47.192" y="20.242" fill="#E6EBF3" width="12.842" height="14.833"/>
+<path fill="#DC3E55" d="M64.064,31.346c0.58-2.007,1.076-4.046,1.416-6.099C68.208,8.952,60.03,0,60.03,0S47.444,9.579,43.91,23.393
+	c-0.548,2.152-0.956,4.376-1.278,6.601l-7.069,5.722l-3.671,15.563l9.864-4.782c0.114,4.774,0.474,7.977,0.474,7.977h11.018
+	c0,0,1.933-2.868,4.293-7.337l7.342,4.741l3.356-14.464L64.064,31.346z M57.717,28.06c-0.613,2.7-2.99,4.463-5.308,3.94
+	c-2.32-0.532-3.704-3.144-3.09-5.846c0.612-2.696,2.988-4.456,5.306-3.934C56.946,22.754,58.329,25.365,57.717,28.06z"/>
+<path fill="#C13751" d="M52.409,32c-2.32-0.532-3.704-3.144-3.09-5.846c0.612-2.696,2.988-4.456,5.306-3.934
+	c0.124,0.029,0.231,0.085,0.352,0.125l5.058-22.34L60.03,0c0,0-12.586,9.579-16.12,23.393c-0.548,2.152-0.956,4.376-1.278,6.601
+	l-7.069,5.722l-3.671,15.563l9.864-4.782c0.102,4.273,0.399,7.269,0.46,7.855l1.891,0.122h3.595l5.08-22.435
+	C52.657,32.024,52.533,32.028,52.409,32z"/>
+</svg>
diff --git a/artwork/liftoff.eps b/artwork/liftoff.eps
new file mode 100644
index 0000000..ad5ec7e
Binary files /dev/null and b/artwork/liftoff.eps differ
diff --git a/artwork/liftoff.png b/artwork/liftoff.png
new file mode 100644
index 0000000..0403fa6
Binary files /dev/null and b/artwork/liftoff.png differ
diff --git a/artwork/liftoff.svg b/artwork/liftoff.svg
new file mode 100644
index 0000000..0d1c9c1
--- /dev/null
+++ b/artwork/liftoff.svg
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="297.339px" height="99.999px" viewBox="0 0 297.339 99.999" enable-background="new 0 0 297.339 99.999"
+	 xml:space="preserve">
+<g>
+	<rect x="61.922" y="18.133" fill="#E6EBF3" width="12.714" height="11.484"/>
+	<g>
+		<path fill="#3A3A3A" d="M55.062,52.154H45.44l-2.21,9.055c-0.302,1.047-0.836,1.929-1.602,2.637
+			C40.867,64.549,39.94,64.9,38.86,64.9H25.931l10.135-43.562h6.895L45.8,8.947H15.345l-2.839,12.391h7.748L10.163,64.9H3.269
+			L0.384,77.246h47.407C49.657,72.218,52.509,62.186,55.062,52.154z"/>
+		<path fill="#3A3A3A" d="M281.273,7.35c-2.272,0.679-4.346,1.66-6.195,3.034c-1.846,1.386-3.443,3.133-4.797,5.251
+			c-1.354,2.119-2.358,4.635-3.021,7.544l-1.801,7.665l-0.77,3.277h-0.004h-9.101l-3.018,13.194h9.022l-8.377,35.625
+			c-0.418,1.771-1.163,3.175-2.384,4.011c-3.538,2.421-10.454,1.391-10.454,1.391l-2.156,9.398
+			c1.113,1.382,9.039,3.472,16.066,1.357c2.276-0.688,4.35-1.664,6.199-3.038c1.846-1.382,3.443-3.133,4.801-5.252
+			c1.345-2.118,2.355-4.635,3.013-7.548l8.468-35.944h9.994l3.062-13.194h-9.948l0.77-3.277l1.398-5.997
+			c0.418-1.771,1.163-3.17,2.384-4.006c3.539-2.421,10.306-0.729,10.306-0.729l2.606-11.406
+			C296.226,7.325,288.305,5.231,281.273,7.35z"/>
+		<path fill="#3A3A3A" d="M241.891,7.35c-2.271,0.679-4.345,1.66-6.195,3.034c-1.85,1.386-3.443,3.133-4.801,5.251
+			c-1.349,2.119-2.354,4.635-3.017,7.544l-1.805,7.665l-0.77,3.277H225.3h-9.097l-3.017,13.194h9.018l-8.373,35.625
+			c-0.418,1.771-1.162,3.175-2.379,4.011c-3.543,2.421-10.459,1.391-10.459,1.391l-2.155,9.398
+			c1.113,1.382,9.038,3.472,16.069,1.357c2.277-0.688,4.346-1.664,6.191-3.038c1.851-1.382,3.447-3.133,4.801-5.252
+			c1.346-2.118,2.355-4.635,3.018-7.548l8.467-35.944h9.995l3.059-13.194h-9.945l0.77-3.277l1.399-5.997
+			c0.418-1.771,1.162-3.17,2.384-4.006c3.538-2.421,10.305-0.729,10.305-0.729l2.604-11.406
+			C256.844,7.325,248.923,5.231,241.891,7.35z"/>
+		<path fill="#3A3A3A" d="M158.793,57.683c0-4.291,0.774-8.19,2.321-11.691c1.549-3.497,3.63-6.485,6.241-8.964
+			c2.607-2.475,5.637-4.391,9.076-5.748c3.438-1.345,7.064-2.024,10.88-2.024c2.943,0,5.699,0.538,8.27,1.622
+			c2.57,1.081,4.801,2.558,6.688,4.437c1.891,1.879,3.381,4.085,4.461,6.626c1.08,2.537,1.619,5.256,1.619,8.178
+			c0,4.114-0.725,7.892-2.182,11.348c-1.461,3.451-3.455,6.447-5.992,8.968s-5.542,4.49-9.014,5.902
+			c-3.465,1.406-7.21,2.118-11.24,2.118c-3.034,0-5.832-0.55-8.397-1.646c-2.57-1.097-4.801-2.587-6.692-4.458
+			c-1.891-1.879-3.369-4.08-4.437-6.604C159.327,63.225,158.793,60.534,158.793,57.683z M173.481,56.02
+			c0,1.315,0.193,2.528,0.587,3.621c0.385,1.101,0.936,2.049,1.644,2.843c0.703,0.795,1.547,1.407,2.52,1.842
+			c0.977,0.438,2.037,0.65,3.179,0.65c1.561,0,3.075-0.336,4.553-1.011c1.465-0.67,2.785-1.605,3.939-2.793
+			c1.155-1.184,2.082-2.578,2.769-4.188c0.695-1.609,1.039-3.34,1.039-5.202c0-1.527-0.219-2.864-0.654-3.989
+			c-0.434-1.126-1.014-2.065-1.733-2.814s-1.564-1.312-2.521-1.692c-0.965-0.373-1.986-0.555-3.066-0.555
+			c-1.714,0-3.311,0.352-4.797,1.051c-1.486,0.708-2.777,1.664-3.878,2.864c-1.093,1.204-1.962,2.607-2.611,4.213
+			C173.8,52.465,173.481,54.186,173.481,56.02z"/>
+		<path fill="#3A3A3A" d="M145.007,47.515h8.637l3.104-13.446h-8.563l0,0l0.778-3.273l2.963-12.416h-15.482l-2.98,12.416
+			l-0.778,3.273h-8.691l-3.104,13.446h8.604h0.008l-3.406,14.589c-0.72,2.946-0.985,5.396-0.791,7.366
+			c0.199,1.962,0.766,3.538,1.713,4.705s2.239,2.003,3.878,2.504c1.635,0.493,3.534,0.741,5.699,0.741
+			c1.349,0,2.607-0.059,3.758-0.157c1.159-0.112,2.255-0.257,3.29-0.451s2.024-0.443,2.951-0.745
+			c0.931-0.298,1.875-0.629,2.839-0.993l-0.178-10.313c-0.844,0.244-1.635,0.422-2.367,0.542c-0.737,0.12-1.419,0.179-2.049,0.179
+			c-0.782,0-1.465-0.22-2.053-0.65c-0.587-0.438-0.906-1.154-0.968-2.144c-0.058-0.993,0.137-2.326,0.588-4.011L145.007,47.515z"/>
+		<path fill="#3A3A3A" d="M110.467,7.35c-2.272,0.679-4.346,1.66-6.195,3.034c-1.85,1.386-3.443,3.133-4.801,5.251
+			c-1.349,2.119-2.355,4.635-3.017,7.544l-1.805,7.665l-0.77,3.277h-0.004h-9.101l-3.017,13.194h9.022L82.404,82.94
+			c-0.418,1.771-1.163,3.175-2.38,4.011c-0.045,0.028-0.099,0.05-0.145,0.078c4.416,0.873,7.942,5.343,8.795,9.672
+			c0.335-0.215,0.683-0.405,1.001-0.642c1.85-1.382,3.447-3.133,4.8-5.252c1.345-2.118,2.355-4.635,3.017-7.548l8.463-35.944h9.999
+			l3.058-13.194h-9.949l0.773-3.277l1.399-5.997c0.418-1.771,1.163-3.17,2.384-4.006c3.539-2.421,10.305-0.729,10.305-0.729
+			l2.603-11.406C125.42,7.325,117.499,5.231,110.467,7.35z"/>
+	</g>
+	<path fill="#BFBFBF" d="M78.977,90.473c-0.195,0-0.385,0.062-0.575,0.087c0.757,1.47,0.997,3.249,0.567,5.396
+		c-0.05,0.146-0.145,0.141-0.249,0.083c-1.159-4.879-5.455-8.521-10.611-8.521c-0.231,0-0.451,0.059-0.679,0.07
+		c0.236,1.693,0.195,3.53-0.215,5.496c-0.24,1.155-0.7,1.605-0.716,1.155l-0.004-0.005c0.012-0.302,0.021-0.587,0.021-0.819
+		c0-5.409-3.174-10.061-7.725-12.154c0.925-13.699,6.595-34.834,6.595-34.834l-4.482-0.224c0,0-6.065,25.233-9.781,34.077
+		c-6.167,1.097-10.855,6.556-10.855,13.136c0,0.232,0.008,0.518,0.021,0.819v0.005c-0.021,0.45-0.48,0-0.72-1.155
+		c-0.41-1.966-0.451-3.803-0.211-5.496c-0.231-0.012-0.447-0.07-0.683-0.07c-5.157,0-9.448,3.643-10.611,8.521
+		c-0.104,0.058-0.195,0.062-0.249-0.083c-0.43-2.147-0.186-3.927,0.571-5.396c-0.194-0.024-0.381-0.087-0.58-0.087
+		c-3.621,0-6.56,4.494-6.56,8.178l0,0h64.291l0,0C85.537,94.967,82.598,90.473,78.977,90.473z"/>
+	<g>
+		<path fill="#FFFFFF" d="M85.644,2.21"/>
+	</g>
+	<path fill="#DC3E55" d="M77.202,28.589c0.53-1.829,0.98-3.691,1.291-5.562C80.98,8.165,73.522,0,73.522,0
+		S62.042,8.736,58.82,21.334c-0.501,1.966-0.874,3.994-1.167,6.021l-6.448,5.219L47.857,46.77l8.998-4.362
+		c0.104,4.354,0.43,7.275,0.43,7.275h10.05c0,0,1.763-2.616,3.916-6.692l6.696,4.325l3.062-13.194L77.202,28.589z M71.412,25.593
+		c-0.559,2.462-2.727,4.068-4.842,3.592c-2.115-0.484-3.377-2.868-2.818-5.331c0.559-2.458,2.728-4.064,4.842-3.588
+		C70.708,20.75,71.97,23.134,71.412,25.593z"/>
+	<path fill="#A3A2A2" d="M62.276,49.683l-1.954-1.246c-1.971,7.824-6.524,25.475-9.2,31.842
+		c-6.167,1.097-10.855,6.556-10.855,13.136c0,0.232,0.008,0.518,0.021,0.819v0.005c-0.021,0.45-0.48,0-0.72-1.155
+		c-0.41-1.966-0.451-3.803-0.211-5.496c-0.231-0.012-0.447-0.07-0.683-0.07c-5.157,0-9.448,3.643-10.611,8.521
+		c-0.104,0.058-0.195,0.062-0.249-0.083c-0.43-2.147-0.186-3.927,0.571-5.396c-0.194-0.024-0.381-0.087-0.58-0.087
+		c-3.621,0-6.56,4.494-6.56,8.178h29.949L62.276,49.683z"/>
+	<path fill="#C13751" d="M66.569,29.185c-2.115-0.484-3.377-2.868-2.818-5.331c0.559-2.458,2.728-4.064,4.842-3.588
+		c0.094,0.021,0.177,0.066,0.267,0.095l4.58-20.297c-0.84,0.654-11.526,9.169-14.62,21.27c-0.501,1.966-0.874,3.994-1.167,6.021
+		l-6.448,5.219L47.857,46.77l8.998-4.362c0.104,4.354,0.43,7.275,0.43,7.275h4.959l4.619-20.467
+		C66.765,29.202,66.667,29.208,66.569,29.185z"/>
+</g>
+</svg>
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..0c0a8d5
--- /dev/null
+++ b/index.js
@@ -0,0 +1,210 @@
+const fs = require('fs');
+const util = require('util');
+const path = require('path');
+const EE = require('events').EventEmitter;
+
+const extend = require('extend');
+const resolve = require('resolve');
+const flaggedRespawn = require('flagged-respawn');
+const isPlainObject = require('lodash.isplainobject');
+const mapValues = require('lodash.mapvalues');
+const fined = require('fined');
+
+const findCwd = require('./lib/find_cwd');
+const findConfig = require('./lib/find_config');
+const fileSearch = require('./lib/file_search');
+const parseOptions = require('./lib/parse_options');
+const silentRequire = require('./lib/silent_require');
+const buildConfigName = require('./lib/build_config_name');
+const registerLoader = require('./lib/register_loader');
+
+
+function Liftoff (opts) {
+  EE.call(this);
+  extend(this, parseOptions(opts));
+}
+util.inherits(Liftoff, EE);
+
+Liftoff.prototype.requireLocal = function (module, basedir) {
+  try {
+    var result = require(resolve.sync(module, {basedir: basedir}));
+    this.emit('require', module, result);
+    return result;
+  } catch (e) {
+    this.emit('requireFail', module, e);
+  }
+};
+
+Liftoff.prototype.buildEnvironment = function (opts) {
+  opts = opts || {};
+
+  // get modules we want to preload
+  var preload = opts.require || [];
+
+  // ensure items to preload is an array
+  if (!Array.isArray(preload)) {
+    preload = [preload];
+  }
+
+  // make a copy of search paths that can be mutated for this run
+  var searchPaths = this.searchPaths.slice();
+
+  // calculate current cwd
+  var cwd = findCwd(opts);
+
+  // if cwd was provided explicitly, only use it for searching config
+  if (opts.cwd) {
+    searchPaths = [cwd];
+  } else {
+    // otherwise just search in cwd first
+    searchPaths.unshift(cwd);
+  }
+
+  // calculate the regex to use for finding the config file
+  var configNameSearch = buildConfigName({
+    configName: this.configName,
+    extensions: Object.keys(this.extensions)
+  });
+
+  // calculate configPath
+  var configPath = findConfig({
+    configNameSearch: configNameSearch,
+    searchPaths: searchPaths,
+    configPath: opts.configPath
+  });
+
+  // if we have a config path, save the directory it resides in.
+  var configBase;
+  if (configPath) {
+    configBase = path.dirname(configPath);
+    // if cwd wasn't provided explicitly, it should match configBase
+    if (!opts.cwd) {
+      cwd = configBase;
+    }
+    // resolve symlink if needed
+    if (fs.lstatSync(configPath).isSymbolicLink()) {
+      configPath = fs.realpathSync(configPath);
+    }
+  }
+
+  // TODO: break this out into lib/
+  // locate local module and package next to config or explicitly provided cwd
+  var modulePath, modulePackage;
+  try {
+    var delim = (process.platform === 'win32' ? ';' : ':'),
+        paths = (process.env.NODE_PATH ? process.env.NODE_PATH.split(delim) : []);
+    modulePath = resolve.sync(this.moduleName, {basedir: configBase || cwd, paths: paths});
+    modulePackage = silentRequire(fileSearch('package.json', [modulePath]));
+  } catch (e) {}
+
+  // if we have a configuration but we failed to find a local module, maybe
+  // we are developing against ourselves?
+  if (!modulePath && configPath) {
+    // check the package.json sibling to our config to see if its `name`
+    // matches the module we're looking for
+    var modulePackagePath = fileSearch('package.json', [configBase]);
+    modulePackage = silentRequire(modulePackagePath);
+    if (modulePackage && modulePackage.name === this.moduleName) {
+      // if it does, our module path is `main` inside package.json
+      modulePath = path.join(path.dirname(modulePackagePath), modulePackage.main || 'index.js');
+      cwd = configBase;
+    } else {
+      // clear if we just required a package for some other project
+      modulePackage = {};
+    }
+  }
+
+  // load any modules which were requested to be required
+  if (preload.length) {
+    // unique results first
+    preload.filter(function (value, index, self) {
+      return self.indexOf(value) === index;
+    }).forEach(function (dep) {
+      this.requireLocal(dep, findCwd(opts));
+    }, this);
+  }
+
+  var exts = this.extensions;
+  var eventEmitter = this;
+  registerLoader(eventEmitter, exts, configPath, cwd);
+
+  var configFiles = {};
+  if (isPlainObject(this.configFiles)) {
+    var notfound = { path: null };
+    configFiles = mapValues(this.configFiles, function(prop, name) {
+      var defaultObj = { name: name, cwd: cwd, extensions: exts };
+      return mapValues(prop, function(pathObj) {
+        var found = fined(pathObj, defaultObj) || notfound;
+        if (isPlainObject(found.extension)) {
+          registerLoader(eventEmitter, found.extension, found.path, cwd);
+        }
+        return found.path;
+      });
+    });
+  }
+
+  return {
+    cwd: cwd,
+    require: preload,
+    configNameSearch: configNameSearch,
+    configPath: configPath,
+    configBase: configBase,
+    modulePath: modulePath,
+    modulePackage: modulePackage || {},
+    configFiles: configFiles
+  };
+};
+
+Liftoff.prototype.handleFlags = function (cb) {
+  if (typeof this.v8flags === 'function') {
+    this.v8flags(function (err, flags) {
+      if (err) {
+        cb(err);
+      } else {
+        cb(null, flags);
+      }
+    });
+  } else {
+    process.nextTick(function () {
+      cb(null, this.v8flags);
+    }.bind(this));
+  }
+};
+
+Liftoff.prototype.launch = function (opts, fn) {
+  if (typeof fn !== 'function') {
+    throw new Error('You must provide a callback function.');
+  }
+  process.title = this.processTitle;
+
+  var completion = opts.completion;
+  if (completion && this.completions) {
+    return this.completions(completion);
+  }
+
+  this.handleFlags(function (err, flags) {
+    if (err) {
+      throw err;
+    } else {
+      if (flags) {
+        flaggedRespawn(flags, process.argv, function (ready, child) {
+          if (child !== process) {
+            this.emit('respawn', process.argv.filter(function (arg) {
+              var flag = arg.split('=')[0];
+              return flags.indexOf(flag) !== -1;
+            }.bind(this)), child);
+          }
+          if (ready) {
+            fn.call(this, this.buildEnvironment(opts));
+          }
+        }.bind(this));
+      } else {
+        fn.call(this, this.buildEnvironment(opts));
+      }
+    }
+  }.bind(this));
+};
+
+
+
+module.exports = Liftoff;
diff --git a/lib/build_config_name.js b/lib/build_config_name.js
new file mode 100644
index 0000000..b83e185
--- /dev/null
+++ b/lib/build_config_name.js
@@ -0,0 +1,17 @@
+module.exports = function (opts) {
+  opts = opts || {};
+  var configName = opts.configName;
+  var extensions = opts.extensions;
+  if (!configName) {
+    throw new Error('Please specify a configName.');
+  }
+  if (configName instanceof RegExp) {
+    return [configName];
+  }
+  if (!Array.isArray(extensions)) {
+    throw new Error('Please provide an array of valid extensions.');
+  }
+  return extensions.map(function (ext) {
+    return configName + ext;
+  });
+};
diff --git a/lib/file_search.js b/lib/file_search.js
new file mode 100644
index 0000000..76dadd6
--- /dev/null
+++ b/lib/file_search.js
@@ -0,0 +1,14 @@
+const findup = require('findup-sync');
+
+module.exports = function (search, paths) {
+  var path;
+  var len = paths.length;
+  for (var i = 0; i < len; i++) {
+    if (path) {
+      break;
+    } else {
+      path = findup(search, {cwd: paths[i], nocase: true});
+    }
+  }
+  return path;
+};
diff --git a/lib/find_config.js b/lib/find_config.js
new file mode 100644
index 0000000..71c3f07
--- /dev/null
+++ b/lib/find_config.js
@@ -0,0 +1,25 @@
+const fs = require('fs');
+const path = require('path');
+const fileSearch = require('./file_search');
+
+module.exports = function (opts) {
+  opts = opts || {};
+  var configNameSearch = opts.configNameSearch;
+  var configPath = opts.configPath;
+  var searchPaths = opts.searchPaths;
+  // only search for a config if a path to one wasn't explicitly provided
+  if (!configPath) {
+    if (!Array.isArray(searchPaths)) {
+      throw new Error('Please provide an array of paths to search for config in.');
+    }
+    if (!configNameSearch) {
+      throw new Error('Please provide a configNameSearch.');
+    }
+    configPath = fileSearch(configNameSearch, searchPaths);
+  }
+  // confirm the configPath exists and return an absolute path to it
+  if (fs.existsSync(configPath)) {
+    return path.resolve(configPath);
+  }
+  return null;
+};
diff --git a/lib/find_cwd.js b/lib/find_cwd.js
new file mode 100644
index 0000000..2a029b9
--- /dev/null
+++ b/lib/find_cwd.js
@@ -0,0 +1,18 @@
+const path = require('path');
+
+module.exports = function (opts) {
+  if (!opts) {
+    opts = {};
+  }
+  var cwd = opts.cwd;
+  var configPath = opts.configPath;
+  // if a path to the desired config was specified
+  // but no cwd was provided, use configPath dir
+  if (typeof configPath === 'string' && !cwd) {
+    cwd = path.dirname(path.resolve(configPath));
+  }
+  if (typeof cwd === 'string') {
+    return path.resolve(cwd);
+  }
+  return process.cwd();
+};
diff --git a/lib/parse_options.js b/lib/parse_options.js
new file mode 100644
index 0000000..ab416b5
--- /dev/null
+++ b/lib/parse_options.js
@@ -0,0 +1,35 @@
+const extend = require('extend');
+
+module.exports = function (opts) {
+  var defaults = {
+    extensions: {
+      '.js': null,
+      '.json': null
+    },
+    searchPaths: []
+  };
+  if (!opts) {
+    opts = {};
+  }
+  if (opts.name) {
+    if (!opts.processTitle) {
+      opts.processTitle = opts.name;
+    }
+    if (!opts.configName) {
+      opts.configName = opts.name + 'file';
+    }
+    if (!opts.moduleName) {
+      opts.moduleName = opts.name;
+    }
+  }
+  if (!opts.processTitle) {
+    throw new Error('You must specify a processTitle.');
+  }
+  if (!opts.configName) {
+    throw new Error('You must specify a configName.');
+  }
+  if (!opts.moduleName) {
+    throw new Error('You must specify a moduleName.');
+  }
+  return extend(defaults, opts);
+};
diff --git a/lib/register_loader.js b/lib/register_loader.js
new file mode 100644
index 0000000..2b5f4cb
--- /dev/null
+++ b/lib/register_loader.js
@@ -0,0 +1,25 @@
+const rechoir = require('rechoir');
+const isString = require('lodash.isstring');
+
+module.exports = function(eventEmitter, extensions, configPath, cwd) {
+  extensions = extensions || {};
+
+  if (!isString(configPath)) {
+    return;
+  }
+
+  var autoloads = rechoir.prepare(extensions, configPath, cwd, true);
+  if (autoloads instanceof Error) {
+    autoloads = autoloads.failures;
+  }
+
+  if (Array.isArray(autoloads)) {
+    autoloads.forEach(function (attempt) {
+      if (attempt.error) {
+        eventEmitter.emit('requireFail', attempt.moduleName, attempt.error);
+      } else {
+        eventEmitter.emit('require', attempt.moduleName, attempt.module);
+      }
+    });
+  }
+};
diff --git a/lib/silent_require.js b/lib/silent_require.js
new file mode 100644
index 0000000..7b4dfe4
--- /dev/null
+++ b/lib/silent_require.js
@@ -0,0 +1,5 @@
+module.exports = function (path) {
+  try {
+    return require(path);
+  } catch (e) {}
+};
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..aa828de
--- /dev/null
+++ b/package.json
@@ -0,0 +1,48 @@
+{
+  "name": "liftoff",
+  "description": "Launch your command line tool with ease.",
+  "version": "2.3.0",
+  "homepage": "https://github.com/js-cli/js-liftoff",
+  "author": {
+    "name": "Tyler Kellen",
+    "url": "http://goingslowly.com/"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/js-cli/js-liftoff.git"
+  },
+  "bugs": {
+    "url": "https://github.com/js-cli/js-liftoff/issues"
+  },
+  "license": "MIT",
+  "main": "index.js",
+  "engines": {
+    "node": ">= 0.8"
+  },
+  "scripts": {
+    "test": "jshint lib index.js && jscs lib index.js && mocha -t 5000 -b -R spec test/index"
+  },
+  "devDependencies": {
+    "chai": "^3.5.0",
+    "coffee-script": "^1.10.0",
+    "istanbul": "^0.4.3",
+    "jscs": "^2.11.0",
+    "jshint": "^2.9.2",
+    "mocha": "^2.4.5",
+    "sinon": "~1.17.4"
+  },
+  "keywords": [
+    "command line"
+  ],
+  "dependencies": {
+    "extend": "^3.0.0",
+    "findup-sync": "^0.4.2",
+    "fined": "^1.0.1",
+    "flagged-respawn": "^0.3.2",
+    "lodash.isplainobject": "^4.0.4",
+    "lodash.isstring": "^4.0.1",
+    "lodash.mapvalues": "^4.4.0",
+    "rechoir": "^0.6.2",
+    "resolve": "^1.1.7"
+  }
+}
diff --git a/test/fixtures/case/Mochafile.js b/test/fixtures/case/Mochafile.js
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/coffee/mochafile.coffee b/test/fixtures/coffee/mochafile.coffee
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/coffee/mochafile.coffee.md b/test/fixtures/coffee/mochafile.coffee.md
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/coffee/mochafile.iced b/test/fixtures/coffee/mochafile.iced
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/configfiles/README.txt b/test/fixtures/configfiles/README.txt
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/configfiles/index.json b/test/fixtures/configfiles/index.json
new file mode 100644
index 0000000..954fb25
--- /dev/null
+++ b/test/fixtures/configfiles/index.json
@@ -0,0 +1 @@
+{"aaa":"AAA"}
diff --git a/test/fixtures/configfiles/require-md.js b/test/fixtures/configfiles/require-md.js
new file mode 100644
index 0000000..6c26e92
--- /dev/null
+++ b/test/fixtures/configfiles/require-md.js
@@ -0,0 +1,10 @@
+(function() {
+
+const path = require('path');
+
+require.extensions['.md'] = function(module, filepath) {
+  module.loaded = true;
+  module.exports = 'Load ' + path.basename(filepath) + ' by require-md';
+};
+
+}());
diff --git a/test/fixtures/configfiles/require-txt.js b/test/fixtures/configfiles/require-txt.js
new file mode 100644
index 0000000..1a7a91d
--- /dev/null
+++ b/test/fixtures/configfiles/require-txt.js
@@ -0,0 +1,10 @@
+(function() {
+
+const path = require('path');
+
+require.extensions['.txt'] = function(module, filepath) {
+  module.loaded = true;
+  module.exports = 'Load ' + path.basename(filepath) + ' by require-txt';
+};
+
+}());
diff --git a/test/fixtures/mochafile.js b/test/fixtures/mochafile.js
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/register_loader/app.cfg b/test/fixtures/register_loader/app.cfg
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/register_loader/app.rc b/test/fixtures/register_loader/app.rc
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/register_loader/app.tmp b/test/fixtures/register_loader/app.tmp
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/register_loader/require-cfg.js b/test/fixtures/register_loader/require-cfg.js
new file mode 100644
index 0000000..f09a8e0
--- /dev/null
+++ b/test/fixtures/register_loader/require-cfg.js
@@ -0,0 +1,10 @@
+(function() {
+
+const path = require('path');
+
+require.extensions['.cfg'] = function(module, filepath) {
+  module.loaded = true;
+  module.exports = 'Load ' + path.basename(filepath) + ' by require-cfg';
+};
+
+}());
diff --git a/test/fixtures/register_loader/require-fail.js b/test/fixtures/register_loader/require-fail.js
new file mode 100644
index 0000000..83965c3
--- /dev/null
+++ b/test/fixtures/register_loader/require-fail.js
@@ -0,0 +1 @@
+throw Error('Fail to register!');
diff --git a/test/fixtures/register_loader/require-rc.js b/test/fixtures/register_loader/require-rc.js
new file mode 100644
index 0000000..538ae11
--- /dev/null
+++ b/test/fixtures/register_loader/require-rc.js
@@ -0,0 +1,10 @@
+(function() {
+
+const path = require('path');
+
+require.extensions['.rc'] = function(module, filepath) {
+  module.loaded = true;
+  module.exports = 'Load ' + path.basename(filepath) + ' by require-rc';
+};
+
+}());
diff --git a/test/fixtures/search_path/mochafile.js b/test/fixtures/search_path/mochafile.js
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/symlink/.gitkeep b/test/fixtures/symlink/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/v8flags.js b/test/fixtures/v8flags.js
new file mode 100644
index 0000000..cacfc8e
--- /dev/null
+++ b/test/fixtures/v8flags.js
@@ -0,0 +1,13 @@
+const Liftoff = require('../..');
+
+const Test = new Liftoff({
+  name: 'test',
+  v8flags: ['--lazy']
+});
+Test.on('respawn', function (proc) {
+  console.log('saw respawn');
+});
+
+Test.launch({}, function (env) {
+  console.error(process.execArgv.join(''));
+});
diff --git a/test/fixtures/v8flags_function.js b/test/fixtures/v8flags_function.js
new file mode 100644
index 0000000..8661607
--- /dev/null
+++ b/test/fixtures/v8flags_function.js
@@ -0,0 +1,17 @@
+const Liftoff = require('../..');
+
+const Test = new Liftoff({
+  name: 'test',
+  v8flags: function (cb) {
+    process.nextTick(function () {
+      cb(null, ['--lazy']);
+    })
+  }
+});
+Test.on('respawn', function (proc) {
+  console.log('saw respawn');
+});
+
+Test.launch({}, function (env) {
+  console.error(process.execArgv.join(''));
+});
diff --git a/test/fixtures/v8flags_value.js b/test/fixtures/v8flags_value.js
new file mode 100644
index 0000000..f4e5a9c
--- /dev/null
+++ b/test/fixtures/v8flags_value.js
@@ -0,0 +1,13 @@
+const Liftoff = require('../..');
+
+const Test = new Liftoff({
+  name: 'test',
+  v8flags: ['--stack_size']
+});
+
+Test.on('respawn', function (flags) {
+  console.error(flags.join(' '));
+});
+
+Test.launch({}, function () {
+});
diff --git a/test/index.js b/test/index.js
new file mode 100644
index 0000000..7aa8631
--- /dev/null
+++ b/test/index.js
@@ -0,0 +1,427 @@
+const Liftoff = require('../');
+const fs = require('fs');
+const path = require('path');
+const expect = require('chai').expect;
+const sinon = require('sinon');
+const resolve = require('resolve');
+const exec = require('child_process').exec;
+
+const NAME = 'mocha';
+var app = new Liftoff({
+  processTitle: NAME,
+  configName: NAME+'file',
+  moduleName: NAME,
+  extensions: {
+    '.js': null,
+    '.json': null,
+    '.coffee': 'coffee-script/register',
+    '.coffee.md': 'coffee-script/register',
+  },
+  searchPaths: ['test/fixtures/search_path']
+});
+
+describe('Liftoff', function () {
+
+  before(function () {
+    if (!fs.existsSync('test/fixtures/symlink/mochafile.js')) {
+      fs.symlinkSync(
+        '../mochafile.js',
+        'test/fixtures/symlink/mochafile.js'
+      );
+    }
+  });
+
+  describe('buildEnvironment', function () {
+
+    it('should attempt pre-loading local modules if they are requested', function () {
+      app.on('require', function (moduleName, module) {
+        expect(moduleName).to.equal('coffee-script/register');
+        expect(module).to.equal(require('coffee-script/register'));
+      });
+      var env = app.buildEnvironment({require:['coffee-script/register']});
+      expect(env.require).to.deep.equal(['coffee-script/register']);
+    });
+
+    it('should attempt pre-loading local modules based on extension option', function () {
+      app.on('require', function (moduleName, module) {
+        expect(moduleName).to.equal('coffee-script/register');
+        expect(module).to.equal(require('coffee-script/register'));
+      });
+      var env = app.buildEnvironment({
+        configPath: 'test/fixtures/coffee/mochafile.coffee'
+      });
+    });
+
+    it('should locate local module using cwd if no config is found', function () {
+      var test = new Liftoff({name:'chai'});
+      var cwd = 'explicit/cwd';
+      var spy = sinon.spy(resolve, 'sync');
+      // NODE_PATH might be defined.
+      delete process.env.NODE_PATH;
+      test.buildEnvironment({cwd:cwd});
+      expect(spy.calledWith('chai', {basedir:path.join(process.cwd(),cwd),paths:[]})).to.be.true;
+      spy.restore();
+    });
+
+    it('should locate global module using NODE_PATH if defined', function () {
+      var test = new Liftoff({name:'dummy'});
+      var cwd = 'explicit/cwd';
+      var spy = sinon.spy(resolve, 'sync');
+      process.env.NODE_PATH = path.join(process.cwd(),cwd)
+      test.buildEnvironment();
+      expect(spy.calledWith('dummy', {basedir:process.cwd(),paths:[path.join(process.cwd(),cwd)]})).to.be.true;
+      spy.restore();
+    });
+
+    it('if cwd is explicitly provided, don\'t use search_paths', function () {
+      expect(app.buildEnvironment({cwd:'./'}).configPath).to.equal(null);
+    });
+
+    it('should find case sensitive configPath', function () {
+      var expected = path.resolve(__dirname, 'fixtures', 'case', (process.platform === 'linux' ? 'Mochafile.js' : 'mochafile.js'));
+      expect(app.buildEnvironment({cwd:path.join(__dirname, 'fixtures', 'case')}).configPath).to.equal(expected);
+    });
+
+    it('should find module in the directory next to config', function () {
+      expect(app.buildEnvironment().modulePath).to.equal(path.resolve('node_modules/mocha/index.js'));
+    });
+
+    it('should require the package sibling to the module', function () {
+      expect(app.buildEnvironment().modulePackage).to.equal(require('../node_modules/mocha/package.json'));
+    });
+
+    it('should set cwd to match the directory of the config file as long as cwd wasn\'t explicitly provided', function () {
+      expect(app.buildEnvironment().cwd).to.equal(path.resolve('test/fixtures/search_path'));
+    });
+
+    it('should resolve symlinks if config is one', function () {
+      var env = app.buildEnvironment({
+        cwd: 'test/fixtures/symlink'
+      });
+      expect(env.configPath).to.equal(path.resolve('test/fixtures/mochafile.js'));
+    });
+
+    it('should set configBase to the folder of the symlink if configPath is a symlink', function () {
+      var env = app.buildEnvironment({
+        configPath: 'test/fixtures/symlink/mochafile.js'
+      });
+      expect(env.cwd).to.equal(path.resolve('test/fixtures/symlink'));
+    })
+
+  });
+
+  describe('launch', function () {
+
+    it('should set the process.title to the moduleName', function () {
+      app.launch({}, function(){});
+      expect(process.title).to.equal(app.moduleName);
+    });
+
+    it('should return early if completions are available and requested', function (done) {
+      var test = new Liftoff({
+        name: 'whatever',
+        completions: function () {
+          done();
+        }
+      });
+      test.launch({completion:true}, function () {});
+    });
+
+    it('should call launch with liftoff instance as context', function (done) {
+      app.launch({}, function () {
+        expect(this).to.equal(app);
+        done();
+      });
+    });
+
+    it('should pass environment to first argument of launch callback', function (done) {
+      app.launch({}, function (env) {
+        expect(env).to.deep.equal(app.buildEnvironment());
+        done();
+      });
+    });
+
+    it('should skip respawning if process.argv has no values from v8flags in it', function (done) {
+      exec('node test/fixtures/v8flags.js', function (err, stdout, stderr) {
+        expect(stderr).to.equal('\n');
+        exec('node test/fixtures/v8flags_function.js', function (err, stdout, stderr) {
+          expect(stderr).to.equal('\n');
+          done();
+        });
+      });
+
+    });
+
+    it('should respawn if process.argv has values from v8flags in it', function (done) {
+      exec('node test/fixtures/v8flags.js --lazy', function (err, stdout, stderr) {
+        expect(stderr).to.equal("--lazy\n");
+        exec('node test/fixtures/v8flags_function.js --lazy', function (err, stdout, stderr) {
+          expect(stderr).to.equal("--lazy\n");
+          done();
+        });
+      });
+    });
+
+    it('should emit a respawn event if a respawn is required', function (done) {
+      exec('node test/fixtures/v8flags.js', function (err, stdout) {
+        expect(stdout).to.be.empty;
+        exec('node test/fixtures/v8flags_function.js --lazy', function (err, stdout) {
+          expect(stdout).to.equal('saw respawn\n');
+          done();
+        });
+      });
+    });
+
+    it('should respawn if process.argv has v8flags with values in it', function (done) {
+      exec('node test/fixtures/v8flags_value.js --stack_size=2048', function (err, stdout, stderr) {
+        expect(stderr).to.equal("--stack_size=2048\n");
+        done();
+      });
+    });
+
+  });
+
+  describe('requireLocal', function () {
+
+    it('should emit `require` with the name of the module and the required module', function (done) {
+      var requireTest = new Liftoff({name:'require'});
+      requireTest.on('require', function (name, module) {
+        expect(name).to.equal('mocha');
+        expect(module).to.equal(require('mocha'));
+        done();
+      });
+      requireTest.requireLocal('mocha', __dirname);
+    });
+
+    it('should emit `requireFail` with an error if a module can\'t be found.', function (done) {
+      var requireFailTest = new Liftoff({name:'requireFail'});
+      requireFailTest.on('requireFail', function (name) {
+        expect(name).to.equal('badmodule');
+        done();
+      });
+      requireFailTest.requireLocal('badmodule', __dirname);
+    });
+
+  });
+
+  describe('configFiles', function() {
+    it('should be empty if not specified', function(done) {
+      var app = new Liftoff({
+        name: 'myapp',
+      });
+      app.launch({}, function(env) {
+        expect(env.configFiles).to.deep.equal({});
+        done();
+      });
+    });
+
+    it('should find multiple files if specified', function(done) {
+      var app = new Liftoff({
+        name: 'myapp',
+        configFiles: {
+          index: {
+            currentdir: '.',
+            test: {
+              path: 'test/fixtures/configfiles',
+            },
+            findingup: {
+              path: 'test',
+              cwd: 'test/fixtures/configfiles',
+              findUp: true,
+            },
+          },
+          package: {
+            currentdir: '.',
+            test: {
+              path: 'test/fixtures/configfiles',
+            },
+            findingup: {
+              path: 'test',
+              cwd: 'test/fixtures/configfiles',
+              findUp: true,
+            },
+          },
+        },
+      });
+      app.launch({}, function(env) {
+        expect(env.configFiles).to.deep.equal({
+          index: {
+            currentdir: path.resolve('./index.js'),
+            test: path.resolve('./test/fixtures/configfiles/index.json'),
+            findingup: path.resolve('./test/index.js'),
+          },
+          package: {
+            currentdir: path.resolve('./package.json'),
+            test: null,
+            findingup: null,
+          },
+        });
+        done();
+      });
+    });
+
+    it('should use default cwd if not specified', function(done) {
+      var app = new Liftoff({
+        name: 'myapp',
+        configFiles: {
+          index: {
+            cwd: {
+              path: '.',
+              extensions: ['.js', '.json'],
+            },
+          },
+        },
+      });
+      app.launch({
+        cwd: 'test/fixtures/configfiles',
+      }, function(env) {
+        expect(env.configFiles).to.deep.equal({
+          index: {
+            cwd: path.resolve('./test/fixtures/configfiles/index.json'),
+          },
+        });
+        done();
+      });
+    });
+
+    it('should use default extensions if not specified', function(done) {
+      var app = new Liftoff({
+        extensions: { '.md': null, '.txt': null },
+        name: 'myapp',
+        configFiles: {
+          README: {
+            markdown: {
+              path: '.',
+            },
+            text: {
+              path: 'test/fixtures/configfiles',
+            },
+            markdown2: {
+              path: '.',
+              extensions: [ '.json', '.js' ],
+            },
+            text2: {
+              path: 'test/fixtures/configfiles',
+              extensions: [ '.json', '.js' ],
+            },
+          },
+        },
+      });
+      app.launch({}, function(env) {
+        expect(env.configFiles).to.deep.equal({
+          README: {
+            markdown: path.resolve('./README.md'),
+            text: path.resolve('./test/fixtures/configfiles/README.txt'),
+            markdown2: null,
+            text2: null,
+          },
+        });
+        done();
+      });
+    });
+
+    it('should use specified loaders', function(done) {
+      var logRequire = [];
+      var logFailure = [];
+
+      var app = new Liftoff({
+        extensions: {
+          '.md': './test/fixtures/configfiles/require-md',
+        },
+        name: 'myapp',
+        configFiles: {
+          README: {
+            text_null: {
+              path: 'test/fixtures/configfiles',
+            },
+            text_err: {
+              path: 'test/fixtures/configfiles',
+              extensions: {
+                '.txt': './test/fixtures/configfiles/require-non-exist'
+              },
+            },
+            text: {
+              path: 'test/fixtures/configfiles',
+              extensions: {
+                '.txt': './test/fixtures/configfiles/require-txt'
+              },
+            },
+            markdown: {
+              path: '.',
+            },
+            markdown_badext: {
+              path: '.',
+              extensions: {
+                '.txt': './test/fixtures/configfiles/require-txt'
+              },
+            },
+            markdown_badext2: {
+              path: '.',
+              extensions: {
+                '.txt': './test/fixtures/configfiles/require-non-exist'
+              },
+            },
+          },
+          // Intrinsic extension-loader mappings are prioritized.
+          index: {
+            test: {
+              path: 'test/fixtures/configfiles',
+              extensions: { // ignored
+                '.js': './test/fixtures/configfiles/require-js',
+                '.json': './test/fixtures/configfiles/require-json',
+              },
+            },
+          },
+        },
+      });
+      app.on('requireFail', function(moduleName, error) {
+        logFailure.push({ moduleName: moduleName, error: error });
+      });
+      app.on('require', function(moduleName, module) {
+        logRequire.push({ moduleName: moduleName, module: module }); 
+      });
+      app.launch({}, function(env) {
+        expect(env.configFiles).to.deep.equal({
+          README: {
+            text: path.resolve('./test/fixtures/configfiles/README.txt'),
+            text_null: null,
+            text_err: path.resolve('./test/fixtures/configfiles/README.txt'),
+            markdown: path.resolve('./README.md'),
+            markdown_badext: null,
+            markdown_badext2: null,
+          },
+          index: {
+            test: path.resolve('./test/fixtures/configfiles/index.json'),
+          },
+        });
+
+        expect(logRequire.length).to.equal(2);
+        expect(logRequire[0].moduleName)
+          .to.equal('./test/fixtures/configfiles/require-txt');
+        expect(logRequire[1].moduleName)
+          .to.equal('./test/fixtures/configfiles/require-md');
+
+        expect(logFailure.length).to.equal(1);
+        expect(logFailure[0].moduleName)
+          .to.equal('./test/fixtures/configfiles/require-non-exist');
+
+        expect(require(env.configFiles.README.markdown))
+          .to.equal('Load README.md by require-md');
+        expect(require(env.configFiles.README.text)).to
+          .to.equal('Load README.txt by require-txt');
+        expect(require(env.configFiles.index.test))
+          .to.deep.equal({ "aaa": "AAA" });
+        done();
+      });
+    });
+  });
+
+});
+
+require('./lib/build_config_name');
+require('./lib/file_search');
+require('./lib/find_config');
+require('./lib/find_cwd');
+require('./lib/parse_options');
+require('./lib/silent_require');
+require('./lib/register_loader');
diff --git a/test/lib/build_config_name.js b/test/lib/build_config_name.js
new file mode 100644
index 0000000..f190743
--- /dev/null
+++ b/test/lib/build_config_name.js
@@ -0,0 +1,28 @@
+const expect = require('chai').expect;
+const buildConfigName = require('../../lib/build_config_name');
+
+describe('buildConfigName', function () {
+
+  it('should throw if no configName is provided', function () {
+    expect(function(){buildConfigName();}).to.throw;
+  });
+
+  it('should use configName directly if it is a regex', function () {
+    var configNameSearch = /mocha/;
+    expect(buildConfigName({configName:configNameSearch})).to.deep.equal([configNameSearch]);
+  });
+
+  it('should throw if no array of extensions are provided and config is not a regex already', function () {
+    expect(function(){buildConfigName({configName:'foo'});}).to.throw;
+    expect(function(){buildConfigName({configName:'foo',extensions:'?'});}).to.throw;
+    expect(function(){buildConfigName({configName:'foo',extensions:['.js']});}).to.not.throw;
+  });
+
+  it('should build an array of possible config names', function () {
+    var multiExtension = buildConfigName({configName:'foo',extensions:['.js','.coffee']});
+    expect(multiExtension).to.deep.equal(['foo.js', 'foo.coffee']);
+    var singleExtension = buildConfigName({configName:'foo',extensions:['.js']});
+    expect(singleExtension).to.deep.equal(['foo.js']);
+  });
+
+});
diff --git a/test/lib/file_search.js b/test/lib/file_search.js
new file mode 100644
index 0000000..ace360b
--- /dev/null
+++ b/test/lib/file_search.js
@@ -0,0 +1,12 @@
+const expect = require('chai').expect;
+const fileSearch = require('../../lib/file_search');
+const path = require('path');
+
+describe('fileSearch', function () {
+
+  it('should locate a file using findup from an array of possible base paths', function () {
+    expect(fileSearch('mochafile.js', ['../../'])).to.be.null;
+    expect(fileSearch('package.json', [process.cwd()])).to.equal(path.resolve(__dirname,'..','..','package.json'));
+  });
+
+});
diff --git a/test/lib/find_config.js b/test/lib/find_config.js
new file mode 100644
index 0000000..737d0be
--- /dev/null
+++ b/test/lib/find_config.js
@@ -0,0 +1,30 @@
+const path = require('path');
+const expect = require('chai').expect;
+const findConfig = require('../../lib/find_config');
+
+describe('findConfig', function () {
+
+  it('should throw if searchPaths or configNameRegex are empty when configName isn\'t explicltly provided', function () {
+    expect(function(){findConfig();}).to.throw;
+    expect(function(){findConfig({searchPaths:['../']});}).to.throw;
+    expect(function(){findConfig({configNameRegex:'dude'});}).to.throw;
+  });
+
+  it('if configPath is explicitly provided, return the absolute path to the file or null if it doesn\'t actually exist', function () {
+    var configPath = path.resolve('test/fixtures/mochafile.js');
+    expect(findConfig({configPath:configPath})).to.equal(configPath);
+    expect(findConfig({configPath:'path/to/nowhere'})).to.equal(null);
+  });
+
+  it('should return the absolute path to the first config file found in searchPaths', function () {
+    expect(findConfig({
+      configNameSearch: ['mochafile.js', 'mochafile.coffee'],
+      searchPaths: ['test/fixtures']
+    })).to.equal(path.resolve('test/fixtures/mochafile.js'));
+    expect(findConfig({
+      configNameSearch: ['mochafile.js', 'mochafile.coffee'],
+      searchPaths: ['test/fixtures/search_path', 'test/fixtures/coffee']
+    })).to.equal(path.resolve('test/fixtures/search_path/mochafile.js'));
+  });
+
+});
diff --git a/test/lib/find_cwd.js b/test/lib/find_cwd.js
new file mode 100644
index 0000000..0a87771
--- /dev/null
+++ b/test/lib/find_cwd.js
@@ -0,0 +1,31 @@
+const expect = require('chai').expect;
+const findCwd = require('../../lib/find_cwd');
+const path = require('path');
+
+describe('findCwd', function () {
+
+  it('should return process.cwd if no options are passed', function () {
+    expect(findCwd()).to.equal(process.cwd());
+  });
+
+  it('should return path from cwd if supplied', function () {
+    expect(findCwd({cwd:'../'})).to.equal(path.resolve('../'));
+  });
+
+  it('should return directory of config if configPath defined', function () {
+    expect(findCwd({configPath:'test/fixtures/mochafile.js'})).to.equal(path.resolve('test/fixtures'));
+  });
+
+  it('should return path from cwd if both it and configPath are defined', function () {
+    expect(findCwd({cwd:'../',configPath:'test/fixtures/mochafile.js'})).to.equal(path.resolve('../'));
+  });
+
+  it('should ignore cwd if it isn\'t a string', function () {
+    expect(findCwd({cwd:true})).to.equal(process.cwd());
+  });
+
+  it('should ignore configPath if it isn\'t a string', function () {
+    expect(findCwd({configPath:true})).to.equal(process.cwd());
+  });
+
+});
diff --git a/test/lib/parse_options.js b/test/lib/parse_options.js
new file mode 100644
index 0000000..9cf1318
--- /dev/null
+++ b/test/lib/parse_options.js
@@ -0,0 +1,32 @@
+const expect = require('chai').expect;
+const parseOptions = require('../../lib/parse_options');
+const NAME = 'mocha';
+const opts = parseOptions({name:NAME});
+
+describe('parseOptions', function () {
+
+  it('should auto-set processTitle, moduleName, & configFile if `name` is provided.', function () {
+    expect(opts.processTitle).to.equal(NAME);
+    expect(opts.configName).to.equal(NAME+'file');
+    expect(opts.moduleName).to.equal(NAME);
+  });
+
+  it('should set a title to be used for the process at launch', function () {
+    expect(opts.processTitle).to.equal(NAME);
+    expect(function () {
+      parseOptions();
+    }).throws('You must specify a processTitle.');
+  });
+
+  it('should set the configuration file to look for at launch', function () {
+    expect(opts.configName).to.equal(NAME+'file');
+    expect(function () {
+      parseOptions({processTitle:NAME});
+    }).throws('You must specify a configName.');
+  });
+
+  it('should set a local module to resolve at launch', function () {
+    expect(opts.moduleName).to.equal(NAME);
+  });
+
+});
diff --git a/test/lib/register_loader.js b/test/lib/register_loader.js
new file mode 100644
index 0000000..976219a
--- /dev/null
+++ b/test/lib/register_loader.js
@@ -0,0 +1,218 @@
+const expect = require('chai').expect;
+const registerLoader = require('../../lib/register_loader');
+const path = require('path');
+const util = require('util');
+const EE = require('events').EventEmitter;
+
+const testDir = path.resolve(__dirname, '../fixtures/register_loader');
+
+
+function App() {
+  EE.call(this);
+}
+util.inherits(App, EE);
+
+function handlerNotEmit(moduleName, moduleOrError) {
+  expect.fail(null, null, 'Should not pass this line.');
+}
+
+
+describe('registerLoader', function() {
+  describe('register loader', function() {
+    it('Should emit a "require" event when registering loader succeeds',
+        function(done) {
+
+      var loaderPath = path.join(testDir, 'require-cfg.js');
+      var configPath = path.join(testDir, 'app.cfg');
+      var extensions = { '.cfg': loaderPath };
+
+      var app = new App();
+      app.on('require', function(moduleName, module) {
+        expect(moduleName).to.be.equal(loaderPath);
+        expect(require(configPath)).to.equal('Load app.cfg by require-cfg');
+        done();
+      });
+      app.on('requireFail', handlerNotEmit);
+
+      registerLoader(app, extensions, configPath);
+    });
+
+    it('Should emit a "requireFail" event when loader is not found',
+        function(done) {
+
+      var loaderPath = path.join(testDir, 'require-tmp.js');
+      var configPath = path.join(testDir, 'app.tmp');
+      var extensions = { '.tmp': loaderPath };
+
+      var app = new App();
+      app.on('requireFail', function(moduleName, error) {
+        expect(moduleName).to.be.equal(loaderPath);
+        expect(error).to.be.an('error');
+        expect(error.message).to.contain('Cannot find module');
+        done();
+      });
+      app.on('require', handlerNotEmit);
+
+      registerLoader(app, extensions, configPath);
+    });
+
+    it('Should emit a "requireFail" event when registering loader failed',
+        function(done) {
+      var loaderPath = path.join(testDir, 'require-fail.js');
+      var configPath = path.join(testDir, 'app.tmp');
+      var extensions = { '.tmp': loaderPath };
+
+      var app = new App();
+      app.on('requireFail', function(moduleName, error) {
+        expect(moduleName).to.be.equal(loaderPath);
+        expect(error).to.be.an('error');
+        expect(error.message).to.contain('Fail to register!');
+        done();
+      });
+      app.on('require', handlerNotEmit);
+
+      registerLoader(app, extensions, configPath);
+    });
+  });
+
+  describe('cwd', function() {
+    it('Should use "cwd" as a base directory of loaded file path if specified',
+        function(done) {
+
+      var loaderPath = path.join(testDir, 'require-rc.js');
+      var configPath = 'app.rc';
+      var extensions = { '.rc': loaderPath };
+
+      var app = new App();
+      app.on('require', function(moduleName, module) {
+        expect(moduleName).to.be.equal(loaderPath);
+        var loadedFile = path.join(testDir, configPath);
+        expect(require(loadedFile)).to.equal('Load app.rc by require-rc');
+        done();
+      });
+      app.on('requireFail', handlerNotEmit);
+
+      registerLoader(app, extensions, configPath, testDir);
+    });
+  });
+
+  describe('extensions', function() {
+    it('Should do nothing when extensions is null', function(done) {
+      var app = new App();
+      app.on('require', handlerNotEmit);
+      app.on('requireFail', handlerNotEmit);
+
+      registerLoader(app);
+
+      registerLoader(app, null, 'aaa/bbb.cfg');
+      registerLoader(app, null, 'aaa/bbb.cfg', '.');
+
+      // .js is one of default extensions
+      registerLoader(app, null, 'aaa/bbb.js');
+      registerLoader(app, null, 'aaa/bbb.js', '.');
+      done();
+    });
+
+    it('Should do nothing when extensions is illegal type', function(done) {
+      var app = new App();
+      app.on('require', handlerNotEmit);
+      app.on('requireFail', handlerNotEmit);
+
+      registerLoader(app, 123, 'aaa/bbb.cfg');
+      registerLoader(app, true, 'aaa/bbb.cfg');
+      registerLoader(app, function(){}, 'aaa/bbb.cfg');
+      registerLoader(app, ['.rc', '.cfg'], 'aaa/bbb.cfg');
+
+      // .js is one of default extensions
+      registerLoader(app, 123, 'aaa/bbb.js');
+      registerLoader(app, true, 'aaa/bbb.js');
+      registerLoader(app, function(){}, 'aaa/bbb.js');
+      registerLoader(app, ['.js', '.json'], 'aaa/bbb.js');
+      done();
+    });
+
+    it('Should do nothing when extensions is a string', function(done) {
+      var app = new App();
+      app.on('require', handlerNotEmit);
+      app.on('requireFail', handlerNotEmit);
+
+      registerLoader(app, '.cfg', 'aaa/bbb.cfg');
+      registerLoader(app, '.js', 'aaa/bbb.js');
+      done();
+    });
+  });
+
+  describe('configPath', function() {
+    it('Should do nothing when configPath is null', function(done) {
+      var extensions0 = ['.js', '.json', '.coffee', '.coffee.md'];
+      var extensions1 = {
+        '.js': null,
+        '.json': null,
+        '.coffee': 'coffee-script/register',
+        '.coffee.md': 'coffee-script/register',
+      };
+
+      var app = new App();
+      app.on('require', handlerNotEmit);
+      app.on('requireFail', handlerNotEmit);
+
+      registerLoader(app, extensions0);
+      registerLoader(app, extensions1);
+      registerLoader(app, extensions0, null, '.');
+      registerLoader(app, extensions1, null, '.');
+      done();
+    });
+
+    it('Should do nothing when configPath is illegal type', function(done) {
+      var extensions0 = ['.js', '.json', '.coffee', '.coffee.md'];
+      var extensions1 = {
+        '.js': null,
+        '.json': null,
+        '.coffee': 'coffee-script/register',
+        '.coffee.md': 'coffee-script/register',
+      };
+
+      var app = new App();
+      app.on('require', handlerNotEmit);
+      app.on('requireFail', handlerNotEmit);
+
+      registerLoader(app, extensions0, 123);
+      registerLoader(app, extensions0, ['aaa', 'bbb']);
+      registerLoader(app, extensions1, {});
+      registerLoader(app, extensions1, function() {});
+      done();
+    });
+
+    it('Should do nothing when configPath does not end with one of extensions',
+        function(done) {
+
+      var loaderPath = path.join(testDir, 'require-rc.js');
+      var configPath = path.join(testDir, 'app.xxx');
+      var extensions = { '.cfg': loaderPath };
+
+      var app = new App();
+      app.on('require', handlerNotEmit);
+      app.on('requireFail', handlerNotEmit);
+
+      registerLoader(app, extensions, configPath);
+      done();
+    });
+
+    it('Should do nothing when configPath ends with one of extensions \n\t' +
+       'of which the loader was already registered', function(done) {
+
+      var loaderPath = path.join(testDir, 'require-cfg.js');
+      var configPath = path.join(testDir, 'app.cfg');
+      var extensions = { '.cfg': loaderPath };
+
+      var app = new App();
+      app.on('require', handlerNotEmit);
+      app.on('requireFail', handlerNotEmit);
+
+      registerLoader(app, extensions, configPath);
+      done();
+    });
+  });
+
+});
+
diff --git a/test/lib/silent_require.js b/test/lib/silent_require.js
new file mode 100644
index 0000000..0c56105
--- /dev/null
+++ b/test/lib/silent_require.js
@@ -0,0 +1,15 @@
+const expect = require('chai').expect;
+const path = require('path');
+const silentRequire = require('../../lib/silent_require');
+
+describe('silentRequire', function () {
+
+  it('should require a file', function () {
+    expect(silentRequire(path.resolve('./package'))).to.deep.equal(require('../../package'));
+  });
+
+  it('should not throw if file is not found', function () {
+    expect(silentRequire('path/to/nowhere')).to.not.throw;
+  });
+
+});

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-liftoff.git



More information about the Pkg-javascript-commits mailing list