diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 2138f2d..0000000 --- a/.babelrc +++ /dev/null @@ -1,11 +0,0 @@ -{ - plugins: [ - "array-includes", - "add-module-exports", - "transform-es2015-modules-commonjs", - "transform-es2015-spread", - "transform-es2015-parameters", - "transform-es2015-destructuring", - "transform-object-rest-spread" - ] -} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig index 435585c..7137783 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,8 +4,7 @@ end_of_line = lf insert_final_newline = true indent_style = space -indent_size = 4 - -[{package.json,.travis.yml,.babelrc}] -indent_style = space indent_size = 2 + +[*.js] +indent_size = 4 diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 3d3d077..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,28 +0,0 @@ -module.exports = { - "env": { - "es6": true, - "node": true - }, - "extends": "eslint:recommended", - "parserOptions": { - "sourceType": "module" - }, - "rules": { - "indent": [ - "error", - 4 - ], - "linebreak-style": [ - "error", - "unix" - ], - "quotes": [ - "error", - "single" - ], - "semi": [ - "error", - "always" - ] - } -}; \ No newline at end of file diff --git a/.gitignore b/.gitignore index 58f4dbd..33433c2 100644 --- a/.gitignore +++ b/.gitignore @@ -15,9 +15,13 @@ pids logs results +dist/ npm-debug.log node_modules +.idea + +# app specific +test/node-addon test/reporter-custom-mochawesome/mochawesome-report/ mochawesome-report/ - -.idea +test/reporter-custom-jenkins/result.xml diff --git a/.npmignore b/.npmignore deleted file mode 100644 index f317b03..0000000 --- a/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -bin/ -lib/ -test/ -.babelrc -.editorconfig -.travis.yml diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..45a4fb7 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +8 diff --git a/.travis.yml b/.travis.yml index 9fff047..05486bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,8 @@ language: node_js node_js: - "stable" - - "6" - - "5" - - "4" + - "8" + - "9" env: - CXX=g++-4.8 addons: @@ -13,3 +12,4 @@ addons: packages: - gcc-4.8 - g++-4.8 +script: npm run test:ci diff --git a/CHANGELOG.md b/CHANGELOG.md index 3945553..75f9c01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ -## (upcoming release) +## 2.0.0 - * ... +More: https://github.com/yandex/mocha-parallel-tests/wiki/v2-release-notes ## 1.2.10 diff --git a/README.md b/README.md index 2412f0f..5287a98 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,61 @@ -# Mocha parallel tests runner - -[![Greenkeeper badge](https://badges.greenkeeper.io/yandex/mocha-parallel-tests.svg)](https://greenkeeper.io/) +# mocha-parallel-tests [![Build Status](https://img.shields.io/travis/yandex/mocha-parallel-tests/master.svg?style=flat)](https://travis-ci.org/yandex/mocha-parallel-tests) -[![DevDependency Status](http://img.shields.io/david/dev/yandex/mocha-parallel-tests.svg?style=flat)](https://david-dm.org/yandex/mocha-parallel-tests#info=devDependencies) -[![npm version](https://img.shields.io/npm/v/mocha-parallel-tests.svg?style=flat)](https://www.npmjs.com/package/mocha-parallel-tests) +`mocha-parallel-tests` is a test runner for tests written with `mocha` testing framework which allows you to run them in parallel. `mocha-parallel-tests` executes **each of your test files in a separate process** while maintaining the output structure of `mocha`. Compared to the other tools which try to parallelize `mocha` tests execution, `mocha-parallel-tests` doesn't require you to write the code in a different way or use some specific APIs - just run your tests with `mocha-parallel-tests` instead of `mocha` and you will see the difference. Or if you prefer to use `mocha` programmatic API replace it with `mocha-parallel-tests` default export and you're done! -Normally tests written with mocha run sequentially. This happens so because each test suite should not depend on another. But if you are running tests which take a lot of time (for example tests with Selenium Webdriver) waiting for so much time is impossible. +## Installation -If you're sure that running any of your test suites doesn't affect others, you should try to parallel them with `mocha-parallel-tests`. The only thing that changes for you is that you use not `mocha` but `mocha-parallel-tests` executable because it supports all of mocha options. +`npm install --save-dev mocha mocha-parallel-tests` -Also `mocha-parallel-tests` supports its own `--max-parallel` (max parallel running tests) and `--retry` (number of retries) options. +**ATTENTION**: `mocha` is a peer dependency of `mocha-parallel-tests` so you also need to install `mocha`. Currently `mocha` versions 3, 4 and 5 are supported. -## Installation +## Usage -`npm install --save-dev mocha-parallel-tests mocha` +### CLI -**ATTENTION**: Starting from 1.0.0 `mocha-parallel-tests` adds mocha as a [peerDependency](https://nodejs.org/en/blog/npm/peer-dependencies/) so you should specify what `mocha` version you want to run tests with. Only latest (3.x) mocha versions are supported. +```bash +# mocha example +$ mocha -R xunit --timeout 10000 --slow 1000 test/*.spec.js -## Usage +# mocha-parallel-tests example +$ mocha-parallel-tests -R xunit --timeout 10000 --slow 1000 test/*.spec.js +``` + +Most of `mocha` CLI options are supported. If you're missing some of the options support you're welcome to submit a PR: all options are applied in a same simple way. + +### Programmatic API + +```javascript +// mocha example +import * as Mocha from 'mocha'; +const mocha = new Mocha(); +mocha.addFile(`${__dirname}/index.spec.js`); +mocha.run(); + +// mocha-parallel-tests example +// if you're using TypeScript you don't need to install @types/mocha-parallel-tests +// because package comes with typings in it +import Mocha from 'mocha-parallel-tests'; // or `const Mocha = require('mocha-parallel-tests').default` if you're using CommonJS +const mocha = new Mocha(); +mocha.addFile(`${__dirname}/index.spec.js`); +mocha.run(); +``` + +## Parallel limit -* `./node_modules/.bin/mocha-parallel-tests your_test_directory/` -* `./node_modules/.bin/mocha-parallel-tests **/*.js` +`mocha-parallel-tests` CLI executable has its own `--max-parallel` option which is the amount of tests executed at the same time. By default it's equal to the number of logical CPI cores (`os.cpus().length`) on your computer but you can also specify your own number or set it to 0, which means that all test files will be started executing at the same time. However this is not recommended especially on machines with low number of CPUs and big number of tests executed. -## Options -Own options: +## Differences with mocha -* `--max-parallel ` - max number of running parallel tests -* `--retry ` - number of retries (0 by default) +Main difference with `mocha` comes from the fact that all files are executed in separate processes and don't share the scope. This means that even global variables values that you could've used to share the data between test suites will not be reliable. There's also some specific behaviour for some of the `mocha` CLI options like `--bail`: it's just applied to each test in its process. You can see the full list of differences [here](https://github.com/yandex/mocha-parallel-tests/wiki/Differences-with-mocha). -And all options supported by mocha: +From the reporter perspective the main difference between tests executed with `mocha` and `mocha-parallel-tests` is another level of nesting which again comes from the fact that main process adds one more "suite" level and all tests results are merged into that: -* `--reporter ` - specify the reporter to use -* `--timeout ` - set test-case timeout in milliseconds -* `--slow ` - "slow" test threshold in milliseconds +**mocha** -## Differences with `mocha` +![mocha reporter output](https://user-images.githubusercontent.com/73191/40283858-ac9611ce-5cc8-11e8-83a7-dc7be36817ff.png) - * `--bail` behaviour can differ due to parallel running of tests. See [this issue](https://github.com/yandex/mocha-parallel-tests/issues/88) for more info. +**mocha-parallel-tests** -## Tests -`mocha-parallel-tests` is highly covered with tests itself. If you find something bad, feel free to [post an issue](https://github.com/yandex/mocha-parallel-tests/issues/new). +![mocha-parallel-tests reporter output](https://user-images.githubusercontent.com/73191/40283870-cf470624-5cc8-11e8-8849-67718cfc3a05.png) diff --git a/api.js b/api.js deleted file mode 100644 index 54035cc..0000000 --- a/api.js +++ /dev/null @@ -1,98 +0,0 @@ -'use strict'; - -import Mocha from 'mocha'; -import Reporter from './lib/reporter'; -import {createInstance as createRunnerInstance} from './lib/runner'; -import { - addTest, - runTests, - setOptions as setWatcherOptions -} from './lib/watcher'; - -class MochaParallelTests extends Mocha { - constructor() { - super(); - - this._customRunner = createRunnerInstance(); - this._filesTotal = 0; - this._reporterName = null; - this._reporterOptions = null; - - // prevent uncaught exception listeners count warning - process.setMaxListeners(0); - } - - addFile(file) { - addTest(file); - this._filesTotal++; - - return this; - } - - reporter(reporterName, reporterOptions) { - if (reporterName === undefined) { - return super.reporter.call(this, reporterName, reporterOptions); - } - - this._reporterName = reporterName; - this._reporterOptions = reporterOptions; - - return this; - } - - setOwnOptions({maxParallel, retry}) { - setWatcherOptions({ - maxParallelTests: maxParallel, - retryCount: retry - }); - } - - run(callback) { - this._customRunner.on('end', ({failsOccured, reporter}) => { - if (reporter.done) { - reporter.done(failsOccured, callback); - } else { - callback(failsOccured); - } - }); - - runTests({ - options: Object.assign({}, { - reporterName: this._reporterName, - reporterOptions: this._reporterOptions, - reporter: Reporter, - testsLength: this._filesTotal - }), - throttledCalls: this._throttledCalls - }); - - return this._customRunner; - } -} - -Object.keys(Mocha.prototype).forEach(key => { - if (typeof Mocha.prototype[key] !== 'function') { - return; - } - - // we have our own implementations of these methods - // other methods should be saved and re-applied during runTests() - if (key === 'run' || key === 'addFile' || key === 'reporter') { - return; - } - - MochaParallelTests.prototype[key] = function (...args) { - // mocha calls some of its methods inside constructor - // so MochaParallelTests own constructor function can still be in progress here - this._throttledCalls = this._throttledCalls || []; - - this._throttledCalls.push({ - args, - method: key - }); - - return this; - }; -}); - -export default MochaParallelTests; diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..9071bcc --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,22 @@ +# Test against the latest version of this Node.js version +environment: + matrix: + - nodejs_version: "8" + - nodejs_version: "9" + - nodejs_version: "10" + +# Install scripts. (runs after repo cloning) +install: + - ps: Install-Product node $env:nodejs_version + - npm install + +# Post-install test scripts. +test_script: + # Output useful info for debugging. + - node --version + - npm --version + # run tests + - npm run test:ci + +# Don't actually build. +build: off diff --git a/bin-helper.js b/bin-helper.js deleted file mode 100644 index 253604d..0000000 --- a/bin-helper.js +++ /dev/null @@ -1,119 +0,0 @@ -'use strict'; - -import assert from 'assert'; -import path from 'path'; -import Reporter from './lib/reporter'; -import {createInstance as createRunnerInstance} from './lib/runner'; -import prepareRequire from './lib/utils/prepare-require'; -import RequireCacheWatcher from './lib/utils/require-cache-watcher'; -import { - patch as patchGlobalHooks, - restore as restoreGlobalHooks -} from './lib/utils/hooks'; - -import { - addTest, - runTests, - setOptions as setWatcherOptions -} from './lib/watcher'; - -// files lookup in mocha is complex, so it's better to just run original code -import {lookupFiles as mochaLookupFiles} from 'mocha/lib/utils'; -import processRequireOption from './lib/utils/process-require-option'; - -export default function binHelper(options) { - process.setMaxListeners(0); - - if (typeof options.compilers === 'string') { - options.compilers = [options.compilers]; - } - - const extensions = ['js']; - (options.compilers || []).forEach(compiler => { - const [ext, mod] = compiler.split(':'); - let compilerMod = mod; - - if (mod[0] === '.') { - compilerMod = path.join(process.cwd(), mod); - } - - require(prepareRequire(compilerMod)); - extensions.push(ext); - }); - - // --no-timeouts option - if (typeof options.timeouts === 'boolean') { - options.enableTimeouts = options.timeouts; - } - - // require --require'd files - processRequireOption(options); - - // default files to test/*.{js,coffee} - const patterns = (options._ || []).slice(2); - if (!patterns.length) { - patterns.push('test'); - } - - // get test files with original mocha utils.lookupFiles() function - let files = []; - patterns.forEach(testPath => { - try { - files = files.concat(mochaLookupFiles(testPath, extensions, options.recursive)); - } catch (ex) { - if (ex.message.startsWith('cannot resolve path')) { - console.error(`Warning: Could not find any test files matching pattern: ${testPath}`); // eslint-disable-line no-console - return; - } - - throw ex; - } - }); - - assert(files.length, 'No test files found'); - - // time to create our own runner - const customRunner = createRunnerInstance(); - - // watcher monitors running files - setWatcherOptions({ - maxParallelTests: options.maxParallel, - retryCount: options.retry - }); - - // require(testFile) needs some global hooks (describe, it etc) - patchGlobalHooks(); - - const cacheWatcher = new RequireCacheWatcher; - cacheWatcher.start(); - - files.forEach(file => { - // does this file have a syntax error? - // require() will show that - const absFilePath = path.resolve(file); - require(absFilePath); - - addTest(absFilePath); - }); - - // okay, all files are valid JavaScript - // now it's time for mocha to set its own global hooks - restoreGlobalHooks(); - - // also we need to delete files from require.cache - // which are involved into all tests - const cacheMark = cacheWatcher.getStateMark(); - cacheWatcher.flushRequireCache(cacheMark); - - runTests({ - options: Object.assign({}, options, { - reporterName: options.R || options.reporter, - reporter: Reporter, - testsLength: files.length - }) - }); - - return new Promise((resolve) => { - customRunner.on('end', resolve); - }); -} diff --git a/bin/mocha-parallel-tests b/bin/mocha-parallel-tests deleted file mode 100755 index 1204436..0000000 --- a/bin/mocha-parallel-tests +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -import yargs from 'yargs'; -import binHelper from '../bin-helper'; -import getOptions from 'mocha/bin/options'; - -// support --opts -// getOptions() changes process.argv -getOptions(); - -const argv = yargs.parse(process.argv); - -function onExit(failsOccured) { - if (argv.exit === undefined) { - process.exit(failsOccured); - } else { - // if --no-exit argv is present wait for exit event - process.on('exit', () => { - process.exit(failsOccured); - }); - } -} - -binHelper(argv).then(({failsOccured, reporter}) => { - // wait for reporter to finish its operations - // exit with code equal to number of failed tests - process.nextTick(() => { - if (reporter.done) { - reporter.done(failsOccured, onExit); - } else { - onExit(failsOccured); - } - }); -}); diff --git a/dist/.gitignore b/dist/.gitignore deleted file mode 100644 index 390db8e..0000000 --- a/dist/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -* - -!.gitignore -!.npmignore \ No newline at end of file diff --git a/dist/.npmignore b/dist/.npmignore deleted file mode 100644 index e69de29..0000000 diff --git a/lib/reporter.js b/lib/reporter.js deleted file mode 100644 index 3a69eab..0000000 --- a/lib/reporter.js +++ /dev/null @@ -1,224 +0,0 @@ -/** - * Important thing is that no event is fired until `end` happens. - * This is because of `retry` option which appeared in 0.2 version - * - * In fact `retry` changes everything: - * in 0.1 mocha-parallel-tests could emit events as soon as they appear. - * The only limitation is some kind of `currently being executed file`, i.e. - * we can't emit events in the middle of another test file being processed - * - * In 0.2 `retry` options appears and error can happen after 2 successful tests in one suite. - * Also by this time other files could've finished executing so it's safe to show their events - * So `mocha-parallel-tests` waits for `end` event and emits accumulated events then - */ -'use strict'; - -import path from 'path'; -import debug from 'debug'; -import MochaBaseReporter from 'mocha/lib/reporters/base'; -import {getInstance as getRunnerInstance} from './runner'; -import {stdStreamsEmitter} from './watcher'; - -const RUNNER_EVENTS = [ - 'suite', - 'suite end', - 'test', - 'pending', - 'pass', - 'test end', - 'fail', - 'failRetry' -]; - -const debugLog = debug('mocha-parallel-tests:reporter'); -let reporterInstance; -let testsFinished = 0; -let failsOccured = 0; - -// save original standard streams methods -// because lib/watcher.js overwites them -const originalWrites = { - stderr: process.stderr.write.bind(process.stderr), - stdout: process.stdout.write.bind(process.stdout) -}; - -// some console.logs are fired before instance of Reporter is created -// so it's better to have some kind of hash table with intercepted messages -const stdMessagesMap = new Map; -stdStreamsEmitter.on('message', function (data) { - if (!stdMessagesMap.has(data.file)) { - stdMessagesMap.set(data.file, []); - } - - const fileMessages = stdMessagesMap.get(data.file); - fileMessages.push({ - streamName: data.streamName, - message: data.message, - timestamp: Date.now() - }); - - stdMessagesMap.set(data.file, fileMessages); -}); - -class AbstractReporter extends MochaBaseReporter { - constructor(runner, options = {}) { - super(runner); - - this._runnerWrapped = getRunnerInstance(); - this._runner = runner; - this._options = options; - this._eventsNotEmited = []; // array events - - // create "real" reporter to output our runner events - this._setReporterOnce(); - - this._detectFile(); - this._listenToRunnerEvents(); - - // start queue - this._runnerWrapped.start(); - } - - _detectFile() { - if (this._runner.suite.suites.length) { - this._testFile = this._runner.suite.suites[0].file; - this._relativeFilePath = path.relative(__filename, this._testFile); - - // if fail happens and another try is available - // clear intercepted messages - stdStreamsEmitter.on('fail', file => stdMessagesMap.delete(file)); - } - } - - _setReporterOnce() { - if (reporterInstance) { - return; - } - - this._options.reporterName = this._options.reporterName || 'spec'; - let UserReporter; - - if (typeof this._options.reporterName === 'function') { - UserReporter = this._options.reporterName; - } else { - const reporterTryPaths = [ - `mocha/lib/reporters/${this._options.reporterName}`, - this._options.reporterName, - path.resolve(process.cwd(), this._options.reporterName) - ]; - - for (let reporterPath of reporterTryPaths) { - try { - UserReporter = require(reporterPath); - break; - } catch (evt) { - // pass - } - } - } - - if (!UserReporter) { - throw new Error(`Invalid reporter "${this._options.reporterName}"`); - } - - reporterInstance = new UserReporter(this._runnerWrapped, this._options); - } - - _storeEventData({eventType, args}) { - debugLog(`${eventType} event fired (${this._relativeFilePath})`); - - this._eventsNotEmited.push({ - type: eventType, - data: args, - timestamp: Date.now() - }); - } - - _listenToRunnerEvents() { - for (let eventType of RUNNER_EVENTS) { - this._runner.on(eventType, (...args) => { - this._storeEventData({eventType, args}); - - if (eventType === 'fail') { - failsOccured++; - } else if (eventType === 'failRetry') { - failsOccured--; - this._runnerWrapped.emit(eventType, ...args); - } - }); - } - - this._runner.on('end', () => { - debugLog(`end event fired (${this._relativeFilePath})`); - - // combine stored events and intercepted messages - const allEvents = []; - - // first append all runner events - this._eventsNotEmited.forEach((eventData, index) => { - allEvents.push({ - type: 'runnerEvent', - payload: { - type: eventData.type, - data: eventData.data - }, - meta: { - index, - timestamp: eventData.timestamp - } - }); - }); - - // then append all standard streams messages - (stdMessagesMap.get(this._testFile) || []).forEach((data, index) => { - allEvents.push({ - type: 'stdStreamMessage', - payload: { - streamName: data.streamName, - message: data.message - }, - meta: { - index, - timestamp: data.timestamp - } - }); - }); - - // sort all events by timestamp and re-emit - allEvents.sort((a, b) => { - const timestampDiff = a.meta.timestamp - b.meta.timestamp; - - if (timestampDiff) { - return timestampDiff; - } - - const aEventWeight = (a.type === 'runnerEvent') ? 1 : 0; - const bEventWeight = (b.type === 'runnerEvent') ? 1 : 0; - const weightDiff = aEventWeight - bEventWeight; - - if (weightDiff) { - return weightDiff; - } - - return a.meta.index - b.meta.index; - }).forEach(eventData => { - if (eventData.type === 'runnerEvent') { - this._runnerWrapped.emit(eventData.payload.type, ...eventData.payload.data); - } else if (eventData.type === 'stdStreamMessage') { - originalWrites[eventData.payload.streamName](eventData.payload.message); - } - }); - - testsFinished++; - - if (testsFinished === this._options.testsLength) { - debugLog(`This is the end: ${testsFinished}/${this._options.testsLength}`); - this._runnerWrapped.end(failsOccured, reporterInstance); - } else { - debugLog(`This is still not the end: ${testsFinished}/${this._options.testsLength}`); - } - }); - } -} - -export default AbstractReporter; diff --git a/lib/runner.js b/lib/runner.js deleted file mode 100644 index 5687c0d..0000000 --- a/lib/runner.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -import assert from 'assert'; -import Mocha from 'mocha'; -import RunnerWrapped from './utils/runner-wrapped'; - -let runnerInstance; - -export const createInstance = () => { - assert(!runnerInstance, 'Runner instance has already been created. Use getInstance() instead'); - - const mocha = new Mocha; - runnerInstance = new RunnerWrapped(mocha.suite, 0); - - return runnerInstance; -}; - -export const getInstance = () => { - assert(runnerInstance, 'No runner instance is available. Run createInstance() first'); - return runnerInstance; -}; diff --git a/lib/utils/hooks.js b/lib/utils/hooks.js deleted file mode 100644 index 21c894b..0000000 --- a/lib/utils/hooks.js +++ /dev/null @@ -1,46 +0,0 @@ -'use strict'; - -import debug from 'debug'; - -const hooks = [ - 'afterEach', - 'after', - 'beforeEach', - 'before', - 'describe', - 'context', - 'it', - 'setup', - 'specify', - 'suiteSetup', - 'suiteTeardown', - 'suite', - 'teardown', - 'test', - 'run', - 'xcontext', - 'xdescribe', - 'xit', - 'xspecify' -]; - -const debugLog = debug('mocha-parallel-tests:hooks'); -const noop = () => {}; - -export const patch = () => { - debugLog('Patch global hooks'); - - for (let hook of hooks) { - global[hook] = noop; - global[hook].only = noop; - global[hook].skip = noop; - } -}; - -export const restore = () => { - debugLog('Restore global hooks'); - - for (let hook of hooks) { - delete global[hook]; - } -}; diff --git a/lib/utils/prepare-require.js b/lib/utils/prepare-require.js deleted file mode 100644 index 9435542..0000000 --- a/lib/utils/prepare-require.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -/** - * Mocha is often passed files to require through its options - * One should detect under the hood if it's an existing file or a module - * This file helps with this problem - */ -import {resolve} from 'path'; -import {statSync} from 'fs'; - -const existsSync = (path) => { - try { - statSync(path); - return true; - } catch (ex) { - return false; - } -}; - -export default function (requiredFile) { - const fileExists = existsSync(requiredFile) || existsSync(`${requiredFile}.js`); - return fileExists ? resolve(requiredFile) : requiredFile; -} diff --git a/lib/utils/process-require-option.js b/lib/utils/process-require-option.js deleted file mode 100644 index 4cc58d8..0000000 --- a/lib/utils/process-require-option.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -import prepareRequire from './prepare-require'; - -const originalRequire = require; - -export default function ({require}) { - if (!require) { - return; - } - - const requiredFiles = Array.isArray(require) ? require : [require]; - for (let requiredFile of requiredFiles) { - originalRequire(prepareRequire(requiredFile)); - } -} diff --git a/lib/utils/require-cache-watcher.js b/lib/utils/require-cache-watcher.js deleted file mode 100644 index d7e56f2..0000000 --- a/lib/utils/require-cache-watcher.js +++ /dev/null @@ -1,66 +0,0 @@ -'use strict'; - -import debug from 'debug'; -import {relative} from 'path'; - -const debugLog = debug('mocha-parallel-tests:cache'); - -/** - * It's important to watch for require.cache during mocha tests run - * This helper is here to help: it starts watching required files when - * you call start(), stops watching when you call stop() and flushes them - * from require.cache when you call flushRequireCache() - */ -const difference = (a, b) => { - const aSet = new Set(a); - const bSet = new Set(b); - - const diffElementsSet = new Set( - [ - ...a.filter(x => !bSet.has(x)), - ...b.filter(x => !aSet.has(x)), - ] - ); - - return [...diffElementsSet]; -}; - -class RequireCacheWatcher { - start() { - this._cached = this._getRequireCache(); - this._namedMarks = new Map; - } - - getStateMark() { - const mark = `mark_${Date.now()}_${Math.random()}`; - this._namedMarks.set(mark, this._getRequireCache()); - - return mark; - } - - flushRequireCache(stateMark) { - const markRelatedCache = this._namedMarks.get(stateMark); - const diff = difference(this._cached, markRelatedCache); - - for (let filePath of diff) { - const isInternalDependency = this._isInternalDependency(filePath); - - if (!isInternalDependency && !/\.node$/.test(filePath)) { - debugLog(`Flush ${filePath} from cache`); - delete require.cache[filePath]; - } - } - } - - _getRequireCache() { - return Object.keys(require.cache); - } - - _isInternalDependency(filePath) { - // "../../node_modules/moment/..." -> "mode_modules/moment/..." - const relativePath = relative(__filename, filePath).replace(/\.\.\//g, ''); - return relativePath.includes('node_modules', 1); - } -} - -export default RequireCacheWatcher; diff --git a/lib/utils/runner-wrapped.js b/lib/utils/runner-wrapped.js deleted file mode 100644 index bb64966..0000000 --- a/lib/utils/runner-wrapped.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict'; - -import debug from 'debug'; -import MochaRunner from 'mocha/lib/runner'; - -const debugLog = debug('mocha-parallel-tests:runner-wrapped'); - -export default class RunnerWrapped extends MochaRunner { - constructor(suite, delay) { - super(suite, delay); - - // this is default mocha runner behaviour - // when runner is created in mocha.run() - this.ignoreLeaks = true; - - this._started = false; - } - - start(test) { - if (this._started) { - return; - } - - this._started = true; - this.emit('start', test); - } - - end(failsOccured, reporter) { - this.emit('end', {failsOccured, reporter}); - } - - /** - * Emit events - * - * @param {String} evt - event title - * @param {Object} test - test data - */ - emit(...args) { - debugLog(`Emit ${args[0]} event`); - - // emit data - super.emit.apply(this, args); - return this; - } -} diff --git a/lib/watcher.js b/lib/watcher.js deleted file mode 100644 index a00329b..0000000 --- a/lib/watcher.js +++ /dev/null @@ -1,321 +0,0 @@ -'use strict'; - -import EventEmitter from 'events'; -import path from 'path'; -import debug from 'debug'; -import Mocha from 'mocha'; -import RequireCacheWatcher from './utils/require-cache-watcher'; - -const debugLog = debug('mocha-parallel-tests:watcher'); -const cacheWatcher = new RequireCacheWatcher; -const pendingTests = []; -const testsList = []; - -let maxParallel = Number.POSITIVE_INFINITY; -let retryCount = 0; -let ignoreStdOutput = false; - -const runners = new Map; -const testFilesFailes = new Map; - -const streams = ['stderr', 'stdout']; -const originalWrites = {}; - -export const stdStreamsEmitter = new EventEmitter; -stdStreamsEmitter.setMaxListeners(0); // Disable EventEmitter memory leak warning - -// empty function -const noop = () => {}; - -const getRunnerId = (testFile, suiteIndex) => { - return `${testFile}:${suiteIndex}`; -}; - -/** - * Get intersection between arrays - * - * @param {Array} a - * @param {Array} b - * @return {Array} - */ -const intersection = (a, b) => { - const bSet = new Set(b); - const commonElementsSet = new Set(a.filter(x => bSet.has(x))); - - return [...commonElementsSet]; -}; - -const getSuiteFile = executionStack => { - const stackFiles = executionStack.split('\n').reduce((stackFiles, chunk) => { - const matches = chunk.match(/\((.+?):[\d]+:[\d]+\)/); - - if (matches && !stackFiles.includes(matches[1])) { - stackFiles.push(matches[1]); - } - - return stackFiles; - }, []); - - const commonFiles = intersection(stackFiles, testsList); - return commonFiles.length ? commonFiles[0] : null; -}; - -const getSuiteHumanIndex = (suiteIndex) => { - return (suiteIndex === -1) ? '(all)' : `#${suiteIndex}`; -}; - -const scheduleMochaRun = ({file, suiteIndex = -1, options, throttledCalls}) => { - const relativeFile = path.relative(__filename, file); - const fileRunners = []; - const suiteHumanIndex = getSuiteHumanIndex(suiteIndex); - - debugLog(`Run mocha suite ${suiteHumanIndex} from file: ${relativeFile}`); - - // start ignoring std output - ignoreStdOutput = true; - - const stateMark = cacheWatcher.getStateMark(); - cacheWatcher.flushRequireCache(stateMark); - - const mocha = new Mocha(); - mocha.addFile(file); - - /** - * Suites are stored inside `mocha.suite.suites` array - * but it's populated only after mocha.run() is called. - * - * The fact is that when mocha.run() is called - * it loads test files first with mocha.loadFiles() - * - * What mocha.loadFiles() does is: it iterates over all test files - * and emits 3 events: "pre-require", "require" and "post-require" - * - * "pre-require" is somewhat intresting to us: - * it populates hook functions needed for suite execution (before, beforeAll etc) - * - * "require" and "post-require" are not so much intresting but "require" requires test files - * which leads to populating `suites` property of `mocha.suite`. - * - * At the same time nothing has happened to this moment, so it's safe to just stop here - * and realize how much suites does the file have - * - * loadFiles() requires test files which can also require files - * these files should be then removed from require cache - * @see https://github.com/yandex/mocha-parallel-tests/issues/39 - */ - mocha.loadFiles(); - debugLog(`Suites length is ${mocha.suite.suites.length} for ${file}`); - - // stop watching require.cache changes - const beforeTestsRunCacheStateMark = cacheWatcher.getStateMark(); - - // stop ignoring std output - ignoreStdOutput = false; - - const onEnd = suiteIndex => { - const suiteHumanIndex = getSuiteHumanIndex(suiteIndex); - - return () => { - debugLog(`Suite ${suiteHumanIndex} processed (success): ${relativeFile}`); - - const runnerId = getRunnerId(file, suiteIndex); - runners.delete(runnerId); - - // run pending tests if any - if (runners.size || pendingTests.length) { - runTestsRecursive({options, throttledCalls}); - } - }; - }; - - const onFail = suiteIndex => { - const runnerId = getRunnerId(file, suiteIndex); - const suiteHumanIndex = getSuiteHumanIndex(suiteIndex); - - return testArg => { - if (!testArg.file && testArg.type !== 'hook') { - return; - } - - debugLog(`Suite ${suiteHumanIndex} processed (fail): ${relativeFile}`); - - testFilesFailes.set( - runnerId, - testFilesFailes.get(runnerId) + 1 - ); - - // if number of fails of this file exceeds retryCount, do nothing - // otherwise re-add this file into queue - if (testFilesFailes.get(runnerId) <= retryCount) { - originalWrites.stderr(`[${relativeFile}]:[${suiteIndex}] try #${testFilesFailes.get(runnerId)} failed: ${testArg.err.message}\n`); - - // notify reporter about retry - fileRunners[suiteIndex].emit('failRetry', file); - - // stop listening to `end` event - fileRunners[suiteIndex].removeAllListeners('end'); - - // and mock all further events - fileRunners[suiteIndex].emit = noop; - - // send event to reporter constructor - // so that it can clear intercepted messages - stdStreamsEmitter.emit('fail', file); - - debugLog(`Test failed, re-run it: ${relativeFile}`); - runners.delete(runnerId); - - // remove test file from require.cache otherwise it won't run - delete require.cache[file]; - - // re-add test file back to queue - addTest(file, suiteIndex); - - // re-run pending tests - runTestsRecursive({options, throttledCalls}); - } - }; - }; - - // files without test suites have zero length of `mocha.suite.suites` array - const testsSuites = Math.max(mocha.suite.suites.length, 1); - - if (testsSuites > 1 && suiteIndex === -1) { - // if file contains more than 1 test suite, options.testsLength should be increased - // otherwise reporter doesn't know anything about this file and could emit end before - // it should actually be emitted - options.testsLength += testsSuites - 1; - } - - for (let i = 0; i < testsSuites; i++) { - if (suiteIndex !== -1 && suiteIndex !== i) { - continue; - } - - /** - * Suites are being populated by calling describe - * Describe(), before() and after() are set via global.describe in mocha itself - * The only way we can re-run describes is to clear cache associated with the test file - */ - cacheWatcher.flushRequireCache(beforeTestsRunCacheStateMark); - - const mocha = new Mocha(options); - mocha.addFile(file); - - for (let {method, args} of throttledCalls) { - mocha[method](...args); - } - - /** - * This is a simple hack to let all describes inside one file run in parallel. - * Mocha emits 3 hooks: "pre-require" to populate hooks, "require" which does nothing - * but require(testFile) is happening under the hood and "post-require" which is also - * useless. Luckily it's useful for us: it's possible to change suites number inside - * of post-require handler. Using this we can set `mocha` from the 1st loop to run - * only 1st suite, `mocha` from the 2nd loop to run 2nd etc - */ - mocha.suite.on('post-require', () => { - // delete all suites except the one with current index - if (mocha.suite.suites.length) { - mocha.suite.suites = [mocha.suite.suites[i]]; - } - }); - - const runner = mocha.run(); - - // delayed tests emit `waiting` event - // however simple javascript files are executed without timeout - // and by this time `end` event has already happened - // we also can't subscribe to `start` event because it is synchronous - // the only way to know does this file contain tests is `total` property of runner object - if (runner.total) { - runner - .on('end', onEnd(i)) - .on('fail', onFail(i)); - } else { - setImmediate(onEnd(i)); - } - - fileRunners.push(runner); - } - - return fileRunners; -}; - -// save stream original functions for further calls -streams.forEach(streamName => { - const stream = process[streamName]; - originalWrites[streamName] = stream.write.bind(stream); - - // mute standard streams - // also replace process.stdout.write with process.stderr.write - // because this is current mocha behaviour - stream.write = arg => { - // sometimes we need to ignore data which is written to std output - // for instance when suites number calculation is in progress - if (!ignoreStdOutput) { - const currentExecStack = new Error().stack; - const suiteTestFile = getSuiteFile(currentExecStack, arg); - - if (suiteTestFile) { - stdStreamsEmitter.emit('message', { - streamName, - file: suiteTestFile, - message: arg - }); - } else { // this write is from reporter - originalWrites[streamName](arg); - } - } - - return stream; - }; -}); - -const runTestsRecursive = ({options, throttledCalls}) => { - while (pendingTests.length) { - if (runners.size === maxParallel) { - debugLog(`Hit maximum parallel tests running number (${maxParallel}), wait`); - break; - } - - const {file: testFile, suiteIndex} = pendingTests.shift(); - const fileRunners = scheduleMochaRun({ - options, - throttledCalls, - suiteIndex, - file: testFile - }); - - debugLog(`Chose test file from queue: ${path.relative(__filename, testFile)}`); - - fileRunners.forEach((runner, i) => { - const runnerId = getRunnerId(testFile, i); - - testFilesFailes.set(runnerId, testFilesFailes.get(runnerId) || 0); - runners.set(runnerId, runner); - }); - } -}; - -export const setOptions = options => { - maxParallel = options.maxParallelTests; - retryCount = options.retryCount || 0; -}; - -export const addTest = (file, suiteIndex = -1) => { - pendingTests.push({file, suiteIndex}); - testsList.push(file); -}; - -export const runTests = ({ - options, - throttledCalls = [] -}) => { - cacheWatcher.start(); - - runTestsRecursive({ - options, - throttledCalls - }); -}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b877dc9 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3492 @@ +{ + "name": "mocha-parallel-tests", + "version": "2.0.0-alpha.3", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/circular-json": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@types/circular-json/-/circular-json-0.4.0.tgz", + "integrity": "sha512-7+kYB7x5a7nFWW1YPBh3KxhwKfiaI4PbZ1RvzBU91LZy7lWJO822CI+pqzSre/DZ7KsCuMKdHnLHHFu8AyXbQg==", + "dev": true + }, + "@types/debug": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-0.0.30.tgz", + "integrity": "sha512-orGL5LXERPYsLov6CWs3Fh6203+dXzJkR7OnddIr2514Hsecwc8xRpzCapshBbKFImCsvS/mk6+FWiN5LyZJAQ==", + "dev": true + }, + "@types/mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-YeDiSEzznwZwwp766SJ6QlrTyBYUGPSIwmREHVTmktUYiT/WADdWtpt9iH0KuUSf8lZLdI4lP0X6PBzPo5//JQ==", + "dev": true + }, + "@types/node": { + "version": "8.10.16", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.16.tgz", + "integrity": "sha512-KlK7YiZXSY8E6v8E4+cCor9IT071bfZrfYqKf0SEj8SJ0Qk4DEz1sgL02Wt6mebNNM9d7870PEoJRHAsUcJPrw==", + "dev": true + }, + "@types/yargs": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-11.0.0.tgz", + "integrity": "sha512-vFql3tOxs6clgh+WVoLW3nOkNGCdeKsMU6mQZkOerJpV/CR9Xc1c1lZ+kYU+hNSobrQIOcNovWfPFDJIhcG5Pw==", + "dev": true + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "acorn": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz", + "integrity": "sha512-jd5MkIUlbbmb07nXH0DT3y7rDVtkzDi4XZOUVWAer8ajmF/DTSSbl5oNFyDOl/OXA33Bl79+ypHhl2pN20VeOQ==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "3.3.0" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "ajv-keywords": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "dev": true + }, + "ansi-escapes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "dev": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.6" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "1.0.3" + } + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "1.0.3" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", + "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + } + }, + "babel-core": { + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "babel-generator": "6.26.1", + "babel-helpers": "6.24.1", + "babel-messages": "6.23.0", + "babel-register": "6.26.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "convert-source-map": "1.5.1", + "debug": "2.6.9", + "json5": "0.5.1", + "lodash": "4.17.10", + "minimatch": "3.0.4", + "path-is-absolute": "1.0.1", + "private": "0.1.8", + "slash": "1.0.0", + "source-map": "0.5.7" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "requires": { + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "detect-indent": "4.0.0", + "jsesc": "1.3.0", + "lodash": "4.17.10", + "source-map": "0.5.7", + "trim-right": "1.0.1" + } + }, + "babel-helper-builder-react-jsx": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz", + "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "esutils": "2.0.2" + } + }, + "babel-helpers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", + "dev": true + }, + "babel-plugin-transform-react-jsx": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz", + "integrity": "sha1-hAoCjn30YN/DotKfDA2R9jduZqM=", + "dev": true, + "requires": { + "babel-helper-builder-react-jsx": "6.26.0", + "babel-plugin-syntax-jsx": "6.18.0", + "babel-runtime": "6.26.0" + } + }, + "babel-register": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "dev": true, + "requires": { + "babel-core": "6.26.3", + "babel-runtime": "6.26.0", + "core-js": "2.5.6", + "home-or-tmp": "2.0.0", + "lodash": "4.17.10", + "mkdirp": "0.5.1", + "source-map-support": "0.4.18" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.6", + "regenerator-runtime": "0.11.1" + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "lodash": "4.17.10" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "debug": "2.6.9", + "globals": "9.18.0", + "invariant": "2.2.4", + "lodash": "4.17.10" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "esutils": "2.0.2", + "lodash": "4.17.10", + "to-fast-properties": "1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "boom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "dev": true, + "requires": { + "hoek": "4.2.1" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "buffer-from": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", + "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + } + } + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, + "cheerio": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", + "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", + "dev": true, + "requires": { + "css-select": "1.2.0", + "dom-serializer": "0.1.0", + "entities": "1.1.1", + "htmlparser2": "3.9.2", + "lodash.assignin": "4.2.0", + "lodash.bind": "4.2.1", + "lodash.defaults": "4.2.0", + "lodash.filter": "4.6.0", + "lodash.flatten": "4.4.0", + "lodash.foreach": "4.5.0", + "lodash.map": "4.6.0", + "lodash.merge": "4.6.1", + "lodash.pick": "4.4.0", + "lodash.reduce": "4.6.0", + "lodash.reject": "4.6.0", + "lodash.some": "4.6.0" + } + }, + "ci-info": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.3.tgz", + "integrity": "sha512-SK/846h/Rcy8q9Z9CAwGBLfCJ6EkjJWdpelWDufQpqVDYq2Wnnv8zlSO6AMQap02jvhVruKKpEtQOufo3pFhLg==", + "dev": true + }, + "circular-json": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.4.tgz", + "integrity": "sha512-vnJA8KS0BfOihugYEUkLRcnmq21FbuivbxgzDLXNs3zIk4KllV4Mx4UuTzBXht9F00C7QfD1YqMXg1zP6EXpig==" + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "requires": { + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "color-convert": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "requires": { + "graceful-readlink": "1.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "1.0.0", + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "typedarray": "0.0.6" + } + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, + "convert-source-map": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", + "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", + "dev": true + }, + "core-js": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.6.tgz", + "integrity": "sha512-lQUVfQi0aLix2xpyjrrJEvfuYCqPc/HwmTKsC/VNf8q0zsjX7SQZtp4+oRONN5Tsur9GDETPjj+Ub2iDiGZfSQ==", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "4.1.3", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, + "cryptiles": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "dev": true, + "requires": { + "boom": "5.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "dev": true, + "requires": { + "hoek": "4.2.1" + } + } + } + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "requires": { + "boolbase": "1.0.0", + "css-what": "2.1.0", + "domutils": "1.5.1", + "nth-check": "1.0.1" + } + }, + "css-what": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", + "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + } + }, + "dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "5.0.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.1", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "rimraf": "2.6.2" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "requires": { + "repeating": "2.0.1" + } + }, + "diff": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", + "integrity": "sha1-JLuwAcSn1VIhaefKvbLCgU7ZHPQ=", + "dev": true + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "2.0.2" + } + }, + "dom-serializer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "dev": true, + "requires": { + "domelementtype": "1.1.3", + "entities": "1.1.1" + }, + "dependencies": { + "domelementtype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", + "dev": true + } + } + }, + "domelementtype": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", + "dev": true + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "requires": { + "domelementtype": "1.3.0" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0.1.0", + "domelementtype": "1.3.0" + } + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "dev": true, + "requires": { + "iconv-lite": "0.4.23" + } + }, + "entities": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", + "dev": true + }, + "es6-promise": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz", + "integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", + "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", + "dev": true, + "requires": { + "ajv": "5.5.2", + "babel-code-frame": "6.26.0", + "chalk": "2.4.1", + "concat-stream": "1.6.2", + "cross-spawn": "5.1.0", + "debug": "3.1.0", + "doctrine": "2.1.0", + "eslint-scope": "3.7.1", + "eslint-visitor-keys": "1.0.0", + "espree": "3.5.4", + "esquery": "1.0.1", + "esutils": "2.0.2", + "file-entry-cache": "2.0.0", + "functional-red-black-tree": "1.0.1", + "glob": "7.1.2", + "globals": "11.5.0", + "ignore": "3.3.8", + "imurmurhash": "0.1.4", + "inquirer": "3.3.0", + "is-resolvable": "1.1.0", + "js-yaml": "3.11.0", + "json-stable-stringify-without-jsonify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.10", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "natural-compare": "1.4.0", + "optionator": "0.8.2", + "path-is-inside": "1.0.2", + "pluralize": "7.0.0", + "progress": "2.0.0", + "regexpp": "1.1.0", + "require-uncached": "1.0.3", + "semver": "5.5.0", + "strip-ansi": "4.0.0", + "strip-json-comments": "2.0.1", + "table": "4.0.2", + "text-table": "0.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + }, + "globals": { + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.5.0.tgz", + "integrity": "sha512-hYyf+kI8dm3nORsiiXUQigOU62hDLfJ9G01uyGMxhc6BKsircrUhC4uJPQPUSuq2GrTmiiEt7ewxlMdBewfmKQ==", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "eslint-scope": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", + "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "dev": true, + "requires": { + "esrecurse": "4.2.1", + "estraverse": "4.2.0" + } + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "requires": { + "acorn": "5.5.3", + "acorn-jsx": "3.0.1" + } + }, + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "4.2.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "4.2.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "requires": { + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "dev": true + }, + "external-editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "dev": true, + "requires": { + "chardet": "0.4.2", + "iconv-lite": "0.4.23", + "tmp": "0.0.33" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fbjs": { + "version": "0.8.16", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz", + "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=", + "dev": true, + "requires": { + "core-js": "1.2.7", + "isomorphic-fetch": "2.2.1", + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "promise": "7.3.1", + "setimmediate": "1.0.5", + "ua-parser-js": "0.7.18" + }, + "dependencies": { + "core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=", + "dev": true + } + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "1.3.0", + "object-assign": "4.1.1" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "2.0.0" + } + }, + "flat-cache": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", + "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "dev": true, + "requires": { + "circular-json": "0.3.3", + "del": "2.2.2", + "graceful-fs": "4.1.11", + "write": "0.2.1" + }, + "dependencies": { + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + } + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "dev": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.18" + } + }, + "fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "jsonfile": "4.0.0", + "universalify": "0.1.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.2" + } + }, + "fsu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fsu/-/fsu-1.1.1.tgz", + "integrity": "sha512-xQVsnjJ/5pQtcKh+KjUoZGzVWn4uNkchxTF6Lwjr4Gf7nQr8fmUfhKJ62zE77+xQg9xnxi5KUps7XGs+VC986A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + } + } + }, + "get-caller-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=" + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "arrify": "1.0.1", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "dev": true, + "requires": { + "ajv": "5.5.2", + "har-schema": "2.0.0" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true + }, + "hawk": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "dev": true, + "requires": { + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.1", + "sntp": "2.1.0" + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "hoek": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==", + "dev": true + }, + "home-or-tmp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "htmlparser2": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", + "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", + "dev": true, + "requires": { + "domelementtype": "1.3.0", + "domhandler": "2.4.2", + "domutils": "1.5.1", + "entities": "1.1.1", + "inherits": "2.0.3", + "readable-stream": "2.3.6" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.14.1" + } + }, + "husky": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-0.14.3.tgz", + "integrity": "sha512-e21wivqHpstpoiWA/Yi8eFti8E+sQDSS53cpJsPptPs295QTOQR0ZwnHo2TXy1XOpZFD9rPOd3NpmqTK6uMLJA==", + "dev": true, + "requires": { + "is-ci": "1.1.0", + "normalize-path": "1.0.0", + "strip-indent": "2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, + "requires": { + "safer-buffer": "2.1.2" + } + }, + "ignore": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.8.tgz", + "integrity": "sha512-pUh+xUQQhQzevjRHHFqqcTy0/dP/kS9I8HSrUydhihjuD09W6ldVWFtIrwhXdUJHis3i2rZNqEHpZH/cbinFbg==", + "dev": true + }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "dev": true, + "requires": { + "ansi-escapes": "3.1.0", + "chalk": "2.4.1", + "cli-cursor": "2.1.0", + "cli-width": "2.2.0", + "external-editor": "2.2.0", + "figures": "2.0.0", + "lodash": "4.17.10", + "mute-stream": "0.0.7", + "run-async": "2.3.0", + "rx-lite": "4.0.8", + "rx-lite-aggregates": "4.0.8", + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "through": "2.3.8" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "1.3.1" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + }, + "is-ci": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.1.0.tgz", + "integrity": "sha512-c7TnwxLePuqIlxHgr7xtxzycJPegNHFuIrBkwbf8hc58//+Op1CqFkyS+xnIMkwn9UsJIwc174BIjkyBmSpjKg==", + "dev": true, + "requires": { + "ci-info": "1.1.3" + } + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "1.0.1" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "1.0.2" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", + "dev": true, + "requires": { + "node-fetch": "1.7.3", + "whatwg-fetch": "2.0.4" + } + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", + "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", + "dev": true, + "requires": { + "argparse": "1.0.10", + "esprima": "4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jszip": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.1.5.tgz", + "integrity": "sha512-5W8NUaFRFRqTOL7ZDDrx5qWHJyBXy6velVudIzQUSoqAAYqzSh2Z7/m0Rf1QbmQJccegD0r+YZxBjzqoBiEeJQ==", + "dev": true, + "requires": { + "core-js": "2.3.0", + "es6-promise": "3.0.2", + "lie": "3.1.1", + "pako": "1.0.6", + "readable-stream": "2.0.6" + }, + "dependencies": { + "core-js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.3.0.tgz", + "integrity": "sha1-+rg/uwstjchfpjbEudNMdUIMbWU=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "requires": { + "invert-kv": "1.0.0" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" + } + }, + "lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=", + "dev": true, + "requires": { + "immediate": "3.0.6" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "2.0.0", + "path-exists": "3.0.0" + } + }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "requires": { + "lodash._basecopy": "3.0.1", + "lodash.keys": "3.1.2" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basecreate": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", + "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash.assignin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", + "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=", + "dev": true + }, + "lodash.bind": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", + "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=", + "dev": true + }, + "lodash.create": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", + "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", + "dev": true, + "requires": { + "lodash._baseassign": "3.2.0", + "lodash._basecreate": "3.0.3", + "lodash._isiterateecall": "3.0.9" + } + }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", + "dev": true + }, + "lodash.filter": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", + "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=", + "dev": true + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", + "dev": true + }, + "lodash.foreach": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=", + "dev": true + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" + } + }, + "lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=", + "dev": true + }, + "lodash.merge": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz", + "integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==", + "dev": true + }, + "lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=", + "dev": true + }, + "lodash.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=", + "dev": true + }, + "lodash.reject": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", + "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=", + "dev": true + }, + "lodash.some": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", + "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=", + "dev": true + }, + "loose-envify": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", + "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", + "dev": true, + "requires": { + "js-tokens": "3.0.2" + } + }, + "lru-cache": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "make-error": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz", + "integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==", + "dev": true + }, + "mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "requires": { + "mimic-fn": "1.2.0" + } + }, + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "requires": { + "mime-db": "1.33.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", + "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.9.0", + "debug": "2.6.8", + "diff": "3.2.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.1", + "growl": "1.9.2", + "he": "1.1.1", + "json3": "3.3.2", + "lodash.create": "3.1.1", + "mkdirp": "0.5.1", + "supports-color": "3.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "diff": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", + "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", + "dev": true + }, + "glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "supports-color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", + "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + } + } + }, + "mocha-jenkins-reporter": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/mocha-jenkins-reporter/-/mocha-jenkins-reporter-0.3.12.tgz", + "integrity": "sha512-iSRJRA/3zJgyZ6MajwyRcjPBK5axu53huRVJ/W2OPI17YqbO62By88ucZmrNvWB1qY96GjUtkA4s6Fv4CGl0sA==", + "dev": true, + "requires": { + "diff": "1.0.7", + "mkdirp": "0.5.1", + "mocha": "3.5.3", + "xml": "1.0.1" + } + }, + "mocha-teamcity-reporter": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mocha-teamcity-reporter/-/mocha-teamcity-reporter-2.4.0.tgz", + "integrity": "sha512-DSLAuh/0DiYUBbKKaCgRxoBmzedVmSZjw+nzB+ql9mg9l5MstmcR2xzTlGqrqC+NXkInWTZ4CQ1FjBmosev3vA==", + "dev": true, + "requires": { + "mocha": "3.5.3" + } + }, + "mochawesome": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mochawesome/-/mochawesome-3.0.2.tgz", + "integrity": "sha512-2fdl+Y5rSPlslVmuBRjT3829GYj/hh7Cyber+EkIubD60W44EkMR58jPHeopG5eBGgk3HWRl6Rm/g2knDeSbEA==", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "chalk": "2.4.1", + "diff": "3.5.0", + "json-stringify-safe": "5.0.1", + "lodash": "4.17.10", + "mochawesome-report-generator": "3.1.2", + "strip-ansi": "4.0.0", + "uuid": "3.2.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "mochawesome-report-generator": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/mochawesome-report-generator/-/mochawesome-report-generator-3.1.2.tgz", + "integrity": "sha512-tR6yZ72ZzoQrTEhEu04SUQQLDpBbrLoNdbnouygH2hTAcf9EkkMi7+vtWz0PYIc1EUzvb+kUBfZZUjG4zu2OOA==", + "dev": true, + "requires": { + "chalk": "2.4.1", + "dateformat": "3.0.3", + "fs-extra": "4.0.3", + "fsu": "1.1.1", + "lodash.isfunction": "3.0.9", + "opener": "1.4.3", + "prop-types": "15.6.1", + "react": "16.3.2", + "react-dom": "16.3.2", + "tcomb": "3.2.27", + "tcomb-validation": "3.4.1", + "validator": "9.4.1", + "yargs": "10.1.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + }, + "yargs": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-10.1.2.tgz", + "integrity": "sha512-ivSoxqBGYOqQVruxD35+EyCFDYNEFL/Uo6FcOnz+9xZdZzK0Zzw4r4KhbrME1Oo2gOggwJod2MnsdamSG7H9ig==", + "dev": true, + "requires": { + "cliui": "4.1.0", + "decamelize": "1.2.0", + "find-up": "2.1.0", + "get-caller-file": "1.0.2", + "os-locale": "2.1.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "8.1.0" + } + }, + "yargs-parser": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-8.1.0.tgz", + "integrity": "sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==", + "dev": true, + "requires": { + "camelcase": "4.1.0" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "dev": true, + "requires": { + "encoding": "0.1.12", + "is-stream": "1.1.0" + } + }, + "node-gyp": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.6.2.tgz", + "integrity": "sha1-m/vlRWIoYoSDjnUOrAUpWFP6HGA=", + "dev": true, + "requires": { + "fstream": "1.0.11", + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "nopt": "3.0.6", + "npmlog": "4.1.2", + "osenv": "0.1.5", + "request": "2.86.0", + "rimraf": "2.6.2", + "semver": "5.3.0", + "tar": "2.2.1", + "which": "1.3.0" + }, + "dependencies": { + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true + } + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1.1.1" + } + }, + "normalize-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-1.0.0.tgz", + "integrity": "sha1-MtDkcvkf80VwHBWoMRAY07CpA3k=", + "dev": true + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "2.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "nth-check": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", + "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", + "dev": true, + "requires": { + "boolbase": "1.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "1.2.0" + } + }, + "opener": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.4.3.tgz", + "integrity": "sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=", + "dev": true + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "requires": { + "execa": "0.7.0", + "lcid": "1.0.0", + "mem": "1.1.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-limit": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", + "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", + "requires": { + "p-try": "1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "1.2.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "pako": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", + "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "progress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "dev": true + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "requires": { + "asap": "2.0.6" + } + }, + "prop-types": { + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.1.tgz", + "integrity": "sha512-4ec7bY1Y66LymSUOH/zARVYObB23AT2h8cf6e/O6ZALB/N0sqZFEx7rq6EYPX2MkOdKORuooI/H5k9TlR4q7kQ==", + "dev": true, + "requires": { + "fbjs": "0.8.16", + "loose-envify": "1.3.1", + "object-assign": "4.1.1" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/react/-/react-16.3.2.tgz", + "integrity": "sha512-o5GPdkhciQ3cEph6qgvYB7LTOHw/GB0qRI6ZFNugj49qJCFfgHwVNjZ5u+b7nif4vOeMIOuYj3CeYe2IBD74lg==", + "dev": true, + "requires": { + "fbjs": "0.8.16", + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "prop-types": "15.6.1" + } + }, + "react-dom": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.3.2.tgz", + "integrity": "sha512-MMPko3zYncNrz/7gG17wJWUREZDvskZHXOwbttzl0F0L3wDmToyuETuo/r8Y5yvDejwYcRyWI1lvVBjLJWFwKA==", + "dev": true, + "requires": { + "fbjs": "0.8.16", + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "prop-types": "15.6.1" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "regexpp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", + "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "1.0.2" + } + }, + "request": { + "version": "2.86.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.86.0.tgz", + "integrity": "sha512-BQZih67o9r+Ys94tcIW4S7Uu8pthjrQVxhsZ/weOwHbDfACxvIyvnAbzFQxjy1jMtvFSzv5zf4my6cZsJBbVzw==", + "dev": true, + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.7.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.18", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.6.0", + "uuid": "3.2.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "0.1.0", + "resolve-from": "1.0.1" + } + }, + "resolve": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", + "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", + "dev": true, + "requires": { + "path-parse": "1.0.5" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "2.0.1", + "signal-exit": "3.0.2" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "2.1.0" + } + }, + "rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "dev": true + }, + "rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "dev": true, + "requires": { + "rx-lite": "4.0.8" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "selenium-webdriver": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", + "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", + "dev": true, + "requires": { + "jszip": "3.1.5", + "rimraf": "2.6.2", + "tmp": "0.0.30", + "xml2js": "0.4.19" + }, + "dependencies": { + "tmp": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", + "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", + "dev": true, + "requires": { + "os-tmpdir": "1.0.2" + } + } + } + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0" + } + }, + "sntp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "dev": true, + "requires": { + "hoek": "4.2.1" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "0.5.7" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", + "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", + "dev": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "3.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "dev": true, + "requires": { + "ajv": "5.5.2", + "ajv-keywords": "2.1.1", + "chalk": "2.4.1", + "lodash": "4.17.10", + "slice-ansi": "1.0.0", + "string-width": "2.1.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "dev": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "tcomb": { + "version": "3.2.27", + "resolved": "https://registry.npmjs.org/tcomb/-/tcomb-3.2.27.tgz", + "integrity": "sha512-XWdJW7F/M3YzXhDEUP8ycmNWoYymBtsHwCHoda0YF44RthJsls95TqDrmpAlC1sB/KXaCvkdBlcNRq+AaV6klA==", + "dev": true + }, + "tcomb-validation": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tcomb-validation/-/tcomb-validation-3.4.1.tgz", + "integrity": "sha512-urVVMQOma4RXwiVCa2nM2eqrAomHROHvWPuj6UkDGz/eb5kcy0x6P0dVt6kzpUZtYMNoAqJLWmz1BPtxrtjtrA==", + "dev": true, + "requires": { + "tcomb": "3.2.27" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "1.0.2" + } + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "tough-cookie": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "dev": true, + "requires": { + "punycode": "1.4.1" + } + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "ts-node": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-6.0.3.tgz", + "integrity": "sha512-ARaOMNFEPKg2ZuC1qJddFvHxHNFVckR0g9xLxMIoMqSSIkDc8iS4/LoV53EdDWWNq2FGwqcEf0bVVGJIWpsznw==", + "dev": true, + "requires": { + "arrify": "1.0.1", + "chalk": "2.4.1", + "diff": "3.5.0", + "make-error": "1.3.4", + "minimist": "1.2.0", + "mkdirp": "0.5.1", + "source-map-support": "0.5.6", + "yn": "2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.6.tgz", + "integrity": "sha512-N4KXEz7jcKqPf2b2vZF11lQIz9W5ZMuUcIOGj243lduidkf2fjkVKJS9vNxVWn3u/uxX38AcE8U9nnH9FPcq+g==", + "dev": true, + "requires": { + "buffer-from": "1.0.0", + "source-map": "0.6.1" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "tslib": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.1.tgz", + "integrity": "sha512-avfPS28HmGLLc2o4elcc2EIq2FcH++Yo5YxpBZi9Yw93BCTGFthI4HPE4Rpep6vSYQaK8e69PelM44tPj+RaQg==", + "dev": true + }, + "tslint": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.10.0.tgz", + "integrity": "sha1-EeJrzLiK+gLdDZlWyuPUVAtfVMM=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "builtin-modules": "1.1.1", + "chalk": "2.4.1", + "commander": "2.15.1", + "diff": "3.5.0", + "glob": "7.1.2", + "js-yaml": "3.11.0", + "minimatch": "3.0.4", + "resolve": "1.7.1", + "semver": "5.5.0", + "tslib": "1.9.1", + "tsutils": "2.27.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "tsutils": { + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.27.0.tgz", + "integrity": "sha512-JcyX25oM9pFcb3zh60OqG1St8p/uSqC5Bgipdo3ieacB/Ao4dPhm7hAtKT9NrEu23CyYrrgJPV3CqYfo+/+T4w==", + "dev": true, + "requires": { + "tslib": "1.9.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "typescript": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.3.tgz", + "integrity": "sha512-K7g15Bb6Ra4lKf7Iq2l/I5/En+hLIHmxWZGq3D4DIRNFxMNV6j2SHSvDOqs2tGd4UvD/fJvrwopzQXjLrT7Itw==", + "dev": true + }, + "ua-parser-js": { + "version": "0.7.18", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.18.tgz", + "integrity": "sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA==", + "dev": true + }, + "universalify": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", + "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", + "dev": true + }, + "validator": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/validator/-/validator-9.4.1.tgz", + "integrity": "sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA==", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + } + }, + "whatwg-fetch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==", + "dev": true + }, + "which": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "requires": { + "isexe": "2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "wide-align": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "dev": true, + "requires": { + "string-width": "1.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + } + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "1.0.1" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "2.1.1" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "0.5.1" + } + }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", + "dev": true + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "dev": true, + "requires": { + "sax": "1.2.4", + "xmlbuilder": "9.0.7" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", + "dev": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + }, + "yargs": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.0.0.tgz", + "integrity": "sha512-Rjp+lMYQOWtgqojx1dEWorjCofi1YN7AoFvYV7b1gx/7dAAeuI4kN5SZiEvr0ZmsZTOpDRcCqrpI10L31tFkBw==", + "requires": { + "cliui": "4.1.0", + "decamelize": "1.2.0", + "find-up": "2.1.0", + "get-caller-file": "1.0.2", + "os-locale": "2.1.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "9.0.2" + } + }, + "yargs-parser": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", + "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", + "requires": { + "camelcase": "4.1.0" + } + }, + "yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true + } + } +} diff --git a/package.json b/package.json index 70bc698..2b8a61e 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,23 @@ { "name": "mocha-parallel-tests", - "version": "1.2.10", + "version": "2.0.0-alpha.3", "homepage": "https://github.com/yandex/mocha-parallel-tests", "description": "Run mocha tests in parallel", - "main": "./dist/api.js", - "bin": "./dist/bin/mocha-parallel-tests", - "author": "Maria Motkina ", + "main": "./dist/main/mocha.js", + "module": "./dist/main/mocha.js", + "bin": { + "mocha-parallel-tests": "dist/bin/cli.js" + }, "contributors": [ "Dmitrii Sorin ", "Gabriel Fürstenheim", "Kirill Molchanov ", + "Maria Motkina ", "Nikolay Basov", "Vadim Kolontsov " ], "dependencies": { + "circular-json": "^0.5.4", "debug": "3.1.0", "yargs": "11.0.0" }, @@ -24,47 +28,59 @@ "bugs": { "url": "https://github.com/yandex/mocha-parallel-tests/issues" }, + "files": [ + "bin", + "dist", + "lib" + ], + "types": "dist/main/mocha.d.ts", "keywords": [ + "mocha", "mocha-parallel-tests", "parallel tests", "unit tests", "tests" ], "scripts": { - "clean": "rm -rf ./node_modules", - "lint": "./node_modules/.bin/eslint lib/ test/ bin/ *.js", - "prepublish": "npm run prepublish:entrypoint && npm run prepublish:libs:clean && npm run prepublish:libs:build && npm run prepublish:bin:clean && npm run prepublish:bin:build", - "prepublish:entrypoint": "./node_modules/.bin/babel *.js --out-dir dist/", - "prepublish:bin:clean": "rm -fr ./dist/bin", - "prepublish:bin:build": "mkdir ./dist/bin && ./node_modules/.bin/babel bin/mocha-parallel-tests --out-file dist/bin/mocha-parallel-tests && chmod +x dist/bin/mocha-parallel-tests", - "prepublish:libs:clean": "rm -fr ./dist/lib", - "prepublish:libs:build": "./node_modules/.bin/babel lib/ --out-dir ./dist/lib", - "test": "npm run lint && npm run prepublish && ./test/index.sh" + "build": "tsc && chmod +x dist/bin/cli.js", + "clean": "rm -fr dist", + "eslint": "eslint -c test/.eslintrc.js test/ *.js", + "lint": "npm run eslint && npm run tslint", + "precommit": "npm run lint", + "prepublishOnly": "npm run clean && npm run build", + "pretest": "npm run prepublishOnly", + "test": "bash test/index.sh", + "test:ci": "npm run tslint && npm run test:mocha-3 && npm run test:mocha-4 && npm run test:mocha-5", + "test:mocha-3": "npm install mocha@3 --no-save && npm test", + "test:mocha-4": "npm install mocha@4 --no-save && npm test", + "test:mocha-5": "npm install mocha@5 --no-save && npm test", + "tslint": "tslint -p . -c tslint.json 'src/**/*.ts'" }, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" }, "peerDependencies": { - "mocha": "^3" + "mocha": "3.0.0 - 5.x.x" }, "devDependencies": { - "babel-cli": "^6.24.1", - "babel-plugin-add-module-exports": "^0.2.1", - "babel-plugin-array-includes": "^2.0.3", - "babel-plugin-transform-es2015-destructuring": "^6.23.0", - "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", - "babel-plugin-transform-es2015-parameters": "^6.24.1", - "babel-plugin-transform-es2015-spread": "^6.22.0", - "babel-plugin-transform-object-rest-spread": "^6.23.0", + "@types/circular-json": "^0.4.0", + "@types/debug": "0.0.30", + "@types/mocha": "^5.2.0", + "@types/node": "^8.10.7", + "@types/yargs": "^11.0.0", "babel-plugin-transform-react-jsx": "^6.24.1", - "babel-register": "^6.24.1", + "babel-register": "^6.26.0", "cheerio": "^0.22.0", "eslint": "^4.19.0", + "husky": "^0.14.3", "mocha-jenkins-reporter": "^0.3.7", "mocha-teamcity-reporter": "^2.2.2", "mochawesome": "^3.0.2", "node-gyp": "^3.6.0", - "selenium-webdriver": "^3.3.0" + "selenium-webdriver": "^3.3.0", + "ts-node": "^6.0.3", + "tslint": "^5.10.0", + "typescript": "^2.8.3" } } diff --git a/src/bin/cli.ts b/src/bin/cli.ts new file mode 100755 index 0000000..a04e1ac --- /dev/null +++ b/src/bin/cli.ts @@ -0,0 +1,170 @@ +#!/usr/bin/env node + +import * as yargs from 'yargs'; + +import MochaWrapper from '../main/mocha'; +import { setProcessExitListeners } from '../util'; + +import applyAsyncOnly from './options/async-only'; +import applyBail from './options/bail'; +import applyCheckLeaks from './options/check-leaks'; +import applyCompilers from './options/compilers'; +import applyDelay from './options/delay'; +import applyExit from './options/exit'; +import applyForbidOnly from './options/forbid-only'; +import applyForbidPending from './options/forbid-pending'; +import applyFullTrace from './options/full-trace'; +import applyMaxParallel from './options/max-parallel'; +import applyNoTimeouts from './options/no-timeouts'; +import applyReporter from './options/reporter'; +import applyReporterOptions from './options/reporter-options'; +import applyRequires from './options/require'; +import getFilesList from './options/rest'; +import applyRetries from './options/retries'; +import applySlow from './options/slow'; +import applyTimeout from './options/timeout'; + +setProcessExitListeners(); + +// --opts changes process.argv so it should be executed first +// tslint:disable-next-line:no-var-requires +const getOptions = require('mocha/bin/options'); +getOptions(); + +const mocha = new MochaWrapper(); +const argv = yargs + .option('async-only', { + alias: 'A', + boolean: true, + }) + .option('bail', { + alias: 'b', + boolean: true, + }) + .option('check-leaks', { + boolean: true, + }) + .option('compilers', { + default: [], + }) + .option('delay', { + boolean: true, + }) + .option('exit', { + boolean: true, + }) + .option('forbidOnly', { + boolean: true, + }) + .option('forbidPending', { + boolean: true, + }) + .option('full-trace', { + boolean: true, + }) + .option('max-parallel', { + number: true, + }) + .option('recursive', { + boolean: true, + }) + .option('reporter', { + alias: 'R', + default: 'spec', + string: true, + }) + .option('reporter-options', { + alias: 'O', + string: true, + }) + .option('retries', { + number: true, + }) + .option('require', { + alias: 'r', + default: [], + }) + .option('slow', { + alias: 's', + number: true, + }) + .option('timeout', { + alias: 't', + number: true, + }) + .parse(process.argv); + +// --async-only +applyAsyncOnly(mocha, argv.asyncOnly); + +// --bail +applyBail(mocha, argv.bail); + +// --check-leaks +applyCheckLeaks(mocha, argv.checkLeaks); + +// --compilers +const { compilers, extensions } = applyCompilers(argv.compilers); +mocha.addCompilersForSubprocess(compilers); + +// --delay +applyDelay(mocha, argv.delay); + +// --forbid-only +applyForbidOnly(mocha, argv.forbidOnly); + +// --forbid-pending +applyForbidPending(mocha, argv.forbidPending); + +// --full-trace +applyFullTrace(mocha, argv.fullTrace); + +// --max-parallel +applyMaxParallel(mocha, argv.maxParallel); + +// --no-timeouts +applyNoTimeouts(mocha, argv.timeouts); + +// --r, --require +const requires = applyRequires(argv.require); +mocha.addRequiresForSubprocess(requires); + +// --reporter-options +const reporterOptions = argv.reporterOptions !== undefined + ? applyReporterOptions(argv.reporterOptions) + : {}; + +// --reporter +applyReporter(mocha, argv.reporter, reporterOptions); + +// --retries +applyRetries(mocha, argv.retries); + +// --slow +applySlow(mocha, argv.slow); + +// --timeout +applyTimeout(mocha, argv.timeout); + +// find files +const files = getFilesList(argv._.slice(2), extensions, argv.recursive); + +if (!files.length) { + // tslint:disable-next-line:no-console + console.error('No test files found'); + process.exit(1); +} + +for (const file of files) { + mocha.addFile(file); +} + +// --exit +const onComplete = applyExit(argv.exit); + +const isTypescriptRun = argv.$0.endsWith('.ts'); +if (isTypescriptRun) { + mocha.setTypescriptRunMode(); +} + +mocha.run(onComplete); diff --git a/src/bin/options/async-only.ts b/src/bin/options/async-only.ts new file mode 100644 index 0000000..067e305 --- /dev/null +++ b/src/bin/options/async-only.ts @@ -0,0 +1,7 @@ +import * as Mocha from 'mocha'; + +export default function applyAsyncOnly(mocha: Mocha, asyncOnly: boolean) { + if (asyncOnly) { + (mocha as any).asyncOnly(); + } +} diff --git a/src/bin/options/bail.ts b/src/bin/options/bail.ts new file mode 100644 index 0000000..1ba6fca --- /dev/null +++ b/src/bin/options/bail.ts @@ -0,0 +1,5 @@ +import * as Mocha from 'mocha'; + +export default function applyBail(mocha: Mocha, bail: boolean) { + (mocha.suite as any).bail(bail); +} diff --git a/src/bin/options/check-leaks.ts b/src/bin/options/check-leaks.ts new file mode 100644 index 0000000..f801ff7 --- /dev/null +++ b/src/bin/options/check-leaks.ts @@ -0,0 +1,7 @@ +import * as Mocha from 'mocha'; + +export default function applyCheckLeaks(mocha: Mocha, checkLeaks: boolean) { + if (checkLeaks) { + mocha.checkLeaks(); + } +} diff --git a/src/bin/options/compilers.ts b/src/bin/options/compilers.ts new file mode 100644 index 0000000..690e2e6 --- /dev/null +++ b/src/bin/options/compilers.ts @@ -0,0 +1 @@ +export { applyCompilers as default } from '../../util'; diff --git a/src/bin/options/delay.ts b/src/bin/options/delay.ts new file mode 100644 index 0000000..ba0b85e --- /dev/null +++ b/src/bin/options/delay.ts @@ -0,0 +1 @@ +export { applyDelay as default } from '../../util'; diff --git a/src/bin/options/exit.ts b/src/bin/options/exit.ts new file mode 100644 index 0000000..932e640 --- /dev/null +++ b/src/bin/options/exit.ts @@ -0,0 +1,20 @@ +// tslint:disable:no-console +function exitLater(code) { + process.on('exit', function onExit() { + process.exit(Math.min(code, 255)); + }); +} + +function exit(code) { + const clampedCode = Math.min(code, 255); + + // that's what mocha does + console.log(''); + console.error(''); + + process.exitCode = clampedCode; +} + +export default function applyExit(shouldExitImmediately: any) { + return shouldExitImmediately ? exit : exitLater; +} diff --git a/src/bin/options/forbid-only.ts b/src/bin/options/forbid-only.ts new file mode 100644 index 0000000..c472ead --- /dev/null +++ b/src/bin/options/forbid-only.ts @@ -0,0 +1,7 @@ +import * as Mocha from 'mocha'; + +export default function applyForbidOnly(mocha: Mocha, forbidOnly: boolean) { + if (forbidOnly) { + (mocha as any).forbidOnly(); + } +} diff --git a/src/bin/options/forbid-pending.ts b/src/bin/options/forbid-pending.ts new file mode 100644 index 0000000..29e7cf6 --- /dev/null +++ b/src/bin/options/forbid-pending.ts @@ -0,0 +1,7 @@ +import * as Mocha from 'mocha'; + +export default function applyForbidPending(mocha: Mocha, forbidPending: boolean) { + if (forbidPending) { + (mocha as any).forbidPending(); + } +} diff --git a/src/bin/options/full-trace.ts b/src/bin/options/full-trace.ts new file mode 100644 index 0000000..f85c967 --- /dev/null +++ b/src/bin/options/full-trace.ts @@ -0,0 +1,7 @@ +import * as Mocha from 'mocha'; + +export default function applyFullTrace(mocha: Mocha, fullTrace: boolean) { + if (fullTrace) { + (mocha as any).fullTrace(); + } +} diff --git a/src/bin/options/max-parallel.ts b/src/bin/options/max-parallel.ts new file mode 100644 index 0000000..bb08bd0 --- /dev/null +++ b/src/bin/options/max-parallel.ts @@ -0,0 +1,7 @@ +import * as Mocha from 'mocha'; + +export default function applyMaxParallel(mocha: Mocha, maxParallel?: number) { + if (maxParallel !== undefined) { + (mocha as any).setMaxParallel(maxParallel); + } +} diff --git a/src/bin/options/no-timeouts.ts b/src/bin/options/no-timeouts.ts new file mode 100644 index 0000000..a671d0e --- /dev/null +++ b/src/bin/options/no-timeouts.ts @@ -0,0 +1 @@ +export { applyNoTimeouts as default } from '../../util'; diff --git a/src/bin/options/reporter-options.ts b/src/bin/options/reporter-options.ts new file mode 100644 index 0000000..543e80a --- /dev/null +++ b/src/bin/options/reporter-options.ts @@ -0,0 +1,23 @@ +import * as assert from 'assert'; +import { ICLIReporterOptions } from '../../interfaces'; + +export default function applyReporterOptions(reporterOptions: string) { + assert.strictEqual(typeof reporterOptions, 'string', '--reporter-options option can be specified only once'); + const output: ICLIReporterOptions = {}; + + reporterOptions.split(',').forEach((opt) => { + const L = opt.split('='); + + if (L.length > 2 || L.length === 0) { + throw new Error(`Invalid reporter option "${opt}"`); + } + + if (L.length === 2) { + output[L[0]] = L[1]; + } else { + output[L[0]] = true; + } + }); + + return output; +} diff --git a/src/bin/options/reporter.ts b/src/bin/options/reporter.ts new file mode 100644 index 0000000..ae04c54 --- /dev/null +++ b/src/bin/options/reporter.ts @@ -0,0 +1,28 @@ +import * as assert from 'assert'; +import * as Mocha from 'mocha'; +import { join } from 'path'; +import { ICLIReporterOptions } from '../../interfaces'; + +export default function applyReporter(mocha: Mocha, reporter: any, reporterOptions: ICLIReporterOptions) { + assert.strictEqual(typeof reporter, 'string', '--reporter option can be specified only once'); + mocha.reporter(reporter, reporterOptions); + + // load reporter + let Reporter: any; + + // required reporter can be in the process CWD + const cwd = process.cwd(); + module.paths.push(cwd, join(cwd, 'node_modules')); + + try { + Reporter = require(`mocha/lib/reporters/${reporter}`); + } catch (ex) { + try { + Reporter = require(reporter); + } catch (ex) { + throw new Error(`Reporter "${reporter}" does not exist`); + } + } + + return Reporter; +} diff --git a/src/bin/options/require.ts b/src/bin/options/require.ts new file mode 100644 index 0000000..85a1ec4 --- /dev/null +++ b/src/bin/options/require.ts @@ -0,0 +1 @@ +export { applyRequires as default } from '../../util'; diff --git a/src/bin/options/rest.ts b/src/bin/options/rest.ts new file mode 100644 index 0000000..dcd6193 --- /dev/null +++ b/src/bin/options/rest.ts @@ -0,0 +1,26 @@ +// tslint:disable-next-line:no-var-requires +const { lookupFiles: mochaLookupFiles } = require('mocha/lib/utils'); + +export default function getFilesList(rest: string[], extensions: string[], recursive: boolean): string[] { + const filesList = rest.length ? rest : ['test']; + const output: string[] = []; + + for (const file of filesList) { + try { + const newFiles = mochaLookupFiles(file, extensions, recursive) as string[] | string; + const newFilesList = Array.isArray(newFiles) ? newFiles : [newFiles]; + + output.push(...newFilesList); + } catch (err) { + if (err.message.startsWith('cannot resolve path')) { + // tslint:disable-next-line:no-console + console.error(`Warning: Could not find any test files matching pattern: ${file}`); + continue; + } + + throw err; + } + } + + return output; +} diff --git a/src/bin/options/retries.ts b/src/bin/options/retries.ts new file mode 100644 index 0000000..97539d1 --- /dev/null +++ b/src/bin/options/retries.ts @@ -0,0 +1,7 @@ +import * as Mocha from 'mocha'; + +export default function applyRetries(mocha: Mocha, retries: number) { + if (retries) { + (mocha.suite as any).retries(retries); + } +} diff --git a/src/bin/options/slow.ts b/src/bin/options/slow.ts new file mode 100644 index 0000000..5927d24 --- /dev/null +++ b/src/bin/options/slow.ts @@ -0,0 +1,7 @@ +import * as Mocha from 'mocha'; + +export default function applySlow(mocha: Mocha, slow: number) { + if (slow) { + (mocha.suite as any).slow(slow); + } +} diff --git a/src/bin/options/timeout.ts b/src/bin/options/timeout.ts new file mode 100644 index 0000000..6b8c025 --- /dev/null +++ b/src/bin/options/timeout.ts @@ -0,0 +1 @@ +export { applyTimeouts as default } from '../../util'; diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..9a3ebe5 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,15 @@ +export const RUNNABLE_IPC_PROP = '__mpt_randomId__'; +export const SUBPROCESS_RETRIED_SUITE_ID = '__mpt_suiteId_'; + +export const SUITE_OWN_OPTIONS = [ + 'timeout', + 'enableTimeouts', + 'slow', + 'bail', + 'retries', +]; + +export const DEBUG_SUBPROCESS = { + argument: '--debug-mpt-subprocess', + yargs: 'debugMptSubprocess', +}; diff --git a/src/interfaces.ts b/src/interfaces.ts new file mode 100644 index 0000000..6308421 --- /dev/null +++ b/src/interfaces.ts @@ -0,0 +1,100 @@ +import { EventEmitter } from 'events'; +import * as Mocha from 'mocha'; +import { + IContextDefinition, + IRunnable, + IRunner as MochaRunner, + ISuite as MochaSuite, + ITest as MochaTest, + reporters, +} from 'mocha'; + +import { RUNNABLE_IPC_PROP, SUBPROCESS_RETRIED_SUITE_ID } from './config'; + +export type IRunner = MochaRunner & EventEmitter; + +export interface IContext { + test?: ITest | IHook; + _runnable?: ITest | IHook; +} + +export interface IMochaParallelTestsRunnerObject { + [RUNNABLE_IPC_PROP]: string; +} + +export interface ITest extends MochaTest, IMochaParallelTestsRunnerObject { + body: string; + type: 'test'; + file: string; +} + +export interface IRetriedTest extends ITest { + [SUBPROCESS_RETRIED_SUITE_ID]: string; +} + +export class BaseReporter extends reporters.Base { + runner: IRunner; +} + +export interface ISuite extends MochaSuite, IMochaParallelTestsRunnerObject { + _beforeEach: IHook[]; + _beforeAll: IHook[]; + _afterEach: IHook[]; + _afterAll: IHook[]; + + root: boolean; + suites: ISuite[]; + tests: ITest[]; + ctx: IContext; +} + +export interface IHook extends IRunnable, IMochaParallelTestsRunnerObject { + parent: ISuite; + ctx: IContextDefinition; +} + +export interface ISubprocessRunnerMessage { + event: string; + data: any; + type: 'runner'; +} + +export interface ISubprocessOutputMessage { + event: undefined; + data: Buffer; + type: 'stdout' | 'stderr'; +} + +export interface ISubprocessResult { + file: string; + output: Array; + execTime: number; +} + +export interface ISubprocessTestArtifacts { + file: string; + suiteIndex: number; + output: Array; + execTime: number; +} + +export interface IRunnerDecl { + new(suite: ISuite, delay: number): IRunner; +} + +export const Runner: IRunnerDecl = (Mocha as any).Runner; + +export interface ICLIReporterOptions { + [key: string]: string | boolean; +} + +export interface ICLICompilers { + compilers: string[]; + extensions: string[]; +} + +export type Task = () => Promise; +export interface ITaskOutput { + task: Task; + output?: T; +} diff --git a/src/main/mocha.ts b/src/main/mocha.ts new file mode 100644 index 0000000..c7a80bf --- /dev/null +++ b/src/main/mocha.ts @@ -0,0 +1,225 @@ +import * as assert from 'assert'; +import { fork } from 'child_process'; +import * as CircularJSON from 'circular-json'; +import * as debug from 'debug'; +import * as Mocha from 'mocha'; +import { resolve as pathResolve } from 'path'; + +import RunnerMain from './runner'; +import TaskManager from './task-manager'; +import { + removeDebugArgs, + subprocessParseReviver, +} from './util'; + +import { DEBUG_SUBPROCESS, SUITE_OWN_OPTIONS } from '../config'; +import { + IRetriedTest, + ISubprocessOutputMessage, + ISubprocessResult, + ISubprocessRunnerMessage, + ISubprocessTestArtifacts, + ISuite, +} from '../interfaces'; + +const debugLog = debug('mocha-parallel-tests'); + +export default class MochaWrapper extends Mocha { + // as any + private files: string[]; + private isTypescriptRunMode = false; + private maxParallel: number | undefined; + private requires: string[] = []; + private compilers: string[] = []; + + setTypescriptRunMode() { + this.isTypescriptRunMode = true; + } + + /** + * All `--require` options should be applied for subprocesses + */ + addRequiresForSubprocess(requires: string[]) { + this.requires = requires; + } + + /** + * All `--compiler` options should be applied for subprocesses + */ + addCompilersForSubprocess(compilers: string[]) { + this.compilers = compilers; + } + + setMaxParallel(maxParallel: number) { + this.maxParallel = maxParallel; + } + + run(onComplete?: (failures: number) => void): RunnerMain { + const { + asyncOnly, + ignoreLeaks, + forbidOnly, + forbidPending, + fullStackTrace, + hasOnly, // looks like a private mocha API + } = (this as any).options; + + const rootSuite = this.suite as ISuite; + + const runner = new RunnerMain(rootSuite); + (runner as any).ignoreLeaks = ignoreLeaks !== false; + (runner as any).forbidOnly = forbidOnly; + (runner as any).forbidPending = forbidPending; + (runner as any).hasOnly = hasOnly; + (runner as any).fullStackTrace = fullStackTrace; + (runner as any).asyncOnly = asyncOnly; + + const taskManager = new TaskManager(this.maxParallel); + for (const file of this.files) { + const task = () => this.spawnTestProcess(file); + taskManager.add(task); + } + + (this as any).options.files = this.files; + + // Refer to mocha lib/mocha.js run() method for more info here + // @ts-ignore + const reporter = new this._reporter( + runner, + (this as any).options, + ); + + // emit `start` and `suite` events + // so that reporters can record the start time + runner.emitStartEvents(); + + taskManager.run().then((res) => { + const retriedTests: IRetriedTest[] = []; + + // merge data from subprocess tests into the root suite + for (const testTesult of res) { + this.addSubprocessSuites(testTesult); + retriedTests.push(...this.extractSubprocessRetriedTests(testTesult)); + } + + const done = (failures: number) => { + if (reporter.done) { + reporter.done(failures, onComplete); + } else if (onComplete) { + onComplete(failures); + } + }; + + // re-emit events + runner.setTestResults(res, retriedTests, done); + }); + + return runner; + } + + private findRootData(testArtifacts: ISubprocessTestArtifacts) { + const endRunnerMessage = testArtifacts.output.find(({ event, type }) => { + return type === 'runner' && event === 'end'; + }) as ISubprocessRunnerMessage; + + assert( + endRunnerMessage, + `Subprocess ${testArtifacts.file}{${testArtifacts.suiteIndex}} didn't send an "end" message`, + ); + + return endRunnerMessage.data; + } + + private addSubprocessSuites(testArtifacts: ISubprocessTestArtifacts): void { + const rootSuite = this.suite; + const serialized = this.findRootData(testArtifacts); + const { rootSuite: testRootSuite } = CircularJSON.parse(serialized.results, subprocessParseReviver); + + Object.assign(testRootSuite, { + parent: rootSuite, + root: false, + }); + + rootSuite.suites.push(testRootSuite); + } + + private extractSubprocessRetriedTests(testArtifacts: ISubprocessTestArtifacts): IRetriedTest[] { + const serialized = this.findRootData(testArtifacts); + const { retriesTests } = CircularJSON.parse(serialized.retries, subprocessParseReviver); + + return retriesTests as IRetriedTest[]; + } + + private spawnTestProcess(file: string): Promise { + return new Promise((resolve) => { + const extension = this.isTypescriptRunMode ? 'ts' : 'js'; + const runnerPath = pathResolve(__dirname, `../subprocess/runner.${extension}`); + const resolvedFilePath = pathResolve(file); + + const forkArgs: string[] = ['--test', resolvedFilePath]; + for (const option of SUITE_OWN_OPTIONS) { + const suiteProp = `_${option}`; + forkArgs.push(`--${option}`, this.suite[suiteProp]); + } + + for (const requirePath of this.requires) { + forkArgs.push('--require', requirePath); + } + + for (const compilerPath of this.compilers) { + forkArgs.push('--compilers', compilerPath); + } + + if ((this as any).options.delay) { + forkArgs.push('--delay'); + } + + const test = fork(runnerPath, forkArgs, { + // otherwise `--inspect-brk` and other params will be passed to subprocess + execArgv: process.execArgv.filter(removeDebugArgs), + stdio: ['ipc'], + }); + + const executable = this.isTypescriptRunMode ? 'ts-node' : 'node'; + debugLog('Process spawned. You can run it manually with this command:'); + debugLog(`${executable} ${runnerPath} ${forkArgs.concat([DEBUG_SUBPROCESS.argument]).join(' ')}`); + + const output: Array = []; + const startedAt = Date.now(); + + test.on('message', function onMessageHandler({ event, data }) { + output.push({ + data, + event, + type: 'runner', + }); + }); + + test.stdout.on('data', function onStdoutData(data: Buffer) { + output.push({ + data, + event: undefined, + type: 'stdout', + }); + }); + + test.stderr.on('data', function onStderrData(data: Buffer) { + output.push({ + data, + event: undefined, + type: 'stderr', + }); + }); + + test.on('close', (code) => { + debugLog(`Runner exited with code ${code}`); + + resolve({ + execTime: Date.now() - startedAt, + file, + output, + }); + }); + }); + } +} diff --git a/src/main/runner.ts b/src/main/runner.ts new file mode 100644 index 0000000..22b5bb3 --- /dev/null +++ b/src/main/runner.ts @@ -0,0 +1,225 @@ +import * as assert from 'assert'; + +// as any +// const { Runner } = require('mocha'); + +import { RUNNABLE_IPC_PROP, SUBPROCESS_RETRIED_SUITE_ID } from '../config'; +import { + IHook, + IRetriedTest, + ISubprocessTestArtifacts, + ISuite, + ITest, + Runner, +} from '../interfaces'; + +export default class RunnerMain extends Runner { + private rootSuite: ISuite; + private retriedTests: IRetriedTest[] = []; + private subprocessTestResults: ISubprocessTestArtifacts[]; + private onComplete?: (failures: number) => void; + + constructor(rootSuite: ISuite) { + super(rootSuite, 0); + this.rootSuite = rootSuite; + + this.once('end', this.onExecutionComplete); + this.on('fail', this.onFail); + } + + emitStartEvents() { + this.emit('start'); + this.emit('suite', this.rootSuite); + } + + emitFinishEvents() { + this.emit('suite end', this.rootSuite); + this.emit('end'); + } + + setTestResults( + testResults: ISubprocessTestArtifacts[], + retriedTests: IRetriedTest[], + onComplete?: (failures: number) => void, + ) { + this.subprocessTestResults = testResults; + this.onComplete = onComplete; + this.setRetriesTests(retriedTests); + + this.emitRestEvents(); + this.emitFinishEvents(); + } + + private onFail = () => { + this.failures++; + } + + private onExecutionComplete = () => { + if (this.onComplete) { + if ((this as any).forbidOnly && (this as any).hasOnly) { + this.failures += (this as any).stats.tests; + } + if ((this as any).forbidPending) { + this.failures += (this as any).stats.pending; + } + + this.onComplete(this.failures); + } + } + + private setRetriesTests(tests: IRetriedTest[]) { + for (const test of tests) { + const suite = this.findSuiteById(test[SUBPROCESS_RETRIED_SUITE_ID]); + assert(suite, 'Couldn\'t find retried test suite'); + + test.parent = suite!; + this.retriedTests.push(test); + } + } + + private findSuiteById(id: string, rootSuite: ISuite = this.rootSuite): ISuite | null { + if (rootSuite[RUNNABLE_IPC_PROP] === id) { + return rootSuite; + } + + for (const suite of rootSuite.suites) { + const inner = this.findSuiteById(id, suite); + if (inner) { + return inner; + } + } + + return null; + } + + private findRetriedTestById(id: string): ITest | undefined { + return this.retriedTests.find((test) => test[RUNNABLE_IPC_PROP] === id); + } + + private findTestById(id: string, rootSuite: ISuite = this.rootSuite): ITest | null { + for (const test of rootSuite.tests) { + if (test[RUNNABLE_IPC_PROP] === id) { + return test; + } + } + + for (const suite of rootSuite.suites) { + const inner = this.findTestById(id, suite); + if (inner) { + return inner; + } + } + + return null; + } + + private findHookById(id: string, rootSuite: ISuite = this.rootSuite): IHook | null { + for (const hookType of ['_beforeEach', '_beforeAll', '_afterEach', '_afterAll']) { + for (const hook of rootSuite[hookType]) { + if (hook[RUNNABLE_IPC_PROP] === id) { + return hook; + } + } + } + + for (const suite of rootSuite.suites) { + const inner = this.findHookById(id, suite); + if (inner) { + return inner; + } + } + + return null; + } + + /** + * Sometimes mocha "forgets" to replace the test in suite.tests + * Example of this can be a sync test which fails twice and passes on third run + * If the test is executed with `--retries 2` we will get this result + */ + private findForgottenTestById(id: string, rootSuite: ISuite = this.rootSuite): ITest | null { + if (rootSuite.ctx.test && rootSuite.ctx.test[RUNNABLE_IPC_PROP] === id) { + return rootSuite.ctx.test as ITest; + } + + for (const suite of rootSuite.suites) { + const inner = this.findForgottenTestById(id, suite); + if (inner) { + return inner; + } + } + + return null; + } + + private emitRestEvents() { + for (const testArtifacts of this.subprocessTestResults) { + this.emitSubprocessEvents(testArtifacts); + } + } + + private emitSubprocessEvents(testArtifacts: ISubprocessTestArtifacts) { + for (const { event, data, type } of testArtifacts.output) { + if (type === 'runner') { + switch (event) { + case 'start': + case 'end': + // ignore these events from subprocess + break; + + case 'waiting': + const waitingSuite = this.findSuiteById(data.id); + assert(waitingSuite, `Couldn't find suite by id: ${data.id}`); + + this.emit(event, waitingSuite); + break; + + case 'suite': + case 'suite end': { + const suite = this.findSuiteById(data.id); + assert(suite, `Couldn't find suite by id: ${data.id}`); + + this.emit(event, suite); + break; + } + + case 'test': + case 'test end': + case 'pending': + case 'pass': { + const test = this.findTestById(data.id) + || this.findRetriedTestById(data.id) + || this.findForgottenTestById(data.id); + assert(test, `Couldn't find test by id: ${data.id}`); + + this.emit(event, test); + break; + } + + case 'fail': { + const test = this.findTestById(data.id); + const hook = this.findHookById(data.id); + assert(test || hook, `Couldn't find test or hook by id: ${data.id}`); + + this.emit(event, test || hook, data.err); + break; + } + + case 'hook': + case 'hook end': { + const hook = this.findHookById(data.id); + assert(hook, `Couldn't find hook by id: ${data.id}`); + + this.emit(event, hook); + break; + } + + default: + throw new Error(`Unknown event: ${event}`); + } + } else { + process[type].write(data); + } + } + } +} diff --git a/src/main/task-manager.ts b/src/main/task-manager.ts new file mode 100644 index 0000000..6afd0e8 --- /dev/null +++ b/src/main/task-manager.ts @@ -0,0 +1,79 @@ +import * as assert from 'assert'; +import * as EventEmitter from 'events'; +import { cpus } from 'os'; +import { ITaskOutput, Task } from '../interfaces'; + +export default class TaskManager extends EventEmitter { + private maxParallel: number; + private tasks: Array> = []; + private remainingTasks = new Set(); + private processingTasks = new Set(); + + constructor(maxParallel: number = cpus().length) { + super(); + + this.maxParallel = maxParallel || Number.POSITIVE_INFINITY; + this.on('processingFinished', this.onTaskProcessingFinished); + } + + add(task: Task): void { + this.tasks.push({ task }); + this.remainingTasks.add(task); + } + + run(): Promise { + this.runAvailableTasks(); + + return new Promise((resolve) => { + this.on('end', this.onFinishedProcessing(resolve)); + }); + } + + private onTaskProcessingFinished = (finishedTask: Task, output: TaskResult) => { + assert(!this.remainingTasks.has(finishedTask), 'Unknown task, cannot finalize it'); + assert(this.processingTasks.has(finishedTask), 'Task has not been started processing'); + + const taskIndex = this.tasks.findIndex(({ task }) => task === finishedTask); + assert(taskIndex !== -1, 'Unknown task, cannot write its output'); + + this.tasks[taskIndex].output = output; + this.processingTasks.delete(finishedTask); + + this.runAvailableTasks(); + this.emitEndIfAllFinished(); + } + + private onFinishedProcessing = (resolver: (data: TaskResult[]) => void) => () => { + const resolverOutput = this.tasks.map(({ output }) => output!); + resolver(resolverOutput); + } + + private async startTaskProcessing(task: Task) { + assert(this.remainingTasks.has(task), 'Unknown task, cannot process it'); + assert(!this.processingTasks.has(task), 'Task has already started processing'); + + this.remainingTasks.delete(task); + this.processingTasks.add(task); + + const res = await task(); + this.emit('processingFinished', task, res); + } + + private runAvailableTasks() { + for (const task of this.remainingTasks) { + this.startTaskProcessing(task); + + if (this.processingTasks.size >= this.maxParallel) { + break; + } + } + } + + private emitEndIfAllFinished() { + const shouldEmit = this.tasks.every(({ output }) => output !== undefined); + + if (shouldEmit) { + this.emit('end'); + } + } +} diff --git a/src/main/util.ts b/src/main/util.ts new file mode 100644 index 0000000..82f4257 --- /dev/null +++ b/src/main/util.ts @@ -0,0 +1,32 @@ +// @ts-ignore +import { Hook, Suite, Test } from 'mocha'; + +const DEBUG_CLI_ARGS = ['--inspect', '--debug', '--debug-brk', '--inspect-brk']; +const noop = () => null; + +export function removeDebugArgs(arg: string): boolean { + return !DEBUG_CLI_ARGS.includes(arg); +} + +export function subprocessParseReviver(_: string, value: any): any { + if (typeof value !== 'object' || value === null) { + return value; + } + + if (value.type === 'test') { + const test = new Test(value.title, noop); + return Object.assign(test, value); + } + + if (value.type === 'hook') { + const hook = new Hook(value.title, noop); + return Object.assign(hook, value); + } + + if (Array.isArray(value.suites)) { + const suite = new Suite(value.title, {}); + return Object.assign(suite, value); + } + + return value; +} diff --git a/src/subprocess/runner.ts b/src/subprocess/runner.ts new file mode 100755 index 0000000..447c815 --- /dev/null +++ b/src/subprocess/runner.ts @@ -0,0 +1,201 @@ +import * as CircularJSON from 'circular-json'; +import * as Mocha from 'mocha'; +import * as yargs from 'yargs'; + +import { + DEBUG_SUBPROCESS, + RUNNABLE_IPC_PROP, + SUBPROCESS_RETRIED_SUITE_ID, +} from '../config'; +import { + BaseReporter, + IHook, + IRunner, + ISuite, + ITest, +} from '../interfaces'; + +import { SUITE_OWN_OPTIONS } from '../config'; +import { + applyCompilers, + applyDelay, + applyNoTimeouts, + applyRequires, + applyTimeouts, + randomId, +} from '../util'; + +const argv = yargs + .boolean('bail') + .option('compilers', { + array: true, + default: [], + }) + .boolean('delay') + .boolean('enableTimeouts') + .number('slow') + .option('require', { + array: true, + default: [], + }) + .number('retries') + .number('timeout') + .parse(process.argv); + +const debugSubprocess = argv[DEBUG_SUBPROCESS.yargs]; + +class Reporter extends BaseReporter { + /** + * If `--retries N` option is specified runner can emit `test` events + * multiple times for retried test cases. These test cases do not exist + * if the final root suite structure, so we need to store them and return + * to the main process after the end + */ + private runningTests = new Set(); + + constructor(runner: IRunner) { + super(runner); + + runner.on('waiting', this.onRunnerWaiting); + runner.on('start', this.onRunnerStart); + runner.on('end', this.onRunnerEnd); + + runner.on('suite', this.onRunnerSuiteStart); + runner.on('suite end', this.onRunnerSuiteEnd); + + runner.on('test', this.onTestStart); + runner.on('test end', this.onTestEnd); + + runner.on('pass', this.onRunnerPass); + runner.on('fail', this.onRunnerFail); + runner.on('pending', this.onRunnerPending); + + runner.on('hook', this.onRunnerHookStart); + runner.on('hook end', this.onRunnerHookEnd); + } + + private onRunnerStart = () => { + this.notifyParentThroughIPC('start'); + } + + private onRunnerEnd = () => { + const rootSuite = this.runner.suite; + const retriesTests = [...this.runningTests].map((test) => { + return Object.assign(test, { + [SUBPROCESS_RETRIED_SUITE_ID]: test.parent[RUNNABLE_IPC_PROP], + parent: null, + }); + }); + + this.notifyParentThroughIPC('end', { + // can't use the root suite because it will not get revived in the master process + // @see https://github.com/WebReflection/circular-json/issues/44 + results: CircularJSON.stringify({ rootSuite }), + retries: CircularJSON.stringify({ retriesTests }), + }); + } + + private onRunnerSuiteStart = (suite: ISuite) => { + const id = randomId(); + suite[RUNNABLE_IPC_PROP] = id; + + this.notifyParentThroughIPC('suite', { id }); + } + + private onRunnerSuiteEnd = (suite: ISuite) => { + this.notifyParentThroughIPC('suite end', { + id: suite[RUNNABLE_IPC_PROP], + }); + } + + private onRunnerWaiting = (/* rootSuite: ISuite */) => { + this.notifyParentThroughIPC('waiting'); + } + + private onTestStart = (test: ITest) => { + const id = randomId(); + test[RUNNABLE_IPC_PROP] = id; + + this.runningTests.add(test); + this.notifyParentThroughIPC('test', { id }); + } + + private onTestEnd = (test: ITest) => { + this.runningTests.delete(test); + + this.notifyParentThroughIPC('test end', { + id: test[RUNNABLE_IPC_PROP], + }); + } + + private onRunnerPass = (test: ITest) => { + this.notifyParentThroughIPC('pass', { + id: test[RUNNABLE_IPC_PROP], + }); + } + + private onRunnerFail = (test: ITest, err: Error) => { + this.notifyParentThroughIPC('fail', { + err: { + message: err.message, + name: err.name, + stack: err.stack, + }, + id: test[RUNNABLE_IPC_PROP], + }); + } + + private onRunnerPending = (test: ITest) => { + this.notifyParentThroughIPC('pending', { + id: test[RUNNABLE_IPC_PROP], + }); + } + + private onRunnerHookStart = (hook: IHook) => { + const id = hook[RUNNABLE_IPC_PROP] || randomId(); + hook[RUNNABLE_IPC_PROP] = id; + + this.notifyParentThroughIPC('hook', { id }); + } + + private onRunnerHookEnd = (hook: IHook) => { + this.notifyParentThroughIPC('hook end', { + id: hook[RUNNABLE_IPC_PROP], + }); + } + + private notifyParentThroughIPC(event: string, data = {}) { + if (debugSubprocess) { + // tslint:disable-next-line:no-console + console.log({ event, data }); + } else { + process.send!({ event, data }); + } + } +} + +const mocha = new Mocha(); +mocha.addFile(argv.test); + +// --compilers +applyCompilers(argv.compilers); + +// --delay +applyDelay(mocha, argv.delay); + +// --enableTimeouts +applyNoTimeouts(mocha, argv.enableTimeouts); + +// --require +applyRequires(argv.require); + +// --timeout +applyTimeouts(mocha, argv.timeout); + +// apply main process root suite properties +for (const option of SUITE_OWN_OPTIONS) { + const suiteProp = `_${option}`; + mocha.suite[suiteProp] = argv[option]; +} + +mocha.reporter(Reporter).run(); diff --git a/src/util.ts b/src/util.ts new file mode 100644 index 0000000..1ef6e9c --- /dev/null +++ b/src/util.ts @@ -0,0 +1,86 @@ +import { randomBytes } from 'crypto'; +import { existsSync } from 'fs'; +import { join, resolve } from 'path'; +import { ICLICompilers } from './interfaces'; + +export function randomId(): string { + return randomBytes(16).toString('hex'); +} + +export function setProcessExitListeners() { + process.on('unhandledRejection', (reason) => { + // tslint:disable-next-line:no-console + console.error(`Unhaldled asynchronous exception: ${reason.stack}`); + process.exit(1); + }); + + process.on('uncaughtException', (err) => { + // tslint:disable-next-line:no-console + console.error(`Uncaught exception: ${err.stack}`); + process.exit(1); + }); +} + +export function applyRequires(requires: any): string[] { + const requiresList: string[] = Array.isArray(requires) ? requires : [requires]; + const output: string[] = []; + + // required file can be in the process CWD + const cwd = process.cwd(); + module.paths.push(cwd, join(cwd, 'node_modules')); + + for (const mod of requiresList) { + const abs = existsSync(mod) || existsSync(`${mod}.js`); + const requirePath = abs ? resolve(mod) : mod; + + require(requirePath); + output.push(requirePath); + } + + return output; +} + +export function applyCompilers(compilers: any): ICLICompilers { + const compilersList: string[] = Array.isArray(compilers) ? compilers : [compilers]; + const output: ICLICompilers = { + compilers: compilersList, + extensions: ['js'], + }; + + // required compiler can be in the process CWD + const cwd = process.cwd(); + module.paths.push(cwd, join(cwd, 'node_modules')); + + for (const compiler of compilersList) { + const idx = compiler.indexOf(':'); + const ext = compiler.slice(0, idx); + let mod = compiler.slice(idx + 1); + + if (mod.startsWith('.')) { + mod = join(process.cwd(), mod); + } + + require(mod); + output.extensions.push(ext); + } + + return output; +} + +export function applyDelay(mocha: Mocha, delay: boolean) { + if (delay) { + (mocha as any).delay(); + } +} + +export function applyNoTimeouts(mocha: Mocha, allowTimeouts: boolean) { + if (allowTimeouts === false) { + mocha.enableTimeouts(false); + } +} + +export function applyTimeouts(mocha: Mocha, timeout: number) { + if (timeout) { + (mocha.suite as any).timeout(timeout); + } +} diff --git a/test/.eslintrc.js b/test/.eslintrc.js index dc4d385..b8483df 100644 --- a/test/.eslintrc.js +++ b/test/.eslintrc.js @@ -1,8 +1,30 @@ module.exports = { "env": { - "mocha": true + "mocha": true, + "es6": true, + "node": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "sourceType": "module" }, "rules": { + "indent": [ + "error", + 4 + ], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "single" + ], + "semi": [ + "error", + "always" + ], "no-console": 0 } }; diff --git a/test/cli-target/index.sh b/test/cli-target/index.sh index 635638a..d1a97ae 100755 --- a/test/cli-target/index.sh +++ b/test/cli-target/index.sh @@ -1,6 +1,6 @@ #!/bin/bash -OUTPUT=$(dist/bin/mocha-parallel-tests 2>&1) +OUTPUT=$(dist/bin/cli.js 2>&1) MPT_STATUS_CODE=$? node_modules/.bin/mocha 1>/dev/null 2>&1 diff --git a/test/console-log-inject/index.js b/test/console-log-inject/index.js index 338873b..dbfcf63 100755 --- a/test/console-log-inject/index.js +++ b/test/console-log-inject/index.js @@ -5,7 +5,7 @@ var assert = require('assert'); var path = require('path'); var exec = require('child_process').exec; -var libExecutable = path.resolve(__dirname, '../../dist/bin/mocha-parallel-tests'); +var libExecutable = path.resolve(__dirname, '../../dist/bin/cli.js'); exec(libExecutable + ' --timeout 60000 --slow 30000 test/console-log-inject/tests', { cwd: path.resolve(__dirname, '../../') @@ -22,7 +22,7 @@ exec(libExecutable + ' --timeout 60000 --slow 30000 test/console-log-inject/test if (/^suite\s#[\d]+/i.test(chunk)) { logs.push(chunk.toLowerCase()); } - + return logs; }, []); diff --git a/test/delay/README b/test/delay/README new file mode 100644 index 0000000..16b0a1b --- /dev/null +++ b/test/delay/README @@ -0,0 +1 @@ +Support mocha --delay option diff --git a/test/delay/index.js b/test/delay/index.js new file mode 100755 index 0000000..c399329 --- /dev/null +++ b/test/delay/index.js @@ -0,0 +1,30 @@ +#!/usr/bin/env node + +'use strict'; + +const assert = require('assert'); +const { resolve } = require('path'); +const { exec } = require('child_process'); +const libExecutable = resolve(__dirname, '../../dist/bin/cli.js'); + +exec(`${libExecutable} --delay -R test/delay/reporter.js test/delay/index.spec.js`, { + cwd: resolve(__dirname, '../../'), +}, (err, stdout) => { + if (err) { + console.error(err); + process.exit(1); + } + + const runnerEvents = stdout + .toString() + .trim() + .split('\n') + .map((evtName) => evtName.trim()); + + assert.strictEqual(runnerEvents[0], 'start'); + assert.strictEqual(runnerEvents[1], 'suite'); // main process root suite + assert.strictEqual(runnerEvents[2], 'waiting'); // subprocess waiting event + assert.strictEqual(runnerEvents[3], 'suite'); // subprocess root suite + assert.strictEqual(runnerEvents[4], 'suite'); // subprocess top level suite + assert(runnerEvents.includes('pass'), 'Test case was not executed'); +}); diff --git a/test/delay/index.spec.js b/test/delay/index.spec.js new file mode 100644 index 0000000..accecad --- /dev/null +++ b/test/delay/index.spec.js @@ -0,0 +1,7 @@ +setTimeout(() => { + describe('suite', () => { + it('should finish immediately', () => {}); + }); + + run(); +}, 100); diff --git a/test/delay/reporter.js b/test/delay/reporter.js new file mode 100644 index 0000000..59ed300 --- /dev/null +++ b/test/delay/reporter.js @@ -0,0 +1,32 @@ +'use strict'; + +const Base = require('mocha').reporters.Base; + +class Reporter extends Base { + constructor(runner) { + super(runner); + + [ + 'waiting', + 'start', + 'end', + 'suite', + 'suite end', + 'test', + 'test end', + 'pass', + 'fail', + 'pending', + 'hook', + 'hook end', + ].forEach((evtName) => { + runner.on(evtName, this.onRunnerEventFired.bind(this, evtName)); + }); + } + + onRunnerEventFired(evtName) { + console.log(evtName); + } +} + +exports = module.exports = Reporter; diff --git a/test/describe-inside-describe/index.js b/test/describe-inside-describe/index.js index 4ebbaea..efce7a6 100755 --- a/test/describe-inside-describe/index.js +++ b/test/describe-inside-describe/index.js @@ -4,15 +4,15 @@ var assert = require('assert'); var path = require('path'); var exec = require('child_process').exec; var cheerio = require('cheerio'); -var libExecutable = path.resolve(__dirname, '../../dist/bin/mocha-parallel-tests'); +var libExecutable = path.resolve(__dirname, '../../dist/bin/cli.js'); exec(libExecutable + ' -R doc --timeout 60000 --slow 30000 test/describe-inside-describe/tests', { cwd: path.resolve(__dirname, '../../') }, function (err, stderr) { var xmlReporterOutput = stderr.toString(); var $ = cheerio.load(xmlReporterOutput); - var firstSuite = $('.suite'); + var firstSuite = $('.suite .suite'); - assert.strictEqual($('.suite').length, 3, 'Suites length is wrong'); + assert.strictEqual($('.suite').length, 5, 'Suites length is wrong'); assert.strictEqual(firstSuite.find('.suite').length, 1, 'Inner suites length is wrong'); }); diff --git a/test/describe-onefile-fail/README b/test/describe-onefile-fail/README deleted file mode 100644 index 0d584f4..0000000 --- a/test/describe-onefile-fail/README +++ /dev/null @@ -1 +0,0 @@ -Check that if file contains multiple descrive() sections and one of them fails, only this failed section is retried diff --git a/test/describe-onefile-fail/index.js b/test/describe-onefile-fail/index.js deleted file mode 100755 index bfaee1b..0000000 --- a/test/describe-onefile-fail/index.js +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const assert = require('assert'); -const path = require('path'); -const exec = require('child_process').exec; -const libExecutable = path.resolve(__dirname, '../../dist/bin/mocha-parallel-tests'); - -exec(`${libExecutable} -R json --retry 1 test/describe-onefile-fail/test.spec.js`, { - cwd: path.resolve(__dirname, '../../') -}, (err, stdout) => { - assert(!err, `Error occured: ${err}`); - - let jsonReporterOutput = stdout.toString(); - try { - jsonReporterOutput = JSON.parse(jsonReporterOutput); - } catch (ex) { - console.error(`Native JSON reporter output is not valid JSON: ${jsonReporterOutput || '(empty)'}`); - process.exit(1); - } - - assert.strictEqual(jsonReporterOutput.stats.suites, 3); - assert.strictEqual(jsonReporterOutput.stats.tests, 3); - assert.strictEqual(jsonReporterOutput.stats.passes, 3); - assert.strictEqual(jsonReporterOutput.stats.failures, 0); -}); diff --git a/test/describe-onefile-fail/test.spec.js b/test/describe-onefile-fail/test.spec.js deleted file mode 100644 index 32e7309..0000000 --- a/test/describe-onefile-fail/test.spec.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -const assert = require('assert'); - -global[`${__filename}:suite1`] = global[`${__filename}:suite1`] || 0; -global[`${__filename}:suite2`] = global[`${__filename}:suite2`] || 0; -global[`${__filename}:suite3`] = global[`${__filename}:suite3`] || 0; - -describe('suite 1', () => { - it('should be okay during the first run', () => { - assert.strictEqual(global[`${__filename}:suite1`], 0); - global[`${__filename}:suite1`]++; - }); -}); - -describe('suite 2', () => { - it('should be okay starting from the second run', () => { - const suiteValue = global[`${__filename}:suite2`]; - - global[`${__filename}:suite2`]++; - assert.strictEqual(suiteValue, 1); - }); -}); - -describe('suite 3', () => { - it('should be okay during the first run', () => { - assert.strictEqual(global[`${__filename}:suite3`], 0); - global[`${__filename}:suite3`]++; - }); -}); diff --git a/test/describe-onefile/README b/test/describe-onefile/README deleted file mode 100644 index 8b2acfb..0000000 --- a/test/describe-onefile/README +++ /dev/null @@ -1 +0,0 @@ -Check that describes inside one file are executed in parallel diff --git a/test/describe-onefile/index.sh b/test/describe-onefile/index.sh deleted file mode 100755 index 39f9efc..0000000 --- a/test/describe-onefile/index.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -TIMESTAMP_START="$(date +%s)" -OUTPUT=$(dist/bin/mocha-parallel-tests test/describe-onefile/test.js --slow 3000 --timeout 3000 2>&1) -STATUS=$? -TIMESTAMP_FINISH="$(date +%s)" -TIMESTAMP_DIFF=`expr $TIMESTAMP_FINISH - $TIMESTAMP_START` -echo "Tests running time was $TIMESTAMP_DIFF second(s)" - -if [ $TIMESTAMP_DIFF -ge 1 ] && [ $TIMESTAMP_DIFF -le 3 ]; then - if [ $STATUS -ne 0 ]; then - if [[ $OUTPUT == *"2 passing"* ]] && [[ $OUTPUT == *"1 failing"* ]]; then - exit 0 - else - echo "Wrong output: $OUTPUT" - exit 1 - fi - else - echo "Result code is wrong (should not be 0)" - exit 1 - fi -else - echo "Wrong tests execution time" - exit 1 -fi diff --git a/test/describe-onefile/test.js b/test/describe-onefile/test.js deleted file mode 100644 index f57c08e..0000000 --- a/test/describe-onefile/test.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -for (let i = 0; i < 3; i++) { - describe(`suite #${i}`, () => { - it('should run its case', done => { - const timeout = 1000 + Math.round(Math.random() * 1000); - - if (i === 0) { - setTimeout(done, timeout, new Error('oops')); - } else { - setTimeout(done, timeout); - } - }); - }); -} diff --git a/test/global-hooks-directory/index.sh b/test/global-hooks-directory/index.sh index fa2b6ee..f7ee581 100755 --- a/test/global-hooks-directory/index.sh +++ b/test/global-hooks-directory/index.sh @@ -1,6 +1,6 @@ #!/bin/bash -OUTPUT=$(dist/bin/mocha-parallel-tests test/global-hooks-directory/tests/ 2>&1) +OUTPUT=$(dist/bin/cli.js test/global-hooks-directory/tests/ 2>&1) STATUS=$? if [ $STATUS -eq 2 ]; then diff --git a/test/global-hooks-require/index.sh b/test/global-hooks-require/index.sh index b836064..a7ae5de 100755 --- a/test/global-hooks-require/index.sh +++ b/test/global-hooks-require/index.sh @@ -1,6 +1,6 @@ #!/bin/bash -OUTPUT=$(dist/bin/mocha-parallel-tests test/global-hooks-require/test.js 2>&1) +OUTPUT=$(dist/bin/cli.js test/global-hooks-require/test.js 2>&1) STATUS=$? if [ $STATUS -eq 2 ]; then diff --git a/test/global-hooks/index.sh b/test/global-hooks/index.sh index c77f9b0..f50157b 100755 --- a/test/global-hooks/index.sh +++ b/test/global-hooks/index.sh @@ -1,6 +1,6 @@ #!/bin/bash -OUTPUT=$(dist/bin/mocha-parallel-tests --retry 2 test/global-hooks/test.js 2>&1) +OUTPUT=$(dist/bin/cli.js --retry 2 test/global-hooks/test.js 2>&1) STATUS=$? if [ $STATUS -eq 2 ]; then diff --git a/test/index.sh b/test/index.sh index a91df27..853ebeb 100755 --- a/test/index.sh +++ b/test/index.sh @@ -13,6 +13,10 @@ function test { return $status } +MOCHA_VERSION=`mocha --version` +echo "You're running tests with mocha version $MOCHA_VERSION" +echo "" + echo 'TESTCASE: native (json) reporter' test test/reporter-native-json/index.sh echo $? @@ -28,7 +32,7 @@ echo $? echo 'TESTCASE: custom (mochawesome) reporter' test test/reporter-custom-mochawesome/index.sh echo $? -echo 'TESTCASE: cli targets' +echo 'TESTCASE: cli target' test test/cli-target/index.sh echo $? echo 'TESTCASE: pwd-based reporter' @@ -58,12 +62,6 @@ echo $? echo 'TESTCASE: describe inside describe' test test/describe-inside-describe/index.js echo $? -echo 'TESTCASE: multiple suites in one file' -test test/describe-onefile/index.sh -echo $? -echo 'TESTCASE: multiple suites in one file, one fails' -test test/describe-onefile-fail/index.js -echo $? echo 'TESTCASE: missing test' test test/missing-test/index.js echo $? @@ -82,12 +80,6 @@ echo $? echo 'TESTCASE: --recursive option if no target is set' test test/recursive-no-target/index.js echo $? -echo 'TESTCASE: retries' -test test/retry/index.js -echo $? -echo 'TESTCASE: retries debug messages' -test test/retry-errors/index.js -echo $? echo 'TESTCASE: total time' test test/total-time/index.js echo $? @@ -106,9 +98,6 @@ echo $? echo 'TESTCASE: js compilers with --require support' test test/js-compilers-2/index.sh echo $? -echo 'TESTCASE: retry support in mocha hooks' -test test/retry-before/index.sh -echo $? echo 'TESTCASE: reporter with options' test test/reporter-options/index.sh echo $? @@ -139,6 +128,12 @@ echo $? echo 'TESTCASE: skip-test' test test/skip-test/index.sh echo $? +echo 'TESTCASE: --delay option support' +test test/delay/index.js +echo $? +echo 'TESTCASE: --retries option support' +test test/retries/index.js +echo $? if [ $SAUCE_USERNAME ] && [ $SAUCE_ACCESS_KEY ]; then echo 'TESTCASE: selenium-webdriver' diff --git a/test/js-compilers-1/index.sh b/test/js-compilers-1/index.sh index cf184db..6a4a9c2 100755 --- a/test/js-compilers-1/index.sh +++ b/test/js-compilers-1/index.sh @@ -1,6 +1,6 @@ #!/bin/bash -OUTPUT=$(dist/bin/mocha-parallel-tests --compilers js:test/js-compilers-1/babel-register.js test/js-compilers-1/test.js 2>&1) +OUTPUT=$(dist/bin/cli.js --compilers js:test/js-compilers-1/babel-register.js test/js-compilers-1/test.js 2>&1) STATUS=$? if [ $STATUS -eq 0 ]; then diff --git a/test/js-compilers-2/index.sh b/test/js-compilers-2/index.sh index 38bb27e..61d92a4 100755 --- a/test/js-compilers-2/index.sh +++ b/test/js-compilers-2/index.sh @@ -1,7 +1,7 @@ #!/bin/bash TESTDIR="test/js-compilers-2" -OUTPUT=$(dist/bin/mocha-parallel-tests --compilers js:${TESTDIR}/babel-register.js --require ${TESTDIR}/setup.js ${TESTDIR}/test.js 2>&1) +OUTPUT=$(dist/bin/cli.js --compilers js:${TESTDIR}/babel-register.js --require ${TESTDIR}/setup.js ${TESTDIR}/test.js 2>&1) STATUS=$? if [ $STATUS -eq 0 ]; then diff --git a/test/js-compilers/index.sh b/test/js-compilers/index.sh index 595b37b..5df1aad 100755 --- a/test/js-compilers/index.sh +++ b/test/js-compilers/index.sh @@ -1,6 +1,6 @@ #!/bin/bash -OUTPUT=$(dist/bin/mocha-parallel-tests --compilers js:babel-register test/js-compilers/test.js 2>&1) +OUTPUT=$(dist/bin/cli.js --compilers js:babel-register test/js-compilers/test.js 2>&1) STATUS=$? if [ $STATUS -eq 0 ]; then diff --git a/test/max-parallel-1/index.sh b/test/max-parallel-1/index.sh index 02d2841..711655c 100755 --- a/test/max-parallel-1/index.sh +++ b/test/max-parallel-1/index.sh @@ -1,12 +1,11 @@ #!/bin/bash TIMESTAMP_START="$(date +%s)" -OUTPUT=$(dist/bin/mocha-parallel-tests -R spec test/max-parallel-1/tests --timeout 30000 --slow 10000 --max-parallel 1) +OUTPUT=$(dist/bin/cli.js -R spec test/max-parallel-1/tests --timeout 30000 --slow 10000 --max-parallel 1) TIMESTAMP_FINISH="$(date +%s)" TIMESTAMP_DIFF=`expr $TIMESTAMP_FINISH - $TIMESTAMP_START` -echo "Tests running time was $TIMESTAMP_DIFF second(s)" -if [ $TIMESTAMP_DIFF -ge 6 ] && [ $TIMESTAMP_DIFF -le 7 ]; then +if [ $TIMESTAMP_DIFF -ge 6 ]; then if [[ $OUTPUT == *"3 passing"* ]]; then exit 0 else @@ -14,6 +13,6 @@ if [ $TIMESTAMP_DIFF -ge 6 ] && [ $TIMESTAMP_DIFF -le 7 ]; then exit 1 fi else - echo "Wrong tests execution time" + echo "Wrong tests execution time: $TIMESTAMP_DIFF second(s)" exit 1 fi diff --git a/test/max-parallel-empty/index.sh b/test/max-parallel-empty/index.sh index 244685f..9bdcc4a 100755 --- a/test/max-parallel-empty/index.sh +++ b/test/max-parallel-empty/index.sh @@ -2,7 +2,7 @@ TIMESTAMP_START="$(date +%s)" -OUTPUT=$(dist/bin/mocha-parallel-tests -R spec test/max-parallel-empty/tests --timeout 30000 --slow 10000 --max-parallel 3) +OUTPUT=$(dist/bin/cli.js -R spec test/max-parallel-empty/tests --timeout 30000 --slow 10000 --max-parallel 3) # parallel1.js ends in 1 second, parallel2.js in 2 seconds etc # when any test is over, next should start @@ -13,17 +13,14 @@ OUTPUT=$(dist/bin/mocha-parallel-tests -R spec test/max-parallel-empty/tests --t TIMESTAMP_FINISH="$(date +%s)" TIMESTAMP_DIFF=`expr $TIMESTAMP_FINISH - $TIMESTAMP_START` -echo "Tests output is $OUTPUT" -echo "Tests running time was $TIMESTAMP_DIFF seconds" - -if [ $TIMESTAMP_DIFF -ge 3 ] && [ $TIMESTAMP_DIFF -le 4 ]; then +if [ $TIMESTAMP_DIFF -ge 3 ]; then if [[ $OUTPUT == *"1 passing"* ]]; then exit 0 else - echo "Wrong output" + echo "Wrong output: $OUTPUT" exit 1 fi else - echo "Wrong tests execution time" + echo "Wrong tests execution time: $TIMESTAMP_DIFF seconds" exit 1 fi diff --git a/test/max-parallel/index.sh b/test/max-parallel/index.sh index 6f0761b..9708e09 100755 --- a/test/max-parallel/index.sh +++ b/test/max-parallel/index.sh @@ -2,7 +2,7 @@ TIMESTAMP_START="$(date +%s)" -OUTPUT=$(dist/bin/mocha-parallel-tests -R spec test/max-parallel/tests --timeout 30000 --slow 10000 --max-parallel 3) +OUTPUT=$(dist/bin/cli.js -R spec test/max-parallel/tests --timeout 30000 --slow 10000 --max-parallel 3) # parallel1.js ends in 1 second, parallel2.js in 2 seconds etc # when any test is over, next should start @@ -12,10 +12,10 @@ OUTPUT=$(dist/bin/mocha-parallel-tests -R spec test/max-parallel/tests --timeout TIMESTAMP_FINISH="$(date +%s)" TIMESTAMP_DIFF=`expr $TIMESTAMP_FINISH - $TIMESTAMP_START` -echo "Tests running time was $TIMESTAMP_DIFF seconds" if [ $TIMESTAMP_DIFF -gt 6 ] && [ $TIMESTAMP_DIFF -lt 10 ]; then exit 0 else + echo "Tests running time was $TIMESTAMP_DIFF seconds" exit 1 fi diff --git a/test/missing-test/index.js b/test/missing-test/index.js index da4562b..d4bd4cf 100755 --- a/test/missing-test/index.js +++ b/test/missing-test/index.js @@ -3,7 +3,7 @@ var assert = require('assert'); var path = require('path'); var exec = require('child_process').exec; -var libExecutable = path.resolve(__dirname, '../../dist/bin/mocha-parallel-tests'); +var libExecutable = path.resolve(__dirname, '../../dist/bin/cli.js'); exec(libExecutable + ' -R doc --timeout 60000 --slow 30000 test/missing-test/missing.js', { cwd: path.resolve(__dirname, '../../') diff --git a/test/mocha-opts/index.sh b/test/mocha-opts/index.sh index 164d28f..0aad51a 100755 --- a/test/mocha-opts/index.sh +++ b/test/mocha-opts/index.sh @@ -2,7 +2,7 @@ TIMESTAMP_START="$(date +%s)" -OUTPUT=$(dist/bin/mocha-parallel-tests -R spec test/mocha-opts/tests --slow 10000 --opts test/mocha-opts/mocha.opts) +OUTPUT=$(dist/bin/cli.js -R spec test/mocha-opts/tests --slow 10000 --opts test/mocha-opts/mocha.opts) STATUS=$? TIMESTAMP_FINISH="$(date +%s)" TIMESTAMP_DIFF=`expr $TIMESTAMP_FINISH - $TIMESTAMP_START` diff --git a/test/nesting/nesting.sh b/test/nesting/nesting.sh index 7728f1d..92725a0 100755 --- a/test/nesting/nesting.sh +++ b/test/nesting/nesting.sh @@ -1,16 +1,17 @@ #!/bin/bash -OUTPUT=$(dist/bin/mocha-parallel-tests -R spec --timeout 60000 --slow 30000 test/nesting/nesting.js) -strindex() { +OUTPUT=$(dist/bin/cli.js -R spec --timeout 60000 --slow 30000 test/nesting/nesting.js) +strindex() { x="${1%%$2*}" [[ $x = $1 ]] && echo -1 || echo ${#x} } PARENT=$(strindex "$OUTPUT" "parent") CHILD=$(strindex "$OUTPUT" "child") -echo "${PARENT}" -echo "${CHILD}" + if [[ PARENT -lt CHILD ]] then - exit 0; - else - exit 1; + exit 0 +else + echo "${PARENT}" + echo "${CHILD}" + exit 1 fi diff --git a/test/no-timeouts/index.sh b/test/no-timeouts/index.sh index ea7097b..5ae118f 100755 --- a/test/no-timeouts/index.sh +++ b/test/no-timeouts/index.sh @@ -1,15 +1,15 @@ #!/bin/bash -dist/bin/mocha-parallel-tests test/no-timeouts/tests #1>/dev/null 2>&1 +dist/bin/cli.js -R test/util/silent-reporter.js test/no-timeouts/tests #1>/dev/null 2>&1 MPT_STATUS_CODE=$? -dist/bin/mocha-parallel-tests --no-timeouts test/no-timeouts/tests #1>/dev/null 2>&1 +dist/bin/cli.js --no-timeouts -R test/util/silent-reporter.js test/no-timeouts/tests #1>/dev/null 2>&1 MPT_STATUS_CODE_NO_TIMEOUT=$? # currently mocha exits with 2 if 2 tests failed, 3 if 3 tests failed etc # though it's a strange behaviour, mocha-parallel-tests behaviour should be the same if [ $MPT_STATUS_CODE -eq 2 ] && [ $MPT_STATUS_CODE_NO_TIMEOUT -eq 0 ]; then - echo "Timeouts are ignored if flag is provided" + exit 0 else echo "The flag no-timeouts did not work as expected: $MPT_STATUS_CODE (no flag) instead of 2 and $MPT_STATUS_CODE_NO_TIMEOUT (flag) instead of 0" exit 1 diff --git a/test/node-addon/index.sh b/test/node-addon/index.sh index 999e974..312cfe2 100755 --- a/test/node-addon/index.sh +++ b/test/node-addon/index.sh @@ -1,6 +1,6 @@ #!/bin/bash -OUTPUT=$(npm install test/node-addon/test_addon 2>&1 && dist/bin/mocha-parallel-tests test/node-addon/test.js 2>&1) +OUTPUT=$(npm install test/node-addon/test_addon --no-save 2>&1 && dist/bin/cli.js test/node-addon/test.js 2>&1) STATUS=$? if [ $STATUS -eq 0 ]; then diff --git a/test/only-tests-run/index.js b/test/only-tests-run/index.js index 8c22c6e..8816162 100755 --- a/test/only-tests-run/index.js +++ b/test/only-tests-run/index.js @@ -3,7 +3,7 @@ var assert = require('assert'); var path = require('path'); var exec = require('child_process').exec; -var libExecutable = path.resolve(__dirname, '../../dist/bin/mocha-parallel-tests'); +var libExecutable = path.resolve(__dirname, '../../dist/bin/cli.js'); exec(libExecutable + ' -R json --timeout 60000 --slow 30000 test/only-tests-run/tests', { cwd: path.resolve(__dirname, '../../') @@ -22,7 +22,7 @@ exec(libExecutable + ' -R json --timeout 60000 --slow 30000 test/only-tests-run/ process.exit(1); } - assert.strictEqual(jsonReporterOutput.stats.suites, 2, 'Suites number is wrong'); + assert.strictEqual(jsonReporterOutput.stats.suites, 4, 'Suites number is wrong'); assert.strictEqual(jsonReporterOutput.stats.tests, 3, 'Tests number is wrong'); assert.strictEqual(jsonReporterOutput.stats.passes, 3, 'Passes number is wrong'); }); diff --git a/test/parallel-order/index.js b/test/parallel-order/index.js index 78abaae..7ad1723 100755 --- a/test/parallel-order/index.js +++ b/test/parallel-order/index.js @@ -3,7 +3,7 @@ var assert = require('assert'); var path = require('path'); var exec = require('child_process').exec; -var libExecutable = path.resolve(__dirname, '../../dist/bin/mocha-parallel-tests'); +var libExecutable = path.resolve(__dirname, '../../dist/bin/cli.js'); var start = Date.now(); exec(libExecutable + ' -R json --timeout 60000 --slow 30000 test/parallel-order/tests', { @@ -22,7 +22,7 @@ exec(libExecutable + ' -R json --timeout 60000 --slow 30000 test/parallel-order/ } assert.equal(typeof jsonReporterOutput.stats, 'object', 'Reporter should contain stats object'); - assert.equal(jsonReporterOutput.stats.suites, 3, 'Reporter should contain information about 3 suites'); + assert.equal(jsonReporterOutput.stats.suites, 6, 'Reporter should contain information about 3 suites'); assert.equal(jsonReporterOutput.stats.tests, 4, 'Reporter should contain information about all run tests'); assert.equal(jsonReporterOutput.stats.passes, 3, 'Reporter should contain information about all passes'); assert.equal(jsonReporterOutput.stats.pending, 0, 'Reporter should contain information about all pendings'); @@ -45,7 +45,7 @@ exec(libExecutable + ' -R json --timeout 60000 --slow 30000 test/parallel-order/ // check failure assert.equal(Array.isArray(jsonReporterOutput.failures), true, 'Reporter should contain failures array'); assert.equal(jsonReporterOutput.failures.length, 1, 'Reporter should contain one error'); - assert.equal(jsonReporterOutput.failures[0].fullTitle, 'Test suite #2 should fail'); + assert.equal(jsonReporterOutput.failures[0].fullTitle.trim(), 'Test suite #2 should fail'); assert.equal(jsonReporterOutput.failures[0].err.message, 'some error'); return; diff --git a/test/parallel/parallel.sh b/test/parallel/parallel.sh index 5d0473c..129858e 100755 --- a/test/parallel/parallel.sh +++ b/test/parallel/parallel.sh @@ -2,7 +2,7 @@ TIMESTAMP_START="$(date +%s)" -OUTPUT=$(dist/bin/mocha-parallel-tests -R spec test/parallel/tests --timeout 10000 --slow 10000) +OUTPUT=$(dist/bin/cli.js -R spec test/parallel/tests --timeout 10000 --slow 10000) TIMESTAMP_FINISH="$(date +%s)" TIMESTAMP_DIFF=`expr $TIMESTAMP_FINISH - $TIMESTAMP_START` diff --git a/test/recursive-no-target/index.js b/test/recursive-no-target/index.js index 8aba0bb..0d280ef 100755 --- a/test/recursive-no-target/index.js +++ b/test/recursive-no-target/index.js @@ -5,7 +5,7 @@ const assert = require('assert'); const path = require('path'); const exec = require('child_process').exec; -const libExecutable = path.resolve(__dirname, '../../dist/bin/mocha-parallel-tests'); +const libExecutable = path.resolve(__dirname, '../../dist/bin/cli.js'); exec(`${libExecutable} -R json --recursive`, { cwd: path.dirname(__filename) @@ -23,7 +23,8 @@ exec(`${libExecutable} -R json --recursive`, { process.exit(1); } - assert.strictEqual(jsonReporterOutput.stats.suites, 2); + // 2 suite events for each file (:root suite and own top level suite) + assert.strictEqual(jsonReporterOutput.stats.suites, 4); assert.strictEqual(jsonReporterOutput.stats.tests, 2); assert.strictEqual(jsonReporterOutput.stats.passes, 2); assert.strictEqual(jsonReporterOutput.stats.failures, 0); diff --git a/test/reporter-custom-jenkins/index.sh b/test/reporter-custom-jenkins/index.sh index afe1db4..ce49899 100755 --- a/test/reporter-custom-jenkins/index.sh +++ b/test/reporter-custom-jenkins/index.sh @@ -2,7 +2,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" RESULT="$DIR/result.xml" -OUTPUT=$(JUNIT_REPORT_PATH=$RESULT dist/bin/mocha-parallel-tests -R mocha-jenkins-reporter test/reporter-custom-jenkins/suite.js 2>&1) +OUTPUT=$(JUNIT_REPORT_PATH=$RESULT dist/bin/cli.js -R mocha-jenkins-reporter test/reporter-custom-jenkins/suite.js 2>&1) STATUS=$? if [ $STATUS -eq 0 ]; then diff --git a/test/reporter-custom-mochawesome/index.sh b/test/reporter-custom-mochawesome/index.sh index cd230d5..a298161 100755 --- a/test/reporter-custom-mochawesome/index.sh +++ b/test/reporter-custom-mochawesome/index.sh @@ -6,7 +6,7 @@ MOCHAWESOME_REPORT_DIR="mochawesome-report" # clean directory rm -fr $DIR/$MOCHAWESOME_REPORT_DIR -OUTPUT=$(cd $DIR && ../../dist/bin/mocha-parallel-tests -R mochawesome $DIR/suite.js) +OUTPUT=$(cd $DIR && ../../dist/bin/cli.js -R mochawesome $DIR/suite.js) if [ ! -d "$DIR/$MOCHAWESOME_REPORT_DIR" ]; then echo "$MOCHAWESOME_REPORT_DIR directory doesn't exist'" diff --git a/test/reporter-custom-teamcity/index.sh b/test/reporter-custom-teamcity/index.sh index 8d85c82..3e4c195 100755 --- a/test/reporter-custom-teamcity/index.sh +++ b/test/reporter-custom-teamcity/index.sh @@ -1,6 +1,6 @@ #!/bin/bash -OUTPUT=$(dist/bin/mocha-parallel-tests -R mocha-teamcity-reporter --timeout 60000 --slow 30000 test/reporter-custom-teamcity/suite.js) +OUTPUT=$(dist/bin/cli.js -R mocha-teamcity-reporter --timeout 60000 --slow 30000 test/reporter-custom-teamcity/suite.js) if [[ $OUTPUT == *"##teamcity"* ]]; then echo "Teamcity reporter is used" diff --git a/test/reporter-end-no-exit/index.js b/test/reporter-end-no-exit/index.js index 60d5ac2..b5c0f05 100755 --- a/test/reporter-end-no-exit/index.js +++ b/test/reporter-end-no-exit/index.js @@ -11,7 +11,7 @@ const exec = require('child_process').exec; const fileName = `${Date.now()}.txt`; const filePath = `${os.tmpdir()}/${fileName}`; -const libExecutable = path.resolve(__dirname, '../../dist/bin/mocha-parallel-tests'); +const libExecutable = path.resolve(__dirname, '../../dist/bin/cli.js'); exec(`FILE=${fileName} ${libExecutable} --no-exit -R test/reporter-end-no-exit/tests/reporter/spec-file test/reporter-end-no-exit/tests/*.js`, { cwd: path.resolve(__dirname, '../../') @@ -22,4 +22,4 @@ exec(`FILE=${fileName} ${libExecutable} --no-exit -R test/reporter-end-no-exit/t } assert.equal(data.toString('utf8'), 'test', `expected ${filePath} to contain 'test' string`); }); -}); \ No newline at end of file +}); diff --git a/test/reporter-native-json/index.sh b/test/reporter-native-json/index.sh index 210e18f..14f7dbe 100755 --- a/test/reporter-native-json/index.sh +++ b/test/reporter-native-json/index.sh @@ -1,11 +1,11 @@ #!/bin/bash -OUTPUT=$(dist/bin/mocha-parallel-tests -R json --timeout 60000 --slow 30000 test/reporter-native-json/suite.js) +OUTPUT=$(dist/bin/cli.js -R json --timeout 60000 --slow 30000 test/reporter-native-json/suite.js) if [[ $OUTPUT == *"stats"* ]]; then - echo "Native JSON reporter is used" - exit 0 + echo "Native JSON reporter is used" + exit 0 else - echo "Reporter output doesn't have much common with JSON: $OUTPUT" - exit 1 + echo "Reporter output doesn't have much common with JSON: $OUTPUT" + exit 1 fi diff --git a/test/reporter-native-tap/index.sh b/test/reporter-native-tap/index.sh index 20cc44b..795f270 100755 --- a/test/reporter-native-tap/index.sh +++ b/test/reporter-native-tap/index.sh @@ -1,6 +1,6 @@ #!/bin/bash -OUTPUT=$(dist/bin/mocha-parallel-tests -R tap test/reporter-native-tap/suite.js 2>&1) +OUTPUT=$(dist/bin/cli.js -R tap test/reporter-native-tap/suite.js 2>&1) STATUS=$? if [ $STATUS -eq 0 ]; then diff --git a/test/reporter-options/index.sh b/test/reporter-options/index.sh index 06049d7..9da36ca 100755 --- a/test/reporter-options/index.sh +++ b/test/reporter-options/index.sh @@ -1,6 +1,6 @@ #!/bin/bash -OUTPUT=$(dist/bin/mocha-parallel-tests -R xunit test/reporter-options/suite.js 2>&1) +OUTPUT=$(dist/bin/cli.js -R xunit test/reporter-options/suite.js 2>&1) STATUS=$? if [ $STATUS -eq 0 ]; then diff --git a/test/reporter-pwd/index.sh b/test/reporter-pwd/index.sh index 0e9c899..222a0b1 100755 --- a/test/reporter-pwd/index.sh +++ b/test/reporter-pwd/index.sh @@ -1,10 +1,9 @@ #!/bin/bash CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -OUTPUT=$(cd ${CURRENT_DIR} && ../../dist/bin/mocha-parallel-tests -R reporter-pwd suite.js) +OUTPUT=$(cd ${CURRENT_DIR} && ../../dist/bin/cli.js -R reporter-pwd suite.js) if [[ $OUTPUT == *"startfinish"* ]]; then - echo "PWD-based reporter is supported" exit 0 else echo "PWD-based reporter is not supported. OUTPUT: $OUTPUT" diff --git a/test/require-option/index.sh b/test/require-option/index.sh index ba5a567..e7617b4 100755 --- a/test/require-option/index.sh +++ b/test/require-option/index.sh @@ -1,6 +1,6 @@ #!/bin/bash -OUTPUT=$(dist/bin/mocha-parallel-tests --require test/require-option/require-module.js test/require-option/test.js) +OUTPUT=$(dist/bin/cli.js --require test/require-option/require-module.js test/require-option/test.js) STATUS=$? if [ $STATUS -eq 0 ]; then diff --git a/test/retries/index.js b/test/retries/index.js new file mode 100755 index 0000000..08c0468 --- /dev/null +++ b/test/retries/index.js @@ -0,0 +1,18 @@ +#!/usr/bin/env node + +const assert = require('assert'); +const MochaParallelTests = require('../../dist/main/mocha').default; +const { setProcessExitListeners } = require('../../dist/util'); +const SilentReporter = require('../util/silent-reporter'); + +setProcessExitListeners(); + +const mocha = new MochaParallelTests; +mocha.reporter(SilentReporter); +mocha.suite.retries(2); +mocha.addFile(`${__dirname}/index.spec.js`); + +mocha.run().on('end', function () { + assert.strictEqual(this.stats.passes, 1, `Passes number is wrong: ${this.stats.passes}`); + assert.strictEqual(this.stats.failures, 0, `Failures number is wrong: ${this.stats.failures}`); +}); diff --git a/test/retries/index.spec.js b/test/retries/index.spec.js new file mode 100644 index 0000000..920ea53 --- /dev/null +++ b/test/retries/index.spec.js @@ -0,0 +1,9 @@ +const assert = require('assert'); +let run = 0; + +describe('suite', () => { + it('case', () => { + run++; + assert(run === 3); + }); +}); diff --git a/test/retry-before/README b/test/retry-before/README deleted file mode 100644 index 56e34b5..0000000 --- a/test/retry-before/README +++ /dev/null @@ -1 +0,0 @@ -Check that retry option works for mocha hooks (before/after/etc) \ No newline at end of file diff --git a/test/retry-before/index.sh b/test/retry-before/index.sh deleted file mode 100755 index e4ccc8b..0000000 --- a/test/retry-before/index.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -OUTPUT=$(dist/bin/mocha-parallel-tests --retry 2 test/retry-before/test.js 2>&1) -STATUS=$? - -if [ $STATUS -eq 0 ]; then - exit 0 -else - echo "Exit code is $STATUS" - echo "Output: $OUTPUT" - - exit 1 -fi diff --git a/test/retry-before/test.js b/test/retry-before/test.js deleted file mode 100644 index 21b0177..0000000 --- a/test/retry-before/test.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -global.__retryBeforeCounter = global.__retryBeforeCounter || 0; - -describe('Test suite', () => { - before(() => { - if (global.__retryBeforeCounter === 0) { - global.__retryBeforeCounter++; - throw new Error('First launch'); - } - }); - - it('should end immediately', () => {}); -}); diff --git a/test/retry-errors/README b/test/retry-errors/README deleted file mode 100644 index 7755540..0000000 --- a/test/retry-errors/README +++ /dev/null @@ -1 +0,0 @@ -Check that retry errors are visible in stderr diff --git a/test/retry-errors/index.js b/test/retry-errors/index.js deleted file mode 100755 index bfc1824..0000000 --- a/test/retry-errors/index.js +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env node - -var assert = require('assert'); -var path = require('path'); -var exec = require('child_process').exec; -var libExecutable = path.resolve(__dirname, '../../dist/bin/mocha-parallel-tests'); - -exec(libExecutable + ' --retry 2 --slow 3000 test/retry-errors/tests', { - cwd: path.resolve(__dirname, '../../') -}, function (err, stdout, stderr) { - if (err) { - console.error(err); - process.exit(1); - } - - var debugMessagesOutput = stderr.toString(); - assert.notStrictEqual(debugMessagesOutput.indexOf('try #1 failed'), -1, 'No info about retry #1 in stderr'); - assert.notStrictEqual(debugMessagesOutput.indexOf('try #2 failed'), -1, 'No info about retry #2 in stderr'); -}); diff --git a/test/retry-errors/tests/parallel1.js b/test/retry-errors/tests/parallel1.js deleted file mode 100644 index 1ddcc6d..0000000 --- a/test/retry-errors/tests/parallel1.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -describe('Test suite #1', function () { - it('should end now', function () {}); - - it('should end shortly', function () { - describe('Inner suite #1', function () { - it('should end now', function () {}); - }); - }); -}); diff --git a/test/retry-errors/tests/parallel2.js b/test/retry-errors/tests/parallel2.js deleted file mode 100644 index 426f048..0000000 --- a/test/retry-errors/tests/parallel2.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -// this is the place where variables are not cleared between tests -global.__counter = global.__counter || 0; - -describe('Test suite #2', function () { - it('should end now', function () {}); - - it('should end in 1 second', function (done) { - setTimeout(function () { - global.__counter++; - - if (global.__counter % 3 === 0) { - done(); - } else { - done(new Error('Oops, flaky error occured: ' + global.__counter)); - } - }, 1000); - }); -}); diff --git a/test/retry/README b/test/retry/README deleted file mode 100644 index 9ce4b22..0000000 --- a/test/retry/README +++ /dev/null @@ -1 +0,0 @@ -Check that retry option is supported diff --git a/test/retry/index.js b/test/retry/index.js deleted file mode 100755 index 2cf8778..0000000 --- a/test/retry/index.js +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env node - -var assert = require('assert'); -var path = require('path'); -var exec = require('child_process').exec; -var libExecutable = path.resolve(__dirname, '../../dist/bin/mocha-parallel-tests'); - -exec(libExecutable + ' --retry 1 -R json --slow 3000 test/retry/tests', { - cwd: path.resolve(__dirname, '../../') -}, function (err, stdout) { - var jsonReporterOutput = stdout.toString(); - - try { - jsonReporterOutput = JSON.parse(jsonReporterOutput); - } catch (ex) { - console.error('Native JSON reporter output is not valid JSON: ' + jsonReporterOutput); - process.exit(1); - } - - assert.strictEqual(jsonReporterOutput.stats.suites, 2, 'Suites number is wrong'); - assert.strictEqual(jsonReporterOutput.stats.tests, 4, 'Tests number is wrong'); - assert.strictEqual(jsonReporterOutput.stats.passes, 4, 'Passes number is wrong'); -}); diff --git a/test/retry/tests/parallel1.js b/test/retry/tests/parallel1.js deleted file mode 100644 index 1ddcc6d..0000000 --- a/test/retry/tests/parallel1.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -describe('Test suite #1', function () { - it('should end now', function () {}); - - it('should end shortly', function () { - describe('Inner suite #1', function () { - it('should end now', function () {}); - }); - }); -}); diff --git a/test/retry/tests/parallel2.js b/test/retry/tests/parallel2.js deleted file mode 100644 index 8b51a6a..0000000 --- a/test/retry/tests/parallel2.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -// this is the place where variables are not cleared between tests -global.__counter = global.__counter || 0; - -describe('Test suite #2', function () { - it('should end now', function () {}); - - it('should end in 1 second', function (done) { - setTimeout(function () { - global.__counter++; - - if (global.__counter % 2 === 0) { - done(); - } else { - done(new Error('Oops, flaky error occured: ' + global.__counter)); - } - }, 1000); - }); -}); diff --git a/test/run-programmatically/_spec/parallel1.js b/test/run-programmatically/_spec/parallel1.js index faffae3..a9518e7 100644 --- a/test/run-programmatically/_spec/parallel1.js +++ b/test/run-programmatically/_spec/parallel1.js @@ -1,9 +1,7 @@ 'use strict'; -for (let i = 0; i < 100; i++) { - describe(`Test suite #1 - ${i}`, function () { - it('should end after 3 seconds', function (done) { - setTimeout(done, 3000); - }); +describe('Test suite #1', function () { + it('should end after 3 seconds', function (done) { + setTimeout(done, 3000); }); -} +}); diff --git a/test/run-programmatically/_spec/parallel2.js b/test/run-programmatically/_spec/parallel2.js index a1bb788..6c2692b 100644 --- a/test/run-programmatically/_spec/parallel2.js +++ b/test/run-programmatically/_spec/parallel2.js @@ -1,9 +1,7 @@ 'use strict'; -for (let i = 0; i < 100; i++) { - describe(`Test suite #2 - ${i}`, function () { - it('should end after 3 seconds', function (done) { - setTimeout(done, 3000); - }); +describe('Test suite #2', function () { + it('should end after 3 seconds', function (done) { + setTimeout(done, 3000); }); -} +}); diff --git a/test/run-programmatically/callback/index.js b/test/run-programmatically/callback/index.js index d5660cf..6893868 100755 --- a/test/run-programmatically/callback/index.js +++ b/test/run-programmatically/callback/index.js @@ -3,7 +3,7 @@ 'use strict'; const assert = require('assert'); -const MochaParallelTests = require('../../../dist/api.js'); +const MochaParallelTests = require('../../../dist/main/mocha').default; const STREAMS = ['stdout', 'stderr']; const originalWrites = {}; @@ -44,10 +44,10 @@ process.on('exit', () => { assert(jsonResult !== undefined, '"end" event was not fired'); assert(jsonResult !== null && typeof jsonResult === 'object', `Reporter output is not valid JSON: ${jsonResult}`); - assert.strictEqual(jsonResult.stats.suites, 200); - assert.strictEqual(jsonResult.stats.tests, 200); - assert.strictEqual(jsonResult.stats.passes, 200); - assert(jsonResult.stats.duration < 6000, `Duration is too long: ${jsonResult.stats.duration}`); + assert.strictEqual(jsonResult.stats.suites, 4); + assert.strictEqual(jsonResult.stats.tests, 2); + assert.strictEqual(jsonResult.stats.passes, 2); + assert(jsonResult.stats.duration < 4000, `Duration is too long: ${jsonResult.stats.duration}`); }); // patch streams so that stdout is muted diff --git a/test/run-programmatically/reporter-done/index.js b/test/run-programmatically/reporter-done/index.js index 952c58f..22dadf9 100755 --- a/test/run-programmatically/reporter-done/index.js +++ b/test/run-programmatically/reporter-done/index.js @@ -4,7 +4,7 @@ const assert = require('assert'); const Mocha = require('mocha'); -const MochaParallelTests = require('../../../dist/api.js'); +const MochaParallelTests = require('../../../dist/main/mocha').default; const mocha = new MochaParallelTests; let doneExecuted = false; diff --git a/test/selenium-webdriver-1/index.js b/test/selenium-webdriver-1/index.js index 44fd24a..bb00f90 100755 --- a/test/selenium-webdriver-1/index.js +++ b/test/selenium-webdriver-1/index.js @@ -6,7 +6,7 @@ const assert = require('assert'); const path = require('path'); const exec = require('child_process').exec; // const spawn_process = require('../spawn_process'); -const libExecutable = path.resolve(__dirname, '../../dist/bin/mocha-parallel-tests'); +const libExecutable = path.resolve(__dirname, '../../dist/bin/cli.js'); // SauceLabs has limitation for concurrent tests for OSS projects. // But TravisCI runs tests for different node versions in parallel. diff --git a/test/selenium-webdriver-2/index.js b/test/selenium-webdriver-2/index.js index 6b64feb..82d8a4f 100755 --- a/test/selenium-webdriver-2/index.js +++ b/test/selenium-webdriver-2/index.js @@ -3,7 +3,7 @@ 'use strict'; const assert = require('assert'); -const MochaParallelTests = require('../../dist/api.js'); +const MochaParallelTests = require('../../dist/main/mocha').default; const EXPECTED_EXECUTION_TIME_MOCHA_MS = 1000; const EXEC_START_TIME = Date.now(); diff --git a/test/selenium-webdriver/index.js b/test/selenium-webdriver/index.js index 5109c02..6d87338 100755 --- a/test/selenium-webdriver/index.js +++ b/test/selenium-webdriver/index.js @@ -6,7 +6,7 @@ var assert = require('assert'); var path = require('path'); var exec = require('child_process').exec; var inspect = require('util').inspect; -var libExecutable = path.resolve(__dirname, '../../dist/bin/mocha-parallel-tests'); +var libExecutable = path.resolve(__dirname, '../../dist/bin/cli.js'); // SauceLabs has limitation for concurrent tests for OSS projects. // But TravisCI runs tests for different node versions in parallel. diff --git a/test/skip-suite/index.sh b/test/skip-suite/index.sh index 2c1ccfe..5ed2b12 100755 --- a/test/skip-suite/index.sh +++ b/test/skip-suite/index.sh @@ -1,6 +1,6 @@ #!/bin/bash -OUTPUT=$(dist/bin/mocha-parallel-tests test/skip-suite/test.js 2>&1) +OUTPUT=$(dist/bin/cli.js test/skip-suite/test.js 2>&1) STATUS=$? if [ $STATUS -eq 0 ]; then diff --git a/test/skip-test/index.sh b/test/skip-test/index.sh index 581f92b..8506198 100755 --- a/test/skip-test/index.sh +++ b/test/skip-test/index.sh @@ -1,6 +1,6 @@ #!/bin/bash -OUTPUT=$(dist/bin/mocha-parallel-tests test/skip-test/test.js 2>&1) +OUTPUT=$(dist/bin/cli.js test/skip-test/test.js 2>&1) STATUS=$? if [ $STATUS -eq 0 ]; then diff --git a/test/syntax-errors/index.js b/test/syntax-errors/index.js index 93fe9c8..3222620 100755 --- a/test/syntax-errors/index.js +++ b/test/syntax-errors/index.js @@ -3,7 +3,7 @@ const assert = require('assert'); const path = require('path'); const exec = require('child_process').exec; -const libExecutable = path.resolve(__dirname, '../../dist/bin/mocha-parallel-tests'); +const libExecutable = path.resolve(__dirname, '../../dist/bin/cli.js'); exec(`${libExecutable} test/syntax-errors/tests/`, { cwd: path.resolve(__dirname, '../../') diff --git a/test/timeouts-exit-code/index.sh b/test/timeouts-exit-code/index.sh index 40ad780..05b23f7 100755 --- a/test/timeouts-exit-code/index.sh +++ b/test/timeouts-exit-code/index.sh @@ -1,6 +1,6 @@ #!/bin/bash -dist/bin/mocha-parallel-tests --timeout 3000 --slow 30000 test/timeouts-exit-code/suite.js 1>/dev/null 2>&1 +dist/bin/cli.js --timeout 3000 --slow 30000 test/timeouts-exit-code/suite.js 1>/dev/null 2>&1 MPT_STATUS_CODE=$? node_modules/.bin/mocha --timeout 3000 --slow 30000 test/timeouts-exit-code/suite.js 1>/dev/null 2>&1 diff --git a/test/total-time/index.js b/test/total-time/index.js index f939416..2f47487 100755 --- a/test/total-time/index.js +++ b/test/total-time/index.js @@ -3,7 +3,7 @@ var assert = require('assert'); var path = require('path'); var exec = require('child_process').exec; -var libExecutable = path.resolve(__dirname, '../../dist/bin/mocha-parallel-tests'); +var libExecutable = path.resolve(__dirname, '../../dist/bin/cli.js'); exec(libExecutable + ' -R json --timeout 60000 --slow 60000 --max-parallel 2 test/total-time/tests', { cwd: path.resolve(__dirname, '../../') diff --git a/test/util/silent-reporter.js b/test/util/silent-reporter.js new file mode 100644 index 0000000..97191bc --- /dev/null +++ b/test/util/silent-reporter.js @@ -0,0 +1,2 @@ +const Base = require('mocha').reporters.Base; +module.exports = class SilentReporter extends Base {}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..059b504 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "outDir": "dist", + "module": "commonjs", + "allowJs": false, + "alwaysStrict": true, + "declaration": true, + "importHelpers": true, + "moduleResolution": "classic", + "noFallthroughCasesInSwitch": true, + "noImplicitAny": false, + "noImplicitThis": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "sourceMap": true, + "strictNullChecks": true, + "target": "esnext" + }, + "include": [ + "./src/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..9abc048 --- /dev/null +++ b/tslint.json @@ -0,0 +1,10 @@ +{ + "extends": "tslint:recommended", + "rules": { + "indent": [true, "spaces", 2], + "member-access": [true, "no-public"], + "only-arrow-functions": false, + "quotemark": [true, "single"], + "switch-default": true + } +}