[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