diff --git a/docs/index.md b/docs/index.md index 3e00e3468e..cfa4cbc04e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1087,16 +1087,36 @@ Files specified using `--file` _are not affected_ by this option. Can be specified multiple times. -### `--extension , --watch-extensions ` - -> _Updated in v6.0.0. Previously `--watch-extensions`, but now expanded to affect general test file loading behavior. `--watch-extensions` is now an alias_ +### `--extension ` Files having this extension will be considered test files. Defaults to `js`. -Affects `--watch` behavior. - Specifying `--extension` will _remove_ `.js` as a test file extension; use `--extension js` to re-add it. For example, to load `.mjs` and `.js` test files, you must supply `--extension mjs --extension js`. +The option can be given multiple times. The option accepts a comma-delimited list: `--extension a,b` is equivalent to `--extension a --extension b` + +### `--watch-files ` + +> _New in v7.0.0_ + +List of paths or globs to watch when `--watch` is set. If a file matching the given glob changes or is added or removed mocha will rerun all tests. + +If the path is a directory all files and subdirectories will be watched. + +By default all files in the current directory having one of the extensions provided by `--extension` and not contained in the `node_modules` or `.git` folders are watched. + +The option can be given multiple times. The option accepts a comma-delimited list: `--watch-files a,b` is equivalent to `--watch-files a --watch-files b` + +### `--watch-ignore ` + +> _New in v7.0.0_ + +List of paths or globs to exclude from watching. Defaults to `node_modules` and `.git`. + +To exclude all files in a directory it is preferable to use `foo/bar` instead of `foo/bar/**/*`. The latter will still watch the directory `foo/bar` but will ignore all changes to the content of that directory. + +The option can be given multiple times. The option accepts a comma-delimited list: `--watch-ignore a,b` is equivalent to `--watch-ignore a --watch-ignore b` + ### `--file ` Explicitly _include_ a test file to be loaded before other test files files. Multiple uses of `--file` are allowed, and will be loaded in order given. @@ -1132,9 +1152,9 @@ Sort test files (by absolute path) using [Array.prototype.sort][mdn-array-sort]. ### `--watch, -w` -Executes tests on changes to JavaScript in the current working directory (and once initially). +Rerun tests on file changes. -By default, only files with extension `.js` are watched. Use `--extension` to change this behavior. +The `--watch-files` and `--watch-ignore` options can be used to control which files are watched for changes. ### `--fgrep , -f ` diff --git a/example/config/.mocharc.js b/example/config/.mocharc.js index 5d9c7fa770..e45d456a11 100644 --- a/example/config/.mocharc.js +++ b/example/config/.mocharc.js @@ -12,5 +12,7 @@ module.exports = { reporter: 'spec', slow: 75, timeout: 2000, - ui: 'bdd' + ui: 'bdd', + 'watch-files': ['lib/**/*.js', 'test/**/*.js'], + 'watch-ignore': ['lib/vendor'] }; diff --git a/example/config/.mocharc.json b/example/config/.mocharc.json index 07f954da67..5bb4529807 100644 --- a/example/config/.mocharc.json +++ b/example/config/.mocharc.json @@ -10,5 +10,7 @@ "reporter": "spec", "slow": 75, "timeout": 2000, - "ui": "bdd" + "ui": "bdd", + "watch-files": ["lib/**/*.js", "test/**/*.js"], + "watch-ignore": ["lib/vendor"] } diff --git a/example/config/.mocharc.jsonc b/example/config/.mocharc.jsonc index d86055f870..a8ade1d87a 100644 --- a/example/config/.mocharc.jsonc +++ b/example/config/.mocharc.jsonc @@ -10,5 +10,8 @@ "reporter": /* 📋 */ "spec", "slow": 75, "timeout": 2000, - "ui": "bdd" + "ui": "bdd", + // Camel-casing options are also accepted + "watchFiles": ["lib/**/*.js", "test/**/*.js"], + "watchIgnore": ["lib/vendor"] } diff --git a/example/config/.mocharc.yml b/example/config/.mocharc.yml index 466b4956f1..0701d3d94f 100644 --- a/example/config/.mocharc.yml +++ b/example/config/.mocharc.yml @@ -44,3 +44,8 @@ trace-warnings: true # node flags ok ui: bdd v8-stack-trace-limit: 100 # V8 flags are prepended with "v8-" watch: false +watch-files: + - 'lib/**/*.js' + - 'test/**/*.js' +watch-ignore: + - 'lib/vendor' diff --git a/lib/cli/run-helpers.js b/lib/cli/run-helpers.js index 7b3c7aa4fe..6c662a665f 100644 --- a/lib/cli/run-helpers.js +++ b/lib/cli/run-helpers.js @@ -118,13 +118,14 @@ exports.runMocha = (mocha, options) => { const { watch = false, extension = [], - ui = 'bdd', exit = false, ignore = [], file = [], recursive = false, sort = false, - spec = [] + spec = [], + watchFiles, + watchIgnore } = options; const fileCollectParams = { @@ -137,7 +138,7 @@ exports.runMocha = (mocha, options) => { }; if (watch) { - watchRun(mocha, {ui}, fileCollectParams); + watchRun(mocha, {watchFiles, watchIgnore}, fileCollectParams); } else { exports.singleRun(mocha, {exit}, fileCollectParams); } diff --git a/lib/cli/run-option-metadata.js b/lib/cli/run-option-metadata.js index d146ceeca9..d0bc92ffbe 100644 --- a/lib/cli/run-option-metadata.js +++ b/lib/cli/run-option-metadata.js @@ -18,9 +18,11 @@ exports.types = { 'file', 'global', 'ignore', - 'require', 'reporter-option', - 'spec' + 'require', + 'spec', + 'watch-files', + 'watch-ignore' ], boolean: [ 'allow-uncaught', @@ -68,7 +70,6 @@ exports.aliases = { 'async-only': ['A'], bail: ['b'], color: ['c', 'colors'], - extension: ['watch-extensions'], fgrep: ['f'], global: ['globals'], grep: ['g'], diff --git a/lib/cli/run.js b/lib/cli/run.js index dc94c101a4..3490a6bdbd 100644 --- a/lib/cli/run.js +++ b/lib/cli/run.js @@ -88,7 +88,7 @@ exports.builder = yargs => extension: { default: defaults.extension, defaultDescription: 'js', - description: 'File extension(s) to load and/or watch', + description: 'File extension(s) to load', group: GROUPS.FILES, requiresArg: true, coerce: list @@ -241,6 +241,19 @@ exports.builder = yargs => watch: { description: 'Watch files in the current working directory for changes', group: GROUPS.FILES + }, + 'watch-files': { + description: 'List of paths or globs to watch', + group: GROUPS.FILES, + requiresArg: true, + coerce: list + }, + 'watch-ignore': { + description: 'List of paths or globs to exclude from watching', + group: GROUPS.FILES, + requiresArg: true, + coerce: list, + default: ['node_modules', '.git'] } }) .positional('spec', { diff --git a/lib/cli/watch-run.js b/lib/cli/watch-run.js index e7ef34c632..4fcd33c02d 100644 --- a/lib/cli/watch-run.js +++ b/lib/cli/watch-run.js @@ -1,8 +1,8 @@ 'use strict'; -const utils = require('../utils'); +const path = require('path'); +const chokidar = require('chokidar'); const Context = require('../context'); -const Mocha = require('../mocha'); const collectFiles = require('./collect-files'); /** @@ -16,15 +16,42 @@ const collectFiles = require('./collect-files'); * Run Mocha in "watch" mode * @param {Mocha} mocha - Mocha instance * @param {Object} opts - Options - * @param {string} opts.ui - User interface + * @param {string[]} [opts.watchFiles] - List of paths and patterns to + * watch. If not provided all files with an extension included in + * `fileColletionParams.extension` are watched. See first argument of + * `chokidar.watch`. + * @param {string[]} opts.watchIgnore - List of paths and patterns to + * exclude from watching. See `ignored` option of `chokidar`. * @param {Object} fileCollectParams - Parameters that control test * file collection. See `lib/cli/collect-files.js`. - * @param {string[]} fileCollectParams.extension - List of extensions to watch + * @param {string[]} fileCollectParams.extension - List of extensions + * to watch if `opts.watchFiles` is not given. * @private */ -module.exports = (mocha, {ui}, fileCollectParams) => { - let runner; - const files = collectFiles(fileCollectParams); +module.exports = (mocha, {watchFiles, watchIgnore}, fileCollectParams) => { + if (!watchFiles) { + watchFiles = fileCollectParams.extension.map(ext => `**/*.${ext}`); + } + + const watcher = chokidar.watch(watchFiles, { + ignored: watchIgnore, + ignoreInitial: true + }); + + const rerunner = createRerunner(mocha, () => { + getWatchedFiles(watcher).forEach(file => { + delete require.cache[file]; + }); + mocha.files = collectFiles(fileCollectParams); + }); + + watcher.on('ready', () => { + rerunner.run(); + }); + + watcher.on('all', () => { + rerunner.scheduleRun(); + }); console.log(); hideCursor(); @@ -35,17 +62,30 @@ module.exports = (mocha, {ui}, fileCollectParams) => { // killed by SIGINT which has portable number 2. process.exit(128 + 2); }); +}; - const watchFiles = utils.files(process.cwd(), fileCollectParams.extension); - let runAgain = false; +/** + * Create an object that allows you to rerun tests on the mocha + * instance. `beforeRun` is called everytime before `mocha.run()` is + * called. + * + * @param {Mocha} mocha - Mocha instance + * @param {function} beforeRun - Called just before `mocha.run()` + */ +const createRerunner = (mocha, beforeRun) => { + // Set to a `Runner` when mocha is running. Set to `null` when mocha is not + // running. + let runner = null; + + let rerunScheduled = false; - const loadAndRun = () => { + const run = () => { try { - mocha.files = files; - runAgain = false; + beforeRun(); + resetMocha(mocha); runner = mocha.run(() => { runner = null; - if (runAgain) { + if (rerunScheduled) { rerun(); } }); @@ -54,29 +94,62 @@ module.exports = (mocha, {ui}, fileCollectParams) => { } }; - const purge = () => { - watchFiles.forEach(Mocha.unloadFile); - }; - - loadAndRun(); - - const rerun = () => { - purge(); - eraseLine(); - mocha.suite = mocha.suite.clone(); - mocha.suite.ctx = new Context(); - mocha.ui(ui); - loadAndRun(); - }; + const scheduleRun = () => { + if (rerunScheduled) { + return; + } - utils.watch(watchFiles, () => { - runAgain = true; + rerunScheduled = true; if (runner) { runner.abort(); } else { rerun(); } + }; + + const rerun = () => { + rerunScheduled = false; + eraseLine(); + run(); + }; + + return { + scheduleRun, + run + }; +}; + +/** + * Return the list of absolute paths watched by a chokidar watcher. + * + * @param watcher - Instance of a chokidar watcher + * @return {string[]} - List of absolute paths + */ +const getWatchedFiles = watcher => { + const watchedDirs = watcher.getWatched(); + let watchedFiles = []; + Object.keys(watchedDirs).forEach(dir => { + watchedFiles = watchedFiles.concat( + watchedDirs[dir].map(file => path.join(dir, file)) + ); }); + return watchedFiles; +}; + +/** + * Reset the internal state of the mocha instance so that tests can be rerun. + * + * @param {Mocha} mocha - Mocha instance + * @private + */ +const resetMocha = mocha => { + mocha.unloadFiles(); + mocha.suite = mocha.suite.clone(); + mocha.suite.ctx = new Context(); + // Registers a callback on `mocha.suite` that wires new context to the DSL + // (e.g. `describe`) that is exposed as globals when the test files are + // reloaded. + mocha.ui(mocha.options.ui); }; /** diff --git a/lib/utils.js b/lib/utils.js index 996e843507..805d98d463 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -53,80 +53,6 @@ exports.isString = function(obj) { return typeof obj === 'string'; }; -/** - * Watch the given `files` for changes - * and invoke `fn(file)` on modification. - * - * @private - * @param {Array} files - * @param {Function} fn - */ -exports.watch = function(files, fn) { - var options = {interval: 100}; - var debug = require('debug')('mocha:watch'); - files.forEach(function(file) { - debug('file %s', file); - fs.watchFile(file, options, function(curr, prev) { - if (prev.mtime < curr.mtime) { - fn(file); - } - }); - }); -}; - -/** - * Predicate to screen `pathname` for further consideration. - * - * @description - * Returns false for pathname referencing: - *
    - *
  • 'npm' package installation directory - *
  • 'git' version control directory - *
