├── .eslintignore ├── .eslintrc ├── .github └── workflows │ ├── dev.yml │ └── release.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── artwork ├── liftoff-icon.eps ├── liftoff-icon.png ├── liftoff-icon.svg ├── liftoff.eps ├── liftoff.png └── liftoff.svg ├── index.js ├── lib ├── array_find.js ├── build_config_name.js ├── file_search.js ├── find_config.js ├── find_cwd.js ├── get_node_flags.js ├── needs_lookup.js ├── parse_options.js ├── register_loader.js └── silent_require.js ├── package.json └── test ├── .gitkeep ├── array_find.js ├── build_config_name.js ├── file_search.js ├── find_config.js ├── find_cwd.js ├── fixtures ├── case │ └── Mochafile.js ├── coffee │ ├── mochafile.coffee │ ├── mochafile.coffee.md │ └── mochafile.iced ├── configfiles-extends │ ├── circular1.json │ ├── circular2.json │ ├── empty.json │ ├── extend-config.json │ ├── extend-missing.json │ ├── load-empty.json │ ├── local-missing.json │ ├── missing-invalid-obj.json │ ├── missing-name-obj.json │ ├── missing-path-obj.json │ ├── npm-missing.json │ ├── null.json │ ├── testconfig.json │ └── throws.js ├── configfiles │ ├── README.txt │ ├── index.json │ ├── override-config-path-absolute.js │ ├── override-config-path-non-string.js │ ├── override-config-path-relative.json │ ├── preload-array.js │ ├── preload-invalid-array.js │ ├── preload-invalid.js │ ├── preload-string.js │ ├── require-md.js │ ├── require-txt.js │ └── testconfig.json ├── developing_yourself │ ├── app0file.js │ ├── app1 │ │ ├── app1file.js │ │ └── index.js │ ├── app2 │ │ ├── app2file.js │ │ ├── index.js │ │ └── package.json │ ├── main.js │ └── package.json ├── mochafile.js ├── override-the-config-path.js ├── prepare-execute │ ├── nodeflags_only.js │ ├── v8flags.js │ ├── v8flags_config.js │ ├── v8flags_error.js │ ├── v8flags_function.js │ └── v8flags_value.js ├── register_loader │ ├── app.cfg │ ├── app.conf │ ├── app.rc │ ├── app.tmp │ ├── file.a.b │ ├── file.a.b.c │ ├── file.a.b.c.d │ ├── file.a.b.d │ ├── file.a.e.c.d │ ├── file.a.f.c.d │ ├── require-cfg.js │ ├── require-conf.js │ ├── require-fail.js │ ├── require-file-b.js │ ├── require-file-bc.js │ ├── require-file-cd.js │ ├── require-file-d.js │ ├── require-file-ecd.js │ ├── require-file-fcd.js │ └── require-rc.js ├── respawn_and_require.js └── search_path │ └── mochafile.js ├── get_node_flags.js ├── index.js ├── parse_options.js ├── register_loader.js └── silent_require.js /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | test/fixtures/ 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "gulp", 3 | "rules": { 4 | "max-statements": [1, 50], 5 | "no-empty": ["error", { "allowEmptyCatch": true }] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.github/workflows/dev.yml: -------------------------------------------------------------------------------- 1 | name: dev 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | env: 9 | CI: true 10 | 11 | jobs: 12 | prettier: 13 | name: Format code 14 | runs-on: ubuntu-latest 15 | if: ${{ github.event_name == 'push' }} 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | 21 | - name: Prettier 22 | uses: gulpjs/prettier_action@v3.0 23 | with: 24 | commit_message: 'chore: Run prettier' 25 | prettier_options: '--write .' 26 | 27 | test: 28 | name: Tests for Node ${{ matrix.node }} on ${{ matrix.os }} 29 | runs-on: ${{ matrix.os }} 30 | 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | node: [10, 12, 14, 16, 18, 20, 22, 24] 35 | os: [ubuntu-latest, windows-latest, macos-13] 36 | 37 | steps: 38 | - name: Clone repository 39 | uses: actions/checkout@v2 40 | 41 | - name: Set Node.js version 42 | uses: actions/setup-node@v2 43 | with: 44 | node-version: ${{ matrix.node }} 45 | 46 | - run: node --version 47 | - run: npm --version 48 | 49 | - name: Install npm dependencies 50 | run: npm install 51 | 52 | - name: Run lint 53 | run: npm run lint 54 | 55 | - name: Run tests 56 | run: npm test 57 | 58 | - name: Coveralls 59 | uses: coverallsapp/github-action@v1.1.2 60 | with: 61 | github-token: ${{ secrets.GITHUB_TOKEN }} 62 | flag-name: ${{matrix.os}}-node-${{ matrix.node }} 63 | parallel: true 64 | 65 | coveralls: 66 | needs: test 67 | name: Finish up 68 | 69 | runs-on: ubuntu-latest 70 | steps: 71 | - name: Coveralls Finished 72 | uses: coverallsapp/github-action@v1.1.2 73 | with: 74 | github-token: ${{ secrets.GITHUB_TOKEN }} 75 | parallel-finished: true 76 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - main 7 | 8 | jobs: 9 | release-please: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: GoogleCloudPlatform/release-please-action@v2 13 | with: 14 | token: ${{ secrets.GITHUB_TOKEN }} 15 | release-type: node 16 | package-name: release-please-action 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # Garbage files 64 | .DS_Store 65 | 66 | # Test results 67 | test.xunit 68 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | .nyc_output/ 3 | CHANGELOG.md 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### [5.0.1](https://www.github.com/gulpjs/liftoff/compare/v5.0.0...v5.0.1) (2025-06-01) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * Avoid passing a null value to existsSync ([#134](https://www.github.com/gulpjs/liftoff/issues/134)) ([1c54ffb](https://www.github.com/gulpjs/liftoff/commit/1c54ffb39dfd798c58fc036701edcb6d5234027c)) 9 | 10 | ## [5.0.0](https://www.github.com/gulpjs/liftoff/compare/v4.0.0...v5.0.0) (2024-03-16) 11 | 12 | 13 | ### ⚠ BREAKING CHANGES 14 | 15 | * Define `configFiles` with an array to prioritize configs (#133) 16 | * Populate additional preload modules with `configFiles` (#131) 17 | * Lookup `configPath` in `configFiles` (#128) 18 | 19 | ### Features 20 | 21 | * Define `configFiles` with an array to prioritize configs ([#133](https://www.github.com/gulpjs/liftoff/issues/133)) ([55123fc](https://www.github.com/gulpjs/liftoff/commit/55123fcb0b679aa8739a966c791e2bc1c20cdac6)) 22 | * Lookup `configPath` in `configFiles` ([#128](https://www.github.com/gulpjs/liftoff/issues/128)) ([5301335](https://www.github.com/gulpjs/liftoff/commit/5301335a9f2dea21c5802a05eb9f8abdfbbeed51)) 23 | * Populate additional preload modules with `configFiles` ([#131](https://www.github.com/gulpjs/liftoff/issues/131)) ([fad21a9](https://www.github.com/gulpjs/liftoff/commit/fad21a98d1a9b263f320a2258fef94328687a5a0)) 24 | 25 | 26 | ### Bug Fixes 27 | 28 | * Disallow non-string `configPath` overrides ([#130](https://www.github.com/gulpjs/liftoff/issues/130)) ([6bcd381](https://www.github.com/gulpjs/liftoff/commit/6bcd381f328463ff78a43b5b8af3fe6c62bf3bdb)) 29 | 30 | ## [4.0.0](https://www.github.com/gulpjs/liftoff/compare/v3.1.0...v4.0.0) (2021-11-22) 31 | 32 | 33 | ### ⚠ BREAKING CHANGES 34 | 35 | * Support `extends` syntax in config files (#103) 36 | * Normalize repository, dropping node <10.13 support (#118) 37 | * call `env.completion` inside execute to allow additional configuration (#106) 38 | * Rename `opts.require` to `opts.preload` 39 | * Rename events to be more specific 40 | * Remove launch API 41 | 42 | ### Features 43 | 44 | * Add beforeRequire event ([65f350d](https://www.github.com/gulpjs/liftoff/commit/65f350d0140f91467252f58489b5e13bc19f169e)) 45 | * Rename `opts.require` to `opts.preload` ([596926a](https://www.github.com/gulpjs/liftoff/commit/596926a177df254726715ffed7bc4b344e87bef0)) 46 | * Rename events to be more specific ([cbb8456](https://www.github.com/gulpjs/liftoff/commit/cbb8456e0273505d1ba237060aaebb7b79c26112)) 47 | * Support `extends` syntax in config files ([#103](https://www.github.com/gulpjs/liftoff/issues/103)) ([68c9db7](https://www.github.com/gulpjs/liftoff/commit/68c9db7fc4f26b7b9e3e91f8e8c6374d1a9dbb1f)) 48 | 49 | 50 | ### Bug Fixes 51 | 52 | * call `env.completion` inside execute to allow additional configuration ([#106](https://www.github.com/gulpjs/liftoff/issues/106)) ([2a1fc4b](https://www.github.com/gulpjs/liftoff/commit/2a1fc4b632e55effcd45ab3c48bd7aba0ce049bf)) 53 | * Update rechoir to support dots in config name ([33a6286](https://www.github.com/gulpjs/liftoff/commit/33a62869bc2474d4168f17f611dadbd66cc6adac)) 54 | 55 | 56 | ### Miscellaneous Chores 57 | 58 | * Normalize repository, dropping node <10.13 support ([#118](https://www.github.com/gulpjs/liftoff/issues/118)) ([d671e76](https://www.github.com/gulpjs/liftoff/commit/d671e7600bd96f3c6c23697575436e89fa407c99)) 59 | * Remove launch API ([dea6860](https://www.github.com/gulpjs/liftoff/commit/dea68609a669195f8d59df2164a5f4ba6e680004)) 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2017 Tyler Kellen , 2017-2021 Blaine Bublitz , Eric Schoffstall and other contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

8 | 9 | 10 | 11 |

12 | 13 | # liftoff 14 | 15 | [![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][ci-image]][ci-url] [![Coveralls Status][coveralls-image]][coveralls-url] 16 | 17 | Launch your command line tool with ease. 18 | 19 | ## What is it? 20 | 21 | [See this blog post][liftoff-blog], [check out this proof of concept][hacker], or read on. 22 | 23 | Say you're writing a CLI tool. Let's call it [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. 24 | 25 | Now, when you run `hacker`, you want to configure what it does using the `Hackerfile` in your current directory, and you want it to execute using the local installation of your tool. Also, it'd be nice if the `hacker` command was smart enough to traverse up your folders until it finds a `Hackerfile`—for those times when you're not in the root directory of your project. Heck, you might even want to launch `hacker` from a folder outside of your project by manually specifying a working directory. Liftoff manages this for you. 26 | 27 | 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. 28 | 29 | ## Usage 30 | 31 | ```js 32 | const Liftoff = require('liftoff'); 33 | 34 | const Hacker = new Liftoff({ 35 | name: 'hacker', 36 | processTitle: 'hacker', 37 | moduleName: 'hacker', 38 | configName: 'hackerfile', 39 | extensions: { 40 | '.js': null, 41 | '.json': null, 42 | '.coffee': 'coffee-script/register', 43 | }, 44 | v8flags: ['--harmony'], // or v8flags: require('v8flags') 45 | }); 46 | 47 | Hacker.prepare({}, function (env) { 48 | Hacker.execute(env, function (env) { 49 | // Do post-execute things 50 | }); 51 | }); 52 | ``` 53 | 54 | ## API 55 | 56 | ### constructor(opts) 57 | 58 | Create an instance of Liftoff to invoke your application. 59 | 60 | #### opts.name 61 | 62 | Sugar for setting `processTitle`, `moduleName`, `configName` automatically. 63 | 64 | Type: `String` 65 | 66 | Default: `null` 67 | 68 | These are equivalent: 69 | 70 | ```js 71 | const Hacker = Liftoff({ 72 | processTitle: 'hacker', 73 | moduleName: 'hacker', 74 | configName: 'hackerfile', 75 | }); 76 | ``` 77 | 78 | ```js 79 | const Hacker = Liftoff({ name: 'hacker' }); 80 | ``` 81 | 82 | Type: `String` 83 | 84 | Default: `null` 85 | 86 | #### opts.configName 87 | 88 | Sets the name of the configuration file Liftoff will attempt to find. Case-insensitive. 89 | 90 | Type: `String` 91 | 92 | Default: `null` 93 | 94 | #### opts.extensions 95 | 96 | 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. 97 | 98 | Type: `Object` 99 | 100 | Default: `{".js":null,".json":null}` 101 | 102 | **Examples:** 103 | 104 | 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. 105 | 106 | ```js 107 | const MyApp = new Liftoff({ 108 | name: 'myapp', 109 | extensions: { 110 | '.js': null, 111 | '.json': null, 112 | '.coffee': 'coffee-script/register', 113 | }, 114 | }); 115 | ``` 116 | 117 | In this example, Liftoff will look for `.myapp{rc}`. 118 | 119 | ```js 120 | const MyApp = new Liftoff({ 121 | name: 'myapp', 122 | configName: '.myapp', 123 | extensions: { 124 | rc: null, 125 | }, 126 | }); 127 | ``` 128 | 129 | In this example, Liftoff will automatically attempt to load the correct module for any javascript variant supported by [interpret] (as long as it does not require a register method). 130 | 131 | ```js 132 | const MyApp = new Liftoff({ 133 | name: 'myapp', 134 | extensions: require('interpret').jsVariants, 135 | }); 136 | ``` 137 | 138 | #### opts.v8flags 139 | 140 | 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]. To support all v8flags, see [v8flags]. 141 | 142 | Type: `Array` or `Function` 143 | 144 | Default: `null` 145 | 146 | If this method is a function, it should take a node-style callback that yields an array of flags. 147 | 148 | #### opts.processTitle 149 | 150 | Sets what the [process title][process-title] will be. 151 | 152 | Type: `String` 153 | 154 | Default: `null` 155 | 156 | #### opts.completions(type) 157 | 158 | A method to handle bash/zsh/whatever completions. 159 | 160 | Type: `Function` 161 | 162 | Default: `null` 163 | 164 | #### opts.configFiles 165 | 166 | An array of configuration files to find with each value being a [path arguments](#path-arguments). 167 | 168 | The order of the array indicates the priority that config file overrides are applied. See [Config Files](#config-files) for the config file specification and description of overrides. 169 | 170 | **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. 171 | 172 | Type: `Array` 173 | 174 | Default: `null` 175 | 176 | #### Path arguments 177 | 178 | The [`fined`][fined] module accepts a string representing the path to search or an object with the following keys: 179 | 180 | - `path` **(required)** 181 | 182 | The path to search. Using only a string expands to this property. 183 | 184 | Type: `String` 185 | 186 | Default: `null` 187 | 188 | - `name` 189 | 190 | The basename of the file to find. Extensions are appended during lookup. 191 | 192 | Type: `String` 193 | 194 | Default: Top-level key in `configFiles` 195 | 196 | - `extensions` 197 | 198 | The extensions to append to `name` during lookup. See also: [`opts.extensions`](#optsextensions). 199 | 200 | Type: `String` or `Array` or `Object` 201 | Default: The value of [`opts.extensions`](#optsextensions) 202 | 203 | - `cwd` 204 | 205 | The base directory of `path` (if relative). 206 | 207 | Type: `String` 208 | 209 | Default: The value of [`opts.cwd`](#optscwd) 210 | 211 | - `findUp` 212 | 213 | Whether the `path` should be traversed up to find the file. 214 | 215 | Type: `Boolean` 216 | 217 | Default: `false` 218 | 219 | **Examples:** 220 | 221 | In this example Liftoff will look for the `.hacker.js` file relative to the `cwd` as declared in `configFiles`. 222 | 223 | ```js 224 | const MyApp = new Liftoff({ 225 | name: 'hacker', 226 | configFiles: [ 227 | { name: '.hacker', path: '.' } 228 | ], 229 | }); 230 | ``` 231 | 232 | In this example, Liftoff will look for `.hackerrc` in the home directory. 233 | 234 | ```js 235 | const MyApp = new Liftoff({ 236 | name: 'hacker', 237 | configFiles: [ 238 | { 239 | name: '.hacker', 240 | path: '~', 241 | extensions: { 242 | rc: null, 243 | }, 244 | }, 245 | ], 246 | }); 247 | ``` 248 | 249 | In this example, Liftoff will look in the `cwd` and then lookup the tree for the `.hacker.js` file. 250 | 251 | ```js 252 | const MyApp = new Liftoff({ 253 | name: 'hacker', 254 | configFiles: [ 255 | { 256 | name: '.hacker', 257 | path: '.', 258 | findUp: true, 259 | }, 260 | ], 261 | }); 262 | ``` 263 | 264 | In this example, Liftoff will use the home directory as the `cwd` and looks for `~/.hacker.js`. 265 | 266 | ```js 267 | const MyApp = new Liftoff({ 268 | name: 'hacker', 269 | configFiles: [ 270 | { 271 | name: '.hacker', 272 | path: '.', 273 | cwd: '~', 274 | }, 275 | ], 276 | }); 277 | ``` 278 | 279 | ### prepare(opts, callback(env)) 280 | 281 | Prepares the environment for your application with provided options, and invokes your callback with the calculated environment as the first argument. The environment can be modified before using it as the first argument to `execute`. 282 | 283 | **Example Configuration w/ Options Parsing:** 284 | 285 | ```js 286 | const Liftoff = require('liftoff'); 287 | const MyApp = new Liftoff({ name: 'myapp' }); 288 | const argv = require('minimist')(process.argv.slice(2)); 289 | const onExecute = function (env, argv) { 290 | // Do post-execute things 291 | }; 292 | const onPrepare = function (env) { 293 | console.log('my environment is:', env); 294 | console.log('my liftoff config is:', this); 295 | MyApp.execute(env, onExecute); 296 | }; 297 | MyApp.prepare( 298 | { 299 | cwd: argv.cwd, 300 | configPath: argv.myappfile, 301 | preload: argv.preload, 302 | completion: argv.completion, 303 | }, 304 | onPrepare 305 | ); 306 | ``` 307 | 308 | **Example w/ modified environment** 309 | 310 | ```js 311 | const Liftoff = require('liftoff'); 312 | const Hacker = new Liftoff({ 313 | name: 'hacker', 314 | configFiles: [ 315 | { name: '.hacker', path: '.', cwd: '~' } 316 | ], 317 | }); 318 | const onExecute = function (env, argv) { 319 | // Do post-execute things 320 | }; 321 | const onPrepare = function (env) { 322 | const config = env.config['.hacker']; 323 | Hacker.execute(env, config.forcedFlags, onExecute); 324 | }; 325 | Hacker.prepare({}, onPrepare); 326 | ``` 327 | 328 | #### opts.cwd 329 | 330 | Change the current working directory for this execution. Relative paths are calculated against `process.cwd()`. 331 | 332 | Type: `String` 333 | 334 | Default: `process.cwd()` 335 | 336 | **Example Configuration:** 337 | 338 | ```js 339 | const argv = require('minimist')(process.argv.slice(2)); 340 | MyApp.prepare( 341 | { 342 | cwd: argv.cwd, 343 | }, 344 | function (env) { 345 | MyApp.execute(env, invoke); 346 | } 347 | ); 348 | ``` 349 | 350 | **Matching CLI Invocation:** 351 | 352 | ``` 353 | myapp --cwd ../ 354 | ``` 355 | 356 | #### opts.configPath 357 | 358 | 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`. 359 | 360 | Type: `String` 361 | 362 | Default: `null` 363 | 364 | **Example Configuration:** 365 | 366 | ```js 367 | var argv = require('minimist')(process.argv.slice(2)); 368 | MyApp.prepare( 369 | { 370 | configPath: argv.myappfile, 371 | }, 372 | function (env) { 373 | MyApp.execute(env, invoke); 374 | } 375 | ); 376 | ``` 377 | 378 | **Matching CLI Invocation:** 379 | 380 | ```sh 381 | myapp --myappfile /var/www/project/Myappfile.js 382 | ``` 383 | 384 | **Examples using `cwd` and `configPath` together:** 385 | 386 | These are functionally identical: 387 | 388 | ```sh 389 | myapp --myappfile /var/www/project/Myappfile.js 390 | myapp --cwd /var/www/project 391 | ``` 392 | 393 | These can run myapp from a shared directory as though it were located in another project: 394 | 395 | ```sh 396 | myapp --myappfile /Users/name/Myappfile.js --cwd /var/www/project1 397 | myapp --myappfile /Users/name/Myappfile.js --cwd /var/www/project2 398 | ``` 399 | 400 | #### opts.preload 401 | 402 | A string or array of modules to attempt requiring from the local working directory before invoking the execute callback. 403 | 404 | Type: `String|Array` 405 | Default: `null` 406 | 407 | **Example Configuration:** 408 | 409 | ```js 410 | var argv = require('minimist')(process.argv.slice(2)); 411 | MyApp.prepare( 412 | { 413 | preload: argv.preload, 414 | }, 415 | function (env) { 416 | MyApp.execute(env, invoke); 417 | } 418 | ); 419 | ``` 420 | 421 | **Matching CLI Invocation:** 422 | 423 | ```sh 424 | myapp --preload coffee-script/register 425 | ``` 426 | 427 | #### callback(env) 428 | 429 | A function called after your environment is prepared. A good place to modify the environment before calling `execute`. When invoked, `this` will be your instance of Liftoff. The `env` param will contain the following keys: 430 | 431 | - `cwd`: the current working directory 432 | - `preload`: an array of modules that liftoff tried to pre-load 433 | - `configNameSearch`: the config files searched for 434 | - `configPath`: the full path to your configuration file (if found) 435 | - `configBase`: the base directory of your configuration file (if found) 436 | - `modulePath`: the full path to the local module your project relies on (if found) 437 | - `modulePackage`: the contents of the local module's package.json (if found) 438 | - `configFiles`: an array of filepaths for each found config file (filepath values will be null if not found) 439 | - `config`: an array of loaded config objects in the same order as `configFiles` 440 | 441 | ### execute(env, [forcedFlags], callback(env, argv)) 442 | 443 | A function to start your application, based on the `env` given. Optionally takes an array of `forcedFlags`, which will force a respawn with those node or V8 flags during startup. Invokes your callback with the environment and command-line arguments (minus node & v8 flags) after the application has been executed. 444 | 445 | **Example:** 446 | 447 | ```js 448 | const Liftoff = require('liftoff'); 449 | const MyApp = new Liftoff({ name: 'myapp' }); 450 | const onExecute = function (env, argv) { 451 | // Do post-execute things 452 | console.log('my environment is:', env); 453 | console.log('my cli options are:', argv); 454 | console.log('my liftoff config is:', this); 455 | }; 456 | const onPrepare = function (env) { 457 | var forcedFlags = ['--trace-deprecation']; 458 | MyApp.execute(env, forcedFlags, onExecute); 459 | }; 460 | MyApp.prepare({}, onPrepare); 461 | ``` 462 | 463 | #### callback(env, argv) 464 | 465 | A function called after your application is executed. When invoked, `this` will be your instance of Liftoff, `argv` will be all command-line arguments (minus node & v8 flags), and `env` will contain the following keys: 466 | 467 | - `cwd`: the current working directory 468 | - `preload`: an array of modules that liftoff tried to pre-load 469 | - `configNameSearch`: the config files searched for 470 | - `configPath`: the full path to your configuration file (if found) 471 | - `configBase`: the base directory of your configuration file (if found) 472 | - `modulePath`: the full path to the local module your project relies on (if found) 473 | - `modulePackage`: the contents of the local module's package.json (if found) 474 | - `configFiles`: an array of filepaths for each found config file (filepath values will be null if not found) 475 | - `config`: an array of loaded config objects in the same order as `configFiles` 476 | 477 | ### events 478 | 479 | #### `on('preload:before', function(name) {})` 480 | 481 | Emitted before a module is pre-load. (But for only a module which is specified by `opts.preload`.) 482 | 483 | ```js 484 | var Hacker = new Liftoff({ name: 'hacker', preload: 'coffee-script' }); 485 | Hacker.on('preload:before', function (name) { 486 | console.log('Requiring external module: ' + name + '...'); 487 | }); 488 | ``` 489 | 490 | #### `on('preload:success', function(name, module) {})` 491 | 492 | Emitted when a module has been pre-loaded. 493 | 494 | ```js 495 | var Hacker = new Liftoff({ name: 'hacker' }); 496 | Hacker.on('preload:success', function (name, module) { 497 | console.log('Required external module: ' + name + '...'); 498 | // automatically register coffee-script extensions 499 | if (name === 'coffee-script') { 500 | module.register(); 501 | } 502 | }); 503 | ``` 504 | 505 | #### `on('preload:failure', function(name, err) {})` 506 | 507 | Emitted when a requested module cannot be preloaded. 508 | 509 | ```js 510 | var Hacker = new Liftoff({ name: 'hacker' }); 511 | Hacker.on('preload:failure', function (name, err) { 512 | console.log('Unable to load:', name, err); 513 | }); 514 | ``` 515 | 516 | #### `on('loader:success, function(name, module) {})` 517 | 518 | Emitted when a loader that matches an extension has been loaded. 519 | 520 | ```js 521 | var Hacker = new Liftoff({ 522 | name: 'hacker', 523 | extensions: { 524 | '.ts': 'ts-node/register', 525 | }, 526 | }); 527 | Hacker.on('loader:success', function (name, module) { 528 | console.log('Required external module: ' + name + '...'); 529 | }); 530 | ``` 531 | 532 | #### `on('loader:failure', function(name, err) {})` 533 | 534 | Emitted when no loader for an extension can be loaded. Emits an error for each failed loader. 535 | 536 | ```js 537 | var Hacker = new Liftoff({ 538 | name: 'hacker', 539 | extensions: { 540 | '.ts': 'ts-node/register', 541 | }, 542 | }); 543 | Hacker.on('loader:failure', function (name, err) { 544 | console.log('Unable to load:', name, err); 545 | }); 546 | ``` 547 | 548 | #### `on('respawn', function(flags, child) {})` 549 | 550 | Emitted when Liftoff re-spawns your process (when a [`v8flags`](#optsv8flags) is detected). 551 | 552 | ```js 553 | var Hacker = new Liftoff({ 554 | name: 'hacker', 555 | v8flags: ['--harmony'], 556 | }); 557 | Hacker.on('respawn', function (flags, child) { 558 | console.log('Detected node flags:', flags); 559 | console.log('Respawned to PID:', child.pid); 560 | }); 561 | ``` 562 | 563 | Event will be triggered for this command: 564 | `hacker --harmony commmand` 565 | 566 | ## Config files 567 | 568 | Liftoff supports a small definition of config files, but all details provided by users will be available in `env.config`. 569 | 570 | ### `extends` 571 | 572 | All `extends` properties will be traversed and become the basis for the resulting config object. Any path provided for `extends` will be loaded with node's `require`, so all extensions and loaders supported on the Liftoff instance will be available to them. 573 | 574 | ### Field matching the `configName` 575 | 576 | Users can override the `configPath` via their config files by specifying a field with the same name as the primary `configName`. For example, the `hackerfile` property in a `configFile` will resolve the `configPath` and `configBase` against the path. 577 | 578 | ### `preload` 579 | 580 | If specified as a string or array of strings, they will be added to the list of preloads in the environment. 581 | 582 | ## Examples 583 | 584 | Check out how [gulp][gulp-cli-index] uses Liftoff. 585 | 586 | For a bare-bones example, try [the hacker project][hacker-index]. 587 | 588 | To try the example, do the following: 589 | 590 | 1. Install the sample project `hacker` with `npm install -g hacker`. 591 | 2. Make a `Hackerfile.js` with some arbitrary javascript it. 592 | 3. Install hacker next to it with `npm install hacker`. 593 | 4. Run `hacker` while in the same parent folder. 594 | 595 | ## License 596 | 597 | MIT 598 | 599 | 600 | [downloads-image]: https://img.shields.io/npm/dm/liftoff.svg?style=flat-square 601 | [npm-url]: https://www.npmjs.com/package/liftoff 602 | [npm-image]: https://img.shields.io/npm/v/liftoff.svg?style=flat-square 603 | 604 | [ci-url]: https://github.com/gulpjs/liftoff/actions?query=workflow:dev 605 | [ci-image]: https://img.shields.io/github/workflow/status/gulpjs/liftoff/dev?style=flat-square 606 | 607 | [coveralls-url]: https://coveralls.io/r/gulpjs/liftoff 608 | [coveralls-image]: https://img.shields.io/coveralls/gulpjs/liftoff/master.svg?style=flat-square 609 | 610 | 611 | 612 | [liftoff-blog]: https://bocoup.com/blog/building-command-line-tools-in-node-with-liftoff 613 | 614 | [hacker]: https://github.com/gulpjs/hacker 615 | [interpret]: https://github.com/gulpjs/interpret 616 | [flagged-respawn]: http://github.com/gulpjs/flagged-respawn 617 | [v8flags]: https://github.com/gulpjs/v8flags 618 | [fined]: https://github.com/gulpjs/fined 619 | 620 | [process-title]: http://nodejs.org/api/process.html#process_process_title 621 | 622 | [gulp-cli-index]: https://github.com/gulpjs/gulp-cli/blob/master/index.js 623 | [hacker-index]: https://github.com/gulpjs/js-hacker/blob/master/bin/hacker.js 624 | 625 | -------------------------------------------------------------------------------- /artwork/liftoff-icon.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/artwork/liftoff-icon.eps -------------------------------------------------------------------------------- /artwork/liftoff-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/artwork/liftoff-icon.png -------------------------------------------------------------------------------- /artwork/liftoff-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 40 | 42 | 43 | 45 | 46 | 57 | 58 | 60 | 61 | 62 | 68 | 69 | 73 | 77 | 78 | -------------------------------------------------------------------------------- /artwork/liftoff.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/artwork/liftoff.eps -------------------------------------------------------------------------------- /artwork/liftoff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/artwork/liftoff.png -------------------------------------------------------------------------------- /artwork/liftoff.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 13 | 20 | 27 | 39 | 45 | 51 | 52 | 60 | 61 | 62 | 63 | 68 | 73 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var path = require('path'); 3 | var EE = require('events').EventEmitter; 4 | 5 | var extend = require('extend'); 6 | var resolve = require('resolve'); 7 | var flaggedRespawn = require('flagged-respawn'); 8 | var isPlainObject = require('is-plain-object').isPlainObject; 9 | var fined = require('fined'); 10 | 11 | var findCwd = require('./lib/find_cwd'); 12 | var arrayFind = require('./lib/array_find'); 13 | var findConfig = require('./lib/find_config'); 14 | var fileSearch = require('./lib/file_search'); 15 | var needsLookup = require('./lib/needs_lookup'); 16 | var parseOptions = require('./lib/parse_options'); 17 | var silentRequire = require('./lib/silent_require'); 18 | var buildConfigName = require('./lib/build_config_name'); 19 | var registerLoader = require('./lib/register_loader'); 20 | var getNodeFlags = require('./lib/get_node_flags'); 21 | 22 | function isString(val) { 23 | return typeof val === 'string'; 24 | } 25 | 26 | function Liftoff(opts) { 27 | EE.call(this); 28 | extend(this, parseOptions(opts)); 29 | } 30 | util.inherits(Liftoff, EE); 31 | 32 | Liftoff.prototype.requireLocal = function (moduleName, basedir) { 33 | try { 34 | this.emit('preload:before', moduleName); 35 | var result = require(resolve.sync(moduleName, { basedir: basedir })); 36 | this.emit('preload:success', moduleName, result); 37 | return result; 38 | } catch (e) { 39 | this.emit('preload:failure', moduleName, e); 40 | } 41 | }; 42 | 43 | Liftoff.prototype.buildEnvironment = function (opts) { 44 | opts = opts || {}; 45 | 46 | // get modules we want to preload 47 | var preload = opts.preload || []; 48 | 49 | // ensure items to preload is an array 50 | if (!Array.isArray(preload)) { 51 | preload = [preload]; 52 | } 53 | 54 | // make a copy of search paths that can be mutated for this run 55 | var searchPaths = this.searchPaths.slice(); 56 | // store the instance configName to use in closures without access to `this` 57 | var configName = this.configName; 58 | 59 | // calculate current cwd 60 | var cwd = findCwd(opts); 61 | 62 | var exts = this.extensions; 63 | var eventEmitter = this; 64 | 65 | function findAndRegisterLoader(pathObj, defaultObj) { 66 | var found = fined(pathObj, defaultObj); 67 | if (!found) { 68 | return null; 69 | } 70 | if (isPlainObject(found.extension)) { 71 | registerLoader(eventEmitter, found.extension, found.path, cwd); 72 | } 73 | return found.path; 74 | } 75 | 76 | function getModulePath(cwd, xtends) { 77 | // If relative, we need to use fined to look up the file. If not, assume a node_module 78 | if (needsLookup(xtends)) { 79 | var defaultObj = { cwd: cwd, extensions: exts }; 80 | // Using `xtends` like this should allow people to use a string or any object that fined accepts 81 | var foundPath = findAndRegisterLoader(xtends, defaultObj); 82 | if (!foundPath) { 83 | var name; 84 | if (typeof xtends === 'string') { 85 | name = xtends; 86 | } else { 87 | name = xtends.path || xtends.name; 88 | } 89 | var msg = 'Unable to locate one of your extends.'; 90 | if (name) { 91 | msg += ' Looking for file: ' + path.resolve(cwd, name); 92 | } 93 | throw new Error(msg); 94 | } 95 | return foundPath; 96 | } 97 | 98 | return xtends; 99 | } 100 | 101 | var visited = {}; 102 | function loadConfig(cwd, xtends, preferred) { 103 | var configFilePath = getModulePath(cwd, xtends); 104 | 105 | if (visited[configFilePath]) { 106 | throw new Error( 107 | 'We encountered a circular extend for file: ' + 108 | configFilePath + 109 | '. Please remove the recursive extends.' 110 | ); 111 | } 112 | var configFile; 113 | try { 114 | configFile = require(configFilePath); 115 | } catch (e) { 116 | // TODO: Consider surfacing the `require` error 117 | throw new Error( 118 | 'Encountered error when loading config file: ' + configFilePath 119 | ); 120 | } 121 | 122 | // resolve something like `{ gulpfile: "./abc.xyz" }` to the absolute path 123 | // based on the path of the configFile 124 | if (Object.prototype.hasOwnProperty.call(configFile, configName)) { 125 | if (isString(configFile[configName])) { 126 | configFile[configName] = path.resolve(path.dirname(configFilePath), configFile[configName]); 127 | } 128 | } 129 | 130 | visited[configFilePath] = true; 131 | if (configFile && configFile.extends) { 132 | var nextCwd = path.dirname(configFilePath); 133 | return loadConfig(nextCwd, configFile.extends, configFile); 134 | } 135 | // Always extend into an empty object so we can call `delete` on `config.extends` 136 | var config = extend(true /* deep */, {}, configFile, preferred); 137 | delete config.extends; 138 | return config; 139 | } 140 | 141 | var configFiles = []; 142 | if (Array.isArray(this.configFiles)) { 143 | configFiles = this.configFiles.map(function (pathObj) { 144 | var defaultObj = { cwd: cwd, extensions: exts }; 145 | 146 | return findAndRegisterLoader(pathObj, defaultObj); 147 | }); 148 | } 149 | 150 | var config = configFiles.map(function (startingLocation) { 151 | var defaultConfig = {}; 152 | if (!startingLocation) { 153 | return defaultConfig; 154 | } 155 | 156 | return loadConfig(cwd, startingLocation, defaultConfig); 157 | }); 158 | 159 | var configPathOverride = arrayFind(config, function (cfg) { 160 | if (Object.prototype.hasOwnProperty.call(cfg, configName)) { 161 | if (isString(cfg[configName])) { 162 | return cfg[configName]; 163 | } 164 | } 165 | }); 166 | 167 | var additionPreloads = arrayFind(config, function (cfg) { 168 | if (Object.prototype.hasOwnProperty.call(cfg, 'preload')) { 169 | if (Array.isArray(cfg.preload)) { 170 | if (cfg.preload.every(isString)) { 171 | return cfg.preload; 172 | } 173 | } 174 | if (isString(cfg.preload)) { 175 | return cfg.preload; 176 | } 177 | } 178 | }); 179 | 180 | // if cwd was provided explicitly, only use it for searching config 181 | if (opts.cwd) { 182 | searchPaths = [cwd]; 183 | } else { 184 | // otherwise just search in cwd first 185 | searchPaths.unshift(cwd); 186 | } 187 | 188 | // calculate the regex to use for finding the config file 189 | var configNameSearch = buildConfigName({ 190 | configName: configName, 191 | extensions: Object.keys(this.extensions), 192 | }); 193 | 194 | // calculate configPath 195 | var configPath = findConfig({ 196 | configNameSearch: configNameSearch, 197 | searchPaths: searchPaths, 198 | configPath: opts.configPath || configPathOverride, 199 | }); 200 | 201 | // if we have a config path, save the directory it resides in. 202 | var configBase; 203 | if (configPath) { 204 | configBase = path.dirname(configPath); 205 | // if cwd wasn't provided explicitly, it should match configBase 206 | if (!opts.cwd) { 207 | cwd = configBase; 208 | } 209 | } 210 | 211 | // TODO: break this out into lib/ 212 | // locate local module and package next to config or explicitly provided cwd 213 | var modulePath; 214 | var modulePackage; 215 | try { 216 | var delim = path.delimiter; 217 | var paths = process.env.NODE_PATH ? process.env.NODE_PATH.split(delim) : []; 218 | modulePath = resolve.sync(this.moduleName, { 219 | basedir: configBase || cwd, 220 | paths: paths, 221 | }); 222 | modulePackage = silentRequire(fileSearch('package.json', [modulePath])); 223 | } catch (e) {} 224 | 225 | // if we have a configuration but we failed to find a local module, maybe 226 | // we are developing against ourselves? 227 | if (!modulePath && configPath) { 228 | // check the package.json sibling to our config to see if its `name` 229 | // matches the module we're looking for 230 | var modulePackagePath = fileSearch('package.json', [configBase]); 231 | modulePackage = silentRequire(modulePackagePath); 232 | if (modulePackage && modulePackage.name === this.moduleName) { 233 | // if it does, our module path is `main` inside package.json 234 | modulePath = path.join( 235 | path.dirname(modulePackagePath), 236 | modulePackage.main || 'index.js' 237 | ); 238 | cwd = configBase; 239 | } else { 240 | // clear if we just required a package for some other project 241 | modulePackage = {}; 242 | } 243 | } 244 | 245 | return { 246 | cwd: cwd, 247 | preload: preload.concat(additionPreloads || []), 248 | completion: opts.completion, 249 | configNameSearch: configNameSearch, 250 | configPath: configPath, 251 | configBase: configBase, 252 | modulePath: modulePath, 253 | modulePackage: modulePackage || {}, 254 | configFiles: configFiles, 255 | config: config, 256 | }; 257 | }; 258 | 259 | Liftoff.prototype.handleFlags = function (cb) { 260 | if (typeof this.v8flags === 'function') { 261 | this.v8flags(function (err, flags) { 262 | if (err) { 263 | cb(err); 264 | } else { 265 | cb(null, flags); 266 | } 267 | }); 268 | } else { 269 | process.nextTick( 270 | function () { 271 | cb(null, this.v8flags); 272 | }.bind(this) 273 | ); 274 | } 275 | }; 276 | 277 | Liftoff.prototype.prepare = function (opts, fn) { 278 | if (typeof fn !== 'function') { 279 | throw new Error('You must provide a callback function.'); 280 | } 281 | 282 | process.title = this.processTitle; 283 | 284 | var env = this.buildEnvironment(opts); 285 | 286 | fn.call(this, env); 287 | }; 288 | 289 | Liftoff.prototype.execute = function (env, forcedFlags, fn) { 290 | var completion = env.completion; 291 | if (completion && this.completions) { 292 | return this.completions(completion); 293 | } 294 | 295 | if (typeof forcedFlags === 'function') { 296 | fn = forcedFlags; 297 | forcedFlags = undefined; 298 | } 299 | if (typeof fn !== 'function') { 300 | throw new Error('You must provide a callback function.'); 301 | } 302 | 303 | this.handleFlags( 304 | function (err, flags) { 305 | if (err) { 306 | throw err; 307 | } 308 | flags = flags || []; 309 | 310 | flaggedRespawn(flags, process.argv, forcedFlags, execute.bind(this)); 311 | 312 | function execute(ready, child, argv) { 313 | if (child !== process) { 314 | var execArgv = getNodeFlags.fromReorderedArgv(argv); 315 | this.emit('respawn', execArgv, child); 316 | } 317 | if (ready) { 318 | preloadModules(this, env); 319 | registerLoader(this, this.extensions, env.configPath, env.cwd); 320 | fn.call(this, env, argv); 321 | } 322 | } 323 | }.bind(this) 324 | ); 325 | }; 326 | 327 | function preloadModules(inst, env) { 328 | var basedir = env.cwd; 329 | env.preload.filter(toUnique).forEach(function (module) { 330 | inst.requireLocal(module, basedir); 331 | }); 332 | } 333 | 334 | function toUnique(elem, index, array) { 335 | return array.indexOf(elem) === index; 336 | } 337 | 338 | module.exports = Liftoff; 339 | -------------------------------------------------------------------------------- /lib/array_find.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function arrayFind(arr, fn) { 4 | if (!Array.isArray(arr)) { 5 | return; 6 | } 7 | 8 | var idx = 0; 9 | while (idx < arr.length) { 10 | var result = fn(arr[idx]); 11 | if (result) { 12 | return result; 13 | } 14 | idx++; 15 | } 16 | } 17 | 18 | module.exports = arrayFind; 19 | -------------------------------------------------------------------------------- /lib/build_config_name.js: -------------------------------------------------------------------------------- 1 | module.exports = function (opts) { 2 | opts = opts || {}; 3 | var configName = opts.configName; 4 | var extensions = opts.extensions; 5 | if (!configName) { 6 | throw new Error('Please specify a configName.'); 7 | } 8 | if (configName instanceof RegExp) { 9 | return [configName]; 10 | } 11 | if (!Array.isArray(extensions)) { 12 | throw new Error('Please provide an array of valid extensions.'); 13 | } 14 | return extensions.map(function (ext) { 15 | return configName + ext; 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/file_search.js: -------------------------------------------------------------------------------- 1 | var findup = require('findup-sync'); 2 | 3 | module.exports = function (search, paths) { 4 | var path; 5 | var len = paths.length; 6 | for (var i = 0; i < len; i++) { 7 | if (path) { 8 | break; 9 | } else { 10 | path = findup(search, { cwd: paths[i], nocase: true }); 11 | } 12 | } 13 | return path; 14 | }; 15 | -------------------------------------------------------------------------------- /lib/find_config.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var fileSearch = require('./file_search'); 4 | 5 | module.exports = function (opts) { 6 | opts = opts || {}; 7 | var configNameSearch = opts.configNameSearch; 8 | var configPath = opts.configPath; 9 | var searchPaths = opts.searchPaths; 10 | // only search for a config if a path to one wasn't explicitly provided 11 | if (!configPath) { 12 | if (!Array.isArray(searchPaths)) { 13 | throw new Error( 14 | 'Please provide an array of paths to search for config in.' 15 | ); 16 | } 17 | if (!configNameSearch) { 18 | throw new Error('Please provide a configNameSearch.'); 19 | } 20 | configPath = fileSearch(configNameSearch, searchPaths); 21 | } 22 | // confirm the configPath exists and return an absolute path to it 23 | // Skip the call to fs.existsSync if configPath is null 24 | if (configPath && fs.existsSync(configPath)) { 25 | return path.resolve(configPath); 26 | } 27 | return null; 28 | }; 29 | -------------------------------------------------------------------------------- /lib/find_cwd.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = function (opts) { 4 | if (!opts) { 5 | opts = {}; 6 | } 7 | var cwd = opts.cwd; 8 | var configPath = opts.configPath; 9 | // if a path to the desired config was specified 10 | // but no cwd was provided, use configPath dir 11 | if (typeof configPath === 'string' && !cwd) { 12 | cwd = path.dirname(path.resolve(configPath)); 13 | } 14 | if (typeof cwd === 'string') { 15 | return path.resolve(cwd); 16 | } 17 | return process.cwd(); 18 | }; 19 | -------------------------------------------------------------------------------- /lib/get_node_flags.js: -------------------------------------------------------------------------------- 1 | function arrayOrFunction(arrayOrFunc, env) { 2 | if (typeof arrayOrFunc === 'function') { 3 | return arrayOrFunc.call(this, env); 4 | } 5 | if (Array.isArray(arrayOrFunc)) { 6 | return arrayOrFunc; 7 | } 8 | if (typeof arrayOrFunc === 'string') { 9 | return [arrayOrFunc]; 10 | } 11 | return []; 12 | } 13 | 14 | function fromReorderedArgv(reorderedArgv) { 15 | var nodeFlags = []; 16 | for (var i = 1, n = reorderedArgv.length; i < n; i++) { 17 | var arg = reorderedArgv[i]; 18 | if (!/^-/.test(arg) || arg === '--') { 19 | break; 20 | } 21 | nodeFlags.push(arg); 22 | } 23 | return nodeFlags; 24 | } 25 | 26 | module.exports = { 27 | arrayOrFunction: arrayOrFunction, 28 | fromReorderedArgv: fromReorderedArgv, 29 | }; 30 | -------------------------------------------------------------------------------- /lib/needs_lookup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var isPlainObject = require('is-plain-object').isPlainObject; 4 | 5 | function needsLookup(xtends) { 6 | if (typeof xtends === 'string' && xtends[0] === '.') { 7 | return true; 8 | } 9 | 10 | if (isPlainObject(xtends)) { 11 | // Objects always need lookup because they can't be used with `require()` 12 | return true; 13 | } 14 | 15 | return false; 16 | } 17 | 18 | module.exports = needsLookup; 19 | -------------------------------------------------------------------------------- /lib/parse_options.js: -------------------------------------------------------------------------------- 1 | var extend = require('extend'); 2 | 3 | module.exports = function (opts) { 4 | var defaults = { 5 | extensions: { 6 | '.js': null, 7 | '.json': null, 8 | }, 9 | searchPaths: [], 10 | }; 11 | if (!opts) { 12 | opts = {}; 13 | } 14 | if (opts.name) { 15 | if (!opts.processTitle) { 16 | opts.processTitle = opts.name; 17 | } 18 | if (!opts.configName) { 19 | opts.configName = opts.name + 'file'; 20 | } 21 | if (!opts.moduleName) { 22 | opts.moduleName = opts.name; 23 | } 24 | } 25 | if (!opts.processTitle) { 26 | throw new Error('You must specify a processTitle.'); 27 | } 28 | if (!opts.configName) { 29 | throw new Error('You must specify a configName.'); 30 | } 31 | if (!opts.moduleName) { 32 | throw new Error('You must specify a moduleName.'); 33 | } 34 | return extend(defaults, opts); 35 | }; 36 | -------------------------------------------------------------------------------- /lib/register_loader.js: -------------------------------------------------------------------------------- 1 | var rechoir = require('rechoir'); 2 | 3 | module.exports = function (eventEmitter, extensions, configPath, cwd) { 4 | extensions = extensions || {}; 5 | 6 | if (typeof configPath !== 'string') { 7 | return; 8 | } 9 | 10 | var autoloads = rechoir.prepare(extensions, configPath, cwd, true); 11 | if (autoloads instanceof Error) { 12 | // Only errors 13 | autoloads.failures.forEach(function (failed) { 14 | eventEmitter.emit('loader:failure', failed.moduleName, failed.error); 15 | }); 16 | return; 17 | } 18 | 19 | if (!Array.isArray(autoloads)) { 20 | // Already required or no config. 21 | return; 22 | } 23 | 24 | var succeeded = autoloads[autoloads.length - 1]; 25 | eventEmitter.emit('loader:success', succeeded.moduleName, succeeded.module); 26 | }; 27 | -------------------------------------------------------------------------------- /lib/silent_require.js: -------------------------------------------------------------------------------- 1 | module.exports = function (path) { 2 | try { 3 | return require(path); 4 | } catch (e) {} 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "liftoff", 3 | "version": "5.0.1", 4 | "description": "Launch your command line tool with ease.", 5 | "author": "Gulp Team (https://gulpjs.com/)", 6 | "contributors": [ 7 | "Blaine Bublitz (https://github.com/phated)", 8 | "Tyler Kellen (https://github.com/tkellen)", 9 | "Takayuki Sato (https://github.com/sttk)" 10 | ], 11 | "repository": "gulpjs/liftoff", 12 | "license": "MIT", 13 | "engines": { 14 | "node": ">=10.13.0" 15 | }, 16 | "main": "index.js", 17 | "files": [ 18 | "index.js", 19 | "lib", 20 | "LICENSE" 21 | ], 22 | "scripts": { 23 | "lint": "eslint .", 24 | "pretest": "npm run lint", 25 | "test": "nyc mocha --async-only" 26 | }, 27 | "dependencies": { 28 | "extend": "^3.0.2", 29 | "findup-sync": "^5.0.0", 30 | "fined": "^2.0.0", 31 | "flagged-respawn": "^2.0.0", 32 | "is-plain-object": "^5.0.0", 33 | "rechoir": "^0.8.0", 34 | "resolve": "^1.20.0" 35 | }, 36 | "devDependencies": { 37 | "coffeescript": "^2.6.1", 38 | "eslint": "^7.0.0", 39 | "eslint-config-gulp": "^5.0.0", 40 | "eslint-config-prettier": "^6.11.0", 41 | "eslint-plugin-node": "^11.1.0", 42 | "expect": "^27.0.0", 43 | "mocha": "^8.0.0", 44 | "nyc": "^15.0.0", 45 | "sinon": "^11.0.0" 46 | }, 47 | "nyc": { 48 | "reporter": [ 49 | "lcov", 50 | "text-summary" 51 | ] 52 | }, 53 | "prettier": { 54 | "singleQuote": true 55 | }, 56 | "keywords": [ 57 | "command line" 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /test/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/test/.gitkeep -------------------------------------------------------------------------------- /test/array_find.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect'); 2 | 3 | var arrayFind = require('../lib/array_find'); 4 | 5 | describe('buildConfigName', function () { 6 | it('returns undefined if called with non-array', function (done) { 7 | expect(arrayFind({})).toEqual(undefined); 8 | done(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/build_config_name.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect'); 2 | 3 | var buildConfigName = require('../lib/build_config_name'); 4 | 5 | describe('buildConfigName', function () { 6 | it('should throw if no configName is provided', function (done) { 7 | expect(function () { 8 | buildConfigName(); 9 | }).toThrow(); 10 | done(); 11 | }); 12 | 13 | it('should use configName directly if it is a regex', function (done) { 14 | var configNameSearch = /mocha/; 15 | expect(buildConfigName({ configName: configNameSearch })).toEqual([ 16 | configNameSearch, 17 | ]); 18 | done(); 19 | }); 20 | 21 | it('should throw if no array of extensions are provided and config is not a regex already', function (done) { 22 | expect(function () { 23 | buildConfigName({ configName: 'foo' }); 24 | }).toThrow(); 25 | expect(function () { 26 | buildConfigName({ configName: 'foo', extensions: '?' }); 27 | }).toThrow(); 28 | expect(function () { 29 | buildConfigName({ configName: 'foo', extensions: ['.js'] }); 30 | }).not.toThrow(); 31 | done(); 32 | }); 33 | 34 | it('should build an array of possible config names', function (done) { 35 | var multiExtension = buildConfigName({ 36 | configName: 'foo', 37 | extensions: ['.js', '.coffee'], 38 | }); 39 | expect(multiExtension).toEqual(['foo.js', 'foo.coffee']); 40 | var singleExtension = buildConfigName({ 41 | configName: 'foo', 42 | extensions: ['.js'], 43 | }); 44 | expect(singleExtension).toEqual(['foo.js']); 45 | done(); 46 | }); 47 | 48 | it('should throw error if opts is null or empty', function (done) { 49 | expect(function () { 50 | buildConfigName(); 51 | }).toThrow(); 52 | expect(function () { 53 | buildConfigName(null); 54 | }).toThrow(); 55 | expect(function () { 56 | buildConfigName({}); 57 | }).toThrow(); 58 | done(); 59 | }); 60 | 61 | it('should throw error if .configName is null', function (done) { 62 | expect(function () { 63 | buildConfigName({ extensions: ['.js'] }); 64 | }).toThrow(); 65 | done(); 66 | }); 67 | 68 | it('should throw error if .extension is not an array', function (done) { 69 | expect(function () { 70 | buildConfigName({ configName: 'foo' }); 71 | }).toThrow(); 72 | expect(function () { 73 | buildConfigName({ configName: 'foo', extensions: null }); 74 | }).toThrow(); 75 | expect(function () { 76 | buildConfigName({ configName: 'foo', extensions: '.js' }); 77 | }).toThrow(); 78 | done(); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test/file_search.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var expect = require('expect'); 4 | 5 | var fileSearch = require('../lib/file_search'); 6 | 7 | describe('fileSearch', function () { 8 | it('should locate a file using findup from an array of possible base paths', function (done) { 9 | expect(fileSearch('mochafile.js', ['../'])).toEqual(null); 10 | expect(fileSearch('package.json', [process.cwd()])).toEqual( 11 | path.resolve(__dirname, '..', 'package.json') 12 | ); 13 | done(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/find_config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var expect = require('expect'); 4 | 5 | var findConfig = require('../lib/find_config'); 6 | 7 | describe('findConfig', function () { 8 | it("should throw if searchPaths or configNameRegex are empty when configName isn't explicltly provided", function (done) { 9 | expect(function () { 10 | findConfig(); 11 | }).toThrow(); 12 | expect(function () { 13 | findConfig({ searchPaths: ['../'] }); 14 | }).toThrow(); 15 | expect(function () { 16 | findConfig({ configNameRegex: 'dude' }); 17 | }).toThrow(); 18 | done(); 19 | }); 20 | 21 | it("if configPath is explicitly provided, return the absolute path to the file or null if it doesn't actually exist", function (done) { 22 | var configPath = path.resolve('test/fixtures/mochafile.js'); 23 | expect(findConfig({ configPath: configPath })).toEqual(configPath); 24 | expect(findConfig({ configPath: 'path/to/nowhere' })).toEqual(null); 25 | done(); 26 | }); 27 | 28 | it('should return the absolute path to the first config file found in searchPaths', function (done) { 29 | expect( 30 | findConfig({ 31 | configNameSearch: ['mochafile.js', 'mochafile.coffee'], 32 | searchPaths: ['test/fixtures'], 33 | }) 34 | ).toEqual(path.resolve('test/fixtures/mochafile.js')); 35 | expect( 36 | findConfig({ 37 | configNameSearch: ['mochafile.js', 'mochafile.coffee'], 38 | searchPaths: ['test/fixtures/search_path', 'test/fixtures/coffee'], 39 | }) 40 | ).toEqual(path.resolve('test/fixtures/search_path/mochafile.js')); 41 | expect( 42 | findConfig({ 43 | configNameSearch: 'mochafile.js', 44 | searchPaths: ['test/fixtures/search_path', 'test/fixtures/coffee'], 45 | }) 46 | ).toEqual(path.resolve('test/fixtures/search_path/mochafile.js')); 47 | done(); 48 | }); 49 | 50 | it('should throw error if .searchPaths is not an array', function (done) { 51 | expect(function () { 52 | findConfig({ 53 | configNameSearch: ['mochafile.js', 'mochafile.coffee'], 54 | }); 55 | }).toThrow(); 56 | expect(function () { 57 | findConfig({ 58 | configNameSearch: ['mochafile.js', 'mochafile.coffee'], 59 | searchPaths: null, 60 | }); 61 | }).toThrow(); 62 | expect(function () { 63 | findConfig({ 64 | configNameSearch: ['mochafile.js', 'mochafile.coffee'], 65 | searchPaths: 'test/fixtures/search_path', 66 | }); 67 | }).toThrow(); 68 | done(); 69 | }); 70 | 71 | it('should throw error if .configNameSearch is null', function (done) { 72 | expect(function () { 73 | findConfig({ 74 | searchPaths: ['test/fixtures/search_path', 'test/fixtures/coffee'], 75 | }); 76 | }).toThrow(); 77 | expect(function () { 78 | findConfig({ 79 | configNameSearch: null, 80 | searchPaths: ['test/fixtures/search_path', 'test/fixtures/coffee'], 81 | }); 82 | }).toThrow(); 83 | expect(function () { 84 | findConfig({ 85 | configNameSearch: '', 86 | searchPaths: ['test/fixtures/search_path', 'test/fixtures/coffee'], 87 | }); 88 | }).toThrow(); 89 | done(); 90 | }); 91 | 92 | it('should throw error if opts is null or empty', function (done) { 93 | expect(function () { 94 | findConfig(); 95 | }).toThrow(); 96 | expect(function () { 97 | findConfig(null); 98 | }).toThrow(); 99 | expect(function () { 100 | findConfig({}); 101 | }).toThrow(); 102 | done(); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /test/find_cwd.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var expect = require('expect'); 4 | 5 | var findCwd = require('../lib/find_cwd'); 6 | 7 | describe('findCwd', function () { 8 | it('should return process.cwd if no options are passed', function (done) { 9 | expect(findCwd()).toEqual(process.cwd()); 10 | done(); 11 | }); 12 | 13 | it('should return path from cwd if supplied', function (done) { 14 | expect(findCwd({ cwd: '../' })).toEqual(path.resolve('../')); 15 | done(); 16 | }); 17 | 18 | it('should return directory of config if configPath defined', function (done) { 19 | expect(findCwd({ configPath: 'test/fixtures/mochafile.js' })).toEqual( 20 | path.resolve('test/fixtures') 21 | ); 22 | done(); 23 | }); 24 | 25 | it('should return path from cwd if both it and configPath are defined', function (done) { 26 | expect( 27 | findCwd({ cwd: '../', configPath: 'test/fixtures/mochafile.js' }) 28 | ).toEqual(path.resolve('../')); 29 | done(); 30 | }); 31 | 32 | it("should ignore cwd if it isn't a string", function (done) { 33 | expect(findCwd({ cwd: true })).toEqual(process.cwd()); 34 | done(); 35 | }); 36 | 37 | it("should ignore configPath if it isn't a string", function (done) { 38 | expect(findCwd({ configPath: true })).toEqual(process.cwd()); 39 | done(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/fixtures/case/Mochafile.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/test/fixtures/case/Mochafile.js -------------------------------------------------------------------------------- /test/fixtures/coffee/mochafile.coffee: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/test/fixtures/coffee/mochafile.coffee -------------------------------------------------------------------------------- /test/fixtures/coffee/mochafile.coffee.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/test/fixtures/coffee/mochafile.coffee.md -------------------------------------------------------------------------------- /test/fixtures/coffee/mochafile.iced: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/test/fixtures/coffee/mochafile.iced -------------------------------------------------------------------------------- /test/fixtures/configfiles-extends/circular1.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./circular2" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/configfiles-extends/circular2.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./circular1" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/configfiles-extends/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "", 3 | "ccc": "ddd" 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/configfiles-extends/extend-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "aaa": "CCC", 3 | "bbb": "BBB" 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/configfiles-extends/extend-missing.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./local-missing", 3 | "ccc": "ddd" 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/configfiles-extends/load-empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./empty", 3 | "aaa": "bbb" 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/configfiles-extends/local-missing.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./not-exists", 3 | "aaa": "bbb" 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/configfiles-extends/missing-invalid-obj.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": { 3 | "foo": "bar" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/configfiles-extends/missing-name-obj.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": { 3 | "name": "not-exists" 4 | }, 5 | "foo": "bar" 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/configfiles-extends/missing-path-obj.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": { 3 | "path": "./not-exists" 4 | }, 5 | "foo": "bar" 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/configfiles-extends/npm-missing.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "not-installed", 3 | "foo": "bar" 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/configfiles-extends/null.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": null 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/configfiles-extends/testconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./extend-config", 3 | "aaa": "AAA" 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/configfiles-extends/throws.js: -------------------------------------------------------------------------------- 1 | throw new Error('Kaboom'); 2 | -------------------------------------------------------------------------------- /test/fixtures/configfiles/README.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/test/fixtures/configfiles/README.txt -------------------------------------------------------------------------------- /test/fixtures/configfiles/index.json: -------------------------------------------------------------------------------- 1 | { "aaa": "AAA" } 2 | -------------------------------------------------------------------------------- /test/fixtures/configfiles/override-config-path-absolute.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | myappfile: path.join(__dirname, "../override-the-config-path.js"), 5 | }; 6 | -------------------------------------------------------------------------------- /test/fixtures/configfiles/override-config-path-non-string.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | myappfile: {}, 5 | }; 6 | -------------------------------------------------------------------------------- /test/fixtures/configfiles/override-config-path-relative.json: -------------------------------------------------------------------------------- 1 | { 2 | "myappfile": "../override-the-config-path.js" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/configfiles/preload-array.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preload: ['abc', 'xyz'] 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/configfiles/preload-invalid-array.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preload: [{}, 123, 'no'] 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/configfiles/preload-invalid.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preload: {} 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/configfiles/preload-string.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preload: 'abc' 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/configfiles/require-md.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var path = require('path'); 3 | 4 | require.extensions['.md'] = function (module, filepath) { 5 | module.loaded = true; 6 | module.exports = 'Load ' + path.basename(filepath) + ' by require-md'; 7 | }; 8 | })(); 9 | -------------------------------------------------------------------------------- /test/fixtures/configfiles/require-txt.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var path = require('path'); 3 | 4 | require.extensions['.txt'] = function (module, filepath) { 5 | module.loaded = true; 6 | module.exports = 'Load ' + path.basename(filepath) + ' by require-txt'; 7 | }; 8 | })(); 9 | -------------------------------------------------------------------------------- /test/fixtures/configfiles/testconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "aaa": "AAA" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/developing_yourself/app0file.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/test/fixtures/developing_yourself/app0file.js -------------------------------------------------------------------------------- /test/fixtures/developing_yourself/app1/app1file.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/test/fixtures/developing_yourself/app1/app1file.js -------------------------------------------------------------------------------- /test/fixtures/developing_yourself/app1/index.js: -------------------------------------------------------------------------------- 1 | var Liftoff = require('../../../..'); 2 | 3 | var app1 = new Liftoff({ 4 | name: 'app1', 5 | }); 6 | 7 | app1.prepare({}, function (env) { 8 | app1.execute(env, function (env) { 9 | console.log(env.modulePackage); 10 | console.log(env.modulePath); 11 | console.log(env.cwd); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test/fixtures/developing_yourself/app2/app2file.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/test/fixtures/developing_yourself/app2/app2file.js -------------------------------------------------------------------------------- /test/fixtures/developing_yourself/app2/index.js: -------------------------------------------------------------------------------- 1 | var Liftoff = require('../../../..'); 2 | 3 | var app2 = new Liftoff({ 4 | name: 'app2', 5 | }); 6 | 7 | app2.prepare({}, function (env) { 8 | app2.execute(env, function (env) { 9 | console.log(JSON.stringify(env.modulePackage)); 10 | console.log(env.modulePath); 11 | console.log(env.cwd); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test/fixtures/developing_yourself/app2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app2", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "author": "", 9 | "license": "ISC" 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/developing_yourself/main.js: -------------------------------------------------------------------------------- 1 | var Liftoff = require('../../..'); 2 | 3 | var app0 = new Liftoff({ 4 | name: 'app0', 5 | }); 6 | 7 | app0.prepare({}, function (env) { 8 | app0.execute(env, function (env) { 9 | console.log(JSON.stringify(env.modulePackage)); 10 | console.log(env.modulePath); 11 | console.log(env.cwd); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test/fixtures/developing_yourself/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app0", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC" 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/mochafile.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/test/fixtures/mochafile.js -------------------------------------------------------------------------------- /test/fixtures/override-the-config-path.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/test/fixtures/override-the-config-path.js -------------------------------------------------------------------------------- /test/fixtures/prepare-execute/nodeflags_only.js: -------------------------------------------------------------------------------- 1 | var Liftoff = require('../../..'); 2 | 3 | var Test = new Liftoff({ 4 | name: 'test', 5 | }); 6 | 7 | Test.on('respawn', function (execArgv) { 8 | console.log('saw respawn', execArgv); 9 | }); 10 | 11 | Test.prepare({}, function (env) { 12 | var forcedFlags = ['--lazy']; 13 | Test.execute(env, forcedFlags, function (env, argv) { 14 | console.error(argv.slice(1).join(' ')); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/fixtures/prepare-execute/v8flags.js: -------------------------------------------------------------------------------- 1 | var Liftoff = require('../../..'); 2 | 3 | var Test = new Liftoff({ 4 | name: 'test', 5 | v8flags: ['--lazy'], 6 | }); 7 | Test.on('respawn', function (flags, proc) { 8 | console.log('saw respawn'); 9 | }); 10 | 11 | Test.prepare({}, function (env) { 12 | Test.execute(env, function (env) { 13 | console.error(process.execArgv.join('')); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/fixtures/prepare-execute/v8flags_config.js: -------------------------------------------------------------------------------- 1 | var Liftoff = require('../../..'); 2 | 3 | var Test = new Liftoff({ 4 | name: 'test', 5 | v8flags: ['--harmony'], 6 | }); 7 | 8 | Test.on('respawn', function (flags, proc) { 9 | console.log('saw respawn', flags); 10 | }); 11 | 12 | Test.prepare({}, function (env) { 13 | var forcedFlags = ['--lazy']; 14 | Test.execute(env, forcedFlags, function (env, argv) { 15 | console.error(argv.slice(1).join(' ')); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test/fixtures/prepare-execute/v8flags_error.js: -------------------------------------------------------------------------------- 1 | var Liftoff = require('../../..'); 2 | 3 | var Test = new Liftoff({ 4 | name: 'test', 5 | v8flags: function (cb) { 6 | process.nextTick(function () { 7 | cb(new Error('v8flags error!'), ['--lazy']); 8 | }); 9 | }, 10 | }); 11 | Test.on('respawn', function (flags, proc) { 12 | console.log('saw respawn'); 13 | }); 14 | 15 | Test.prepare({}, function (env) { 16 | Test.execute(env, function (env) { 17 | console.error(process.execArgv.join('')); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/fixtures/prepare-execute/v8flags_function.js: -------------------------------------------------------------------------------- 1 | var Liftoff = require('../../..'); 2 | 3 | var Test = new Liftoff({ 4 | name: 'test', 5 | v8flags: function (cb) { 6 | process.nextTick(function () { 7 | cb(null, ['--lazy']); 8 | }); 9 | }, 10 | }); 11 | Test.on('respawn', function (flags, proc) { 12 | console.log('saw respawn'); 13 | }); 14 | 15 | Test.prepare({}, function (env) { 16 | Test.execute(env, function (env) { 17 | console.error(process.execArgv.join('')); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/fixtures/prepare-execute/v8flags_value.js: -------------------------------------------------------------------------------- 1 | var Liftoff = require('../../..'); 2 | 3 | var Test = new Liftoff({ 4 | name: 'test', 5 | v8flags: ['--stack_size'], 6 | }); 7 | 8 | Test.on('respawn', function (flags) { 9 | console.error(flags.join(' ')); 10 | }); 11 | 12 | Test.prepare({}, function (env) { 13 | Test.execute(env, function () {}); 14 | }); 15 | -------------------------------------------------------------------------------- /test/fixtures/register_loader/app.cfg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/test/fixtures/register_loader/app.cfg -------------------------------------------------------------------------------- /test/fixtures/register_loader/app.conf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/test/fixtures/register_loader/app.conf -------------------------------------------------------------------------------- /test/fixtures/register_loader/app.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/test/fixtures/register_loader/app.rc -------------------------------------------------------------------------------- /test/fixtures/register_loader/app.tmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/test/fixtures/register_loader/app.tmp -------------------------------------------------------------------------------- /test/fixtures/register_loader/file.a.b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/test/fixtures/register_loader/file.a.b -------------------------------------------------------------------------------- /test/fixtures/register_loader/file.a.b.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/test/fixtures/register_loader/file.a.b.c -------------------------------------------------------------------------------- /test/fixtures/register_loader/file.a.b.c.d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/test/fixtures/register_loader/file.a.b.c.d -------------------------------------------------------------------------------- /test/fixtures/register_loader/file.a.b.d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/test/fixtures/register_loader/file.a.b.d -------------------------------------------------------------------------------- /test/fixtures/register_loader/file.a.e.c.d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/test/fixtures/register_loader/file.a.e.c.d -------------------------------------------------------------------------------- /test/fixtures/register_loader/file.a.f.c.d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/test/fixtures/register_loader/file.a.f.c.d -------------------------------------------------------------------------------- /test/fixtures/register_loader/require-cfg.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var path = require('path'); 3 | 4 | require.extensions['.cfg'] = function (module, filepath) { 5 | module.loaded = true; 6 | module.exports = 'Load ' + path.basename(filepath) + ' by require-cfg'; 7 | }; 8 | })(); 9 | -------------------------------------------------------------------------------- /test/fixtures/register_loader/require-conf.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var path = require('path'); 3 | 4 | require.extensions['.conf'] = function (module, filepath) { 5 | module.loaded = true; 6 | module.exports = 'Load ' + path.basename(filepath) + ' by require-conf'; 7 | }; 8 | })(); 9 | -------------------------------------------------------------------------------- /test/fixtures/register_loader/require-fail.js: -------------------------------------------------------------------------------- 1 | throw Error('Fail to register!'); 2 | -------------------------------------------------------------------------------- /test/fixtures/register_loader/require-file-b.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var path = require('path'); 3 | 4 | require.extensions['.b'] = function (module, filepath) { 5 | module.loaded = true; 6 | module.exports = 'Load ' + path.basename(filepath) + ' by require-file-b'; 7 | }; 8 | })(); 9 | -------------------------------------------------------------------------------- /test/fixtures/register_loader/require-file-bc.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var path = require('path'); 3 | 4 | require.extensions['.c'] = function (module, filepath) { 5 | module.loaded = true; 6 | module.exports = 'Load ' + path.basename(filepath) + ' by require-file-bc'; 7 | }; 8 | })(); 9 | -------------------------------------------------------------------------------- /test/fixtures/register_loader/require-file-cd.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var path = require('path'); 3 | 4 | require.extensions['.d'] = function (module, filepath) { 5 | module.loaded = true; 6 | module.exports = 'Load ' + path.basename(filepath) + ' by require-file-cd'; 7 | }; 8 | })(); 9 | -------------------------------------------------------------------------------- /test/fixtures/register_loader/require-file-d.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var path = require('path'); 3 | 4 | require.extensions['.d'] = function (module, filepath) { 5 | module.loaded = true; 6 | module.exports = 'Load ' + path.basename(filepath) + ' by require-file-d'; 7 | }; 8 | })(); 9 | -------------------------------------------------------------------------------- /test/fixtures/register_loader/require-file-ecd.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var path = require('path'); 3 | 4 | require.extensions['.d'] = function (module, filepath) { 5 | module.loaded = true; 6 | module.exports = 'Load ' + path.basename(filepath) + ' by require-file-ecd'; 7 | }; 8 | })(); 9 | -------------------------------------------------------------------------------- /test/fixtures/register_loader/require-file-fcd.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var path = require('path'); 3 | 4 | require.extensions['.d'] = function (module, filepath) { 5 | module.loaded = true; 6 | module.exports = 'Load ' + path.basename(filepath) + ' by require-file-fcd'; 7 | }; 8 | })(); 9 | -------------------------------------------------------------------------------- /test/fixtures/register_loader/require-rc.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var path = require('path'); 3 | 4 | require.extensions['.rc'] = function (module, filepath) { 5 | module.loaded = true; 6 | module.exports = 'Load ' + path.basename(filepath) + ' by require-rc'; 7 | }; 8 | })(); 9 | -------------------------------------------------------------------------------- /test/fixtures/respawn_and_require.js: -------------------------------------------------------------------------------- 1 | const Liftoff = require('../..'); 2 | 3 | const Test = new Liftoff({ 4 | name: 'test', 5 | v8flags: ['--harmony'], 6 | }); 7 | 8 | Test.on('respawn', function (flags, proc) { 9 | console.log('saw respawn', flags); 10 | }); 11 | 12 | Test.on('preload:success', function (name) { 13 | console.log('preload:success', name); 14 | }); 15 | 16 | Test.prepare( 17 | { 18 | preload: 'coffeescript/register', 19 | }, 20 | function (env) { 21 | var forcedFlags = ['--lazy']; 22 | Test.execute(env, forcedFlags, function () { 23 | console.log('execute'); 24 | }); 25 | } 26 | ); 27 | -------------------------------------------------------------------------------- /test/fixtures/search_path/mochafile.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gulpjs/liftoff/15da3b3763552ec60376da80c7b76819f27104cf/test/fixtures/search_path/mochafile.js -------------------------------------------------------------------------------- /test/get_node_flags.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect'); 2 | 3 | var getNodeFlags = require('../lib/get_node_flags'); 4 | 5 | describe('getNodeFlags', function () { 6 | describe('arrayOrFunction', function () { 7 | it('should return the first argument when it is an array', function (done) { 8 | var env = { cwd: 'aaa' }; 9 | expect(getNodeFlags.arrayOrFunction([], env)).toEqual([]); 10 | expect( 11 | getNodeFlags.arrayOrFunction( 12 | ['--lazy', '--use_strict', '--harmony'], 13 | env 14 | ) 15 | ).toEqual( 16 | expect.arrayContaining(['--lazy', '--harmony', '--use_strict']) 17 | ); 18 | done(); 19 | }); 20 | 21 | it('should return the exection result of the first argument when it is a function', function (done) { 22 | var env = { cwd: 'aaa' }; 23 | expect( 24 | getNodeFlags.arrayOrFunction(function () { 25 | return []; 26 | }, env) 27 | ).toEqual([]); 28 | expect( 29 | getNodeFlags.arrayOrFunction(function (arg) { 30 | expect(arg).toEqual(env); 31 | return ['--lazy', '--harmony']; 32 | }, env) 33 | ).toEqual(expect.arrayContaining(['--lazy', '--harmony'])); 34 | done(); 35 | }); 36 | 37 | it('should return an array which has an element of the first argument when the first argument is a string', function (done) { 38 | var env = { cwd: 'aaa' }; 39 | expect(getNodeFlags.arrayOrFunction('--lazy', env)).toEqual( 40 | expect.arrayContaining(['--lazy']) 41 | ); 42 | done(); 43 | }); 44 | 45 | it('should return an empty array when the first argument is neither an array, a function nor a string', function (done) { 46 | var env = { cwd: 'aaa' }; 47 | expect(getNodeFlags.arrayOrFunction(undefined, env)).toEqual([]); 48 | expect(getNodeFlags.arrayOrFunction(null, env)).toEqual([]); 49 | expect(getNodeFlags.arrayOrFunction(true, env)).toEqual([]); 50 | expect(getNodeFlags.arrayOrFunction(false, env)).toEqual([]); 51 | expect(getNodeFlags.arrayOrFunction(0, env)).toEqual([]); 52 | expect(getNodeFlags.arrayOrFunction(123, env)).toEqual([]); 53 | expect(getNodeFlags.arrayOrFunction({}, env)).toEqual([]); 54 | expect(getNodeFlags.arrayOrFunction({ length: 1 }, env)).toEqual([]); 55 | done(); 56 | }); 57 | }); 58 | 59 | describe('fromReorderedArgv', function () { 60 | it('should return only node flags from respawning arguments', function (done) { 61 | var env = { cwd: 'aaa' }; 62 | var cmd = [ 63 | 'node', 64 | '--lazy', 65 | '--harmony', 66 | '--use_strict', 67 | './aaa/bbb/app.js', 68 | '--ccc', 69 | 'ddd', 70 | '-e', 71 | 'fff', 72 | ]; 73 | expect(getNodeFlags.fromReorderedArgv(cmd, env)).toEqual([ 74 | '--lazy', 75 | '--harmony', 76 | '--use_strict', 77 | ]); 78 | done(); 79 | }); 80 | 81 | it('should end node flags before "--"', function (done) { 82 | var env = { cwd: 'aaa' }; 83 | var cmd = [ 84 | 'node', 85 | '--lazy', 86 | '--', 87 | '--harmony', 88 | '--use_strict', 89 | './aaa/bbb/app.js', 90 | '--ccc', 91 | 'ddd', 92 | '-e', 93 | 'fff', 94 | ]; 95 | expect(getNodeFlags.fromReorderedArgv(cmd, env)).toEqual(['--lazy']); 96 | done(); 97 | }); 98 | 99 | it('should return node flags when arguments are only node flags', function (done) { 100 | var env = { cwd: 'aaa' }; 101 | var cmd = ['node', '--lazy', '--harmony', '--use_strict']; 102 | expect(getNodeFlags.fromReorderedArgv(cmd, env)).toEqual([ 103 | '--lazy', 104 | '--harmony', 105 | '--use_strict', 106 | ]); 107 | done(); 108 | }); 109 | 110 | it('should return an empty array when no node flags', function (done) { 111 | var env = { cwd: 'aaa' }; 112 | var cmd = ['node', './aaa/bbb/app.js', '--aaa', 'bbb', '-c', 'd']; 113 | expect(getNodeFlags.fromReorderedArgv(cmd, env)).toEqual([]); 114 | done(); 115 | }); 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var Module = require('module'); 3 | var exec = require('child_process').exec; 4 | 5 | var expect = require('expect'); 6 | var resolve = require('resolve'); 7 | 8 | var Liftoff = require('../'); 9 | 10 | var NAME = 'mocha'; 11 | var app = new Liftoff({ 12 | processTitle: NAME, 13 | configName: NAME + 'file', 14 | moduleName: NAME, 15 | extensions: { 16 | '.js': null, 17 | '.json': null, 18 | '.coffee': 'coffee-script/register', 19 | '.coffee.md': 'coffee-script/register', 20 | }, 21 | searchPaths: ['test/fixtures/search_path'], 22 | }); 23 | 24 | // TODO: Consolidate between rechoir & liftoff 25 | // save the original Module._extensions 26 | var originalExtensions = Object.keys(Module._extensions); 27 | var original = originalExtensions.reduce(function (result, key) { 28 | result[key] = require.extensions[key]; 29 | return result; 30 | }, {}); 31 | // save the original cache keys 32 | var originalCacheKeys = Object.keys(require.cache); 33 | 34 | function cleanupCache(key) { 35 | if (originalCacheKeys.indexOf(key) === -1) { 36 | delete require.cache[key]; 37 | } 38 | } 39 | 40 | function cleanupExtensions(ext) { 41 | if (originalExtensions.indexOf(ext) === -1) { 42 | delete Module._extensions[ext]; 43 | } else { 44 | Module._extensions[ext] = original[ext]; 45 | } 46 | } 47 | 48 | function cleanup(done) { 49 | // restore the require.cache to startup state 50 | Object.keys(require.cache).forEach(cleanupCache); 51 | // restore the original Module._extensions 52 | Object.keys(Module._extensions).forEach(cleanupExtensions); 53 | 54 | done(); 55 | } 56 | 57 | describe('Liftoff', function () { 58 | this.timeout(5000); 59 | 60 | beforeEach(cleanup); 61 | 62 | describe('buildEnvironment', function () { 63 | it('should locate local module using cwd if no config is found', function (done) { 64 | if (process.versions.node.startsWith("10.") || process.versions.node.startsWith("12.")) { 65 | this.skip(); 66 | return; 67 | } 68 | 69 | var sinon = require('sinon'); 70 | 71 | var test = new Liftoff({ name: 'chai' }); 72 | var cwd = 'explicit/cwd'; 73 | var spy = sinon.spy(resolve, 'sync'); 74 | // NODE_PATH might be defined. 75 | delete process.env.NODE_PATH; 76 | test.buildEnvironment({ cwd: cwd }); 77 | expect( 78 | spy.calledWith('chai', { 79 | basedir: path.join(process.cwd(), cwd), 80 | paths: [], 81 | }) 82 | ).toBe(true); 83 | spy.restore(); 84 | done(); 85 | }); 86 | 87 | it('should locate global module using NODE_PATH if defined', function (done) { 88 | if (process.versions.node.startsWith("10.") || process.versions.node.startsWith("12.")) { 89 | this.skip(); 90 | return; 91 | } 92 | 93 | var sinon = require('sinon'); 94 | 95 | var test = new Liftoff({ name: 'dummy' }); 96 | var cwd = 'explicit/cwd'; 97 | var spy = sinon.spy(resolve, 'sync'); 98 | process.env.NODE_PATH = path.join(process.cwd(), cwd); 99 | test.buildEnvironment(); 100 | expect( 101 | spy.calledWith('dummy', { 102 | basedir: process.cwd(), 103 | paths: [path.join(process.cwd(), cwd)], 104 | }) 105 | ).toBe(true); 106 | spy.restore(); 107 | done(); 108 | }); 109 | 110 | it("if cwd is explicitly provided, don't use search_paths", function (done) { 111 | expect(app.buildEnvironment({ cwd: './' }).configPath).toEqual(null); 112 | done(); 113 | }); 114 | 115 | it('should find case sensitive configPath', function (done) { 116 | var expected = path.resolve( 117 | __dirname, 118 | 'fixtures', 119 | 'case', 120 | process.platform === 'linux' ? 'Mochafile.js' : 'mochafile.js' 121 | ); 122 | expect( 123 | app.buildEnvironment({ cwd: path.join(__dirname, 'fixtures', 'case') }) 124 | .configPath 125 | ).toEqual(expected); 126 | done(); 127 | }); 128 | 129 | it('should find module in the directory next to config', function (done) { 130 | expect(app.buildEnvironment().modulePath).toEqual( 131 | path.resolve('node_modules/mocha/index.js') 132 | ); 133 | done(); 134 | }); 135 | 136 | it('should require the package sibling to the module', function (done) { 137 | expect(app.buildEnvironment().modulePackage).toEqual( 138 | require('../node_modules/mocha/package.json') 139 | ); 140 | done(); 141 | }); 142 | 143 | it("should set cwd to match the directory of the config file as long as cwd wasn't explicitly provided", function (done) { 144 | expect(app.buildEnvironment().cwd).toEqual( 145 | path.resolve(__dirname, './fixtures/search_path') 146 | ); 147 | done(); 148 | }); 149 | 150 | describe('for developing against yourself', function () { 151 | it('should find and load package.json', function (done) { 152 | var fixturesDir = path.resolve(__dirname, 'fixtures'); 153 | var cwd = path.resolve(fixturesDir, 'developing_yourself'); 154 | 155 | exec('cd ' + cwd + ' && node main.js', cb); 156 | function cb(err, stdout, stderr) { 157 | expect(err).toEqual(null); 158 | expect(stderr).toEqual(''); 159 | var fp = path.resolve(cwd, 'package.json'); 160 | expect(stdout).toEqual( 161 | JSON.stringify(require(fp)) + 162 | '\n' + 163 | path.resolve(cwd, 'main.js') + 164 | '\n' + 165 | cwd + 166 | '\n' 167 | ); 168 | done(); 169 | } 170 | }); 171 | 172 | it('should clear modulePackage if package.json is of different project', function (done) { 173 | var fixturesDir = path.resolve(__dirname, 'fixtures'); 174 | var cwd = path.resolve(fixturesDir, 'developing_yourself/app1'); 175 | 176 | exec('cd ' + cwd + ' && node index.js', cb); 177 | function cb(err, stdout, stderr) { 178 | expect(err).toEqual(null); 179 | expect(stderr).toEqual(''); 180 | expect(stdout).toEqual('{}\n' + 'undefined\n' + cwd + '\n'); 181 | done(); 182 | } 183 | }); 184 | 185 | it( 186 | 'should use `index.js` if `main` property in package.json ' + 187 | 'does not exist', 188 | function (done) { 189 | var fixturesDir = path.resolve(__dirname, 'fixtures'); 190 | var cwd = path.resolve(fixturesDir, 'developing_yourself/app2'); 191 | 192 | exec( 193 | 'cd test/fixtures/developing_yourself/app2 && node index.js', 194 | cb 195 | ); 196 | function cb(err, stdout, stderr) { 197 | expect(err).toEqual(null); 198 | expect(stderr).toEqual(''); 199 | var fp = './fixtures/developing_yourself/app2/package.json'; 200 | expect(stdout).toEqual( 201 | JSON.stringify(require(fp)) + 202 | '\n' + 203 | path.resolve(cwd, 'index.js') + 204 | '\n' + 205 | cwd + 206 | '\n' 207 | ); 208 | done(); 209 | } 210 | } 211 | ); 212 | }); 213 | }); 214 | 215 | describe('prepare', function () { 216 | it('should set the process.title to the moduleName', function (done) { 217 | app.prepare({}, function () { 218 | expect(process.title).toEqual(app.moduleName); 219 | done(); 220 | }); 221 | }); 222 | 223 | it('should call prepare with liftoff instance as context', function (done) { 224 | app.prepare({}, function () { 225 | expect(this).toEqual(app); 226 | done(); 227 | }); 228 | }); 229 | 230 | it('should pass environment to first argument of prepare callback', function (done) { 231 | app.prepare({}, function (env) { 232 | expect(env).toEqual(app.buildEnvironment()); 233 | done(); 234 | }); 235 | }); 236 | 237 | it('should throw if 2nd arg is not a function', function (done) { 238 | expect(function () { 239 | app.prepare({}); 240 | }).toThrow(); 241 | done(); 242 | }); 243 | }); 244 | 245 | describe('execute', function () { 246 | it('should pass environment to first argument of execute callback', function (done) { 247 | var testEnv = app.buildEnvironment(); 248 | app.execute(testEnv, function (env) { 249 | expect(env).toEqual(testEnv); 250 | done(); 251 | }); 252 | }); 253 | 254 | it('should throw if 2nd arg is not a function', function (done) { 255 | expect(function () { 256 | app.execute({}); 257 | }).toThrow(); 258 | done(); 259 | }); 260 | 261 | it('should return early if completions are available and requested', function (done) { 262 | var test = new Liftoff({ 263 | name: 'whatever', 264 | completions: function () { 265 | done(); 266 | }, 267 | }); 268 | test.prepare({ completion: true }, function (env) { 269 | test.execute(env); 270 | }); 271 | }); 272 | 273 | it('should skip respawning if process.argv has no values from v8flags in it', function (done) { 274 | exec( 275 | 'node test/fixtures/prepare-execute/v8flags.js', 276 | function (err, stdout, stderr) { 277 | expect(stderr).toEqual('\n'); 278 | exec( 279 | 'node test/fixtures/prepare-execute/v8flags_function.js', 280 | function (err, stdout, stderr) { 281 | expect(stderr).toEqual('\n'); 282 | done(); 283 | } 284 | ); 285 | } 286 | ); 287 | }); 288 | 289 | it('should respawn if process.argv has values from v8flags in it', function (done) { 290 | exec( 291 | 'node test/fixtures/prepare-execute/v8flags.js --lazy', 292 | function (err, stdout, stderr) { 293 | expect(stderr).toEqual('--lazy\n'); 294 | exec( 295 | 'node test/fixtures/prepare-execute/v8flags_function.js --lazy', 296 | function (err, stdout, stderr) { 297 | expect(stderr).toEqual('--lazy\n'); 298 | done(); 299 | } 300 | ); 301 | } 302 | ); 303 | }); 304 | 305 | it('should throw if v8flags is a function and it causes an error', function (done) { 306 | exec( 307 | 'node test/fixtures/prepare-execute/v8flags_error.js --lazy', 308 | function (err, stdout, stderr) { 309 | expect(err).not.toEqual(null); 310 | expect(stdout).toEqual(''); 311 | expect(stderr).toMatch('v8flags error!'); 312 | done(); 313 | } 314 | ); 315 | }); 316 | 317 | it('should respawn if v8flag is set by forcedFlags', function (done) { 318 | exec('node test/fixtures/prepare-execute/v8flags_config.js 123', cb); 319 | 320 | function cb(err, stdout, stderr) { 321 | expect(err).toEqual(null); 322 | expect(stderr).toEqual( 323 | [ 324 | path.resolve( 325 | __dirname, 326 | './fixtures/prepare-execute/v8flags_config.js' 327 | ), 328 | '123', 329 | ].join(' ') + '\n' 330 | ); 331 | expect(stdout).toEqual("saw respawn [ '--lazy' ]\n"); 332 | done(); 333 | } 334 | }); 335 | 336 | it('should respawn if v8flag is set by both cli flag and forcedFlags', function (done) { 337 | exec( 338 | 'node test/fixtures/prepare-execute/v8flags_config.js 123 --harmony abc', 339 | cb 340 | ); 341 | 342 | function cb(err, stdout, stderr) { 343 | expect(err).toEqual(null); 344 | expect(stderr).toEqual( 345 | [ 346 | path.resolve( 347 | __dirname, 348 | './fixtures/prepare-execute/v8flags_config.js' 349 | ), 350 | '123', 351 | 'abc', 352 | ].join(' ') + '\n' 353 | ); 354 | expect(stdout).toEqual("saw respawn [ '--lazy', '--harmony' ]\n"); 355 | done(); 356 | } 357 | }); 358 | 359 | it('should emit a respawn event if a respawn is required', function (done) { 360 | exec( 361 | 'node test/fixtures/prepare-execute/v8flags.js', 362 | function (err, stdout) { 363 | expect(stdout).toEqual(''); 364 | exec( 365 | 'node test/fixtures/prepare-execute/v8flags_function.js --lazy', 366 | function (err, stdout) { 367 | expect(stdout).toEqual('saw respawn\n'); 368 | done(); 369 | } 370 | ); 371 | } 372 | ); 373 | }); 374 | 375 | it('should respawn if process.argv has v8flags with values in it', function (done) { 376 | exec( 377 | 'node test/fixtures/prepare-execute/v8flags_value.js --stack_size=2048', 378 | function (err, stdout, stderr) { 379 | expect(stderr).toEqual('--stack_size=2048\n'); 380 | done(); 381 | } 382 | ); 383 | }); 384 | 385 | it('should respawn if v8flags is empty but forcedFlags are specified', function (done) { 386 | exec('node test/fixtures/prepare-execute/nodeflags_only.js 123', cb); 387 | 388 | function cb(err, stdout, stderr) { 389 | expect(err).toEqual(null); 390 | expect(stderr).toEqual( 391 | [ 392 | path.resolve( 393 | __dirname, 394 | './fixtures/prepare-execute/nodeflags_only.js' 395 | ), 396 | '123', 397 | ].join(' ') + '\n' 398 | ); 399 | expect(stdout).toEqual("saw respawn [ '--lazy' ]\n"); 400 | done(); 401 | } 402 | }); 403 | }); 404 | 405 | describe('requireLocal', function () { 406 | it('should attempt pre-loading local modules if they are requested', function (done) { 407 | var app = new Liftoff({ name: 'test' }); 408 | var logs = []; 409 | app.on('preload:success', function (moduleName, module) { 410 | expect(moduleName).toEqual('coffeescript/register'); 411 | expect(module).toEqual(require('coffeescript/register')); 412 | logs.push('preload:success'); 413 | }); 414 | app.on('preload:failure', function (moduleName, err) { 415 | done(err); 416 | }); 417 | app.prepare({ preload: ['coffeescript/register'] }, function (env) { 418 | app.execute(env, function (env) { 419 | expect(env.preload).toEqual(['coffeescript/register']); 420 | expect(logs).toEqual(['preload:success']); 421 | done(); 422 | }); 423 | }); 424 | }); 425 | 426 | it('should attempt pre-loading a local module if it is requested', function (done) { 427 | var app = new Liftoff({ name: 'test' }); 428 | var logs = []; 429 | app.on('preload:success', function (moduleName, module) { 430 | expect(moduleName).toEqual('coffeescript/register'); 431 | expect(module).toEqual(require('coffeescript/register')); 432 | logs.push('preload:success'); 433 | }); 434 | app.on('preload:failure', function (moduleName, err) { 435 | done(err); 436 | }); 437 | app.prepare({ preload: 'coffeescript/register' }, function (env) { 438 | app.execute(env, function (env) { 439 | expect(env.preload).toEqual(['coffeescript/register']); 440 | expect(logs).toEqual(['preload:success']); 441 | done(); 442 | }); 443 | }); 444 | }); 445 | 446 | it('should attempt pre-loading local modules but fail', function (done) { 447 | var app = new Liftoff({ name: 'test' }); 448 | var logs = []; 449 | app.on('preload:failure', function (moduleName, err) { 450 | expect(moduleName).toEqual('badmodule'); 451 | expect(err).not.toEqual(null); 452 | logs.push('preload:failure'); 453 | }); 454 | app.prepare({ preload: 'badmodule' }, function (env) { 455 | app.execute(env, function (env) { 456 | expect(env.preload).toEqual(['badmodule']); 457 | expect(logs).toEqual(['preload:failure']); 458 | done(); 459 | }); 460 | }); 461 | }); 462 | 463 | it('should pre-load a local module only once even if be respawned', function (done) { 464 | var fixturesDir = path.resolve(__dirname, 'fixtures'); 465 | 466 | exec('cd ' + fixturesDir + ' && node respawn_and_require.js', cb); 467 | function cb(err, stdout, stderr) { 468 | expect(err).toEqual(null); 469 | expect(stderr).toEqual(''); 470 | expect(stdout).toEqual( 471 | "saw respawn [ '--lazy' ]\n" + 472 | 'preload:success coffeescript/register\n' + 473 | 'execute\n' + 474 | '' 475 | ); 476 | done(); 477 | } 478 | }); 479 | 480 | it('should emit `preload:before` and `preload:success` with the name of the module and the required module', function (done) { 481 | var requireTest = new Liftoff({ name: 'require' }); 482 | var isEmittedBeforeRequired = false; 483 | requireTest.on('preload:before', function (name) { 484 | expect(name).toEqual('mocha'); 485 | isEmittedBeforeRequired = true; 486 | }); 487 | requireTest.on('preload:success', function (name, module) { 488 | expect(name).toEqual('mocha'); 489 | expect(module).toEqual(require('mocha')); 490 | expect(isEmittedBeforeRequired).toEqual(true); 491 | done(); 492 | }); 493 | requireTest.requireLocal('mocha', __dirname); 494 | }); 495 | 496 | it("should emit `preload:before` and `preload:failure` with an error if a module can't be found.", function (done) { 497 | var requireFailTest = new Liftoff({ name: 'preload:failure' }); 498 | var isEmittedBeforeRequired = false; 499 | requireFailTest.on('preload:before', function (name) { 500 | expect(name).toEqual('badmodule'); 501 | isEmittedBeforeRequired = true; 502 | }); 503 | requireFailTest.on('preload:failure', function (name) { 504 | expect(name).toEqual('badmodule'); 505 | expect(isEmittedBeforeRequired).toEqual(true); 506 | done(); 507 | }); 508 | requireFailTest.requireLocal('badmodule', __dirname); 509 | }); 510 | }); 511 | 512 | describe('configFiles', function () { 513 | it('should be empty if not specified', function (done) { 514 | var app = new Liftoff({ 515 | name: 'myapp', 516 | }); 517 | app.prepare({}, function (env) { 518 | expect(env.configFiles).toEqual([]); 519 | done(); 520 | }); 521 | }); 522 | 523 | it('excludes files if value is not an array', function (done) { 524 | var app = new Liftoff({ 525 | name: 'myapp', 526 | configFiles: { 527 | name: "foobar" 528 | } 529 | }); 530 | app.prepare({}, function (env) { 531 | expect(env.configFiles).toEqual([]); 532 | done(); 533 | }); 534 | }); 535 | 536 | it('should find multiple files if specified', function (done) { 537 | var app = new Liftoff({ 538 | name: 'myapp', 539 | configFiles: [ 540 | { name: 'testconfig', path: '.' }, 541 | { name: 'testconfig', path: 'test/fixtures/configfiles' }, 542 | { name: 'testconfig', path: 'test', cwd: 'text/fixtures/configfiles', findUp: true }, 543 | { name: 'package', path: '.' }, 544 | { name: 'package', path: 'test/fixtures/configfiles' }, 545 | { name: 'package', path: 'test', cwd: 'text/fixtures/configfiles', findUp: true }, 546 | ], 547 | }); 548 | app.prepare({}, function (env) { 549 | expect(env.configFiles).toEqual([ 550 | null, 551 | path.resolve(__dirname, './fixtures/configfiles/testconfig.json'), 552 | null, 553 | path.resolve(__dirname, '../package.json'), 554 | null, 555 | null 556 | ]); 557 | done(); 558 | }); 559 | }); 560 | 561 | it('should use prepare cwd if specified', function (done) { 562 | var app = new Liftoff({ 563 | name: 'myapp', 564 | configFiles: [ 565 | { name: 'testconfig', path: '.', extensions: ['.js', '.json'] } 566 | ], 567 | }); 568 | app.prepare( 569 | { 570 | cwd: 'test/fixtures/configfiles', 571 | }, 572 | function (env) { 573 | expect(env.configFiles).toEqual([ 574 | path.resolve(__dirname, './fixtures/configfiles/testconfig.json'), 575 | ]); 576 | done(); 577 | } 578 | ); 579 | }); 580 | 581 | it('overrides the configPath if the configName key exists in the config', function (done) { 582 | var app = new Liftoff({ 583 | name: 'myapp', 584 | configFiles: [ 585 | { name: 'override-config-path-absolute', path: 'test/fixtures/configfiles', extensions: ['.js'] } 586 | ], 587 | }); 588 | app.prepare({}, function (env) { 589 | expect(env.configPath).toMatch(/override-the-config-path\.js$/); 590 | done(); 591 | }); 592 | }); 593 | 594 | it('resolves relative configPath if the configName key exists in the config', function (done) { 595 | var app = new Liftoff({ 596 | name: 'myapp', 597 | configFiles: [ 598 | { name: 'override-config-path-relative', path: 'test/fixtures/configfiles', extensions: ['.json'] } 599 | ], 600 | }); 601 | app.prepare({}, function (env) { 602 | expect(env.configPath).toMatch(/override-the-config-path\.js$/); 603 | done(); 604 | }); 605 | }); 606 | 607 | it('ignores non-string configPath if the configName key exists in the config', function (done) { 608 | var app = new Liftoff({ 609 | name: 'myapp', 610 | configFiles: [ 611 | { name: 'override-config-path-non-string', path: 'test/fixtures/configfiles', extensions: ['.js'] } 612 | ], 613 | }); 614 | app.prepare({}, function (env) { 615 | expect(env.configPath).toEqual(null); 616 | done(); 617 | }); 618 | }); 619 | 620 | it('adds array of preloads specified in config', function (done) { 621 | var app = new Liftoff({ 622 | name: 'myapp', 623 | configFiles: [ 624 | { name: 'preload-array', path: 'test/fixtures/configfiles', extensions: ['.js'] } 625 | ], 626 | }); 627 | app.prepare({}, function (env) { 628 | expect(env.preload).toEqual(['abc', 'xyz']); 629 | done(); 630 | }); 631 | }); 632 | 633 | it('combines array of preloads specified in config', function (done) { 634 | var app = new Liftoff({ 635 | name: 'myapp', 636 | configFiles: [ 637 | { name: 'preload-array', path: 'test/fixtures/configfiles', extensions: ['.js'] } 638 | ], 639 | }); 640 | app.prepare({ 641 | preload: ['123'] 642 | }, function (env) { 643 | expect(env.preload).toEqual(['123', 'abc', 'xyz']); 644 | done(); 645 | }); 646 | }); 647 | 648 | it('adds string preload specified in config', function (done) { 649 | var app = new Liftoff({ 650 | name: 'myapp', 651 | configFiles: [ 652 | { name: 'preload-string', path: 'test/fixtures/configfiles', extensions: ['.js'] } 653 | ], 654 | }); 655 | app.prepare({}, function (env) { 656 | expect(env.preload).toEqual(['abc']); 657 | done(); 658 | }); 659 | }); 660 | 661 | it('combines string preload specified in config', function (done) { 662 | var app = new Liftoff({ 663 | name: 'myapp', 664 | configFiles: [ 665 | { name: 'preload-string', path: 'test/fixtures/configfiles', extensions: ['.js'] } 666 | ], 667 | }); 668 | app.prepare({ 669 | preload: ['xyz'] 670 | }, function (env) { 671 | expect(env.preload).toEqual(['xyz', 'abc']); 672 | done(); 673 | }); 674 | }); 675 | 676 | it('ignores non-string/non-array preload specified in config', function (done) { 677 | var app = new Liftoff({ 678 | name: 'myapp', 679 | configFiles: [ 680 | { name: 'preload-invalid', path: 'test/fixtures/configfiles', extensions: ['.js'] } 681 | ], 682 | }); 683 | app.prepare({}, function (env) { 684 | expect(env.preload).toEqual([]); 685 | done(); 686 | }); 687 | }); 688 | 689 | it('ignores array with any non-strings preload specified in config', function (done) { 690 | var app = new Liftoff({ 691 | name: 'myapp', 692 | configFiles: [ 693 | { name: 'preload-invalid-array', path: 'test/fixtures/configfiles', extensions: ['.js'] } 694 | ], 695 | }); 696 | app.prepare({}, function (env) { 697 | expect(env.preload).toEqual([]); 698 | done(); 699 | }); 700 | }); 701 | 702 | it('should use dirname of configPath if no cwd is specified', function (done) { 703 | var app = new Liftoff({ 704 | name: 'myapp', 705 | configFiles: [ 706 | { name: 'testconfig', path: '.', extensions: ['.js', '.json'] } 707 | ], 708 | }); 709 | app.prepare( 710 | { 711 | configPath: 'test/fixtures/configfiles/myapp.js', 712 | }, 713 | function (env) { 714 | expect(env.configFiles).toEqual([ 715 | path.resolve(__dirname, './fixtures/configfiles/testconfig.json'), 716 | ]); 717 | done(); 718 | } 719 | ); 720 | }); 721 | 722 | it('uses default extensions if not specified (.md)', function (done) { 723 | var app = new Liftoff({ 724 | extensions: { 725 | '.md': './test/fixtures/configfiles/require-md', 726 | }, 727 | name: 'myapp', 728 | configFiles: [ 729 | { name: "README", path: '.' } 730 | ], 731 | }); 732 | app.prepare({}, function (env) { 733 | expect(env.configFiles).toEqual([ 734 | path.resolve(__dirname, '../README.md'), 735 | ]); 736 | done(); 737 | }); 738 | }); 739 | 740 | it('use default extensions if not specified (.txt)', function (done) { 741 | var app = new Liftoff({ 742 | extensions: { 743 | '.txt': './test/fixtures/configfiles/require-txt', 744 | }, 745 | name: 'myapp', 746 | configFiles: [ 747 | { name: 'README', path: 'test/fixtures/configfiles' } 748 | ], 749 | }); 750 | app.prepare({}, function (env) { 751 | expect(env.configFiles).toEqual([ 752 | path.resolve(__dirname, './fixtures/configfiles/README.txt'), 753 | ]); 754 | done(); 755 | }); 756 | }); 757 | 758 | it('does not use default extensions if specified (.js)', function (done) { 759 | var app = new Liftoff({ 760 | extensions: { '.md': null, '.txt': null }, 761 | name: 'myapp', 762 | configFiles: [ 763 | { name: "README", path: '.', extensions: ['.js'] } 764 | ], 765 | }); 766 | app.prepare({}, function (env) { 767 | expect(env.configFiles).toEqual([ 768 | null 769 | ]); 770 | done(); 771 | }); 772 | }); 773 | 774 | it('does not use default extensions if specified (.json)', function (done) { 775 | var app = new Liftoff({ 776 | extensions: { '.md': null, '.txt': null }, 777 | name: 'myapp', 778 | configFiles: [ 779 | { name: "README", path: 'test/fixtures/configfiles', extensions: ['.json'] }, 780 | ], 781 | }); 782 | app.prepare({}, function (env) { 783 | expect(env.configFiles).toEqual([ 784 | null 785 | ]); 786 | done(); 787 | }); 788 | }); 789 | 790 | it('should use specified loaders (.md)', function (done) { 791 | var logRequire = []; 792 | 793 | var app = new Liftoff({ 794 | extensions: { 795 | '.md': './test/fixtures/configfiles/require-md', 796 | }, 797 | name: 'myapp', 798 | configFiles: [ 799 | { name: 'README', path: '.' } 800 | ], 801 | }); 802 | app.on('loader:success', function (moduleName, module) { 803 | logRequire.push({ moduleName: moduleName, module: module }); 804 | }); 805 | app.prepare({}, function (env) { 806 | expect(env.configFiles).toEqual([ 807 | path.resolve(__dirname, '../README.md'), 808 | ]); 809 | 810 | expect(logRequire.length).toEqual(1); 811 | expect(logRequire[0].moduleName).toEqual( 812 | './test/fixtures/configfiles/require-md' 813 | ); 814 | 815 | expect(require(env.configFiles[0])).toEqual( 816 | 'Load README.md by require-md' 817 | ); 818 | done(); 819 | }); 820 | }); 821 | 822 | it('should use specified loaders (.txt)', function (done) { 823 | var logRequire = []; 824 | 825 | var app = new Liftoff({ 826 | name: 'myapp', 827 | configFiles: [ 828 | { 829 | name: 'README', 830 | path: 'test/fixtures/configfiles', 831 | extensions: { '.txt': './test/fixtures/configfiles/require-txt' }, 832 | }, 833 | ], 834 | }); 835 | app.on('loader:success', function (moduleName, module) { 836 | logRequire.push({ moduleName: moduleName, module: module }); 837 | }); 838 | app.prepare({}, function (env) { 839 | expect(env.configFiles).toEqual([ 840 | path.resolve(__dirname, './fixtures/configfiles/README.txt'), 841 | ]); 842 | 843 | expect(logRequire.length).toEqual(1); 844 | expect(logRequire[0].moduleName).toEqual( 845 | './test/fixtures/configfiles/require-txt' 846 | ); 847 | 848 | expect(require(env.configFiles[0])).toEqual( 849 | 'Load README.txt by require-txt' 850 | ); 851 | done(); 852 | }); 853 | }); 854 | 855 | it('emits `loader:failure` but still resolves with invalid loaders', function (done) { 856 | var logFailure = []; 857 | 858 | var app = new Liftoff({ 859 | name: 'myapp', 860 | configFiles: [ 861 | { 862 | name: 'README', 863 | path: 'test/fixtures/configfiles', 864 | extensions: { 865 | '.txt': './test/fixtures/configfiles/require-non-exist', 866 | }, 867 | }, 868 | ], 869 | }); 870 | app.on('loader:failure', function (moduleName, error) { 871 | logFailure.push({ moduleName: moduleName, error: error }); 872 | }); 873 | app.prepare({}, function (env) { 874 | expect(env.configFiles).toEqual([ 875 | path.resolve(__dirname, './fixtures/configfiles/README.txt'), 876 | ]); 877 | 878 | expect(logFailure.length).toEqual(1); 879 | expect(logFailure[0].moduleName).toEqual( 880 | './test/fixtures/configfiles/require-non-exist' 881 | ); 882 | 883 | done(); 884 | }); 885 | }); 886 | 887 | it('prioritizes intrinsic extension-loader mappings', function (done) { 888 | var logRequire = []; 889 | var logFailure = []; 890 | 891 | var app = new Liftoff({ 892 | name: 'myapp', 893 | configFiles: [ 894 | { 895 | name: 'testconfig', 896 | path: 'test/fixtures/configfiles', 897 | extensions: { 898 | // ignored 899 | '.js': './test/fixtures/configfiles/require-js', 900 | '.json': './test/fixtures/configfiles/require-json', 901 | }, 902 | }, 903 | ], 904 | }); 905 | app.on('loader:failure', function (moduleName, error) { 906 | logFailure.push({ moduleName: moduleName, error: error }); 907 | }); 908 | app.on('loader:success', function (moduleName, module) { 909 | logRequire.push({ moduleName: moduleName, module: module }); 910 | }); 911 | app.prepare({}, function (env) { 912 | expect(env.configFiles).toEqual([ 913 | path.resolve( 914 | __dirname, 915 | './fixtures/configfiles/testconfig.json' 916 | ), 917 | ]); 918 | 919 | expect(logRequire.length).toEqual(0); 920 | expect(logFailure.length).toEqual(0); 921 | 922 | expect(require(env.configFiles[0])).toEqual({ aaa: 'AAA' }); 923 | done(); 924 | }); 925 | }); 926 | }); 927 | 928 | describe('config', function () { 929 | it('is empty if `configFiles` is not specified', function (done) { 930 | var app = new Liftoff({ 931 | name: 'myapp', 932 | }); 933 | app.prepare({}, function (env) { 934 | expect(env.config).toEqual([]); 935 | done(); 936 | }); 937 | }); 938 | 939 | it('loads all config for every found configFile', function (done) { 940 | var app = new Liftoff({ 941 | name: 'myapp', 942 | configFiles: [ 943 | { name: 'testconfig', path: 'test/fixtures/configfiles' }, 944 | { name: 'testconfig', path: 'test/fixtures/configfiles-extends' }, 945 | ], 946 | }); 947 | app.prepare({}, function (env) { 948 | expect(env.config).toEqual([ 949 | { 950 | aaa: 'AAA', 951 | }, 952 | { 953 | aaa: 'AAA', // Comes from the base, which overrode `aaa: 'CCC'` in the `extends` 954 | bbb: 'BBB', // Comes from the `extends` 955 | }, 956 | ]); 957 | done(); 958 | }); 959 | }); 960 | 961 | it('loads config if a `configFiles` is found and makes it available with the same key on `config`', function (done) { 962 | var app = new Liftoff({ 963 | name: 'myapp', 964 | configFiles: [ 965 | { name: 'testconfig', path: 'test/fixtures/configfiles' }, 966 | ], 967 | }); 968 | app.prepare({}, function (env) { 969 | expect(env.config).toEqual([ 970 | { 971 | aaa: 'AAA', 972 | }, 973 | ]); 974 | done(); 975 | }); 976 | }); 977 | 978 | it('loads and merges a config file specified if loaded file provides `extends` property', function (done) { 979 | var app = new Liftoff({ 980 | name: 'myapp', 981 | configFiles: [ 982 | { name: 'testconfig', path: 'test/fixtures/configfiles-extends' }, 983 | ] 984 | }); 985 | app.prepare({}, function (env) { 986 | expect(env.config).toEqual([ 987 | { 988 | aaa: 'AAA', // Comes from the base, which overrode `aaa: 'CCC'` in the `extends` 989 | bbb: 'BBB', // Comes from the `extends` 990 | }, 991 | ]); 992 | done(); 993 | }); 994 | }); 995 | 996 | it('throws error on circular extends', function (done) { 997 | var app = new Liftoff({ 998 | name: 'myapp', 999 | configFiles: [ 1000 | { name: 'circular1', path: 'test/fixtures/configfiles-extends' }, 1001 | ], 1002 | }); 1003 | var circPath = path.resolve( 1004 | __dirname, 1005 | './fixtures/configfiles-extends/circular1.json' 1006 | ); 1007 | expect(function () { 1008 | app.prepare({}, function () {}); 1009 | }).toThrow(circPath); // Ensure that the error includes the circular path 1010 | done(); 1011 | }); 1012 | 1013 | it('gracefully handles a null-ish extends', function (done) { 1014 | var app = new Liftoff({ 1015 | name: 'myapp', 1016 | configFiles: [ 1017 | { name: 'null', path: 'test/fixtures/configfiles-extends' }, 1018 | ], 1019 | }); 1020 | app.prepare({}, function (env) { 1021 | expect(env.config).toEqual([ 1022 | {} 1023 | ]); 1024 | done(); 1025 | }); 1026 | }); 1027 | 1028 | it('stops processing extends on an empty (or null-ish) extends', function (done) { 1029 | var app = new Liftoff({ 1030 | name: 'myapp', 1031 | configFiles: [ 1032 | { name: 'load-empty', path: 'test/fixtures/configfiles-extends' }, 1033 | ], 1034 | }); 1035 | app.prepare({}, function (env) { 1036 | expect(env.config).toEqual([ 1037 | { 1038 | aaa: 'bbb', 1039 | ccc: 'ddd', 1040 | }, 1041 | ]); 1042 | done(); 1043 | }); 1044 | }); 1045 | 1046 | it('throws upon extends of missing local file', function (done) { 1047 | var app = new Liftoff({ 1048 | name: 'myapp', 1049 | configFiles: [ 1050 | { name: 'local-missing', path: 'test/fixtures/configfiles-extends' }, 1051 | ], 1052 | }); 1053 | var missingPath = path.resolve( 1054 | __dirname, 1055 | './fixtures/configfiles-extends/not-exists' 1056 | ); 1057 | expect(function () { 1058 | app.prepare({}, function () {}); 1059 | }).toThrow(missingPath); 1060 | done(); 1061 | }); 1062 | 1063 | it('throws (with correct path) upon extends of missing deep in tree', function (done) { 1064 | var app = new Liftoff({ 1065 | name: 'myapp', 1066 | configFiles: [ 1067 | { name: 'extend-missing', path: 'test/fixtures/configfiles-extends' }, 1068 | ], 1069 | }); 1070 | var missingPath = path.resolve( 1071 | __dirname, 1072 | './fixtures/configfiles-extends/not-exists' 1073 | ); 1074 | expect(function () { 1075 | app.prepare({}, function () {}); 1076 | }).toThrow(missingPath); 1077 | done(); 1078 | }); 1079 | 1080 | it('throws (with correct path) upon extends using `fined` object with `path`', function (done) { 1081 | var app = new Liftoff({ 1082 | name: 'myapp', 1083 | configFiles: [ 1084 | { name: 'missing-path-obj', path: 'test/fixtures/configfiles-extends' }, 1085 | ], 1086 | }); 1087 | var missingPath = path.resolve( 1088 | __dirname, 1089 | './fixtures/configfiles-extends/not-exists' 1090 | ); 1091 | expect(function () { 1092 | app.prepare({}, function () {}); 1093 | }).toThrow(missingPath); 1094 | done(); 1095 | }); 1096 | 1097 | it('throws (with correct path) upon extends using `fined` object with `name`', function (done) { 1098 | var app = new Liftoff({ 1099 | name: 'myapp', 1100 | configFiles: [ 1101 | { name: 'missing-name-obj', path: 'test/fixtures/configfiles-extends' }, 1102 | ], 1103 | }); 1104 | var missingPath = path.resolve( 1105 | __dirname, 1106 | './fixtures/configfiles-extends/not-exists' 1107 | ); 1108 | expect(function () { 1109 | app.prepare({}, function () {}); 1110 | }).toThrow(missingPath); 1111 | done(); 1112 | }); 1113 | 1114 | it('throws (without path) upon extends using invalid `fined` object', function (done) { 1115 | var app = new Liftoff({ 1116 | name: 'myapp', 1117 | configFiles: [ 1118 | { name: 'missing-invalid-obj', path: 'test/fixtures/configfiles-extends' }, 1119 | ] 1120 | }); 1121 | expect(function () { 1122 | app.prepare({}, function () {}); 1123 | }).toThrow(/^Unable to locate one of your extends\.$/); // Ensure the error doesn't contain path 1124 | done(); 1125 | }); 1126 | 1127 | it('throws upon extends of missing npm module', function (done) { 1128 | var app = new Liftoff({ 1129 | name: 'myapp', 1130 | configFiles: [ 1131 | { name: 'npm-missing', path: 'test/fixtures/configfiles-extends' }, 1132 | ], 1133 | }); 1134 | var missingModule = 'not-installed'; 1135 | expect(function () { 1136 | app.prepare({}, function () {}); 1137 | }).toThrow(missingModule); 1138 | done(); 1139 | }); 1140 | 1141 | it('throws upon extends if loading file errors', function (done) { 1142 | var app = new Liftoff({ 1143 | name: 'myapp', 1144 | configFiles: [ 1145 | { name: 'throws', path: 'test/fixtures/configfiles-extends' }, 1146 | ], 1147 | }); 1148 | var errModulePath = path.resolve( 1149 | __dirname, 1150 | './fixtures/configfiles-extends/throws.js' 1151 | ); 1152 | expect(function () { 1153 | app.prepare({}, function () {}); 1154 | }).toThrow(errModulePath); 1155 | done(); 1156 | }); 1157 | }); 1158 | }); 1159 | -------------------------------------------------------------------------------- /test/parse_options.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect'); 2 | 3 | var parseOptions = require('../lib/parse_options'); 4 | 5 | var NAME = 'mocha'; 6 | 7 | describe('parseOptions', function () { 8 | it('should auto-set processTitle, moduleName, & configFile if `name` is provided', function (done) { 9 | var opts = parseOptions({ name: NAME }); 10 | expect(opts.processTitle).toEqual(NAME); 11 | expect(opts.configName).toEqual(NAME + 'file'); 12 | expect(opts.moduleName).toEqual(NAME); 13 | done(); 14 | }); 15 | 16 | it('should set a title to be used for the process at launch', function (done) { 17 | var opts = parseOptions({ name: NAME }); 18 | expect(opts.processTitle).toEqual(NAME); 19 | expect(function () { 20 | parseOptions(); 21 | }).toThrow('You must specify a processTitle.'); 22 | done(); 23 | }); 24 | 25 | it('should set the configuration file to look for at launch', function (done) { 26 | var opts = parseOptions({ name: NAME }); 27 | expect(opts.configName).toEqual(NAME + 'file'); 28 | expect(function () { 29 | parseOptions({ processTitle: NAME }); 30 | }).toThrow('You must specify a configName.'); 31 | done(); 32 | }); 33 | 34 | it('should set a local module to resolve at launch', function (done) { 35 | var opts = parseOptions({ name: NAME }); 36 | expect(opts.moduleName).toEqual(NAME); 37 | done(); 38 | }); 39 | 40 | it('should use .processTitle/.configName/.moduleName preferencially', function (done) { 41 | var opts = parseOptions({ 42 | name: 'a', 43 | processTitle: 'b', 44 | configName: 'c', 45 | moduleName: 'd', 46 | }); 47 | expect(opts.processTitle).toEqual('b'); 48 | expect(opts.configName).toEqual('c'); 49 | expect(opts.moduleName).toEqual('d'); 50 | done(); 51 | }); 52 | 53 | it('should throw error if opts does not have .name and .moduleName', function (done) { 54 | expect(function () { 55 | parseOptions({ 56 | processTitle: 'a', 57 | configName: 'b', 58 | }); 59 | }).toThrow(); 60 | done(); 61 | }); 62 | 63 | it('should throw error if opts is null or empty', function (done) { 64 | expect(function () { 65 | parseOptions(null); 66 | }).toThrow(); 67 | expect(function () { 68 | parseOptions(undefined); 69 | }).toThrow(); 70 | expect(function () { 71 | parseOptions({}); 72 | }).toThrow(); 73 | done(); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/register_loader.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var util = require('util'); 3 | var EE = require('events').EventEmitter; 4 | 5 | var expect = require('expect'); 6 | 7 | var registerLoader = require('../lib/register_loader'); 8 | 9 | var testDir = path.resolve(__dirname, './fixtures/register_loader'); 10 | 11 | function App() { 12 | EE.call(this); 13 | } 14 | util.inherits(App, EE); 15 | 16 | function handlerNotEmit() { 17 | throw new Error('Should not have emitted'); 18 | } 19 | 20 | describe('registerLoader', function () { 21 | describe('register loader', function () { 22 | it('Should emit a `loader:success` event when registering loader succeeds', function (done) { 23 | var loaderPath = path.join(testDir, 'require-cfg.js'); 24 | var configPath = path.join(testDir, 'app.cfg'); 25 | var extensions = { '.cfg': loaderPath }; 26 | 27 | var app = new App(); 28 | app.on('loader:success', function (moduleName /* , module */) { 29 | expect(moduleName).toEqual(loaderPath); 30 | expect(require(configPath)).toEqual('Load app.cfg by require-cfg'); 31 | done(); 32 | }); 33 | app.on('loader:failure', handlerNotEmit); 34 | 35 | registerLoader(app, extensions, configPath); 36 | }); 37 | 38 | it('Should emit only a `loader:success` event when registering loader failed and succeeds', function (done) { 39 | var loaderPath = path.join(testDir, 'require-conf.js'); 40 | var configPath = path.join(testDir, 'app.conf'); 41 | var extensions = { '.conf': ['xxx', loaderPath] }; 42 | 43 | var app = new App(); 44 | app.on('loader:success', function (moduleName /* , module */) { 45 | expect(moduleName).toEqual(loaderPath); 46 | expect(require(configPath)).toEqual('Load app.conf by require-conf'); 47 | done(); 48 | }); 49 | app.on('loader:failure', handlerNotEmit); 50 | 51 | registerLoader(app, extensions, configPath); 52 | }); 53 | 54 | it('Should emit a `loader:failure` event when loader is not found', function (done) { 55 | var loaderPath = path.join(testDir, 'require-tmp.js'); 56 | var configPath = path.join(testDir, 'app.tmp'); 57 | var extensions = { '.tmp': ['xxx', loaderPath] }; 58 | 59 | var app = new App(); 60 | var index = 0; 61 | app.on('loader:failure', function (moduleName, error) { 62 | if (index === 0) { 63 | expect(moduleName).toEqual('xxx'); 64 | expect(error).toBeInstanceOf(Error); 65 | expect(error.message).toMatch('Cannot find module'); 66 | } else if (index === 1) { 67 | expect(moduleName).toEqual(loaderPath); 68 | expect(error).toBeInstanceOf(Error); 69 | expect(error.message).toMatch('Cannot find module'); 70 | done(); 71 | } else { 72 | done(new Error('Should not call more than two times')); 73 | } 74 | index++; 75 | }); 76 | app.on('loader:success', handlerNotEmit); 77 | 78 | registerLoader(app, extensions, configPath); 79 | }); 80 | 81 | it('Should emit a `loader:failure` event when registering loader failed', function (done) { 82 | var loaderPath = path.join(testDir, 'require-fail.js'); 83 | var configPath = path.join(testDir, 'app.tmp'); 84 | var extensions = { '.tmp': loaderPath }; 85 | 86 | var app = new App(); 87 | app.on('loader:failure', function (moduleName, error) { 88 | expect(moduleName).toEqual(loaderPath); 89 | expect(error).toBeInstanceOf(Error); 90 | expect(error.message).toMatch('Fail to register!'); 91 | done(); 92 | }); 93 | app.on('loader:success', handlerNotEmit); 94 | 95 | registerLoader(app, extensions, configPath); 96 | }); 97 | }); 98 | 99 | describe('cwd', function () { 100 | it('Should use "cwd" as a base directory of loaded file path if specified', function (done) { 101 | var loaderPath = path.join(testDir, 'require-rc.js'); 102 | var configPath = 'app.rc'; 103 | var extensions = { '.rc': loaderPath }; 104 | 105 | var app = new App(); 106 | app.on('loader:success', function (moduleName /* , module */) { 107 | expect(moduleName).toEqual(loaderPath); 108 | var loadedFile = path.join(testDir, configPath); 109 | expect(require(loadedFile)).toEqual('Load app.rc by require-rc'); 110 | done(); 111 | }); 112 | app.on('loader:failure', handlerNotEmit); 113 | 114 | registerLoader(app, extensions, configPath, testDir); 115 | }); 116 | }); 117 | 118 | describe('extensions', function () { 119 | it('Should do nothing when extensions is null', function (done) { 120 | var app = new App(); 121 | app.on('loader:success', handlerNotEmit); 122 | app.on('loader:failure', handlerNotEmit); 123 | 124 | registerLoader(app); 125 | 126 | registerLoader(app, null, 'aaa/bbb.cfg'); 127 | registerLoader(app, null, 'aaa/bbb.cfg', '.'); 128 | 129 | // .js is one of default extensions 130 | registerLoader(app, null, 'aaa/bbb.js'); 131 | registerLoader(app, null, 'aaa/bbb.js', '.'); 132 | done(); 133 | }); 134 | 135 | it('Should do nothing when extensions is illegal type', function (done) { 136 | var app = new App(); 137 | app.on('loader:success', handlerNotEmit); 138 | app.on('loader:failure', handlerNotEmit); 139 | 140 | registerLoader(app, 123, 'aaa/bbb.cfg'); 141 | registerLoader(app, true, 'aaa/bbb.cfg'); 142 | registerLoader(app, function () {}, 'aaa/bbb.cfg'); 143 | registerLoader(app, ['.rc', '.cfg'], 'aaa/bbb.cfg'); 144 | 145 | // .js is one of default extensions 146 | registerLoader(app, 123, 'aaa/bbb.js'); 147 | registerLoader(app, true, 'aaa/bbb.js'); 148 | registerLoader(app, function () {}, 'aaa/bbb.js'); 149 | registerLoader(app, ['.js', '.json'], 'aaa/bbb.js'); 150 | done(); 151 | }); 152 | 153 | it('Should do nothing when extensions is a string', function (done) { 154 | var app = new App(); 155 | app.on('loader:success', handlerNotEmit); 156 | app.on('loader:failure', handlerNotEmit); 157 | 158 | registerLoader(app, '.cfg', 'aaa/bbb.cfg'); 159 | registerLoader(app, '.js', 'aaa/bbb.js'); 160 | done(); 161 | }); 162 | }); 163 | 164 | describe('configPath', function () { 165 | it('Should do nothing when configPath is null', function (done) { 166 | var extensions0 = ['.js', '.json', '.coffee', '.coffee.md']; 167 | var extensions1 = { 168 | '.js': null, 169 | '.json': null, 170 | '.coffee': 'coffee-script/register', 171 | '.coffee.md': 'coffee-script/register', 172 | }; 173 | 174 | var app = new App(); 175 | app.on('loader:success', handlerNotEmit); 176 | app.on('loader:failure', handlerNotEmit); 177 | 178 | registerLoader(app, extensions0); 179 | registerLoader(app, extensions1); 180 | registerLoader(app, extensions0, null, '.'); 181 | registerLoader(app, extensions1, null, '.'); 182 | done(); 183 | }); 184 | 185 | it('Should do nothing when configPath is illegal type', function (done) { 186 | var extensions0 = ['.js', '.json', '.coffee', '.coffee.md']; 187 | var extensions1 = { 188 | '.js': null, 189 | '.json': null, 190 | '.coffee': 'coffee-script/register', 191 | '.coffee.md': 'coffee-script/register', 192 | }; 193 | 194 | var app = new App(); 195 | app.on('loader:success', handlerNotEmit); 196 | app.on('loader:failure', handlerNotEmit); 197 | 198 | registerLoader(app, extensions0, 123); 199 | registerLoader(app, extensions0, ['aaa', 'bbb']); 200 | registerLoader(app, extensions1, {}); 201 | registerLoader(app, extensions1, function () {}); 202 | done(); 203 | }); 204 | 205 | it('Should do nothing when configPath does not end with one of extensions', function (done) { 206 | var loaderPath = path.join(testDir, 'require-rc.js'); 207 | var configPath = path.join(testDir, 'app.xxx'); 208 | var extensions = { '.cfg': loaderPath }; 209 | 210 | var app = new App(); 211 | app.on('loader:success', handlerNotEmit); 212 | app.on('loader:failure', handlerNotEmit); 213 | 214 | registerLoader(app, extensions, configPath); 215 | done(); 216 | }); 217 | 218 | it('Should do nothing when configPath ends with one of extensions of which the loader was already registered', function (done) { 219 | var loaderPath = path.join(testDir, 'require-cfg.js'); 220 | var configPath = path.join(testDir, 'app.cfg'); 221 | var extensions = { '.cfg': loaderPath }; 222 | 223 | var app = new App(); 224 | app.on('loader:success', handlerNotEmit); 225 | app.on('loader:failure', handlerNotEmit); 226 | 227 | registerLoader(app, extensions, configPath); 228 | done(); 229 | }); 230 | }); 231 | 232 | describe('Multiple extensions', function () { 233 | it('should detect the shortest extension in extension candidates', function (done) { 234 | var loaderPath = path.join(testDir, 'require-file-b.js'); 235 | var configPath = path.join(testDir, 'file.a.b'); 236 | var extensions = { '.b': loaderPath }; 237 | 238 | var app = new App(); 239 | app.on('loader:failure', handlerNotEmit); 240 | app.on('loader:success', function (moduleName /* , module */) { 241 | expect(moduleName).toEqual(loaderPath); 242 | expect(require(configPath)).toEqual('Load file.a.b by require-file-b'); 243 | done(); 244 | }); 245 | 246 | registerLoader(app, extensions, configPath); 247 | }); 248 | 249 | it('should detect not shortest extension in extension candidates', function (done) { 250 | var loaderPath = path.join(testDir, 'require-file-bc.js'); 251 | var configPath = path.join(testDir, 'file.a.b.c'); 252 | var extensions = { '.b.c': loaderPath }; 253 | 254 | var app = new App(); 255 | app.on('loader:failure', handlerNotEmit); 256 | app.on('loader:success', function (moduleName /* , module */) { 257 | expect(moduleName).toEqual(loaderPath); 258 | expect(require(configPath)).toEqual( 259 | 'Load file.a.b.c by require-file-bc' 260 | ); 261 | done(); 262 | }); 263 | 264 | registerLoader(app, extensions, configPath); 265 | }); 266 | 267 | it('Should update a loader of a longer extension but not update a loader of a shorter extension', function (done) { 268 | var loaderPathD = path.join(testDir, 'require-file-d.js'); 269 | var loaderPathCD = path.join(testDir, 'require-file-cd.js'); 270 | var loaderPathECD = path.join(testDir, 'require-file-ecd.js'); 271 | var loaderPathFCD = path.join(testDir, 'require-file-fcd.js'); 272 | 273 | var configPathD = path.join(testDir, 'file.a.b.d'); 274 | var configPathCD = path.join(testDir, 'file.a.b.c.d'); 275 | var configPathECD = path.join(testDir, 'file.a.e.c.d'); 276 | var configPathFCD = path.join(testDir, 'file.a.f.c.d'); 277 | 278 | var extensions = { 279 | '.d': loaderPathD, 280 | '.c.d': loaderPathCD, 281 | '.e.c.d': loaderPathECD, 282 | '.f.c.d': loaderPathFCD, 283 | }; 284 | 285 | var count = 0; 286 | var app = new App(); 287 | app.on('loader:failure', handlerNotEmit); 288 | app.on('loader:success', function (moduleName /* , module */) { 289 | switch (count) { 290 | case 0: { 291 | expect(moduleName).toEqual(loaderPathCD); 292 | expect(require(configPathCD)).toEqual( 293 | 'Load file.a.b.c.d by require-file-cd' 294 | ); 295 | break; 296 | } 297 | case 1: { 298 | expect(moduleName).toEqual(loaderPathECD); 299 | expect(require(configPathECD)).toEqual( 300 | 'Load file.a.e.c.d by require-file-ecd' 301 | ); 302 | break; 303 | } 304 | case 2: { 305 | expect(moduleName).toEqual(loaderPathFCD); 306 | expect(require(configPathFCD)).toEqual( 307 | 'Load file.a.f.c.d by require-file-fcd' 308 | ); 309 | done(); 310 | break; 311 | } 312 | } 313 | count++; 314 | }); 315 | 316 | registerLoader(app, extensions, configPathCD); 317 | registerLoader(app, extensions, configPathECD); 318 | registerLoader(app, extensions, configPathD); // Don't register loader. 319 | registerLoader(app, extensions, configPathFCD); 320 | }); 321 | }); 322 | }); 323 | -------------------------------------------------------------------------------- /test/silent_require.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var expect = require('expect'); 4 | 5 | var silentRequire = require('../lib/silent_require'); 6 | 7 | describe('silentRequire', function () { 8 | it('should require a file', function (done) { 9 | expect(silentRequire(path.resolve('./package'))).toEqual( 10 | require('../package') 11 | ); 12 | done(); 13 | }); 14 | 15 | it('should not throw if file is not found', function (done) { 16 | expect(function () { 17 | silentRequire('path/to/nowhere'); 18 | }).not.toThrow(); 19 | done(); 20 | }); 21 | }); 22 | --------------------------------------------------------------------------------