- * - * @private - * @param {string} pathname - File or directory name to screen - * @return {boolean} whether pathname should be further considered - * @example - * ['node_modules', 'test.js'].filter(considerFurther); // => ['test.js'] - */ -function considerFurther(pathname) { - var ignore = ['node_modules', '.git']; - - return !~ignore.indexOf(pathname); -} - -/** - * Lookup files in the given `dir`. - * - * @description - * Filenames are returned in _traversal_ order by the OS/filesystem. - * **Make no assumption that the names will be sorted in any fashion.** - * - * @private - * @param {string} dir - * @param {string[]} [exts=['js']] - * @param {Array} [ret=[]] - * @return {Array} - */ -exports.files = function(dir, exts, ret) { - ret = ret || []; - exts = exts || ['js']; - - fs.readdirSync(dir) - .filter(considerFurther) - .forEach(function(dirent) { - var pathname = path.join(dir, dirent); - if (fs.lstatSync(pathname).isDirectory()) { - exports.files(pathname, exts, ret); - } else if (hasMatchingExtname(pathname, exts)) { - ret.push(pathname); - } - }); - - return ret; -}; - /** * Compute a slug from the given `str`. * diff --git a/package-lock.json b/package-lock.json index cd6c298e00..ac164dbf11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -729,7 +729,7 @@ "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true }, "arch": { @@ -823,7 +823,7 @@ }, "array-equal": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", "dev": true }, @@ -1294,7 +1294,7 @@ }, "autolinker": { "version": "0.15.3", - "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-0.15.3.tgz", + "resolved": "http://registry.npmjs.org/autolinker/-/autolinker-0.15.3.tgz", "integrity": "sha1-NCQX2PLzRhsUzwkIjV7fh5HcmDI=", "dev": true }, @@ -1774,7 +1774,7 @@ }, "bl": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "dev": true, "requires": { @@ -1797,7 +1797,7 @@ "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha1-LN4J617jQfSEdGuwMJsyU7GxRC8=", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", "dev": true }, "body-parser": { @@ -2051,7 +2051,7 @@ "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha1-uqVZ7hTO1zRSIputcyZGfGH6vWA=" + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" }, "browser-sync": { "version": "2.26.5", @@ -2103,6 +2103,26 @@ "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", "dev": true }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, "cliui": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", @@ -2140,6 +2160,15 @@ "number-is-nan": "^1.0.0" } }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, "lcid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", @@ -2325,7 +2354,7 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { @@ -2379,7 +2408,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { @@ -2662,7 +2691,7 @@ }, "camelcase-keys": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "dev": true, "requires": { @@ -2858,33 +2887,100 @@ "dev": true }, "chokidar": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.2.tgz", - "integrity": "sha512-IwXUx0FXc5ibYmPC2XeEj5mpXoV66sR+t3jqu2NS2GYwCktt3KF1/Qqjws/NkegajBA4RbZ5+DDwlOiJsxDHEg==", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.0.2.tgz", + "integrity": "sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA==", + "requires": { + "anymatch": "^3.0.1", + "braces": "^3.0.2", + "fsevents": "^2.0.6", + "glob-parent": "^5.0.0", + "is-binary-path": "^2.1.0", + "is-glob": "^4.0.1", "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.0" + "readdirp": "^3.1.1" }, "dependencies": { + "anymatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.0.3.tgz", + "integrity": "sha512-c6IvoeBECQlMVuYUjSwimnhmztImpErfxJzWZhIQinIvQWoGOnB0dLIgifbPHQt5heS6mNlaZG16f06H3C8t1g==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==" + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.0.7.tgz", + "integrity": "sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==", + "optional": true + }, + "glob-parent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz", + "integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "requires": { + "binary-extensions": "^2.0.0" + } + }, "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", - "dev": true, + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "requires": { "is-extglob": "^2.1.1" } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "readdirp": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.1.2.tgz", + "integrity": "sha512-8rhl0xs2cxfVsqzreYCvs8EwBfn/DhVdqtoLmw19uI3SC5avYX9teCurlErfpPXGmYtMHReGaP2RsLnFvz/lnw==", + "requires": { + "picomatch": "^2.0.4" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } } } }, @@ -2903,7 +2999,7 @@ "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha1-h2Dk7MJy9MNjUy+SbYdKriwTl94=", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", "dev": true, "requires": { "inherits": "^2.0.1", @@ -2992,7 +3088,7 @@ }, "slice-ansi": { "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "resolved": "http://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", "dev": true }, @@ -3171,7 +3267,7 @@ "dependencies": { "convert-source-map": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", "dev": true }, @@ -3487,7 +3583,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -3545,7 +3641,7 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { @@ -3558,7 +3654,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { @@ -3573,7 +3669,7 @@ "createerror": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/createerror/-/createerror-1.3.0.tgz", - "integrity": "sha1-xma9TNa5TjVBU5ZWnUZJ3QzbMxM=", + "integrity": "sha512-w9UZUtkaGd8MfS7eMG7Sa0lV5vCJghqQfiOnwNVrPhbZScUp5h0jwYoAF933MKlotlG1JAJOCCT3xU6r+SDKNw==", "dev": true }, "cross-env": { @@ -3626,7 +3722,7 @@ }, "css-color-names": { "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "resolved": "http://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", "dev": true }, @@ -4250,7 +4346,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -4423,11 +4519,11 @@ "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha1-gAwN0eCov7yVg1wgKtIg/jF+WhI=" + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" }, "diffie-hellman": { "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { @@ -4597,7 +4693,7 @@ }, "duplexer": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, @@ -4890,7 +4986,7 @@ }, "es6-promisify": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "dev": true, "requires": { @@ -5076,7 +5172,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { "ms": "2.0.0" @@ -5103,7 +5199,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { "ms": "2.0.0" @@ -5149,7 +5245,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { "ms": "2.0.0" @@ -5157,7 +5253,7 @@ }, "doctrine": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "resolved": "http://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", "dev": true, "requires": { @@ -5412,7 +5508,7 @@ }, "event-stream": { "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", "dev": true, "requires": { @@ -5440,7 +5536,7 @@ "evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha1-f8vbGY3HGVlDLv4ThCaE4FJaywI=", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "dev": true, "requires": { "md5.js": "^1.3.4", @@ -6291,7 +6387,7 @@ }, "fs-access": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", + "resolved": "http://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", "dev": true, "requires": { @@ -6897,7 +6993,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=" + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -7142,7 +7238,7 @@ }, "debug": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -7151,7 +7247,7 @@ }, "ms": { "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "resolved": "http://registry.npmjs.org/ms/-/ms-0.7.1.tgz", "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", "dev": true } @@ -7504,7 +7600,7 @@ "html-encoding-sniffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha1-5w2EuU2lOqN14R/jo1G+ZkLKRvg=", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", "dev": true, "requires": { "whatwg-encoding": "^1.0.1" @@ -7553,7 +7649,7 @@ }, "htmlescape": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=", "dev": true }, @@ -8501,8 +8597,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-finite": { "version": "1.0.2", @@ -8592,7 +8687,7 @@ }, "is-obj": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, @@ -8652,7 +8747,7 @@ "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { "isobject": "^3.0.1" @@ -9320,6 +9415,35 @@ "useragent": "2.3.0" }, "dependencies": { + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, "mime": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.3.tgz", @@ -9369,7 +9493,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -10329,7 +10453,7 @@ }, "map-stream": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "resolved": "http://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", "dev": true }, @@ -10509,7 +10633,7 @@ "markdown-toc": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/markdown-toc/-/markdown-toc-1.2.0.tgz", - "integrity": "sha1-RKFWBoREkDFK/ARESD+eexEiwzk=", + "integrity": "sha512-eOsq7EGd3asV0oBfmyqngeEIhrbkc7XVP63OwcJBIhH2EpG2PzFcbZdhy1jutXSlRBBVMNXHvMtSr5LAxSUvUg==", "dev": true, "requires": { "concat-stream": "^1.5.2", @@ -10559,7 +10683,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -10594,7 +10718,7 @@ "dependencies": { "commander": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "resolved": "http://registry.npmjs.org/commander/-/commander-2.9.0.tgz", "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", "dev": true, "requires": { @@ -10705,7 +10829,7 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, @@ -10732,7 +10856,7 @@ "dependencies": { "lru-cache": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.0.tgz", + "resolved": "http://registry.npmjs.org/lru-cache/-/lru-cache-2.5.0.tgz", "integrity": "sha1-2COIrpyWC+y+oMc7uet5tsbOmus=", "dev": true } @@ -10767,7 +10891,7 @@ }, "meow": { "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz", "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, "requires": { @@ -10785,7 +10909,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -10830,7 +10954,7 @@ "miller-rabin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha1-8IA1HIZbDcViqEYpZtqlNUPHik0=", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", "dev": true, "requires": { "bn.js": "^4.0.0", @@ -10892,7 +11016,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "minipass": { @@ -10941,7 +11065,7 @@ "is-extendable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { "is-plain-object": "^2.0.4" @@ -10951,7 +11075,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" @@ -11150,7 +11274,7 @@ "no-case": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", - "integrity": "sha1-YLgTOWvjmz8SiKTB7V0efSi0ZKw=", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", "dev": true, "requires": { "lower-case": "^1.1.1" @@ -11343,8 +11467,7 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "normalize-range": { "version": "0.1.2", @@ -11430,7 +11553,7 @@ "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "requires": { "are-we-there-yet": "~1.1.2", @@ -11760,6 +11883,27 @@ "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", "dev": true }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, "cliui": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", @@ -11786,6 +11930,16 @@ "number-is-nan": "^1.0.0" } }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "optional": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, "lcid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", @@ -11904,7 +12058,7 @@ }, "merge-source-map": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "resolved": false, "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", "dev": true, "requires": { @@ -12160,7 +12314,7 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true }, @@ -12183,7 +12337,7 @@ }, "os-tmpdir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, @@ -12479,7 +12633,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { @@ -12557,7 +12711,7 @@ }, "pause-stream": { "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", "dev": true, "requires": { @@ -12642,14 +12796,14 @@ }, "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, "postcss": { "version": "5.2.18", "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", "dev": true, "requires": { "chalk": "^1.1.3", @@ -12684,9 +12838,14 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, + "picomatch": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", + "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==" + }, "pify": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, @@ -12930,7 +13089,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "supports-color": { @@ -13300,7 +13459,7 @@ "postcss": { "version": "5.2.18", "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha1-ut+hSX1GJE9jkPWLMZgw2RB4U8U=", + "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", "dev": true, "requires": { "chalk": "^1.1.3", @@ -13975,7 +14134,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -14043,7 +14202,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -14621,7 +14780,7 @@ }, "safe-regex": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { @@ -14679,7 +14838,7 @@ "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk=", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true }, "saxes": { @@ -14732,7 +14891,7 @@ "dependencies": { "commander": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", + "resolved": "http://registry.npmjs.org/commander/-/commander-2.8.1.tgz", "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", "dev": true, "requires": { @@ -15006,7 +15165,7 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { @@ -15041,7 +15200,7 @@ }, "shasum": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", + "resolved": "http://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=", "dev": true, "requires": { @@ -15667,7 +15826,7 @@ }, "split": { "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "resolved": "http://registry.npmjs.org/split/-/split-0.3.3.tgz", "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", "dev": true, "requires": { @@ -15772,7 +15931,7 @@ }, "starts-with": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/starts-with/-/starts-with-1.0.2.tgz", + "resolved": "http://registry.npmjs.org/starts-with/-/starts-with-1.0.2.tgz", "integrity": "sha1-Fnk6cp2J1M89T7LtovkIrjV/GW8=", "dev": true }, @@ -15896,7 +16055,7 @@ }, "stream-combiner": { "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", "dev": true, "requires": { @@ -16106,7 +16265,7 @@ }, "strip-eof": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, @@ -16189,7 +16348,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -16430,7 +16589,7 @@ "dependencies": { "bluebird": { "version": "2.9.34", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.9.34.tgz", + "resolved": "http://registry.npmjs.org/bluebird/-/bluebird-2.9.34.tgz", "integrity": "sha1-L3tOyAIWMoqf3evfacjUlC/v99g=", "dev": true }, @@ -16697,7 +16856,7 @@ }, "through": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, @@ -16784,7 +16943,7 @@ "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { "os-tmpdir": "~1.0.2" @@ -17456,9 +17615,9 @@ "dev": true }, "upath": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", - "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", + "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", "dev": true }, "update-notifier": { @@ -17739,6 +17898,35 @@ "xtend": "^4.0.0" }, "dependencies": { + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -17754,7 +17942,7 @@ "webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha1-qFWYCx8LazWbodXZ+zmulB+qY60=", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", "dev": true }, "whatwg-encoding": { @@ -17877,7 +18065,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { diff --git a/package-scripts.js b/package-scripts.js index f4d1dc9845..6d9985b90d 100644 --- a/package-scripts.js +++ b/package-scripts.js @@ -23,7 +23,7 @@ function test(testName, mochaParams) { module.exports = { scripts: { build: { - script: `browserify -e browser-entry.js --plugin ./scripts/dedefine --ignore 'fs' --ignore 'glob' --ignore 'path' --ignore 'supports-color' -o mocha.js`, + script: `browserify -e browser-entry.js --plugin ./scripts/dedefine --ignore 'fs' --ignore 'glob' --ignore 'path' --ignore 'supports-color' --ignore chokidar -o mocha.js`, description: 'Build browser bundle' }, lint: { diff --git a/package.json b/package.json index f53ea0ba15..4a0ed10f11 100644 --- a/package.json +++ b/package.json @@ -514,6 +514,7 @@ "dependencies": { "ansi-colors": "3.2.3", "browser-stdout": "1.3.1", + "chokidar": "3.0.2", "debug": "3.2.6", "diff": "3.5.0", "escape-string-regexp": "1.0.5", @@ -618,7 +619,8 @@ "fs": false, "glob": false, "path": false, - "supports-color": false + "supports-color": false, + "chokidar": false }, "prettier": { "singleQuote": true, diff --git a/test/integration/file-utils.spec.js b/test/integration/file-utils.spec.js index ae97b996e2..0b5b2e92fe 100644 --- a/test/integration/file-utils.spec.js +++ b/test/integration/file-utils.spec.js @@ -116,30 +116,6 @@ describe('file utils', function() { }); }); - describe('.files', function() { - it('should return broken symlink file path', function() { - if (!symlinkSupported) { - return this.skip(); - } - expect( - utils.files(tmpDir, ['js']), - 'to contain', - tmpFile('mocha-utils-link.js'), - tmpFile('mocha-utils.js') - ).and('to have length', 2); - - expect(existsSync(tmpFile('mocha-utils-link.js')), 'to be', true); - - fs.renameSync(tmpFile('mocha-utils.js'), tmpFile('bob')); - - expect(existsSync(tmpFile('mocha-utils-link.js')), 'to be', false); - - expect(utils.files(tmpDir, ['js']), 'to equal', [ - tmpFile('mocha-utils-link.js') - ]); - }); - }); - afterEach(removeTempDir); function makeTempDir() { diff --git a/test/integration/options/watch.spec.js b/test/integration/options/watch.spec.js index 5d9fb2f26a..37e7f93fc7 100644 --- a/test/integration/options/watch.spec.js +++ b/test/integration/options/watch.spec.js @@ -31,7 +31,95 @@ describe('--watch', function() { }); }); - it('reruns test when file matching extension is touched', function() { + it('reruns test when file matching --watch-files changes', function() { + const testFile = path.join(this.tempDir, 'test.js'); + copyFixture('__default__', testFile); + + const watchedFile = path.join(this.tempDir, 'dir/file.xyz'); + touchFile(watchedFile); + + return runMochaWatch( + [testFile, '--watch-files', 'dir/*.xyz'], + this.tempDir, + () => { + touchFile(watchedFile); + } + ).then(results => { + expect(results.length, 'to equal', 2); + }); + }); + + it('reruns test when file matching --watch-files is added', function() { + const testFile = path.join(this.tempDir, 'test.js'); + copyFixture('__default__', testFile); + + const watchedFile = path.join(this.tempDir, 'lib/file.xyz'); + return runMochaWatch( + [testFile, '--watch-files', '**/*.xyz'], + this.tempDir, + () => { + touchFile(watchedFile); + } + ).then(results => { + expect(results, 'to have length', 2); + }); + }); + + it('reruns test when file matching --watch-files is removed', function() { + const testFile = path.join(this.tempDir, 'test.js'); + copyFixture('__default__', testFile); + + const watchedFile = path.join(this.tempDir, 'lib/file.xyz'); + touchFile(watchedFile); + + return runMochaWatch( + [testFile, '--watch-files', 'lib/**/*.xyz'], + this.tempDir, + () => { + fs.removeSync(watchedFile); + } + ).then(results => { + expect(results, 'to have length', 2); + }); + }); + + it('does not rerun test when file not matching --watch-files is changed', function() { + const testFile = path.join(this.tempDir, 'test.js'); + copyFixture('__default__', testFile); + + const watchedFile = path.join(this.tempDir, 'dir/file.js'); + touchFile(watchedFile); + + return runMochaWatch( + [testFile, '--watch-files', 'dir/*.xyz'], + this.tempDir, + () => { + touchFile(watchedFile); + } + ).then(results => { + expect(results.length, 'to equal', 1); + }); + }); + + it('picks up new test files when they are added', function() { + const testFile = path.join(this.tempDir, 'test/a.js'); + copyFixture('__default__', testFile); + + return runMochaWatch( + ['test/**/*.js', '--watch-files', 'test/**/*.js'], + this.tempDir, + () => { + const addedTestFile = path.join(this.tempDir, 'test/b.js'); + copyFixture('passing', addedTestFile); + } + ).then(results => { + expect(results, 'to have length', 2); + expect(results[0].passes, 'to have length', 1); + expect(results[1].passes, 'to have length', 3); + }); + }); + + it('reruns test when file matching --extension is changed', function() { const testFile = path.join(this.tempDir, 'test.js'); copyFixture('__default__', testFile); @@ -75,17 +163,45 @@ describe('--watch', function() { }); }); + it('ignores files matching --watch-ignore', function() { + const testFile = path.join(this.tempDir, 'test.js'); + copyFixture('__default__', testFile); + + const watchedFile = path.join(this.tempDir, 'dir/file-to-ignore.xyz'); + touchFile(watchedFile); + + return runMochaWatch( + [ + testFile, + '--watch-files', + 'dir/*.xyz', + '--watch-ignore', + 'dir/*ignore*' + ], + this.tempDir, + () => { + touchFile(watchedFile); + } + ).then(results => { + expect(results.length, 'to equal', 1); + }); + }); + it('reloads test files when they change', function() { const testFile = path.join(this.tempDir, 'test.js'); copyFixture('options/watch/test-file-change', testFile); - return runMochaWatch([testFile], this.tempDir, () => { - replaceFileContents( - testFile, - 'testShouldFail = true', - 'testShouldFail = false' - ); - }).then(results => { + return runMochaWatch( + [testFile, '--watch-files', '**/*.js'], + this.tempDir, + () => { + replaceFileContents( + testFile, + 'testShouldFail = true', + 'testShouldFail = false' + ); + } + ).then(results => { expect(results, 'to have length', 2); expect(results[0].passes, 'to have length', 0); expect(results[0].failures, 'to have length', 1); @@ -101,13 +217,17 @@ describe('--watch', function() { const dependency = path.join(this.tempDir, 'lib', 'dependency.js'); copyFixture('options/watch/dependency', dependency); - return runMochaWatch([testFile], this.tempDir, () => { - replaceFileContents( - dependency, - 'module.exports.testShouldFail = false', - 'module.exports.testShouldFail = true' - ); - }).then(results => { + return runMochaWatch( + [testFile, '--watch-files', 'lib/**/*.js'], + this.tempDir, + () => { + replaceFileContents( + dependency, + 'module.exports.testShouldFail = false', + 'module.exports.testShouldFail = true' + ); + } + ).then(results => { expect(results, 'to have length', 2); expect(results[0].passes, 'to have length', 1); expect(results[0].failures, 'to have length', 0); diff --git a/test/node-unit/cli/options.spec.js b/test/node-unit/cli/options.spec.js index 3c39cc7f09..84723bafda 100644 --- a/test/node-unit/cli/options.spec.js +++ b/test/node-unit/cli/options.spec.js @@ -28,8 +28,7 @@ const defaults = { timeouts: 1000, t: 1000, opts: '/default/path/to/mocha.opts', - extension: ['js'], - 'watch-extensions': ['js'] + extension: ['js'] }; describe('options', function() {