├── .eslintrc ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── README.md ├── index.js ├── package-lock.json ├── package.json ├── src ├── cli.js ├── common.js ├── node_watcher.js ├── poll_watcher.js ├── utils │ └── recrawl-warning-dedupe.js ├── watchexec_client.js ├── watchexec_watcher.js ├── watchman_client.js └── watchman_watcher.js ├── test ├── plugin_watcher.js ├── test.js ├── utils-test.js ├── watchexec_client-test.js └── watchexec_watcher-test.js └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "env": { 4 | "browser": true, 5 | "commonjs": true, 6 | "node": true, 7 | "es6": true 8 | }, 9 | "parserOptions": { 10 | "ecmaVersion": 8 11 | }, 12 | "rules": { 13 | "no-console": "off", 14 | "strict": ["error", "global"], 15 | "curly": "warn" 16 | } 17 | } -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - 'v*' # older version branches 8 | tags: 9 | - '*' 10 | pull_request: {} 11 | schedule: 12 | - cron: '0 6 * * 0' # weekly, on sundays 13 | 14 | jobs: 15 | test: 16 | name: Tests 17 | runs-on: ${{ matrix.os }} 18 | 19 | strategy: 20 | matrix: 21 | node: ['10', '12'] 22 | os: [ubuntu-latest, macOS-latest] 23 | 24 | steps: 25 | - uses: actions/checkout@v1 26 | - uses: rwjblue/setup-volta@v1 27 | with: 28 | node-version: ${{ matrix.node }} 29 | - name: install osx native dependencies 30 | if: matrix.os == 'macOS-latest' 31 | run: brew install automake autoconf libtool openssl 32 | - name: install dependencies 33 | run: yarn 34 | - name: setup watchman 35 | run: git clone https://github.com/facebook/watchman.git && cd watchman && git checkout v4.7.0 && ./autogen.sh && ./configure --enable-lenient && make && sudo make install 36 | - name: test 37 | run: yarn test 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Try on repl.it](https://repl-badge.jajoosam.repl.co/try.png)](https://repl.it/@amasad/sane-playground) 2 | ![CI](https://github.com/amasad/sane/workflows/CI/badge.svg) 3 | 4 | sane 5 | ---- 6 | 7 | I've been driven to insanity by node filesystem watcher wrappers. 8 | Sane aims to be fast, small, and reliable file system watcher. It does that by: 9 | 10 | * By default stays away from fs polling because it's very slow and cpu intensive 11 | * Uses `fs.watch` by default and sensibly works around the various issues 12 | * Maintains a consistent API across different platforms 13 | * Where `fs.watch` is not reliable you have the choice of using the following alternatives: 14 | * [the facebook watchman library](https://facebook.github.io/watchman/) 15 | * [the watchexec library](https://github.com/watchexec/watchexec) 16 | * polling 17 | 18 | ## Install 19 | 20 | ``` 21 | $ npm install sane 22 | ``` 23 | 24 | ## How to choose a mode 25 | 26 | Don't worry too much about choosing the correct mode upfront because sane 27 | maintains the same API across all modes and will be easy to switch. 28 | 29 | * If you're only supporting Linux and OS X, `watchman` would be the most reliable mode 30 | * If you're using node > v0.10.0 use the default mode 31 | * If you're running OS X and you're watching a lot of directories and you're running into https://github.com/joyent/node/issues/5463, use `watchman` 32 | * If you're in an environment where native file system events aren't available (like Vagrant), you should use polling 33 | * Otherwise, the default mode should work well for you 34 | 35 | ## API 36 | 37 | ### sane(dir, options) 38 | 39 | Watches a directory and all its descendant directories for changes, deletions, and additions on files and directories. 40 | 41 | ```js 42 | var watcher = sane('path/to/dir', {glob: ['**/*.js', '**/*.css']}); 43 | watcher.on('ready', function () { console.log('ready') }); 44 | watcher.on('change', function (filepath, root, stat) { console.log('file changed', filepath); }); 45 | watcher.on('add', function (filepath, root, stat) { console.log('file added', filepath); }); 46 | watcher.on('delete', function (filepath, root) { console.log('file deleted', filepath); }); 47 | // close 48 | watcher.close(); 49 | ``` 50 | 51 | options: 52 | 53 | * `glob`: a single string glob pattern or an array of them. 54 | * `poll`: puts the watcher in polling mode. Under the hood that means `fs.watchFile`. 55 | * `watchman`: makes the watcher use [watchman](https://facebook.github.io/watchman/). 56 | * `watchmanPath`: sets a custom path for `watchman` binary. 57 | * `watchexec`: makes the watcher use [watchexec](https://github.com/watchexec/watchexec). 58 | * `dot`: enables watching files/directories that start with a dot. 59 | * `ignored`: a glob, regex, function, or array of any combination. 60 | 61 | For the glob pattern documentation, see [micromatch](https://github.com/micromatch/micromatch). 62 | If you choose to use `watchman` you'll have to [install watchman yourself](https://facebook.github.io/watchman/docs/install.html)). 63 | If you choose to use `watchexec` you'll have to [install watchexec yourself](https://github.com/watchexec/watchexec)). 64 | For the ignored options, see [anymatch](https://github.com/es128/anymatch). 65 | 66 | ### sane.NodeWatcher(dir, options) 67 | 68 | The default watcher class. Uses `fs.watch` under the hood, and takes the same options as `sane(dir, options)`. 69 | 70 | ### sane.WatchmanWatcher(dir, options) 71 | 72 | The watchman watcher class. Takes the same options as `sane(dir, options)`. 73 | 74 | ### sane.Watchexec(dir, options) 75 | 76 | The watchexec watcher class. Takes the same options as `sane(dir, options)`. 77 | 78 | ### sane.PollWatcher(dir, options) 79 | 80 | The polling watcher class. Takes the same options as `sane(dir, options)` with the addition of: 81 | 82 | * interval: indicates how often the files should be polled. (passed to fs.watchFile) 83 | 84 | ### sane.{Node|Watchman|Watchexec|Poll}Watcher#close 85 | 86 | Stops watching. 87 | 88 | ### sane.{Node|Watchman|Watchexec|Poll}Watcher events 89 | 90 | Emits the following events: 91 | 92 | All events are passed the file/dir path relative to the root directory 93 | * `ready` when the program is ready to detect events in the directory 94 | * `change` when a file changes 95 | * `add` when a file or directory has been added 96 | * `delete` when a file or directory has been deleted 97 | 98 | ## CLI 99 | 100 | This module includes a simple command line interface, which you can install with `npm install sane -g`. 101 | 102 | ``` 103 | Usage: sane [...directory] [--glob=] [--poll] [--watchman] [--watchman-path=] [--dot] [--wait=] 104 | 105 | OPTIONS: 106 | --glob= 107 | A single string glob pattern or an array of them. 108 | 109 | --ignored= 110 | A glob, regex, function, or array of any combination. 111 | 112 | --poll, -p 113 | Use polling mode. 114 | 115 | --watchman, -w 116 | Use watchman (if available). 117 | 118 | --watchman-path= 119 | Sets a custom path for watchman binary (if using this mode). 120 | 121 | --dot, -d 122 | Enables watching files/directories that start with a dot. 123 | 124 | --wait= 125 | Duration, in seconds, that watching will be disabled 126 | after running . Setting this option will 127 | throttle calls to for the specified duration. 128 | --quiet, -q 129 | Disables sane's console output 130 | 131 | --changes-only, -o 132 | Runs only when a change occur. Skips running at startup 133 | ``` 134 | 135 | It will watch the given `directory` and run the given every time a file changes. 136 | 137 | ### CLI example usage 138 | - `sane 'echo "A command ran"'` 139 | - `sane 'echo "A command ran"' --glob='**/*.css'` 140 | - `sane 'echo "A command ran"' site/assets/css --glob='**/*.css'` 141 | - `sane 'echo "A command ran"' --glob='**/*.css' --ignored='**/ignore.css'` 142 | - `sane 'echo "A command ran"' --wait=3` 143 | - `sane 'echo "A command ran"' -p` 144 | 145 | ## License 146 | 147 | MIT 148 | 149 | ## Credits 150 | The CLI was originally based on the [watch CLI](https://github.com/mikeal/watch). Watch is licensed under the Apache License Version 2.0. 151 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const NodeWatcher = require('./src/node_watcher'); 4 | const PollWatcher = require('./src/poll_watcher'); 5 | const WatchmanWatcher = require('./src/watchman_watcher'); 6 | const WatchexecWatcher = require('./src/watchexec_watcher'); 7 | 8 | function throwNoFSEventsSupports() { 9 | throw new Error('Sane >= 4 no longer support the fsevents module.'); 10 | } 11 | 12 | function sane(dir, options) { 13 | options = options || {}; 14 | if (options.watcher) { 15 | const WatcherClass = require(options.watcher); 16 | return new WatcherClass(dir, options); 17 | } else if (options.poll) { 18 | return new PollWatcher(dir, options); 19 | } else if (options.watchman) { 20 | return new WatchmanWatcher(dir, options); 21 | } else if (options.watchexec) { 22 | return new WatchexecWatcher(dir, options); 23 | } else if (options.fsevents) { 24 | throwNoFSEventsSupports(); 25 | } else { 26 | return new NodeWatcher(dir, options); 27 | } 28 | } 29 | 30 | module.exports = sane; 31 | sane.NodeWatcher = NodeWatcher; 32 | sane.PollWatcher = PollWatcher; 33 | sane.WatchmanWatcher = WatchmanWatcher; 34 | sane.WatchexecWatcher = WatchexecWatcher; 35 | 36 | Object.defineProperty(sane, 'FSEventsWatcher', { 37 | get() { 38 | return throwNoFSEventsSupports(); 39 | }, 40 | }); 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sane", 3 | "version": "5.0.1", 4 | "description": "Sane aims to be fast, small, and reliable file system watcher.", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/amasad/sane" 9 | }, 10 | "files": [ 11 | "src", 12 | "index.js" 13 | ], 14 | "scripts": { 15 | "test": "npm run format && eslint src/ test/ index.js && mocha --bail test/test.js && mocha --bail test/utils-test.js && mocha --bail 'test/watchexec_*-test.js'", 16 | "test:debug": "mocha debug --bail", 17 | "format": "prettier --trailing-comma es5 --single-quote --write index.js 'src/**/*.js' 'test/**/*.js'" 18 | }, 19 | "bin": "./src/cli.js", 20 | "keywords": [ 21 | "watch", 22 | "file", 23 | "fswatcher", 24 | "watchfile", 25 | "fs", 26 | "watching" 27 | ], 28 | "author": "amasad", 29 | "license": "MIT", 30 | "dependencies": { 31 | "@cnakazawa/watch": "^1.0.3", 32 | "anymatch": "^3.1.1", 33 | "capture-exit": "^2.0.0", 34 | "exec-sh": "^0.3.4", 35 | "execa": "^4.0.0", 36 | "fb-watchman": "^2.0.1", 37 | "micromatch": "^4.0.2", 38 | "minimist": "^1.1.1", 39 | "walker": "~1.0.5" 40 | }, 41 | "devDependencies": { 42 | "eslint": "^6.8.0", 43 | "mocha": "^6.2.2", 44 | "prettier": "^1.19.1", 45 | "rimraf": "~3.0.0", 46 | "tmp": "0.1.0" 47 | }, 48 | "engines": { 49 | "node": "10.* || >= 12.*" 50 | }, 51 | "bugs": { 52 | "url": "https://github.com/amasad/sane/issues" 53 | }, 54 | "homepage": "https://github.com/amasad/sane", 55 | "volta": { 56 | "node": "12.16.1", 57 | "yarn": "1.22.4" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const sane = require('../'); 5 | const argv = require('minimist')(process.argv.slice(2)); 6 | const execshell = require('exec-sh'); 7 | 8 | if (argv._.length === 0) { 9 | const msg = 10 | 'Usage: sane [...directory] [--glob=] ' + 11 | '[--ignored=] [--poll] [--watchman] [--watchman-path=] [--dot] ' + 12 | '[--wait=] [--only-changes] [--quiet]'; 13 | console.error(msg); 14 | process.exit(); 15 | } 16 | 17 | const opts = {}; 18 | const command = argv._[0]; 19 | const dir = argv._[1] || process.cwd(); 20 | const waitTime = Number(argv.wait || argv.w); 21 | const dot = argv.dot || argv.d; 22 | const glob = argv.glob || argv.g; 23 | const ignored = argv.ignored || argv.i; 24 | const poll = argv.poll || argv.p; 25 | const watchman = argv.watchman || argv.w; 26 | const watchmanPath = argv['watchman-path']; 27 | const onlyChanges = argv['only-changes'] | argv.o; 28 | const quiet = argv.quiet | argv.q; 29 | 30 | if (dot) { 31 | opts.dot = true; 32 | } 33 | if (glob) { 34 | opts.glob = glob; 35 | } 36 | if (ignored) { 37 | opts.ignored = ignored; 38 | } 39 | if (poll) { 40 | opts.poll = true; 41 | } 42 | if (watchman) { 43 | opts.watchman = true; 44 | } 45 | if (watchmanPath) { 46 | opts.watchmanPath = watchmanPath; 47 | } 48 | 49 | let wait = false; 50 | const watcher = sane(dir, opts); 51 | 52 | watcher.on('ready', function() { 53 | if (!quiet) { 54 | console.log('Watching: ', dir + '/' + (opts.glob || '')); 55 | } 56 | if (!onlyChanges) { 57 | execshell(command); 58 | } 59 | }); 60 | 61 | watcher.on('change', function(filepath) { 62 | if (wait) { 63 | return; 64 | } 65 | if (!quiet) { 66 | console.log('Change detected in:', filepath); 67 | } 68 | execshell(command); 69 | 70 | if (waitTime > 0) { 71 | wait = true; 72 | setTimeout(function() { 73 | wait = false; 74 | }, waitTime * 1000); 75 | } 76 | }); 77 | -------------------------------------------------------------------------------- /src/common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const walker = require('walker'); 4 | const anymatch = require('anymatch'); 5 | const micromatch = require('micromatch'); 6 | const path = require('path'); 7 | const platform = require('os').platform(); 8 | 9 | /** 10 | * Constants 11 | */ 12 | 13 | exports.DEFAULT_DELAY = 100; 14 | exports.CHANGE_EVENT = 'change'; 15 | exports.DELETE_EVENT = 'delete'; 16 | exports.ADD_EVENT = 'add'; 17 | exports.ALL_EVENT = 'all'; 18 | 19 | /** 20 | * Assigns options to the watcher. 21 | * 22 | * @param {NodeWatcher|PollWatcher|WatchmanWatcher} watcher 23 | * @param {?object} opts 24 | * @return {boolean} 25 | * @public 26 | */ 27 | 28 | exports.assignOptions = function(watcher, opts) { 29 | opts = opts || {}; 30 | watcher.globs = opts.glob || []; 31 | watcher.dot = opts.dot || false; 32 | watcher.ignored = opts.ignored || false; 33 | 34 | if (!Array.isArray(watcher.globs)) { 35 | watcher.globs = [watcher.globs]; 36 | } 37 | watcher.hasIgnore = 38 | Boolean(opts.ignored) && !(Array.isArray(opts) && opts.length > 0); 39 | watcher.doIgnore = opts.ignored ? anymatch(opts.ignored) : () => false; 40 | 41 | if (opts.watchman && opts.watchmanPath) { 42 | watcher.watchmanPath = opts.watchmanPath; 43 | } 44 | 45 | return opts; 46 | }; 47 | 48 | /** 49 | * Checks a file relative path against the globs array. 50 | * 51 | * @param {array} globs 52 | * @param {string} relativePath 53 | * @return {boolean} 54 | * @public 55 | */ 56 | 57 | exports.isFileIncluded = function(globs, dot, doIgnore, relativePath) { 58 | if (doIgnore(relativePath)) { 59 | return false; 60 | } 61 | return globs.length 62 | ? micromatch.some(relativePath, globs, { dot: dot }) 63 | : dot || micromatch.some(relativePath, '**/*'); 64 | }; 65 | 66 | /** 67 | * Traverse a directory recursively calling `callback` on every directory. 68 | * 69 | * @param {string} dir 70 | * @param {function} dirCallback 71 | * @param {function} fileCallback 72 | * @param {function} endCallback 73 | * @param {*} ignored 74 | * @public 75 | */ 76 | 77 | exports.recReaddir = function( 78 | dir, 79 | dirCallback, 80 | fileCallback, 81 | endCallback, 82 | errorCallback, 83 | ignored 84 | ) { 85 | walker(dir) 86 | .filterDir(currentDir => !anymatch(ignored, currentDir)) 87 | .on('dir', normalizeProxy(dirCallback)) 88 | .on('file', normalizeProxy(fileCallback)) 89 | .on('error', errorCallback) 90 | .on('end', () => { 91 | if (platform === 'win32') { 92 | setTimeout(endCallback, 1000); 93 | } else { 94 | endCallback(); 95 | } 96 | }); 97 | }; 98 | 99 | /** 100 | * Returns a callback that when called will normalize a path and call the 101 | * original callback 102 | * 103 | * @param {function} callback 104 | * @return {function} 105 | * @private 106 | */ 107 | 108 | function normalizeProxy(callback) { 109 | return (filepath, stats) => callback(path.normalize(filepath), stats); 110 | } 111 | -------------------------------------------------------------------------------- /src/node_watcher.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const common = require('./common'); 6 | const platform = require('os').platform(); 7 | const EventEmitter = require('events').EventEmitter; 8 | 9 | /** 10 | * Constants 11 | */ 12 | 13 | const DEFAULT_DELAY = common.DEFAULT_DELAY; 14 | const CHANGE_EVENT = common.CHANGE_EVENT; 15 | const DELETE_EVENT = common.DELETE_EVENT; 16 | const ADD_EVENT = common.ADD_EVENT; 17 | const ALL_EVENT = common.ALL_EVENT; 18 | 19 | /** 20 | * Export `NodeWatcher` class. 21 | * Watches `dir`. 22 | * 23 | * @class NodeWatcher 24 | * @param {String} dir 25 | * @param {Object} opts 26 | * @public 27 | */ 28 | 29 | module.exports = class NodeWatcher extends EventEmitter { 30 | constructor(dir, opts) { 31 | super(); 32 | 33 | common.assignOptions(this, opts); 34 | 35 | this.watched = Object.create(null); 36 | this.changeTimers = Object.create(null); 37 | this.dirRegistery = Object.create(null); 38 | this.root = path.resolve(dir); 39 | this.watchdir = this.watchdir.bind(this); 40 | this.register = this.register.bind(this); 41 | this.checkedEmitError = this.checkedEmitError.bind(this); 42 | 43 | this.watchdir(this.root); 44 | common.recReaddir( 45 | this.root, 46 | this.watchdir, 47 | this.register, 48 | this.emit.bind(this, 'ready'), 49 | this.checkedEmitError, 50 | this.ignored 51 | ); 52 | } 53 | 54 | /** 55 | * Register files that matches our globs to know what to type of event to 56 | * emit in the future. 57 | * 58 | * Registery looks like the following: 59 | * 60 | * dirRegister => Map { 61 | * dirpath => Map { 62 | * filename => true 63 | * } 64 | * } 65 | * 66 | * @param {string} filepath 67 | * @return {boolean} whether or not we have registered the file. 68 | * @private 69 | */ 70 | 71 | register(filepath) { 72 | let relativePath = path.relative(this.root, filepath); 73 | if ( 74 | !common.isFileIncluded(this.globs, this.dot, this.doIgnore, relativePath) 75 | ) { 76 | return false; 77 | } 78 | 79 | let dir = path.dirname(filepath); 80 | if (!this.dirRegistery[dir]) { 81 | this.dirRegistery[dir] = Object.create(null); 82 | } 83 | 84 | let filename = path.basename(filepath); 85 | this.dirRegistery[dir][filename] = true; 86 | 87 | return true; 88 | } 89 | 90 | /** 91 | * Removes a file from the registery. 92 | * 93 | * @param {string} filepath 94 | * @private 95 | */ 96 | 97 | unregister(filepath) { 98 | let dir = path.dirname(filepath); 99 | if (this.dirRegistery[dir]) { 100 | let filename = path.basename(filepath); 101 | delete this.dirRegistery[dir][filename]; 102 | } 103 | } 104 | 105 | /** 106 | * Removes a dir from the registery. 107 | * 108 | * @param {string} dirpath 109 | * @private 110 | */ 111 | 112 | unregisterDir(dirpath) { 113 | if (this.dirRegistery[dirpath]) { 114 | delete this.dirRegistery[dirpath]; 115 | } 116 | } 117 | 118 | /** 119 | * Checks if a file or directory exists in the registery. 120 | * 121 | * @param {string} fullpath 122 | * @return {boolean} 123 | * @private 124 | */ 125 | 126 | registered(fullpath) { 127 | let dir = path.dirname(fullpath); 128 | return ( 129 | this.dirRegistery[fullpath] || 130 | (this.dirRegistery[dir] && 131 | this.dirRegistery[dir][path.basename(fullpath)]) 132 | ); 133 | } 134 | 135 | /** 136 | * Emit "error" event if it's not an ignorable event 137 | * 138 | * @param error 139 | * @private 140 | */ 141 | checkedEmitError(error) { 142 | if (!isIgnorableFileError(error)) { 143 | this.emit('error', error); 144 | } 145 | } 146 | 147 | /** 148 | * Watch a directory. 149 | * 150 | * @param {string} dir 151 | * @private 152 | */ 153 | 154 | watchdir(dir) { 155 | if (this.watched[dir]) { 156 | return; 157 | } 158 | 159 | let watcher = fs.watch( 160 | dir, 161 | { persistent: true }, 162 | this.normalizeChange.bind(this, dir) 163 | ); 164 | this.watched[dir] = watcher; 165 | 166 | watcher.on('error', this.checkedEmitError); 167 | 168 | if (this.root !== dir) { 169 | this.register(dir); 170 | } 171 | } 172 | 173 | /** 174 | * Stop watching a directory. 175 | * 176 | * @param {string} dir 177 | * @private 178 | */ 179 | 180 | stopWatching(dir) { 181 | if (this.watched[dir]) { 182 | this.watched[dir].close(); 183 | delete this.watched[dir]; 184 | } 185 | } 186 | 187 | /** 188 | * End watching. 189 | * 190 | * @public 191 | */ 192 | 193 | close(callback) { 194 | Object.keys(this.watched).forEach(this.stopWatching, this); 195 | this.removeAllListeners(); 196 | if (typeof callback === 'function') { 197 | setImmediate(callback.bind(null, null, true)); 198 | } 199 | } 200 | 201 | /** 202 | * On some platforms, as pointed out on the fs docs (most likely just win32) 203 | * the file argument might be missing from the fs event. Try to detect what 204 | * change by detecting if something was deleted or the most recent file change. 205 | * 206 | * @param {string} dir 207 | * @param {string} event 208 | * @param {string} file 209 | * @public 210 | */ 211 | 212 | detectChangedFile(dir, event, callback) { 213 | if (!this.dirRegistery[dir]) { 214 | return; 215 | } 216 | 217 | let found = false; 218 | let closest = { mtime: 0 }; 219 | let c = 0; 220 | Object.keys(this.dirRegistery[dir]).forEach((file, i, arr) => { 221 | fs.lstat(path.join(dir, file), (error, stat) => { 222 | if (found) { 223 | return; 224 | } 225 | 226 | if (error) { 227 | if (isIgnorableFileError(error)) { 228 | found = true; 229 | callback(file); 230 | } else { 231 | this.emit('error', error); 232 | } 233 | } else { 234 | if (stat.mtime > closest.mtime) { 235 | stat.file = file; 236 | closest = stat; 237 | } 238 | if (arr.length === ++c) { 239 | callback(closest.file); 240 | } 241 | } 242 | }); 243 | }); 244 | } 245 | 246 | /** 247 | * Normalize fs events and pass it on to be processed. 248 | * 249 | * @param {string} dir 250 | * @param {string} event 251 | * @param {string} file 252 | * @public 253 | */ 254 | 255 | normalizeChange(dir, event, file) { 256 | if (!file) { 257 | this.detectChangedFile(dir, event, actualFile => { 258 | if (actualFile) { 259 | this.processChange(dir, event, actualFile); 260 | } 261 | }); 262 | } else { 263 | this.processChange(dir, event, path.normalize(file)); 264 | } 265 | } 266 | 267 | /** 268 | * Process changes. 269 | * 270 | * @param {string} dir 271 | * @param {string} event 272 | * @param {string} file 273 | * @public 274 | */ 275 | 276 | processChange(dir, event, file) { 277 | let fullPath = path.join(dir, file); 278 | let relativePath = path.join(path.relative(this.root, dir), file); 279 | 280 | fs.lstat(fullPath, (error, stat) => { 281 | if (error && error.code !== 'ENOENT') { 282 | this.emit('error', error); 283 | } else if (!error && stat.isDirectory()) { 284 | // win32 emits usless change events on dirs. 285 | if (event !== 'change') { 286 | this.watchdir(fullPath); 287 | if ( 288 | common.isFileIncluded( 289 | this.globs, 290 | this.dot, 291 | this.doIgnore, 292 | relativePath 293 | ) 294 | ) { 295 | this.emitEvent(ADD_EVENT, relativePath, stat); 296 | } 297 | } 298 | } else { 299 | let registered = this.registered(fullPath); 300 | if (error && error.code === 'ENOENT') { 301 | this.unregister(fullPath); 302 | this.stopWatching(fullPath); 303 | this.unregisterDir(fullPath); 304 | if (registered) { 305 | this.emitEvent(DELETE_EVENT, relativePath); 306 | } 307 | } else if (registered) { 308 | this.emitEvent(CHANGE_EVENT, relativePath, stat); 309 | } else { 310 | if (this.register(fullPath)) { 311 | this.emitEvent(ADD_EVENT, relativePath, stat); 312 | } 313 | } 314 | } 315 | }); 316 | } 317 | 318 | /** 319 | * Triggers a 'change' event after debounding it to take care of duplicate 320 | * events on os x. 321 | * 322 | * @private 323 | */ 324 | 325 | emitEvent(type, file, stat) { 326 | let key = type + '-' + file; 327 | let addKey = ADD_EVENT + '-' + file; 328 | if (type === CHANGE_EVENT && this.changeTimers[addKey]) { 329 | // Ignore the change event that is immediately fired after an add event. 330 | // (This happens on Linux). 331 | return; 332 | } 333 | clearTimeout(this.changeTimers[key]); 334 | this.changeTimers[key] = setTimeout(() => { 335 | delete this.changeTimers[key]; 336 | if (type === ADD_EVENT && stat.isDirectory()) { 337 | // Recursively emit add events and watch for sub-files/folders 338 | common.recReaddir( 339 | path.resolve(this.root, file), 340 | function emitAddDir(dir, stats) { 341 | this.watchdir(dir); 342 | this.rawEmitEvent(ADD_EVENT, path.relative(this.root, dir), stats); 343 | }.bind(this), 344 | function emitAddFile(file, stats) { 345 | this.register(file); 346 | this.rawEmitEvent(ADD_EVENT, path.relative(this.root, file), stats); 347 | }.bind(this), 348 | function endCallback() {}, 349 | this.checkedEmitError, 350 | this.ignored 351 | ); 352 | } else { 353 | this.rawEmitEvent(type, file, stat); 354 | } 355 | }, DEFAULT_DELAY); 356 | } 357 | 358 | /** 359 | * Actually emit the events 360 | */ 361 | rawEmitEvent(type, file, stat) { 362 | this.emit(type, file, this.root, stat); 363 | this.emit(ALL_EVENT, type, file, this.root, stat); 364 | } 365 | }; 366 | /** 367 | * Determine if a given FS error can be ignored 368 | * 369 | * @private 370 | */ 371 | function isIgnorableFileError(error) { 372 | return ( 373 | error.code === 'ENOENT' || 374 | // Workaround Windows node issue #4337. 375 | (error.code === 'EPERM' && platform === 'win32') 376 | ); 377 | } 378 | -------------------------------------------------------------------------------- /src/poll_watcher.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const watch = require('@cnakazawa/watch'); 6 | const common = require('./common'); 7 | const EventEmitter = require('events').EventEmitter; 8 | 9 | /** 10 | * Constants 11 | */ 12 | 13 | const DEFAULT_DELAY = common.DEFAULT_DELAY; 14 | const CHANGE_EVENT = common.CHANGE_EVENT; 15 | const DELETE_EVENT = common.DELETE_EVENT; 16 | const ADD_EVENT = common.ADD_EVENT; 17 | const ALL_EVENT = common.ALL_EVENT; 18 | 19 | /** 20 | * Export `PollWatcher` class. 21 | * Watches `dir`. 22 | * 23 | * @class PollWatcher 24 | * @param String dir 25 | * @param {Object} opts 26 | * @public 27 | */ 28 | 29 | module.exports = class PollWatcher extends EventEmitter { 30 | constructor(dir, opts) { 31 | super(); 32 | 33 | opts = common.assignOptions(this, opts); 34 | 35 | this.watched = Object.create(null); 36 | this.root = path.resolve(dir); 37 | 38 | watch.createMonitor( 39 | this.root, 40 | { 41 | interval: (opts.interval || DEFAULT_DELAY) / 1000, 42 | filter: this.filter.bind(this), 43 | }, 44 | this.init.bind(this) 45 | ); 46 | } 47 | 48 | /** 49 | * Given a fullpath of a file or directory check if we need to watch it. 50 | * 51 | * @param {string} filepath 52 | * @param {object} stat 53 | * @private 54 | */ 55 | 56 | filter(filepath, stat) { 57 | return ( 58 | stat.isDirectory() || 59 | common.isFileIncluded( 60 | this.globs, 61 | this.dot, 62 | this.doIgnore, 63 | path.relative(this.root, filepath) 64 | ) 65 | ); 66 | } 67 | 68 | /** 69 | * Initiate the polling file watcher with the event emitter passed from 70 | * `watch.watchTree`. 71 | * 72 | * @param {EventEmitter} monitor 73 | * @public 74 | */ 75 | 76 | init(monitor) { 77 | this.watched = monitor.files; 78 | monitor.on('changed', this.emitEvent.bind(this, CHANGE_EVENT)); 79 | monitor.on('removed', this.emitEvent.bind(this, DELETE_EVENT)); 80 | monitor.on('created', this.emitEvent.bind(this, ADD_EVENT)); 81 | // 1 second wait because mtime is second-based. 82 | setTimeout(this.emit.bind(this, 'ready'), 1000); 83 | } 84 | 85 | /** 86 | * Transform and emit an event comming from the poller. 87 | * 88 | * @param {EventEmitter} monitor 89 | * @public 90 | */ 91 | 92 | emitEvent(type, file, stat) { 93 | file = path.relative(this.root, file); 94 | 95 | if (type === DELETE_EVENT) { 96 | // Matching the non-polling API 97 | stat = null; 98 | } 99 | 100 | this.emit(type, file, this.root, stat); 101 | this.emit(ALL_EVENT, type, file, this.root, stat); 102 | } 103 | 104 | /** 105 | * End watching. 106 | * 107 | * @public 108 | */ 109 | 110 | close(callback) { 111 | Object.keys(this.watched).forEach(filepath => fs.unwatchFile(filepath)); 112 | this.removeAllListeners(); 113 | if (typeof callback === 'function') { 114 | setImmediate(callback.bind(null, null, true)); 115 | } 116 | } 117 | }; 118 | -------------------------------------------------------------------------------- /src/utils/recrawl-warning-dedupe.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class RecrawlWarning { 4 | constructor(root, count) { 5 | this.root = root; 6 | this.count = count; 7 | } 8 | 9 | static findByRoot(root) { 10 | for (let i = 0; i < this.RECRAWL_WARNINGS.length; i++) { 11 | let warning = this.RECRAWL_WARNINGS[i]; 12 | if (warning.root === root) { 13 | return warning; 14 | } 15 | } 16 | } 17 | 18 | static isRecrawlWarningDupe(warningMessage) { 19 | if (typeof warningMessage !== 'string') { 20 | return false; 21 | } 22 | let match = warningMessage.match(this.REGEXP); 23 | if (!match) { 24 | return false; 25 | } 26 | let count = Number(match[1]); 27 | let root = match[2]; 28 | 29 | let warning = this.findByRoot(root); 30 | 31 | if (warning) { 32 | // only keep the highest count, assume count to either stay the same or 33 | // increase. 34 | if (warning.count >= count) { 35 | return true; 36 | } else { 37 | // update the existing warning to the latest (highest) count 38 | warning.count = count; 39 | return false; 40 | } 41 | } else { 42 | this.RECRAWL_WARNINGS.push(new RecrawlWarning(root, count)); 43 | return false; 44 | } 45 | } 46 | } 47 | 48 | RecrawlWarning.RECRAWL_WARNINGS = []; 49 | RecrawlWarning.REGEXP = /Recrawled this watch (\d+) times, most recently because:\n([^:]+)/; 50 | 51 | module.exports = RecrawlWarning; 52 | -------------------------------------------------------------------------------- /src/watchexec_client.js: -------------------------------------------------------------------------------- 1 | /* 2 | This file is the executable run by watchexec 3 | when a change is detected. 4 | 5 | It will extract changes from the environment variables 6 | set by watchexec and write to stdout in a format 7 | readable by the file `../watchexec_watcher.js`. 8 | */ 9 | 'use strict'; 10 | 11 | const { EOL } = require('os'); 12 | 13 | function withPrefixes(prefixes) { 14 | return function withPrefix(arr, i) { 15 | return arr.map(str => { 16 | return `${prefixes[i]} ${str}`; 17 | }); 18 | }; 19 | } 20 | 21 | const allPrefixes = ['write', 'rename', 'remove', 'create']; 22 | 23 | function extractChanges(context) { 24 | const { 25 | WATCHEXEC_COMMON_PATH, 26 | WATCHEXEC_WRITTEN_PATH, 27 | WATCHEXEC_RENAMED_PATH, 28 | WATCHEXEC_REMOVED_PATH, 29 | WATCHEXEC_CREATED_PATH, 30 | } = context; 31 | 32 | let events = [ 33 | WATCHEXEC_WRITTEN_PATH, 34 | WATCHEXEC_RENAMED_PATH, 35 | WATCHEXEC_REMOVED_PATH, 36 | WATCHEXEC_CREATED_PATH, 37 | ]; 38 | 39 | let currentPrefixes = events 40 | .map((l, i) => l && allPrefixes[i]) 41 | .filter(Boolean); 42 | 43 | function toFullPath(arr) { 44 | return arr.map(path => (WATCHEXEC_COMMON_PATH || '') + path); 45 | } 46 | 47 | let message = events 48 | .filter(Boolean) 49 | .map(str => str.split(':')) 50 | .map(toFullPath) 51 | .map(withPrefixes(currentPrefixes)) 52 | .reduce((e, memo) => memo.concat(e), []) 53 | .join(EOL); 54 | 55 | return message; 56 | } 57 | 58 | if (require.main === module) { 59 | let message = extractChanges(process.env); 60 | console.log(message); 61 | } 62 | 63 | module.exports = extractChanges; 64 | -------------------------------------------------------------------------------- /src/watchexec_watcher.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const execa = require('execa'); 4 | 5 | const { statSync } = require('fs'); 6 | const path = require('path'); 7 | const common = require('./common'); 8 | const EventEmitter = require('events').EventEmitter; 9 | 10 | const { EOL } = require('os'); 11 | 12 | /** 13 | * Constants 14 | */ 15 | 16 | const CHANGE_EVENT = common.CHANGE_EVENT; 17 | const DELETE_EVENT = common.DELETE_EVENT; 18 | const ADD_EVENT = common.ADD_EVENT; 19 | const ALL_EVENT = common.ALL_EVENT; 20 | 21 | const typeMap = { 22 | rename: CHANGE_EVENT, 23 | write: CHANGE_EVENT, 24 | remove: DELETE_EVENT, 25 | create: ADD_EVENT, 26 | }; 27 | 28 | const messageRegexp = /(rename|write|remove|create)\s(.+)/; 29 | 30 | /** 31 | * Manages streams from subprocess and turns into sane events 32 | * 33 | * @param {Stream} data 34 | * @private 35 | */ 36 | function _messageHandler(data) { 37 | data 38 | .toString() 39 | .split(EOL) 40 | .filter(str => str.trim().length) 41 | .filter(str => messageRegexp.test(str)) 42 | .map(line => { 43 | const [, command, path] = [...line.match(messageRegexp)]; 44 | return [command, path]; 45 | }) 46 | .forEach(([command, file]) => { 47 | let stat; 48 | const type = typeMap[command]; 49 | if (type === DELETE_EVENT) { 50 | stat = null; 51 | } else { 52 | try { 53 | stat = statSync(file); 54 | } catch (e) { 55 | // There is likely a delete coming down the pipe. 56 | if (e.code === 'ENOENT') { 57 | return; 58 | } 59 | throw e; 60 | } 61 | } 62 | this.emitEvent(type, path.relative(this.root, file), stat); 63 | }); 64 | } 65 | 66 | /** 67 | * Export `WatchexecWatcher` class. 68 | * Watches `dir`. 69 | * 70 | * @class WatchexecWatcher 71 | * @param String dir 72 | * @param {Object} opts 73 | * @public 74 | */ 75 | class WatchexecWatcher extends EventEmitter { 76 | constructor(dir, opts) { 77 | super(); 78 | 79 | common.assignOptions(this, opts); 80 | 81 | this.root = path.resolve(dir); 82 | 83 | this._process = execa( 84 | 'watchexec', 85 | ['-n', '--', 'node', __dirname + '/watchexec_client.js'], 86 | { cwd: dir } 87 | ); 88 | 89 | this._process.stdout.on('data', _messageHandler.bind(this)); 90 | this._readyTimer = setTimeout(this.emit.bind(this, 'ready'), 1000); 91 | } 92 | 93 | close(callback) { 94 | clearTimeout(this._readyTimer); 95 | this.removeAllListeners(); 96 | this._process && !this._process.killed && this._process.kill(); 97 | if (typeof callback === 'function') { 98 | setImmediate(callback.bind(null, null, true)); 99 | } 100 | } 101 | 102 | /** 103 | * Transform and emit an event comming from the poller. 104 | * 105 | * @param {EventEmitter} monitor 106 | * @public 107 | */ 108 | emitEvent(type, file, stat) { 109 | this.emit(type, file, this.root, stat); 110 | this.emit(ALL_EVENT, type, file, this.root, stat); 111 | } 112 | } 113 | 114 | WatchexecWatcher._messageHandler = _messageHandler; 115 | 116 | module.exports = WatchexecWatcher; 117 | -------------------------------------------------------------------------------- /src/watchman_client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const watchman = require('fb-watchman'); 4 | const captureExit = require('capture-exit'); 5 | 6 | function values(obj) { 7 | return Object.keys(obj).map(key => obj[key]); 8 | } 9 | 10 | /** 11 | * Constants 12 | */ 13 | 14 | /** 15 | * Singleton that provides a public API for a connection to a watchman instance for 'sane'. 16 | * It tries to abstract/remove as much of the boilerplate processing as necessary 17 | * from WatchmanWatchers that use it. In particular, they have no idea whether 18 | * we're using 'watch-project' or 'watch', what the 'project root' is when 19 | * we internally use watch-project, whether a connection has been lost 20 | * and reestablished, etc. Also switched to doing things with promises and known-name 21 | * methods in WatchmanWatcher, so as much information as possible can be kept in 22 | * the WatchmanClient, ultimately making this the only object listening directly 23 | * to watchman.Client, then forwarding appropriately (via the known-name methods) to 24 | * the relevant WatchmanWatcher(s). 25 | * 26 | * Note: WatchmanWatcher also added a 'watchmanPath' option for use with the sane CLI. 27 | * Because of that, we actually need a map of watchman binary path to WatchmanClient instance. 28 | * That is set up in getInstance(). Once the WatchmanWatcher has a given client, it doesn't 29 | * change. 30 | * 31 | * @class WatchmanClient 32 | * @param String watchmanBinaryPath 33 | * @public 34 | */ 35 | 36 | class WatchmanClient { 37 | constructor(watchmanBinaryPath) { 38 | captureExit.captureExit(); 39 | 40 | // define/clear some local state. The properties will be initialized 41 | // in _handleClientAndCheck(). This is also called again in _onEnd when 42 | // trying to reestablish connection to watchman. 43 | this._clearLocalVars(); 44 | 45 | this._watchmanBinaryPath = watchmanBinaryPath; 46 | 47 | this._backoffTimes = this._setupBackoffTimes(); 48 | 49 | this._clientListeners = null; // direct listeners from here to watchman.Client. 50 | 51 | // Define a handler for if somehow the Node process gets interrupted. We need to 52 | // close down the watchman.Client, if we have one. 53 | captureExit.onExit(() => this._clearLocalVars()); 54 | } 55 | 56 | // Define 'wildmatch' property, which must be available when we call the 57 | // WatchmanWatcher.createOptions() method. 58 | get wildmatch() { 59 | return this._wildmatch; 60 | } 61 | 62 | /** 63 | * Called from WatchmanWatcher (or WatchmanClient during reconnect) to create 64 | * a watcherInfo entry in our _watcherMap and issue a 'subscribe' to the 65 | * watchman.Client, to be handled here. 66 | */ 67 | subscribe(watchmanWatcher, root) { 68 | let subscription; 69 | let watcherInfo; 70 | 71 | return this._setupClient() 72 | .then(() => { 73 | watcherInfo = this._createWatcherInfo(watchmanWatcher); 74 | subscription = watcherInfo.subscription; 75 | return this._watch(subscription, root); 76 | }) 77 | .then(() => this._clock(subscription)) 78 | .then(() => this._subscribe(subscription)); 79 | // Note: callers are responsible for noting any subscription failure. 80 | } 81 | 82 | /** 83 | * Remove the information about a specific WatchmanWatcher. 84 | * Once done, if no watchers are left, clear the local vars, 85 | * which will end the connection to the watchman.Client, too. 86 | */ 87 | closeWatcher(watchmanWatcher) { 88 | let watcherInfos = values(this._watcherMap); 89 | let numWatchers = watcherInfos.length; 90 | 91 | if (numWatchers > 0) { 92 | let watcherInfo; 93 | 94 | for (let info of watcherInfos) { 95 | if (info.watchmanWatcher === watchmanWatcher) { 96 | watcherInfo = info; 97 | break; 98 | } 99 | } 100 | 101 | if (watcherInfo) { 102 | delete this._watcherMap[watcherInfo.subscription]; 103 | 104 | numWatchers--; 105 | 106 | if (numWatchers === 0) { 107 | this._clearLocalVars(); // nobody watching, so shut the watchman.Client down. 108 | } 109 | } 110 | } 111 | } 112 | 113 | /** 114 | * Simple backoff-time iterator. next() returns times in ms. 115 | * When it's at the last value, it stays there until reset() 116 | * is called. 117 | */ 118 | _setupBackoffTimes() { 119 | return { 120 | _times: [0, 1000, 5000, 10000, 60000], 121 | 122 | _next: 0, 123 | 124 | next() { 125 | let val = this._times[this._next]; 126 | if (this._next < this._times.length - 1) { 127 | this._next++; 128 | } 129 | return val; 130 | }, 131 | 132 | reset() { 133 | this._next = 0; 134 | }, 135 | }; 136 | } 137 | 138 | /** 139 | * Set up the connection to the watchman client. Return a promise 140 | * that is fulfilled when we have a client that has finished the 141 | * capabilityCheck. 142 | */ 143 | _setupClient() { 144 | if (!this._clientPromise) { 145 | this._clientPromise = new Promise((resolve, reject) => { 146 | this._handleClientAndCheck(resolve, reject); 147 | }); 148 | } 149 | 150 | return this._clientPromise; 151 | } 152 | 153 | /** 154 | * Handle the process of creating a client and doing a capability check and 155 | * getting a valid response, then setting up local data based on that. 156 | * 157 | * This is split from _setupClient and _createClientAndCheck so it can 158 | * provide the backoff handling needed during attempts to reconnect. 159 | */ 160 | _handleClientAndCheck(resolve, reject) { 161 | this._createClientAndCheck().then( 162 | value => { 163 | let resp = value.resp; 164 | let client = value.client; 165 | 166 | try { 167 | this._wildmatch = resp.capabilities.wildmatch; 168 | this._relative_root = resp.capabilities.relative_root; 169 | this._client = client; 170 | 171 | client.on('subscription', this._onSubscription.bind(this)); 172 | client.on('error', this._onError.bind(this)); 173 | client.on('end', this._onEnd.bind(this)); 174 | 175 | this._backoffTimes.reset(); 176 | resolve(this); 177 | } catch (error) { 178 | // somehow, even though we supposedly got a valid value back, it's 179 | // malformed, or some other internal error occurred. Reject so 180 | // the promise itself doesn't hang forever. 181 | reject(error); 182 | } 183 | }, 184 | () => { 185 | // create & capability check failed in any of several ways, 186 | // do the retry with backoff. 187 | 188 | // XXX May want to change this later to actually reject/terminate with 189 | // an error in certain of the inner errors (e.g. when we 190 | // can figure out the server is definitely not coming 191 | // back, or something else is not recoverable by just waiting). 192 | // Could also decide after N retries to just quit. 193 | 194 | let backoffMillis = this._backoffTimes.next(); 195 | 196 | // XXX may want to fact we'll attempt reconnect in backoffMillis ms. 197 | setTimeout(() => { 198 | this._handleClientAndCheck(resolve, reject); 199 | }, backoffMillis); 200 | } 201 | ); 202 | } 203 | 204 | /** 205 | * Create a promise that will only be fulfilled when either 206 | * we correctly get capabilities back or we get an 'error' or 'end' 207 | * callback, indicating a problem. The caller _handleClientAndCheck 208 | * then deals with providing a retry and backoff mechanism. 209 | */ 210 | _createClientAndCheck() { 211 | return new Promise((resolve, reject) => { 212 | let client; 213 | 214 | try { 215 | client = new watchman.Client( 216 | this._watchmanBinaryPath 217 | ? { watchmanBinaryPath: this._watchmanBinaryPath } 218 | : {} 219 | ); 220 | } catch (error) { 221 | // if we're here, either the binary path is bad or something 222 | // else really bad happened. The client doesn't even attempt 223 | // to connect until we send it a command, though. 224 | reject(error); 225 | return; 226 | } 227 | 228 | client.on('error', error => { 229 | client.removeAllListeners(); 230 | reject(error); 231 | }); 232 | 233 | client.on('end', () => { 234 | client.removeAllListeners(); 235 | reject(new Error('Disconnected during client capabilities check')); 236 | }); 237 | 238 | client.capabilityCheck( 239 | { optional: ['wildmatch', 'relative_root'] }, 240 | (error, resp) => { 241 | try { 242 | client.removeAllListeners(); 243 | 244 | if (error) { 245 | reject(error); 246 | } else { 247 | resolve({ resp, client }); 248 | } 249 | } catch (err) { 250 | // In case we get something weird in the block using 'resp'. 251 | // XXX We could also just remove the try/catch if we believe 252 | // the resp stuff should always work, but just in case... 253 | reject(err); 254 | } 255 | } 256 | ); 257 | }); 258 | } 259 | 260 | /** 261 | * Clear out local state at the beginning and if we end up 262 | * getting disconnected and try to reconnect. 263 | */ 264 | _clearLocalVars() { 265 | if (this._client) { 266 | this._client.removeAllListeners(); 267 | this._client.end(); 268 | } 269 | 270 | this._client = null; 271 | this._clientPromise = null; 272 | this._wildmatch = false; 273 | this._relative_root = false; 274 | this._subscriptionId = 1; 275 | this._watcherMap = Object.create(null); 276 | 277 | // Note that we do not clear _clientListeners or _watchmanBinaryPath. 278 | } 279 | 280 | _genSubscription() { 281 | let val = 'sane_' + this._subscriptionId++; 282 | return val; 283 | } 284 | 285 | /** 286 | * Create a new watcherInfo entry for the given watchmanWatcher and 287 | * initialize it. 288 | */ 289 | _createWatcherInfo(watchmanWatcher) { 290 | let watcherInfo = { 291 | subscription: this._genSubscription(), 292 | watchmanWatcher: watchmanWatcher, 293 | root: null, // set during 'watch' or 'watch-project' 294 | relativePath: null, // same 295 | since: null, // set during 'clock' 296 | options: null, // created and set during 'subscribe'. 297 | }; 298 | 299 | this._watcherMap[watcherInfo.subscription] = watcherInfo; 300 | 301 | return watcherInfo; 302 | } 303 | 304 | /** 305 | * Find an existing watcherInfo instance. 306 | */ 307 | _getWatcherInfo(subscription) { 308 | return this._watcherMap[subscription]; 309 | } 310 | 311 | /** 312 | * Given a watchmanWatcher and a root, issue the correct 'watch' 313 | * or 'watch-project' command and handle it with the callback. 314 | * Because we're operating in 'sane', we'll keep the results 315 | * of the 'watch' or 'watch-project' here. 316 | */ 317 | _watch(subscription, root) { 318 | return new Promise((resolve, reject) => { 319 | let watcherInfo = this._getWatcherInfo(subscription); 320 | 321 | if (this._relative_root) { 322 | this._client.command(['watch-project', root], (error, resp) => { 323 | if (error) { 324 | reject(error); 325 | } else { 326 | watcherInfo.root = resp.watch; 327 | watcherInfo.relativePath = resp.relative_path 328 | ? resp.relative_path 329 | : ''; 330 | resolve(resp); 331 | } 332 | }); 333 | } else { 334 | this._client.command(['watch', root], (error, resp) => { 335 | if (error) { 336 | reject(error); 337 | } else { 338 | watcherInfo.root = root; 339 | watcherInfo.relativePath = ''; 340 | resolve(resp); 341 | } 342 | }); 343 | } 344 | }); 345 | } 346 | 347 | /** 348 | * Issue the 'clock' command to get the time value for use with the 'since' 349 | * option during 'subscribe'. 350 | */ 351 | _clock(subscription) { 352 | return new Promise((resolve, reject) => { 353 | let watcherInfo = this._getWatcherInfo(subscription); 354 | 355 | this._client.command(['clock', watcherInfo.root], (error, resp) => { 356 | if (error) { 357 | reject(error); 358 | } else { 359 | watcherInfo.since = resp.clock; 360 | resolve(resp); 361 | } 362 | }); 363 | }); 364 | } 365 | 366 | /** 367 | * Do the internal handling of calling the watchman.Client for 368 | * a subscription. 369 | */ 370 | _subscribe(subscription) { 371 | return new Promise((resolve, reject) => { 372 | let watcherInfo = this._getWatcherInfo(subscription); 373 | 374 | // create the 'bare' options w/o 'since' or relative_root. 375 | // Store in watcherInfo for later use if we need to reset 376 | // things after an 'end' caught here. 377 | let options = watcherInfo.watchmanWatcher.createOptions(); 378 | watcherInfo.options = options; 379 | 380 | // Dup the options object so we can add 'relative_root' and 'since' 381 | // and leave the original options object alone. We'll do this again 382 | // later if we need to resubscribe after 'end' and reconnect. 383 | options = Object.assign({}, options); 384 | 385 | if (this._relative_root) { 386 | options.relative_root = watcherInfo.relativePath; 387 | } 388 | 389 | options.since = watcherInfo.since; 390 | 391 | this._client.command( 392 | ['subscribe', watcherInfo.root, subscription, options], 393 | (error, resp) => { 394 | if (error) { 395 | reject(error); 396 | } else { 397 | resolve(resp); 398 | } 399 | } 400 | ); 401 | }); 402 | } 403 | 404 | /** 405 | * Handle the 'subscription' (file change) event, by calling the 406 | * handler on the relevant WatchmanWatcher. 407 | */ 408 | _onSubscription(resp) { 409 | let watcherInfo = this._getWatcherInfo(resp.subscription); 410 | 411 | if (watcherInfo) { 412 | // we're assuming the watchmanWatcher does not throw during 413 | // handling of the change event. 414 | watcherInfo.watchmanWatcher.handleChangeEvent(resp); 415 | } else { 416 | // Note it in the log, but otherwise ignore it 417 | console.error( 418 | "WatchmanClient error - received 'subscription' event " + 419 | "for non-existent subscription '" + 420 | resp.subscription + 421 | "'" 422 | ); 423 | } 424 | } 425 | 426 | /** 427 | * Handle the 'error' event by forwarding to the 428 | * handler on all WatchmanWatchers (errors are generally during processing 429 | * a particular command, but it's not given which command that was, or 430 | * which subscription it belonged to). 431 | */ 432 | _onError(error) { 433 | values(this._watcherMap).forEach(watcherInfo => 434 | watcherInfo.watchmanWatcher.handleErrorEvent(error) 435 | ); 436 | } 437 | 438 | /** 439 | * Handle the 'end' event by creating a new watchman.Client and 440 | * attempting to resubscribe all the existing subscriptions, but 441 | * without notifying the WatchmanWatchers about it. They should 442 | * not be aware the connection was lost and recreated. 443 | * If something goes wrong during any part of the reconnect/setup, 444 | * call the error handler on each existing WatchmanWatcher. 445 | */ 446 | _onEnd() { 447 | console.warn( 448 | '[sane.WatchmanClient] Warning: Lost connection to watchman, reconnecting..' 449 | ); 450 | 451 | // Hold the old watcher map so we use it to recreate all subscriptions. 452 | let oldWatcherInfos = values(this._watcherMap); 453 | 454 | this._clearLocalVars(); 455 | 456 | this._setupClient().then( 457 | () => { 458 | let promises = oldWatcherInfos.map(watcherInfo => 459 | this.subscribe( 460 | watcherInfo.watchmanWatcher, 461 | watcherInfo.watchmanWatcher.root 462 | ) 463 | ); 464 | Promise.all(promises).then( 465 | () => { 466 | console.log('[sane.WatchmanClient]: Reconnected to watchman'); 467 | }, 468 | error => { 469 | console.error( 470 | '[sane.WatchmanClient]: Reconnected to watchman, but failed to ' + 471 | 'reestablish at least one subscription, cannot continue' 472 | ); 473 | console.error(error); 474 | oldWatcherInfos.forEach(watcherInfo => 475 | watcherInfo.watchmanWatcher.handleErrorEvent(error) 476 | ); 477 | // XXX not sure whether to clear all _watcherMap instances here, 478 | // but basically this client is inconsistent now, since at least one 479 | // subscribe failed. 480 | } 481 | ); 482 | }, 483 | error => { 484 | console.error( 485 | '[sane.WatchmanClient]: Lost connection to watchman, ' + 486 | 'reconnect failed, cannot continue' 487 | ); 488 | console.error(error); 489 | oldWatcherInfos.forEach(watcherInfo => 490 | watcherInfo.watchmanWatcher.handleErrorEvent(error) 491 | ); 492 | } 493 | ); 494 | } 495 | } 496 | 497 | module.exports = { 498 | /** 499 | * Create/retrieve an instance of the WatchmanClient. See the header comment 500 | * about the map of client instances, one per watchmanPath. 501 | * Export the getInstance method by itself so the callers cannot do anything until 502 | * they get a real WatchmanClient instance here. 503 | */ 504 | getInstance(watchmanBinaryPath) { 505 | let clientMap = WatchmanClient.prototype._clientMap; 506 | 507 | if (!clientMap) { 508 | clientMap = Object.create(null); 509 | WatchmanClient.prototype._clientMap = clientMap; 510 | } 511 | 512 | if (watchmanBinaryPath == undefined || watchmanBinaryPath === null) { 513 | watchmanBinaryPath = ''; 514 | } 515 | 516 | let watchmanClient = clientMap[watchmanBinaryPath]; 517 | 518 | if (!watchmanClient) { 519 | watchmanClient = new WatchmanClient(watchmanBinaryPath); 520 | clientMap[watchmanBinaryPath] = watchmanClient; 521 | } 522 | 523 | return watchmanClient; 524 | }, 525 | }; 526 | -------------------------------------------------------------------------------- /src/watchman_watcher.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const common = require('./common'); 6 | const watchmanClient = require('./watchman_client'); 7 | const EventEmitter = require('events').EventEmitter; 8 | const RecrawlWarning = require('./utils/recrawl-warning-dedupe'); 9 | 10 | /** 11 | * Constants 12 | */ 13 | 14 | const CHANGE_EVENT = common.CHANGE_EVENT; 15 | const DELETE_EVENT = common.DELETE_EVENT; 16 | const ADD_EVENT = common.ADD_EVENT; 17 | const ALL_EVENT = common.ALL_EVENT; 18 | 19 | /** 20 | * Export `WatchmanWatcher` class. 21 | */ 22 | 23 | module.exports = WatchmanWatcher; 24 | 25 | /** 26 | * Watches `dir`. 27 | * 28 | * @class WatchmanWatcher 29 | * @param String dir 30 | * @param {Object} opts 31 | * @public 32 | */ 33 | 34 | function WatchmanWatcher(dir, opts) { 35 | common.assignOptions(this, opts); 36 | this.root = path.resolve(dir); 37 | this._init(); 38 | } 39 | 40 | WatchmanWatcher.prototype.__proto__ = EventEmitter.prototype; 41 | 42 | /** 43 | * Run the watchman `watch` command on the root and subscribe to changes. 44 | * 45 | * @private 46 | */ 47 | WatchmanWatcher.prototype._init = function() { 48 | if (this._client) { 49 | this._client = null; 50 | } 51 | 52 | // Get the WatchmanClient instance corresponding to our watchmanPath (or nothing). 53 | // Then subscribe, which will do the appropriate setup so that we will receive 54 | // calls to handleChangeEvent when files change. 55 | this._client = watchmanClient.getInstance(this.watchmanPath); 56 | 57 | return this._client.subscribe(this, this.root).then( 58 | resp => { 59 | this._handleWarning(resp); 60 | this.emit('ready'); 61 | }, 62 | error => { 63 | this._handleError(error); 64 | } 65 | ); 66 | }; 67 | 68 | /** 69 | * Called by WatchmanClient to create the options, either during initial 'subscribe' 70 | * or to resubscribe after a disconnect+reconnect. Note that we are leaving out 71 | * the watchman 'since' and 'relative_root' options, which are handled inside the 72 | * WatchmanClient. 73 | */ 74 | WatchmanWatcher.prototype.createOptions = function() { 75 | let options = { 76 | fields: ['name', 'exists', 'new'], 77 | }; 78 | 79 | // If the server has the wildmatch capability available it supports 80 | // the recursive **/*.foo style match and we can offload our globs 81 | // to the watchman server. This saves both on data size to be 82 | // communicated back to us and compute for evaluating the globs 83 | // in our node process. 84 | if (this._client.wildmatch) { 85 | if (this.globs.length === 0) { 86 | if (!this.dot) { 87 | // Make sure we honor the dot option if even we're not using globs. 88 | options.expression = [ 89 | 'match', 90 | '**', 91 | 'wholename', 92 | { 93 | includedotfiles: false, 94 | }, 95 | ]; 96 | } 97 | } else { 98 | options.expression = ['anyof']; 99 | for (let i in this.globs) { 100 | options.expression.push([ 101 | 'match', 102 | this.globs[i], 103 | 'wholename', 104 | { 105 | includedotfiles: this.dot, 106 | }, 107 | ]); 108 | } 109 | } 110 | } 111 | 112 | return options; 113 | }; 114 | 115 | /** 116 | * Called by WatchmanClient when it receives an error from the watchman daemon. 117 | * 118 | * @param {Object} resp 119 | */ 120 | WatchmanWatcher.prototype.handleErrorEvent = function(error) { 121 | this.emit('error', error); 122 | }; 123 | 124 | /** 125 | * Called by the WatchmanClient when it is notified about a file change in 126 | * the tree for this particular watcher's root. 127 | * 128 | * @param {Object} resp 129 | * @private 130 | */ 131 | 132 | WatchmanWatcher.prototype.handleChangeEvent = function(resp) { 133 | if (Array.isArray(resp.files)) { 134 | resp.files.forEach(this.handleFileChange, this); 135 | } 136 | }; 137 | 138 | /** 139 | * Handles a single change event record. 140 | * 141 | * @param {Object} changeDescriptor 142 | * @private 143 | */ 144 | 145 | WatchmanWatcher.prototype.handleFileChange = function(changeDescriptor) { 146 | let absPath; 147 | let relativePath; 148 | 149 | relativePath = changeDescriptor.name; 150 | absPath = path.join(this.root, relativePath); 151 | 152 | if ( 153 | !(this._client.wildmatch && !this.hasIgnore) && 154 | !common.isFileIncluded(this.globs, this.dot, this.doIgnore, relativePath) 155 | ) { 156 | return; 157 | } 158 | 159 | if (!changeDescriptor.exists) { 160 | this.emitEvent(DELETE_EVENT, relativePath, this.root); 161 | } else { 162 | fs.lstat(absPath, (error, stat) => { 163 | // Files can be deleted between the event and the lstat call 164 | // the most reliable thing to do here is to ignore the event. 165 | if (error && error.code === 'ENOENT') { 166 | return; 167 | } 168 | 169 | if (this._handleError(error)) { 170 | return; 171 | } 172 | 173 | let eventType = changeDescriptor.new ? ADD_EVENT : CHANGE_EVENT; 174 | 175 | // Change event on dirs are mostly useless. 176 | if (!(eventType === CHANGE_EVENT && stat.isDirectory())) { 177 | this.emitEvent(eventType, relativePath, this.root, stat); 178 | } 179 | }); 180 | } 181 | }; 182 | 183 | /** 184 | * Dispatches an event. 185 | * 186 | * @param {string} eventType 187 | * @param {string} filepath 188 | * @param {string} root 189 | * @param {fs.Stat} stat 190 | * @private 191 | */ 192 | 193 | WatchmanWatcher.prototype.emitEvent = function( 194 | eventType, 195 | filepath, 196 | root, 197 | stat 198 | ) { 199 | this.emit(eventType, filepath, root, stat); 200 | this.emit(ALL_EVENT, eventType, filepath, root, stat); 201 | }; 202 | 203 | /** 204 | * Closes the watcher. 205 | * 206 | * @param {function} callback 207 | * @private 208 | */ 209 | 210 | WatchmanWatcher.prototype.close = function(callback) { 211 | this._client.closeWatcher(this); 212 | callback && callback(null, true); 213 | }; 214 | 215 | /** 216 | * Handles an error and returns true if exists. 217 | * 218 | * @param {WatchmanWatcher} self 219 | * @param {Error} error 220 | * @private 221 | */ 222 | 223 | WatchmanWatcher.prototype._handleError = function(error) { 224 | if (error != null) { 225 | this.emit('error', error); 226 | return true; 227 | } else { 228 | return false; 229 | } 230 | }; 231 | 232 | /** 233 | * Handles a warning in the watchman resp object. 234 | * 235 | * @param {object} resp 236 | * @private 237 | */ 238 | 239 | WatchmanWatcher.prototype._handleWarning = function(resp) { 240 | if ('warning' in resp) { 241 | if (RecrawlWarning.isRecrawlWarningDupe(resp.warning)) { 242 | return true; 243 | } 244 | console.warn(resp.warning); 245 | return true; 246 | } else { 247 | return false; 248 | } 249 | }; 250 | -------------------------------------------------------------------------------- /test/plugin_watcher.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const watch = require('@cnakazawa/watch'); 6 | const common = require('../src/common'); 7 | const EventEmitter = require('events').EventEmitter; 8 | 9 | /** 10 | * Constants 11 | */ 12 | const { 13 | DEFAULT_DELAY, 14 | CHANGE_EVENT, 15 | DELETE_EVENT, 16 | ADD_EVENT, 17 | ALL_EVENT, 18 | } = common; 19 | 20 | /** 21 | * Export `PluginTestWatcher` class. 22 | * 23 | * This class is based on the PollWatcher but emits a special event to signal that it 24 | * is the watcher that is used to verify that the plugin system is working 25 | * 26 | */ 27 | module.exports = class PluginTestWatcher extends EventEmitter { 28 | /** 29 | * Watches `dir`. 30 | * 31 | * @class PluginTestWatcher 32 | * @param String dir 33 | * @param {Object} opts 34 | * @public 35 | */ 36 | constructor(dir, opts) { 37 | super(); 38 | 39 | opts = common.assignOptions(this, opts); 40 | 41 | this.watched = Object.create(null); 42 | this.root = path.resolve(dir); 43 | 44 | watch.createMonitor( 45 | this.root, 46 | { 47 | interval: (opts.interval || DEFAULT_DELAY) / 1000, 48 | filter: this.filter.bind(this), 49 | }, 50 | this.init.bind(this) 51 | ); 52 | } 53 | 54 | /** 55 | * Given a fullpath of a file or directory check if we need to watch it. 56 | * 57 | * @param {string} filepath 58 | * @param {object} stat 59 | * @private 60 | */ 61 | 62 | filter(filepath, stat) { 63 | return ( 64 | stat.isDirectory() || 65 | common.isFileIncluded( 66 | this.globs, 67 | this.dot, 68 | this.doIgnore, 69 | path.relative(this.root, filepath) 70 | ) 71 | ); 72 | } 73 | 74 | /** 75 | * Initiate the polling file watcher with the event emitter passed from 76 | * `watch.watchTree`. 77 | * 78 | * @param {EventEmitter} monitor 79 | * @public 80 | */ 81 | 82 | init(monitor) { 83 | this.watched = monitor.files; 84 | monitor.on('changed', this.emitEvent.bind(this, CHANGE_EVENT)); 85 | monitor.on('removed', this.emitEvent.bind(this, DELETE_EVENT)); 86 | monitor.on('created', this.emitEvent.bind(this, ADD_EVENT)); 87 | // 1 second wait because mtime is second-based. 88 | setTimeout(this.emit.bind(this, 'ready'), 1000); 89 | 90 | // This event is fired to note that this watcher is the one from the plugin system 91 | setTimeout(this.emit.bind(this, 'is-test-plugin'), 1); 92 | } 93 | 94 | /** 95 | * Transform and emit an event comming from the poller. 96 | * 97 | * @param {EventEmitter} monitor 98 | * @public 99 | */ 100 | 101 | emitEvent(type, file, stat) { 102 | file = path.relative(this.root, file); 103 | 104 | if (type === DELETE_EVENT) { 105 | // Matching the non-polling API 106 | stat = null; 107 | } 108 | 109 | this.emit(type, file, this.root, stat); 110 | this.emit(ALL_EVENT, type, file, this.root, stat); 111 | } 112 | 113 | /** 114 | * End watching. 115 | * 116 | * @public 117 | */ 118 | 119 | close(callback) { 120 | for (let filepath in this.watched) { 121 | fs.unwatchFile(filepath); 122 | } 123 | this.removeAllListeners(); 124 | if (typeof callback === 'function') { 125 | setImmediate(callback.bind(null, null, true)); 126 | } 127 | } 128 | }; 129 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | 'use strict'; 3 | 4 | const fs = require('fs'); 5 | const sane = require('../'); 6 | const rimraf = require('rimraf'); 7 | const path = require('path'); 8 | const assert = require('assert'); 9 | const tmp = require('tmp'); 10 | 11 | tmp.setGracefulCleanup(); 12 | const jo = path.join.bind(path); 13 | 14 | describe('sane in polling mode', function() { 15 | harness.call(this, { poll: true }); 16 | }); 17 | describe('sane in node mode', function() { 18 | harness.call(this, {}); 19 | }); 20 | 21 | describe('sane in fsevents mode', function() { 22 | it('errors in a helpful manner', function() { 23 | assert.throws(() => sane.FSEventsWatcher, 'asdf'); 24 | assert.throws(() => sane('/dev/null', { fsevents: true }), 'asdf'); 25 | }); 26 | }); 27 | 28 | describe('sane in watchman mode', function() { 29 | harness.call(this, { watchman: true }); 30 | }); 31 | describe('sane in watchman mode with offset project', function() { 32 | harness.call(this, { watchman: true, offset: true }); 33 | }); 34 | 35 | describe('sane in watchexec mode', function() { 36 | harness.call(this, { watchexec: true }); 37 | }); 38 | 39 | function getWatcherClass(mode) { 40 | if (mode.watchman) { 41 | return sane.WatchmanWatcher; 42 | } else if (mode.WatchexecWatcher) { 43 | return sane.WatchexecWatcher; 44 | } else if (mode.poll) { 45 | return sane.PollWatcher; 46 | } else { 47 | return sane.NodeWatcher; 48 | } 49 | } 50 | 51 | function harness(mode) { 52 | const defer = fn => setTimeout(fn, mode.poll ? 1000 : 300); 53 | 54 | if (mode.poll) { 55 | this.timeout(5000); 56 | } 57 | 58 | let global_testdir = null; 59 | let testdir = null; 60 | after(function() { 61 | if (global_testdir) { 62 | try { 63 | rimraf.sync(global_testdir.name); 64 | } catch (e) { 65 | // Doesn't exist 66 | } 67 | } 68 | }); 69 | before(function() { 70 | global_testdir = tmp.dirSync({ 71 | prefix: 'sane-test', 72 | unsafeCleanup: true, 73 | }); 74 | testdir = fs.realpathSync(global_testdir.name); 75 | 76 | // Some Watchman deployments are restricted to watching 77 | // project roots. Let's fake one 78 | fs.mkdirSync(jo(testdir, '.git')); 79 | 80 | // If testing watchman watch-project in offset mode, create an offset dir 81 | if (mode.offset) { 82 | testdir = jo(testdir, 'offset'); 83 | fs.mkdirSync(testdir); 84 | } 85 | 86 | for (let i = 0; i < 10; i++) { 87 | fs.writeFileSync(jo(testdir, 'file_' + i), 'test_' + i); 88 | let subdir = jo(testdir, 'sub_' + i); 89 | try { 90 | fs.mkdirSync(subdir); 91 | } catch (e) { 92 | // Already exists. 93 | } 94 | for (let j = 0; j < 10; j++) { 95 | fs.writeFileSync(jo(subdir, 'file_' + j), 'test_' + j); 96 | } 97 | } 98 | }); 99 | 100 | describe('sane plugin', function() { 101 | beforeEach(function() { 102 | this.watcher = sane(testdir, { 103 | glob: '**/file_1', 104 | watcher: './test/plugin_watcher', 105 | }); 106 | }); 107 | 108 | afterEach(function(done) { 109 | this.watcher.close(done); 110 | }); 111 | 112 | it('uses the custom plugin watcher', function(done) { 113 | this.watcher.on('is-test-plugin', function() { 114 | done(); 115 | }); 116 | }); 117 | }); 118 | 119 | describe('sane(file)', function() { 120 | beforeEach(function() { 121 | let Watcher = getWatcherClass(mode); 122 | this.watcher = new Watcher(testdir); 123 | }); 124 | 125 | afterEach(function(done) { 126 | this.watcher.close(done); 127 | }); 128 | 129 | it('emits a ready event', function(done) { 130 | this.watcher.on('ready', done); 131 | this.watcher.on('error', function(error) { 132 | done(error); 133 | }); 134 | }); 135 | 136 | it('change emits event', function(done) { 137 | let testfile = jo(testdir, 'file_1'); 138 | this.watcher.on('error', function(error) { 139 | done(error); 140 | }); 141 | this.watcher.on('change', function(filepath, dir, stat) { 142 | assert(stat instanceof fs.Stats); 143 | assert.equal(filepath, path.relative(testdir, testfile)); 144 | assert.equal(dir, testdir); 145 | done(); 146 | }); 147 | this.watcher.on('ready', function() { 148 | fs.writeFileSync(testfile, 'wow'); 149 | }); 150 | }); 151 | 152 | it('emits change events for subdir files', function(done) { 153 | let subdir = 'sub_1'; 154 | let testfile = jo(testdir, subdir, 'file_1'); 155 | this.watcher.on('change', (filepath, dir) => { 156 | assert.equal(filepath, path.relative(testdir, testfile)); 157 | assert.equal(dir, testdir); 158 | done(); 159 | }); 160 | this.watcher.on('ready', function() { 161 | fs.writeFileSync(testfile, 'wow'); 162 | }); 163 | }); 164 | it('adding a file will trigger an add event', function(done) { 165 | let testfile = jo(testdir, 'file_x' + Math.floor(Math.random() * 10000)); 166 | this.watcher.on('add', (filepath, dir, stat) => { 167 | assert(stat instanceof fs.Stats); 168 | assert.equal(filepath, path.relative(testdir, testfile)); 169 | assert.equal(dir, testdir); 170 | done(); 171 | }); 172 | this.watcher.on('change', () => { 173 | done(new Error('Should not emit change on add')); 174 | }); 175 | this.watcher.on('ready', () => { 176 | fs.writeFileSync(testfile, 'wow'); 177 | }); 178 | }); 179 | 180 | it('removing a file will emit delete event', function(done) { 181 | let testfile = jo(testdir, 'file_9'); 182 | this.watcher.on('delete', (filepath, dir) => { 183 | assert.equal(filepath, path.relative(testdir, testfile)); 184 | assert.equal(dir, testdir); 185 | done(); 186 | }); 187 | this.watcher.on('ready', () => fs.unlinkSync(testfile)); 188 | }); 189 | 190 | it('changing, removing, deleting should emit the "all" event', function(done) { 191 | let toChange = jo(testdir, 'file_4'); 192 | let toDelete = jo(testdir, 'file_5'); 193 | let toAdd = jo(testdir, 'file_x' + Math.floor(Math.random() * 10000)); 194 | let i = 0; 195 | let added = false; 196 | 197 | this.watcher.on('all', (type, filepath, dir, stat) => { 198 | assert.equal(dir, testdir); 199 | if (type === 'change') { 200 | // Windows emits additional change events for newly created files. 201 | if (added && filepath === path.relative(dir, toAdd)) { 202 | return; 203 | } 204 | assert(stat instanceof fs.Stats); 205 | assert.equal(filepath, path.relative(dir, toChange)); 206 | } else if (type === 'delete') { 207 | assert(!stat); 208 | assert.equal(filepath, path.relative(dir, toDelete)); 209 | } else if (type === 'add') { 210 | assert(stat instanceof fs.Stats); 211 | assert.equal(filepath, path.relative(dir, toAdd)); 212 | added = true; 213 | } 214 | if (++i === 3) { 215 | done(); 216 | } 217 | }); 218 | 219 | this.watcher.on('ready', () => { 220 | fs.writeFileSync(toChange, 'hai'); 221 | fs.unlinkSync(toDelete); 222 | fs.writeFileSync(toAdd, 'hai wow'); 223 | }); 224 | }); 225 | 226 | it('removing a dir will emit delete event', function(done) { 227 | let subdir = jo(testdir, 'sub_9'); 228 | this.watcher.on('delete', (filepath, dir) => { 229 | // Ignore delete events for files in the dir. 230 | if (path.dirname(filepath) === path.relative(testdir, subdir)) { 231 | return; 232 | } 233 | assert.equal(filepath, path.relative(testdir, subdir)); 234 | assert.equal(dir, testdir); 235 | done(); 236 | }); 237 | this.watcher.on('ready', () => rimraf.sync(subdir)); 238 | }); 239 | 240 | it('adding a dir will emit an add event', function(done) { 241 | let subdir = jo(testdir, 'sub_x' + Math.floor(Math.random() * 10000)); 242 | this.watcher.on('add', (filepath, dir, stat) => { 243 | assert(stat instanceof fs.Stats); 244 | assert.equal(filepath, path.relative(testdir, subdir)); 245 | assert.equal(dir, testdir); 246 | done(); 247 | }); 248 | this.watcher.on('ready', () => fs.mkdirSync(subdir)); 249 | }); 250 | 251 | it('adding in a subdir will trigger an add event', function(done) { 252 | let subdir = jo(testdir, 'sub_x' + Math.floor(Math.random() * 10000)); 253 | let testfile = jo(subdir, 'file_x' + Math.floor(Math.random() * 10000)); 254 | let i = 0; 255 | this.watcher.on('add', (filepath, dir, stat) => { 256 | assert(stat instanceof fs.Stats); 257 | if (++i === 1) { 258 | assert.equal(filepath, path.relative(testdir, subdir)); 259 | assert.equal(dir, testdir); 260 | } else { 261 | assert.equal(filepath, path.relative(testdir, testfile)); 262 | assert.equal(dir, testdir); 263 | done(); 264 | } 265 | }); 266 | this.watcher.on('ready', () => { 267 | fs.mkdirSync(subdir); 268 | defer(() => fs.writeFileSync(testfile, 'wow')); 269 | }); 270 | }); 271 | 272 | it('closes watchers when dirs are deleted', function(done) { 273 | let subdir = jo(testdir, 'sub_1'); 274 | let testfile = jo(subdir, 'file_1'); 275 | let actualFiles = {}; 276 | let expectedFiles = {}; 277 | expectedFiles[path.relative(testdir, subdir)] = true; 278 | expectedFiles[path.relative(testdir, testfile)] = true; 279 | this.watcher.on('ready', () => { 280 | this.watcher.on('add', filepath => { 281 | // win32 order is not guaranteed and events may leak between tests 282 | if (expectedFiles[filepath]) { 283 | actualFiles[filepath] = true; 284 | } 285 | if (Object.keys(actualFiles).length === 2) { 286 | assert.deepEqual(expectedFiles, actualFiles); 287 | done(); 288 | } 289 | }); 290 | rimraf.sync(subdir); 291 | defer(() => { 292 | fs.mkdirSync(subdir); 293 | defer(() => fs.writeFileSync(testfile, 'wow')); 294 | }); 295 | }); 296 | }); 297 | 298 | it('should be ok to remove and then add the same file', function(done) { 299 | let testfile = jo(testdir, 'sub_8', 'file_1'); 300 | this.watcher.on('add', (filepath, dir) => { 301 | assert.equal(filepath, path.relative(testdir, testfile)); 302 | assert.equal(dir, testdir); 303 | done(); 304 | }); 305 | this.watcher.on('delete', (filepath, dir) => { 306 | assert.equal(filepath, path.relative(testdir, testfile)); 307 | assert.equal(dir, testdir); 308 | }); 309 | this.watcher.on('ready', () => { 310 | fs.unlinkSync(testfile); 311 | defer(() => fs.writeFileSync(testfile, 'wow')); 312 | }); 313 | }); 314 | 315 | if (!mode.poll) { 316 | it('emits events for subdir/subdir files', function(done) { 317 | let subdir1 = 'subsub_1'; 318 | let subdir2 = 'subsub_2'; 319 | let filename = 'file_1'; 320 | let testfile = jo(testdir, subdir1, subdir2, filename); 321 | let addedSubdir1 = false; 322 | let addedSubdir2 = false; 323 | let addedFile = false; 324 | this.watcher.on('add', filepath => { 325 | if (filepath === subdir1) { 326 | assert.equal(addedSubdir1, false); 327 | addedSubdir1 = true; 328 | } else if (filepath === jo(subdir1, subdir2)) { 329 | assert.equal(addedSubdir2, false); 330 | addedSubdir2 = true; 331 | } else if (filepath === jo(subdir1, subdir2, filename)) { 332 | assert.equal(addedFile, false); 333 | addedFile = true; 334 | } 335 | if (addedSubdir1 && addedSubdir2 && addedFile) { 336 | done(); 337 | } 338 | }); 339 | this.watcher.on('ready', () => { 340 | fs.mkdirSync(jo(testdir, subdir1)); 341 | fs.mkdirSync(jo(testdir, subdir1, subdir2)); 342 | fs.writeFileSync(testfile, 'wow'); 343 | }); 344 | }); 345 | it('emits events for subdir/subdir files 2', function(done) { 346 | let subdir1 = 'subsub_1b'; 347 | let subdir2 = 'subsub_2b'; 348 | let filename = 'file_1b'; 349 | let testfile = jo(testdir, subdir1, subdir2, filename); 350 | let addedSubdir1 = false; 351 | let addedSubdir2 = false; 352 | let addedFile = false; 353 | this.watcher.on('add', filepath => { 354 | if (filepath === subdir1) { 355 | assert.equal(addedSubdir1, false); 356 | addedSubdir1 = true; 357 | } else if (filepath === jo(subdir1, subdir2)) { 358 | assert.equal(addedSubdir2, false); 359 | addedSubdir2 = true; 360 | } else if (filepath === jo(subdir1, subdir2, filename)) { 361 | assert.equal(addedFile, false); 362 | addedFile = true; 363 | } 364 | if (addedSubdir1 && addedSubdir2 && addedFile) { 365 | done(); 366 | } 367 | }); 368 | this.watcher.on('ready', () => { 369 | fs.mkdirSync(jo(testdir, subdir1)); 370 | fs.mkdirSync(jo(testdir, subdir1, subdir2)); 371 | setTimeout(() => fs.writeFileSync(testfile, 'wow'), 500); 372 | }); 373 | }); 374 | } 375 | }); 376 | 377 | describe('sane(file, glob)', function() { 378 | beforeEach(function() { 379 | let Watcher = getWatcherClass(mode); 380 | this.watcher = new Watcher(testdir, { glob: ['**/file_1', '**/file_2'] }); 381 | }); 382 | 383 | afterEach(function(done) { 384 | this.watcher.close(done); 385 | }); 386 | 387 | it('ignore files according to glob', function(done) { 388 | let i = 0; 389 | this.watcher.on('change', (filepath, dir) => { 390 | assert.ok(filepath.match(/file_(1|2)/), 'only file_1 and file_2'); 391 | assert.equal(dir, testdir); 392 | if (++i == 2) { 393 | done(); 394 | } 395 | }); 396 | this.watcher.on('ready', () => { 397 | fs.writeFileSync(jo(testdir, 'file_1'), 'wow'); 398 | fs.writeFileSync(jo(testdir, 'file_9'), 'wow'); 399 | fs.writeFileSync(jo(testdir, 'file_3'), 'wow'); 400 | fs.writeFileSync(jo(testdir, 'file_2'), 'wow'); 401 | }); 402 | }); 403 | }); 404 | 405 | describe('sane(dir, {dot: false})', function() { 406 | beforeEach(function() { 407 | let Watcher = getWatcherClass(mode); 408 | this.watcher = new Watcher(testdir, { dot: false }); 409 | }); 410 | 411 | afterEach(function(done) { 412 | this.watcher.close(done); 413 | }); 414 | 415 | it('should ignore dot files', function(done) { 416 | let i = 0; 417 | this.watcher.on('change', (filepath, dir) => { 418 | assert.ok(filepath.match(/file_(1|2)/), 'only file_1 and file_2'); 419 | assert.equal(dir, testdir); 420 | if (++i == 2) { 421 | done(); 422 | } 423 | }); 424 | this.watcher.on('ready', () => { 425 | fs.writeFileSync(jo(testdir, 'file_1'), 'wow'); 426 | fs.writeFileSync(jo(testdir, '.file_9'), 'wow'); 427 | fs.writeFileSync(jo(testdir, '.file_3'), 'wow'); 428 | fs.writeFileSync(jo(testdir, 'file_2'), 'wow'); 429 | }); 430 | }); 431 | 432 | it('should ignore dot dirs', function(done) { 433 | this.watcher.on('change', (filepath, dir) => { 434 | assert.ok(filepath.match(/file_1/), 'only file_1 got : ' + filepath); 435 | assert.equal(dir, testdir); 436 | done(); 437 | }); 438 | 439 | this.watcher.on('add', filepath => { 440 | if (filepath.match(/^\.lol/)) { 441 | done(new Error('Should not emit add events for ignored dirs')); 442 | } 443 | }); 444 | 445 | this.watcher.on('ready', () => { 446 | let subdir = jo(testdir, '.lol' + Math.floor(Math.random() * 10000)); 447 | fs.mkdirSync(subdir); 448 | fs.writeFileSync(jo(subdir, 'file'), 'wow'); 449 | fs.writeFileSync(jo(subdir, '.file_3'), 'wow'); 450 | fs.writeFileSync(jo(testdir, 'file_1'), 'wow'); 451 | }); 452 | }); 453 | }); 454 | 455 | describe('sane(dir, ignored)', function() { 456 | beforeEach(function() { 457 | let Watcher = getWatcherClass(mode); 458 | this.watcher = new Watcher(testdir, { 459 | ignored: ['**/file_3', /file_4/, file => file.indexOf('file_5') !== -1], 460 | }); 461 | }); 462 | 463 | afterEach(function(done) { 464 | this.watcher.close(done); 465 | }); 466 | 467 | it('ignores files', function(done) { 468 | let i = 0; 469 | this.watcher.on('change', (filepath, dir) => { 470 | assert.ok(filepath.match(/file_(1|2)/), 'only file_1 and file_2'); 471 | assert.equal(dir, testdir); 472 | if (++i === 2) { 473 | done(); 474 | } 475 | }); 476 | this.watcher.on('ready', () => { 477 | fs.writeFileSync(jo(testdir, 'file_1'), 'wow'); 478 | fs.writeFileSync(jo(testdir, 'file_4'), 'wow'); 479 | fs.writeFileSync(jo(testdir, 'file_3'), 'wow'); 480 | fs.writeFileSync(jo(testdir, 'file_5'), 'wow'); 481 | fs.writeFileSync(jo(testdir, 'file_2'), 'wow'); 482 | }); 483 | }); 484 | }); 485 | 486 | describe('sane(dir, ignored) - node_watcher directory ignore', function() { 487 | beforeEach(function() { 488 | let Watcher = getWatcherClass({}); // node_watcher only 489 | this.watcher = new Watcher(testdir, { 490 | ignored: [/sub_0/, file => file.indexOf('sub_1') !== -1], 491 | }); 492 | //overwrite standard ignore for test 493 | this.watcher.doIgnore = () => false; 494 | }); 495 | 496 | afterEach(function(done) { 497 | this.watcher.close(done); 498 | }); 499 | 500 | it('ignores folders', function(done) { 501 | let i = 0; 502 | this.watcher.on('change', (filepath, dir) => { 503 | assert.ok( 504 | !filepath.match(/sub_(0|1)/), 505 | 'Found changes in ignored subdir sub_0 and/or sub_1' 506 | ); 507 | assert.equal(dir, testdir); 508 | if (++i == 2) { 509 | done(); 510 | } 511 | }); 512 | this.watcher.on('ready', () => { 513 | fs.writeFileSync(jo(testdir, 'sub_0', 'file_1'), 'wow'); 514 | fs.writeFileSync(jo(testdir, 'sub_1', 'file_1'), 'wow'); 515 | fs.writeFileSync(jo(testdir, 'sub_2', 'file_1'), 'wow'); 516 | fs.writeFileSync(jo(testdir, 'sub_3', 'file_1'), 'wow'); 517 | fs.writeFileSync(jo(testdir, 'sub_4', 'file_1'), 'wow'); 518 | }); 519 | }); 520 | }); 521 | 522 | describe('sane shortcut alias', function() { 523 | beforeEach(function() { 524 | this.watcher = sane(testdir, { 525 | glob: '**/file_1', 526 | poll: mode.poll, 527 | watchman: mode.watchman, 528 | }); 529 | }); 530 | 531 | afterEach(function(done) { 532 | this.watcher.close(done); 533 | }); 534 | 535 | it('allows for shortcut mode using just a string as glob', function(done) { 536 | this.watcher.on('change', (filepath, dir) => { 537 | assert.ok(filepath.match(/file_1/)); 538 | assert.equal(dir, testdir); 539 | done(); 540 | }); 541 | this.watcher.on('ready', () => { 542 | fs.writeFileSync(jo(testdir, 'file_1'), 'wow'); 543 | }); 544 | }); 545 | }); 546 | } 547 | -------------------------------------------------------------------------------- /test/utils-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | 'use strict'; 3 | 4 | const RecrawlWarning = require('../src/utils/recrawl-warning-dedupe'); 5 | const assert = require('assert'); 6 | 7 | describe.only('RecrawlWarning', function() { 8 | afterEach(function() { 9 | RecrawlWarning.RECRAWL_WARNINGS.length = 0; 10 | }); 11 | 12 | describe('.findByRoot', function() { 13 | it('returns undefined, if nothing is found', function() { 14 | assert( 15 | RecrawlWarning.findByRoot('find/nothing') === undefined, 16 | 'expected nothing to be found' 17 | ); 18 | }); 19 | 20 | describe('something to find', function() { 21 | it('returns undefined, if nothing is found', function() { 22 | RecrawlWarning.RECRAWL_WARNINGS.push( 23 | new RecrawlWarning('some/path', 5) 24 | ); 25 | assert( 26 | RecrawlWarning.findByRoot('find/nothing') === undefined, 27 | 'expected nothing to be found' 28 | ); 29 | }); 30 | 31 | it('returns warning, if found', function() { 32 | let warning = new RecrawlWarning('some/path', 5); 33 | RecrawlWarning.RECRAWL_WARNINGS.push(warning); 34 | assert.equal( 35 | RecrawlWarning.findByRoot('some/path'), 36 | warning, 37 | 'expected the warning to be found' 38 | ); 39 | }); 40 | 41 | it('returns FIRST warning, if found', function() { 42 | let warning = new RecrawlWarning('some/path', 5); 43 | let warning2 = new RecrawlWarning('some/path', 5); 44 | RecrawlWarning.RECRAWL_WARNINGS.push(warning); 45 | RecrawlWarning.RECRAWL_WARNINGS.push(warning2); 46 | assert.equal( 47 | RecrawlWarning.findByRoot('some/path'), 48 | warning, 49 | 'expected the warning to be found' 50 | ); 51 | }); 52 | 53 | describe('count', function() { 54 | it('returns first, regardless of count', function() { 55 | let warning = new RecrawlWarning('some/path', 5); 56 | let warning2 = new RecrawlWarning('some/path', 4); 57 | RecrawlWarning.RECRAWL_WARNINGS.push(warning2); 58 | RecrawlWarning.RECRAWL_WARNINGS.push(warning); 59 | assert.equal( 60 | RecrawlWarning.findByRoot('some/path'), 61 | warning2, 62 | 'expected the warning to be found' 63 | ); 64 | }); 65 | }); 66 | }); 67 | }); 68 | 69 | describe('.isRecrawlWarningDupe', function() { 70 | describe('invalid warningMessage', function() { 71 | it('returns false for warning no message', function() { 72 | assert.equal(RecrawlWarning.isRecrawlWarningDupe(), false); 73 | assert.equal(RecrawlWarning.isRecrawlWarningDupe(undefined), false); 74 | assert.equal(RecrawlWarning.isRecrawlWarningDupe(false), false); 75 | assert.equal(RecrawlWarning.isRecrawlWarningDupe(2), false); 76 | assert.equal(RecrawlWarning.isRecrawlWarningDupe([]), false); 77 | }); 78 | 79 | it('returns false for non-matching warning message', function() { 80 | assert.equal(RecrawlWarning.isRecrawlWarningDupe(''), false); 81 | assert.equal(RecrawlWarning.isRecrawlWarningDupe('some string'), false); 82 | }); 83 | }); 84 | 85 | describe('valid warningMessage', function() { 86 | it('new message', function() { 87 | assert.equal( 88 | RecrawlWarning.isRecrawlWarningDupe( 89 | 'Recrawled this watch 1 times, most recently because:\n/foo/bar/baz:' 90 | ), 91 | false 92 | ); 93 | }); 94 | 95 | it('same message twice', function() { 96 | assert.equal( 97 | RecrawlWarning.isRecrawlWarningDupe( 98 | 'Recrawled this watch 2 times, most recently because:\n/foo/bar/baz:' 99 | ), 100 | false 101 | ); 102 | assert.equal( 103 | RecrawlWarning.isRecrawlWarningDupe( 104 | 'Recrawled this watch 2 times, most recently because:\n/foo/bar/baz:' 105 | ), 106 | true 107 | ); 108 | }); 109 | 110 | it('same count, but different root twice', function() { 111 | assert.equal( 112 | RecrawlWarning.isRecrawlWarningDupe( 113 | 'Recrawled this watch 2 times, most recently because:\n/foo/bar/baz:' 114 | ), 115 | false 116 | ); 117 | assert.equal( 118 | RecrawlWarning.isRecrawlWarningDupe( 119 | 'Recrawled this watch 2 times, most recently because:\n/baz/bar/baz:' 120 | ), 121 | false 122 | ); 123 | }); 124 | 125 | it('incrementing count, but fixed root', function() { 126 | assert.equal( 127 | RecrawlWarning.isRecrawlWarningDupe( 128 | 'Recrawled this watch 2 times, most recently because:\n/foo/bar/baz:' 129 | ), 130 | false 131 | ); 132 | assert.equal( 133 | RecrawlWarning.isRecrawlWarningDupe( 134 | 'Recrawled this watch 3 times, most recently because:\n/foo/bar/baz:' 135 | ), 136 | false 137 | ); 138 | assert.equal( 139 | RecrawlWarning.isRecrawlWarningDupe( 140 | 'Recrawled this watch 4 times, most recently because:\n/foo/bar/baz:' 141 | ), 142 | false 143 | ); 144 | }); 145 | 146 | it('decrementing count, but fixed root', function() { 147 | assert.equal( 148 | RecrawlWarning.isRecrawlWarningDupe( 149 | 'Recrawled this watch 4 times, most recently because:\n/foo/bar/baz:' 150 | ), 151 | false 152 | ); 153 | assert.equal( 154 | RecrawlWarning.isRecrawlWarningDupe( 155 | 'Recrawled this watch 3 times, most recently because:\n/foo/bar/baz:' 156 | ), 157 | true 158 | ); 159 | assert.equal( 160 | RecrawlWarning.isRecrawlWarningDupe( 161 | 'Recrawled this watch 2 times, most recently because:\n/foo/bar/baz:' 162 | ), 163 | true 164 | ); 165 | }); 166 | }); 167 | }); 168 | }); 169 | -------------------------------------------------------------------------------- /test/watchexec_client-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | 'use strict'; 3 | 4 | const extractChanges = require('../src/watchexec_client'); 5 | const assert = require('assert'); 6 | 7 | describe('Watchexec client', function() { 8 | it('handles single changes', function() { 9 | // FILE UPDATE 10 | let env = { WATCHEXEC_WRITTEN_PATH: '/path/to/file' }; 11 | assert.equal(extractChanges(env), 'write /path/to/file'); 12 | 13 | // FILE RENAMING 14 | env = { WATCHEXEC_RENAMED_PATH: '/path/to/file' }; 15 | assert.equal(extractChanges(env), 'rename /path/to/file'); 16 | 17 | // FILE REMOVAL 18 | env = { WATCHEXEC_REMOVED_PATH: '/path/to/file' }; 19 | assert.equal(extractChanges(env), 'remove /path/to/file'); 20 | 21 | // FILE CREATION 22 | env = { WATCHEXEC_CREATED_PATH: '/path/to/file' }; 23 | assert.equal(extractChanges(env), 'create /path/to/file'); 24 | }); 25 | 26 | it('handles multiple changes of the same type', function() { 27 | let env = { 28 | WATCHEXEC_WRITTEN_PATH: 'file:second/file', 29 | WATCHEXEC_COMMON_PATH: '/path/to/', 30 | }; 31 | assert.equal( 32 | extractChanges(env), 33 | `write /path/to/file 34 | write /path/to/second/file` 35 | ); 36 | }); 37 | 38 | it('handles multiple changes of multiple types', function() { 39 | let env = { 40 | WATCHEXEC_WRITTEN_PATH: 'file:second/file', 41 | WATCHEXEC_REMOVED_PATH: 'deleted:second_deletion', 42 | WATCHEXEC_COMMON_PATH: '/path/to/', 43 | }; 44 | assert.equal( 45 | extractChanges(env), 46 | `remove /path/to/deleted 47 | remove /path/to/second_deletion 48 | write /path/to/file 49 | write /path/to/second/file` 50 | ); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/watchexec_watcher-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | 'use strict'; 3 | 4 | const { _messageHandler: handler } = require('../src/watchexec_watcher'); 5 | const EventEmitter = require('events').EventEmitter; 6 | const assert = require('assert'); 7 | const { Stats } = require('fs'); 8 | const { relative } = require('path'); 9 | 10 | class WatcherMock extends EventEmitter { 11 | constructor() { 12 | super(); 13 | this.root = '/'; 14 | } 15 | 16 | receive(stream) { 17 | handler.call(this, stream); 18 | } 19 | emitEvent() { 20 | assert.fail('you must override this method in your test'); 21 | } 22 | } 23 | 24 | describe('Watchexec handler', function() { 25 | beforeEach(function() { 26 | this.mock = new WatcherMock(); 27 | this.makeBuffer = function(str) { 28 | return Buffer.from(str || '', 'utf8'); 29 | }; 30 | }); 31 | 32 | it('does not send events on empty strings', function() { 33 | this.mock.emitEvent = function() { 34 | assert.fail('it should not be called never called'); 35 | }; 36 | this.mock.receive(this.makeBuffer('')); 37 | this.mock.receive( 38 | this.makeBuffer(` 39 | 40 | 41 | `) 42 | ); 43 | }); 44 | 45 | it('does not send events on malformed strings', function() { 46 | this.mock.emitEvent = function() { 47 | assert.fail('it should not be called never called'); 48 | }; 49 | this.mock.receive(this.makeBuffer('sorry not sorry:')); 50 | this.mock.receive( 51 | this.makeBuffer(` 52 | never gonna give you up 53 | never gonna let you down 54 | `) 55 | ); 56 | }); 57 | 58 | it('does not send events on malformed strings', function() { 59 | this.mock.emitEvent = function() { 60 | assert.fail('it should not be called never called'); 61 | }; 62 | this.mock.receive(this.makeBuffer('sorry not sorry:')); 63 | this.mock.receive( 64 | this.makeBuffer(` 65 | never gonna give you up 66 | never gonna let you down 67 | `) 68 | ); 69 | }); 70 | 71 | it('sends the correct event on file creation', function() { 72 | const mock = this.mock; 73 | mock.emitEvent = function(type, path, stat) { 74 | assert.equal(type, 'add'); 75 | assert.equal(path, relative(mock.root, __filename)); 76 | assert.ok(stat instanceof Stats); 77 | }; 78 | this.mock.receive(this.makeBuffer(`create ${__filename}`)); 79 | }); 80 | 81 | it('sends the correct event on file update', function() { 82 | const mock = this.mock; 83 | mock.emitEvent = function(type, path, stat) { 84 | assert.equal(type, 'change'); 85 | assert.equal(path, relative(mock.root, __filename)); 86 | assert.ok(stat instanceof Stats); 87 | }; 88 | this.mock.receive(this.makeBuffer(`write ${__filename}`)); 89 | }); 90 | 91 | it('sends the correct event on file renaming', function() { 92 | const mock = this.mock; 93 | mock.emitEvent = function(type, path, stat) { 94 | assert.equal(type, 'change'); 95 | assert.equal(path, relative(mock.root, __filename)); 96 | assert.ok(stat instanceof Stats); 97 | }; 98 | this.mock.receive(this.makeBuffer(`rename ${__filename}`)); 99 | }); 100 | 101 | it('sends the correct event on file deletion', function() { 102 | const mock = this.mock; 103 | mock.emitEvent = function(type, path, stat) { 104 | assert.equal(type, 'delete'); 105 | assert.equal(path, relative(mock.root, __filename)); 106 | assert.ok(!stat); 107 | }; 108 | this.mock.receive(this.makeBuffer(`remove ${__filename}`)); 109 | }); 110 | 111 | it('handles multiline messages', function() { 112 | let count = 0; 113 | this.mock.emitEvent = () => count++; 114 | 115 | this.mock.receive( 116 | this.makeBuffer(` 117 | remove ${__filename} 118 | 119 | 120 | create ${__filename} 121 | this is a wrong message, it will be ignored 122 | rename ${__filename} 123 | `) 124 | ); 125 | if (count !== 3) { 126 | assert.fail('there should be 3 events'); 127 | } 128 | assert.ok('everything went well'); 129 | }); 130 | }); 131 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@babel/code-frame@^7.0.0": 6 | version "7.0.0" 7 | resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" 8 | integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA== 9 | dependencies: 10 | "@babel/highlight" "^7.0.0" 11 | 12 | "@babel/highlight@^7.0.0": 13 | version "7.0.0" 14 | resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" 15 | integrity sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw== 16 | dependencies: 17 | chalk "^2.0.0" 18 | esutils "^2.0.2" 19 | js-tokens "^4.0.0" 20 | 21 | "@cnakazawa/watch@^1.0.3": 22 | version "1.0.3" 23 | resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef" 24 | integrity sha512-r5160ogAvGyHsal38Kux7YYtodEKOj89RGb28ht1jh3SJb08VwRwAKKJL0bGb04Zd/3r9FL3BFIc3bBidYffCA== 25 | dependencies: 26 | exec-sh "^0.3.2" 27 | minimist "^1.2.0" 28 | 29 | acorn-jsx@^5.2.0: 30 | version "5.3.1" 31 | resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" 32 | integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== 33 | 34 | acorn@^7.1.1: 35 | version "7.4.1" 36 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" 37 | integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== 38 | 39 | ajv@^6.10.0: 40 | version "6.12.6" 41 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" 42 | integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== 43 | dependencies: 44 | fast-deep-equal "^3.1.1" 45 | fast-json-stable-stringify "^2.0.0" 46 | json-schema-traverse "^0.4.1" 47 | uri-js "^4.2.2" 48 | 49 | ajv@^6.9.1: 50 | version "6.10.0" 51 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1" 52 | integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg== 53 | dependencies: 54 | fast-deep-equal "^2.0.1" 55 | fast-json-stable-stringify "^2.0.0" 56 | json-schema-traverse "^0.4.1" 57 | uri-js "^4.2.2" 58 | 59 | ansi-colors@3.2.3: 60 | version "3.2.3" 61 | resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" 62 | integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== 63 | 64 | ansi-escapes@^4.2.1: 65 | version "4.3.2" 66 | resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" 67 | integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== 68 | dependencies: 69 | type-fest "^0.21.3" 70 | 71 | ansi-regex@^3.0.0: 72 | version "3.0.0" 73 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" 74 | 75 | ansi-regex@^4.1.0: 76 | version "4.1.0" 77 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" 78 | integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== 79 | 80 | ansi-regex@^5.0.0: 81 | version "5.0.0" 82 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" 83 | integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== 84 | 85 | ansi-styles@^3.2.0, ansi-styles@^3.2.1: 86 | version "3.2.1" 87 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 88 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 89 | dependencies: 90 | color-convert "^1.9.0" 91 | 92 | ansi-styles@^4.1.0: 93 | version "4.3.0" 94 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" 95 | integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== 96 | dependencies: 97 | color-convert "^2.0.1" 98 | 99 | anymatch@^3.1.1: 100 | version "3.1.2" 101 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" 102 | integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== 103 | dependencies: 104 | normalize-path "^3.0.0" 105 | picomatch "^2.0.4" 106 | 107 | argparse@^1.0.7: 108 | version "1.0.10" 109 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" 110 | dependencies: 111 | sprintf-js "~1.0.2" 112 | 113 | astral-regex@^1.0.0: 114 | version "1.0.0" 115 | resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" 116 | integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== 117 | 118 | balanced-match@^1.0.0: 119 | version "1.0.0" 120 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 121 | 122 | brace-expansion@^1.1.7: 123 | version "1.1.11" 124 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 125 | dependencies: 126 | balanced-match "^1.0.0" 127 | concat-map "0.0.1" 128 | 129 | braces@^3.0.1: 130 | version "3.0.2" 131 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 132 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 133 | dependencies: 134 | fill-range "^7.0.1" 135 | 136 | browser-stdout@1.3.1: 137 | version "1.3.1" 138 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" 139 | integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== 140 | 141 | bser@2.1.1: 142 | version "2.1.1" 143 | resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" 144 | integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== 145 | dependencies: 146 | node-int64 "^0.4.0" 147 | 148 | callsites@^3.0.0: 149 | version "3.0.0" 150 | resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.0.0.tgz#fb7eb569b72ad7a45812f93fd9430a3e410b3dd3" 151 | integrity sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw== 152 | 153 | camelcase@^5.0.0: 154 | version "5.2.0" 155 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.2.0.tgz#e7522abda5ed94cc0489e1b8466610e88404cf45" 156 | integrity sha512-IXFsBS2pC+X0j0N/GE7Dm7j3bsEBp+oTpb7F50dwEVX7rf3IgwO9XatnegTsDtniKCUtEJH4fSU6Asw7uoVLfQ== 157 | 158 | capture-exit@^2.0.0: 159 | version "2.0.0" 160 | resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" 161 | integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== 162 | dependencies: 163 | rsvp "^4.8.4" 164 | 165 | chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0: 166 | version "2.4.2" 167 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 168 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 169 | dependencies: 170 | ansi-styles "^3.2.1" 171 | escape-string-regexp "^1.0.5" 172 | supports-color "^5.3.0" 173 | 174 | chalk@^4.1.0: 175 | version "4.1.1" 176 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" 177 | integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== 178 | dependencies: 179 | ansi-styles "^4.1.0" 180 | supports-color "^7.1.0" 181 | 182 | chardet@^0.7.0: 183 | version "0.7.0" 184 | resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" 185 | integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== 186 | 187 | cli-cursor@^3.1.0: 188 | version "3.1.0" 189 | resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" 190 | integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== 191 | dependencies: 192 | restore-cursor "^3.1.0" 193 | 194 | cli-width@^3.0.0: 195 | version "3.0.0" 196 | resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" 197 | integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== 198 | 199 | cliui@^5.0.0: 200 | version "5.0.0" 201 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" 202 | integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== 203 | dependencies: 204 | string-width "^3.1.0" 205 | strip-ansi "^5.2.0" 206 | wrap-ansi "^5.1.0" 207 | 208 | color-convert@^1.9.0: 209 | version "1.9.3" 210 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 211 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 212 | dependencies: 213 | color-name "1.1.3" 214 | 215 | color-convert@^2.0.1: 216 | version "2.0.1" 217 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 218 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 219 | dependencies: 220 | color-name "~1.1.4" 221 | 222 | color-name@1.1.3: 223 | version "1.1.3" 224 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 225 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 226 | 227 | color-name@~1.1.4: 228 | version "1.1.4" 229 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 230 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 231 | 232 | concat-map@0.0.1: 233 | version "0.0.1" 234 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 235 | 236 | cross-spawn@^6.0.5: 237 | version "6.0.5" 238 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" 239 | dependencies: 240 | nice-try "^1.0.4" 241 | path-key "^2.0.1" 242 | semver "^5.5.0" 243 | shebang-command "^1.2.0" 244 | which "^1.2.9" 245 | 246 | cross-spawn@^7.0.0: 247 | version "7.0.3" 248 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" 249 | integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== 250 | dependencies: 251 | path-key "^3.1.0" 252 | shebang-command "^2.0.0" 253 | which "^2.0.1" 254 | 255 | debug@3.2.6: 256 | version "3.2.6" 257 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" 258 | integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== 259 | dependencies: 260 | ms "^2.1.1" 261 | 262 | debug@^4.0.1: 263 | version "4.1.1" 264 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" 265 | integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== 266 | dependencies: 267 | ms "^2.1.1" 268 | 269 | decamelize@^1.2.0: 270 | version "1.2.0" 271 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" 272 | integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= 273 | 274 | deep-is@~0.1.3: 275 | version "0.1.3" 276 | resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" 277 | 278 | define-properties@^1.1.2: 279 | version "1.1.3" 280 | resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" 281 | integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== 282 | dependencies: 283 | object-keys "^1.0.12" 284 | 285 | diff@3.5.0: 286 | version "3.5.0" 287 | resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" 288 | integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== 289 | 290 | doctrine@^3.0.0: 291 | version "3.0.0" 292 | resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" 293 | integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== 294 | dependencies: 295 | esutils "^2.0.2" 296 | 297 | emoji-regex@^7.0.1: 298 | version "7.0.3" 299 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" 300 | integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== 301 | 302 | emoji-regex@^8.0.0: 303 | version "8.0.0" 304 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" 305 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== 306 | 307 | end-of-stream@^1.1.0: 308 | version "1.4.1" 309 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" 310 | dependencies: 311 | once "^1.4.0" 312 | 313 | es-abstract@^1.5.1: 314 | version "1.13.0" 315 | resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" 316 | integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== 317 | dependencies: 318 | es-to-primitive "^1.2.0" 319 | function-bind "^1.1.1" 320 | has "^1.0.3" 321 | is-callable "^1.1.4" 322 | is-regex "^1.0.4" 323 | object-keys "^1.0.12" 324 | 325 | es-to-primitive@^1.2.0: 326 | version "1.2.0" 327 | resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" 328 | integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg== 329 | dependencies: 330 | is-callable "^1.1.4" 331 | is-date-object "^1.0.1" 332 | is-symbol "^1.0.2" 333 | 334 | escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: 335 | version "1.0.5" 336 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 337 | 338 | eslint-scope@^5.0.0: 339 | version "5.1.1" 340 | resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" 341 | integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== 342 | dependencies: 343 | esrecurse "^4.3.0" 344 | estraverse "^4.1.1" 345 | 346 | eslint-utils@^1.4.3: 347 | version "1.4.3" 348 | resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" 349 | integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== 350 | dependencies: 351 | eslint-visitor-keys "^1.1.0" 352 | 353 | eslint-visitor-keys@^1.1.0: 354 | version "1.3.0" 355 | resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" 356 | integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== 357 | 358 | eslint@^6.8.0: 359 | version "6.8.0" 360 | resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" 361 | integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== 362 | dependencies: 363 | "@babel/code-frame" "^7.0.0" 364 | ajv "^6.10.0" 365 | chalk "^2.1.0" 366 | cross-spawn "^6.0.5" 367 | debug "^4.0.1" 368 | doctrine "^3.0.0" 369 | eslint-scope "^5.0.0" 370 | eslint-utils "^1.4.3" 371 | eslint-visitor-keys "^1.1.0" 372 | espree "^6.1.2" 373 | esquery "^1.0.1" 374 | esutils "^2.0.2" 375 | file-entry-cache "^5.0.1" 376 | functional-red-black-tree "^1.0.1" 377 | glob-parent "^5.0.0" 378 | globals "^12.1.0" 379 | ignore "^4.0.6" 380 | import-fresh "^3.0.0" 381 | imurmurhash "^0.1.4" 382 | inquirer "^7.0.0" 383 | is-glob "^4.0.0" 384 | js-yaml "^3.13.1" 385 | json-stable-stringify-without-jsonify "^1.0.1" 386 | levn "^0.3.0" 387 | lodash "^4.17.14" 388 | minimatch "^3.0.4" 389 | mkdirp "^0.5.1" 390 | natural-compare "^1.4.0" 391 | optionator "^0.8.3" 392 | progress "^2.0.0" 393 | regexpp "^2.0.1" 394 | semver "^6.1.2" 395 | strip-ansi "^5.2.0" 396 | strip-json-comments "^3.0.1" 397 | table "^5.2.3" 398 | text-table "^0.2.0" 399 | v8-compile-cache "^2.0.3" 400 | 401 | espree@^6.1.2: 402 | version "6.2.1" 403 | resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" 404 | integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== 405 | dependencies: 406 | acorn "^7.1.1" 407 | acorn-jsx "^5.2.0" 408 | eslint-visitor-keys "^1.1.0" 409 | 410 | esprima@^4.0.0: 411 | version "4.0.1" 412 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" 413 | 414 | esquery@^1.0.1: 415 | version "1.0.1" 416 | resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" 417 | integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA== 418 | dependencies: 419 | estraverse "^4.0.0" 420 | 421 | esrecurse@^4.3.0: 422 | version "4.3.0" 423 | resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" 424 | integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== 425 | dependencies: 426 | estraverse "^5.2.0" 427 | 428 | estraverse@^4.0.0, estraverse@^4.1.1: 429 | version "4.2.0" 430 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" 431 | 432 | estraverse@^5.2.0: 433 | version "5.2.0" 434 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" 435 | integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== 436 | 437 | esutils@^2.0.2: 438 | version "2.0.2" 439 | resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" 440 | 441 | exec-sh@^0.3.2: 442 | version "0.3.2" 443 | resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.2.tgz#6738de2eb7c8e671d0366aea0b0db8c6f7d7391b" 444 | integrity sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg== 445 | 446 | exec-sh@^0.3.4: 447 | version "0.3.6" 448 | resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc" 449 | integrity sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w== 450 | 451 | execa@^4.0.0: 452 | version "4.1.0" 453 | resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" 454 | integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== 455 | dependencies: 456 | cross-spawn "^7.0.0" 457 | get-stream "^5.0.0" 458 | human-signals "^1.1.1" 459 | is-stream "^2.0.0" 460 | merge-stream "^2.0.0" 461 | npm-run-path "^4.0.0" 462 | onetime "^5.1.0" 463 | signal-exit "^3.0.2" 464 | strip-final-newline "^2.0.0" 465 | 466 | external-editor@^3.0.3: 467 | version "3.0.3" 468 | resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.3.tgz#5866db29a97826dbe4bf3afd24070ead9ea43a27" 469 | integrity sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA== 470 | dependencies: 471 | chardet "^0.7.0" 472 | iconv-lite "^0.4.24" 473 | tmp "^0.0.33" 474 | 475 | fast-deep-equal@^2.0.1: 476 | version "2.0.1" 477 | resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" 478 | integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= 479 | 480 | fast-deep-equal@^3.1.1: 481 | version "3.1.3" 482 | resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" 483 | integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== 484 | 485 | fast-json-stable-stringify@^2.0.0: 486 | version "2.0.0" 487 | resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" 488 | integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= 489 | 490 | fast-levenshtein@~2.0.6: 491 | version "2.0.6" 492 | resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" 493 | integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= 494 | 495 | fb-watchman@^2.0.1: 496 | version "2.0.1" 497 | resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" 498 | integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== 499 | dependencies: 500 | bser "2.1.1" 501 | 502 | figures@^3.0.0: 503 | version "3.2.0" 504 | resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" 505 | integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== 506 | dependencies: 507 | escape-string-regexp "^1.0.5" 508 | 509 | file-entry-cache@^5.0.1: 510 | version "5.0.1" 511 | resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" 512 | integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== 513 | dependencies: 514 | flat-cache "^2.0.1" 515 | 516 | fill-range@^7.0.1: 517 | version "7.0.1" 518 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 519 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 520 | dependencies: 521 | to-regex-range "^5.0.1" 522 | 523 | find-up@3.0.0, find-up@^3.0.0: 524 | version "3.0.0" 525 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" 526 | integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== 527 | dependencies: 528 | locate-path "^3.0.0" 529 | 530 | flat-cache@^2.0.1: 531 | version "2.0.1" 532 | resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" 533 | integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== 534 | dependencies: 535 | flatted "^2.0.0" 536 | rimraf "2.6.3" 537 | write "1.0.3" 538 | 539 | flat@^4.1.0: 540 | version "4.1.0" 541 | resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2" 542 | integrity sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw== 543 | dependencies: 544 | is-buffer "~2.0.3" 545 | 546 | flatted@^2.0.0: 547 | version "2.0.0" 548 | resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916" 549 | integrity sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg== 550 | 551 | fs.realpath@^1.0.0: 552 | version "1.0.0" 553 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 554 | 555 | function-bind@^1.1.1: 556 | version "1.1.1" 557 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 558 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 559 | 560 | functional-red-black-tree@^1.0.1: 561 | version "1.0.1" 562 | resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" 563 | integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= 564 | 565 | get-caller-file@^2.0.1: 566 | version "2.0.5" 567 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" 568 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== 569 | 570 | get-stream@^5.0.0: 571 | version "5.2.0" 572 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" 573 | integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== 574 | dependencies: 575 | pump "^3.0.0" 576 | 577 | glob-parent@^5.0.0: 578 | version "5.1.2" 579 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 580 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 581 | dependencies: 582 | is-glob "^4.0.1" 583 | 584 | glob@7.1.3, glob@^7.1.3: 585 | version "7.1.3" 586 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" 587 | integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== 588 | dependencies: 589 | fs.realpath "^1.0.0" 590 | inflight "^1.0.4" 591 | inherits "2" 592 | minimatch "^3.0.4" 593 | once "^1.3.0" 594 | path-is-absolute "^1.0.0" 595 | 596 | globals@^12.1.0: 597 | version "12.4.0" 598 | resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" 599 | integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== 600 | dependencies: 601 | type-fest "^0.8.1" 602 | 603 | growl@1.10.5: 604 | version "1.10.5" 605 | resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" 606 | integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== 607 | 608 | has-flag@^3.0.0: 609 | version "3.0.0" 610 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 611 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 612 | 613 | has-flag@^4.0.0: 614 | version "4.0.0" 615 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" 616 | integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== 617 | 618 | has-symbols@^1.0.0: 619 | version "1.0.0" 620 | resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" 621 | integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= 622 | 623 | has@^1.0.1, has@^1.0.3: 624 | version "1.0.3" 625 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 626 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 627 | dependencies: 628 | function-bind "^1.1.1" 629 | 630 | he@1.2.0: 631 | version "1.2.0" 632 | resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" 633 | integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== 634 | 635 | human-signals@^1.1.1: 636 | version "1.1.1" 637 | resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" 638 | integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== 639 | 640 | iconv-lite@^0.4.24: 641 | version "0.4.24" 642 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 643 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== 644 | dependencies: 645 | safer-buffer ">= 2.1.2 < 3" 646 | 647 | ignore@^4.0.6: 648 | version "4.0.6" 649 | resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" 650 | integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== 651 | 652 | import-fresh@^3.0.0: 653 | version "3.0.0" 654 | resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.0.0.tgz#a3d897f420cab0e671236897f75bc14b4885c390" 655 | integrity sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ== 656 | dependencies: 657 | parent-module "^1.0.0" 658 | resolve-from "^4.0.0" 659 | 660 | imurmurhash@^0.1.4: 661 | version "0.1.4" 662 | resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" 663 | 664 | inflight@^1.0.4: 665 | version "1.0.6" 666 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 667 | dependencies: 668 | once "^1.3.0" 669 | wrappy "1" 670 | 671 | inherits@2: 672 | version "2.0.3" 673 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 674 | 675 | inquirer@^7.0.0: 676 | version "7.3.3" 677 | resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" 678 | integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== 679 | dependencies: 680 | ansi-escapes "^4.2.1" 681 | chalk "^4.1.0" 682 | cli-cursor "^3.1.0" 683 | cli-width "^3.0.0" 684 | external-editor "^3.0.3" 685 | figures "^3.0.0" 686 | lodash "^4.17.19" 687 | mute-stream "0.0.8" 688 | run-async "^2.4.0" 689 | rxjs "^6.6.0" 690 | string-width "^4.1.0" 691 | strip-ansi "^6.0.0" 692 | through "^2.3.6" 693 | 694 | is-buffer@~2.0.3: 695 | version "2.0.3" 696 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725" 697 | integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw== 698 | 699 | is-callable@^1.1.4: 700 | version "1.1.4" 701 | resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" 702 | integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== 703 | 704 | is-date-object@^1.0.1: 705 | version "1.0.1" 706 | resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" 707 | integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= 708 | 709 | is-extglob@^2.1.1: 710 | version "2.1.1" 711 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 712 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= 713 | 714 | is-fullwidth-code-point@^2.0.0: 715 | version "2.0.0" 716 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" 717 | 718 | is-fullwidth-code-point@^3.0.0: 719 | version "3.0.0" 720 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" 721 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== 722 | 723 | is-glob@^4.0.0, is-glob@^4.0.1: 724 | version "4.0.1" 725 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" 726 | integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== 727 | dependencies: 728 | is-extglob "^2.1.1" 729 | 730 | is-number@^7.0.0: 731 | version "7.0.0" 732 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 733 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 734 | 735 | is-regex@^1.0.4: 736 | version "1.0.4" 737 | resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" 738 | integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= 739 | dependencies: 740 | has "^1.0.1" 741 | 742 | is-stream@^2.0.0: 743 | version "2.0.0" 744 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" 745 | integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== 746 | 747 | is-symbol@^1.0.2: 748 | version "1.0.2" 749 | resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" 750 | integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw== 751 | dependencies: 752 | has-symbols "^1.0.0" 753 | 754 | isexe@^2.0.0: 755 | version "2.0.0" 756 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 757 | 758 | js-tokens@^4.0.0: 759 | version "4.0.0" 760 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" 761 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 762 | 763 | js-yaml@3.13.1: 764 | version "3.13.1" 765 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" 766 | integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== 767 | dependencies: 768 | argparse "^1.0.7" 769 | esprima "^4.0.0" 770 | 771 | js-yaml@^3.13.1: 772 | version "3.14.1" 773 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" 774 | integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== 775 | dependencies: 776 | argparse "^1.0.7" 777 | esprima "^4.0.0" 778 | 779 | json-schema-traverse@^0.4.1: 780 | version "0.4.1" 781 | resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" 782 | integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== 783 | 784 | json-stable-stringify-without-jsonify@^1.0.1: 785 | version "1.0.1" 786 | resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" 787 | integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= 788 | 789 | levn@^0.3.0, levn@~0.3.0: 790 | version "0.3.0" 791 | resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" 792 | dependencies: 793 | prelude-ls "~1.1.2" 794 | type-check "~0.3.2" 795 | 796 | locate-path@^3.0.0: 797 | version "3.0.0" 798 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" 799 | integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== 800 | dependencies: 801 | p-locate "^3.0.0" 802 | path-exists "^3.0.0" 803 | 804 | lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19: 805 | version "4.17.21" 806 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" 807 | integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== 808 | 809 | log-symbols@2.2.0: 810 | version "2.2.0" 811 | resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" 812 | integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== 813 | dependencies: 814 | chalk "^2.0.1" 815 | 816 | makeerror@1.0.x: 817 | version "1.0.11" 818 | resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" 819 | dependencies: 820 | tmpl "1.0.x" 821 | 822 | merge-stream@^2.0.0: 823 | version "2.0.0" 824 | resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" 825 | integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== 826 | 827 | micromatch@^4.0.2: 828 | version "4.0.4" 829 | resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" 830 | integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== 831 | dependencies: 832 | braces "^3.0.1" 833 | picomatch "^2.2.3" 834 | 835 | mimic-fn@^2.1.0: 836 | version "2.1.0" 837 | resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" 838 | integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== 839 | 840 | minimatch@3.0.4, minimatch@^3.0.4: 841 | version "3.0.4" 842 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 843 | dependencies: 844 | brace-expansion "^1.1.7" 845 | 846 | minimist@0.0.8: 847 | version "0.0.8" 848 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 849 | 850 | minimist@^1.1.1, minimist@^1.2.0: 851 | version "1.2.0" 852 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" 853 | 854 | minimist@^1.2.5: 855 | version "1.2.5" 856 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 857 | integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== 858 | 859 | mkdirp@0.5.4: 860 | version "0.5.4" 861 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" 862 | integrity sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw== 863 | dependencies: 864 | minimist "^1.2.5" 865 | 866 | mkdirp@^0.5.1: 867 | version "0.5.1" 868 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 869 | dependencies: 870 | minimist "0.0.8" 871 | 872 | mocha@^6.2.2: 873 | version "6.2.3" 874 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-6.2.3.tgz#e648432181d8b99393410212664450a4c1e31912" 875 | integrity sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg== 876 | dependencies: 877 | ansi-colors "3.2.3" 878 | browser-stdout "1.3.1" 879 | debug "3.2.6" 880 | diff "3.5.0" 881 | escape-string-regexp "1.0.5" 882 | find-up "3.0.0" 883 | glob "7.1.3" 884 | growl "1.10.5" 885 | he "1.2.0" 886 | js-yaml "3.13.1" 887 | log-symbols "2.2.0" 888 | minimatch "3.0.4" 889 | mkdirp "0.5.4" 890 | ms "2.1.1" 891 | node-environment-flags "1.0.5" 892 | object.assign "4.1.0" 893 | strip-json-comments "2.0.1" 894 | supports-color "6.0.0" 895 | which "1.3.1" 896 | wide-align "1.1.3" 897 | yargs "13.3.2" 898 | yargs-parser "13.1.2" 899 | yargs-unparser "1.6.0" 900 | 901 | ms@2.1.1, ms@^2.1.1: 902 | version "2.1.1" 903 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" 904 | integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== 905 | 906 | mute-stream@0.0.8: 907 | version "0.0.8" 908 | resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" 909 | integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== 910 | 911 | natural-compare@^1.4.0: 912 | version "1.4.0" 913 | resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" 914 | 915 | nice-try@^1.0.4: 916 | version "1.0.5" 917 | resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" 918 | 919 | node-environment-flags@1.0.5: 920 | version "1.0.5" 921 | resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.5.tgz#fa930275f5bf5dae188d6192b24b4c8bbac3d76a" 922 | integrity sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ== 923 | dependencies: 924 | object.getownpropertydescriptors "^2.0.3" 925 | semver "^5.7.0" 926 | 927 | node-int64@^0.4.0: 928 | version "0.4.0" 929 | resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" 930 | 931 | normalize-path@^3.0.0: 932 | version "3.0.0" 933 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 934 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== 935 | 936 | npm-run-path@^4.0.0: 937 | version "4.0.1" 938 | resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" 939 | integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== 940 | dependencies: 941 | path-key "^3.0.0" 942 | 943 | object-keys@^1.0.11, object-keys@^1.0.12: 944 | version "1.1.0" 945 | resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.0.tgz#11bd22348dd2e096a045ab06f6c85bcc340fa032" 946 | integrity sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg== 947 | 948 | object.assign@4.1.0: 949 | version "4.1.0" 950 | resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" 951 | integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== 952 | dependencies: 953 | define-properties "^1.1.2" 954 | function-bind "^1.1.1" 955 | has-symbols "^1.0.0" 956 | object-keys "^1.0.11" 957 | 958 | object.getownpropertydescriptors@^2.0.3: 959 | version "2.0.3" 960 | resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" 961 | integrity sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY= 962 | dependencies: 963 | define-properties "^1.1.2" 964 | es-abstract "^1.5.1" 965 | 966 | once@^1.3.0, once@^1.3.1, once@^1.4.0: 967 | version "1.4.0" 968 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 969 | dependencies: 970 | wrappy "1" 971 | 972 | onetime@^5.1.0: 973 | version "5.1.2" 974 | resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" 975 | integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== 976 | dependencies: 977 | mimic-fn "^2.1.0" 978 | 979 | optionator@^0.8.3: 980 | version "0.8.3" 981 | resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" 982 | integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== 983 | dependencies: 984 | deep-is "~0.1.3" 985 | fast-levenshtein "~2.0.6" 986 | levn "~0.3.0" 987 | prelude-ls "~1.1.2" 988 | type-check "~0.3.2" 989 | word-wrap "~1.2.3" 990 | 991 | os-tmpdir@~1.0.2: 992 | version "1.0.2" 993 | resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" 994 | integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= 995 | 996 | p-limit@^2.0.0: 997 | version "2.2.0" 998 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2" 999 | integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ== 1000 | dependencies: 1001 | p-try "^2.0.0" 1002 | 1003 | p-locate@^3.0.0: 1004 | version "3.0.0" 1005 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" 1006 | integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== 1007 | dependencies: 1008 | p-limit "^2.0.0" 1009 | 1010 | p-try@^2.0.0: 1011 | version "2.0.0" 1012 | resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1" 1013 | integrity sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ== 1014 | 1015 | parent-module@^1.0.0: 1016 | version "1.0.0" 1017 | resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.0.tgz#df250bdc5391f4a085fb589dad761f5ad6b865b5" 1018 | integrity sha512-8Mf5juOMmiE4FcmzYc4IaiS9L3+9paz2KOiXzkRviCP6aDmN49Hz6EMWz0lGNp9pX80GvvAuLADtyGfW/Em3TA== 1019 | dependencies: 1020 | callsites "^3.0.0" 1021 | 1022 | path-exists@^3.0.0: 1023 | version "3.0.0" 1024 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" 1025 | integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= 1026 | 1027 | path-is-absolute@^1.0.0: 1028 | version "1.0.1" 1029 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 1030 | 1031 | path-key@^2.0.1: 1032 | version "2.0.1" 1033 | resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" 1034 | 1035 | path-key@^3.0.0, path-key@^3.1.0: 1036 | version "3.1.1" 1037 | resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" 1038 | integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== 1039 | 1040 | picomatch@^2.0.4, picomatch@^2.2.3: 1041 | version "2.3.0" 1042 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" 1043 | integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== 1044 | 1045 | prelude-ls@~1.1.2: 1046 | version "1.1.2" 1047 | resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" 1048 | 1049 | prettier@^1.19.1: 1050 | version "1.19.1" 1051 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" 1052 | integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== 1053 | 1054 | progress@^2.0.0: 1055 | version "2.0.3" 1056 | resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" 1057 | integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== 1058 | 1059 | pump@^3.0.0: 1060 | version "3.0.0" 1061 | resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" 1062 | dependencies: 1063 | end-of-stream "^1.1.0" 1064 | once "^1.3.1" 1065 | 1066 | punycode@^2.1.0: 1067 | version "2.1.1" 1068 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" 1069 | integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== 1070 | 1071 | regexpp@^2.0.1: 1072 | version "2.0.1" 1073 | resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" 1074 | integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== 1075 | 1076 | require-directory@^2.1.1: 1077 | version "2.1.1" 1078 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 1079 | integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= 1080 | 1081 | require-main-filename@^2.0.0: 1082 | version "2.0.0" 1083 | resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" 1084 | integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== 1085 | 1086 | resolve-from@^4.0.0: 1087 | version "4.0.0" 1088 | resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" 1089 | integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== 1090 | 1091 | restore-cursor@^3.1.0: 1092 | version "3.1.0" 1093 | resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" 1094 | integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== 1095 | dependencies: 1096 | onetime "^5.1.0" 1097 | signal-exit "^3.0.2" 1098 | 1099 | rimraf@2.6.3: 1100 | version "2.6.3" 1101 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" 1102 | integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== 1103 | dependencies: 1104 | glob "^7.1.3" 1105 | 1106 | rimraf@^2.6.3: 1107 | version "2.7.1" 1108 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" 1109 | integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== 1110 | dependencies: 1111 | glob "^7.1.3" 1112 | 1113 | rimraf@~3.0.0: 1114 | version "3.0.2" 1115 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" 1116 | integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== 1117 | dependencies: 1118 | glob "^7.1.3" 1119 | 1120 | rsvp@^4.8.4: 1121 | version "4.8.4" 1122 | resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.4.tgz#b50e6b34583f3dd89329a2f23a8a2be072845911" 1123 | integrity sha512-6FomvYPfs+Jy9TfXmBpBuMWNH94SgCsZmJKcanySzgNNP6LjWxBvyLTa9KaMfDDM5oxRfrKDB0r/qeRsLwnBfA== 1124 | 1125 | run-async@^2.4.0: 1126 | version "2.4.1" 1127 | resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" 1128 | integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== 1129 | 1130 | rxjs@^6.6.0: 1131 | version "6.6.7" 1132 | resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" 1133 | integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== 1134 | dependencies: 1135 | tslib "^1.9.0" 1136 | 1137 | "safer-buffer@>= 2.1.2 < 3": 1138 | version "2.1.2" 1139 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 1140 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 1141 | 1142 | semver@^5.5.0: 1143 | version "5.5.1" 1144 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" 1145 | 1146 | semver@^5.7.0: 1147 | version "5.7.1" 1148 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 1149 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 1150 | 1151 | semver@^6.1.2: 1152 | version "6.3.0" 1153 | resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" 1154 | integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== 1155 | 1156 | set-blocking@^2.0.0: 1157 | version "2.0.0" 1158 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" 1159 | integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= 1160 | 1161 | shebang-command@^1.2.0: 1162 | version "1.2.0" 1163 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" 1164 | dependencies: 1165 | shebang-regex "^1.0.0" 1166 | 1167 | shebang-command@^2.0.0: 1168 | version "2.0.0" 1169 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" 1170 | integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== 1171 | dependencies: 1172 | shebang-regex "^3.0.0" 1173 | 1174 | shebang-regex@^1.0.0: 1175 | version "1.0.0" 1176 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" 1177 | 1178 | shebang-regex@^3.0.0: 1179 | version "3.0.0" 1180 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" 1181 | integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== 1182 | 1183 | signal-exit@^3.0.2: 1184 | version "3.0.2" 1185 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" 1186 | 1187 | slice-ansi@^2.1.0: 1188 | version "2.1.0" 1189 | resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" 1190 | integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== 1191 | dependencies: 1192 | ansi-styles "^3.2.0" 1193 | astral-regex "^1.0.0" 1194 | is-fullwidth-code-point "^2.0.0" 1195 | 1196 | sprintf-js@~1.0.2: 1197 | version "1.0.3" 1198 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 1199 | 1200 | "string-width@^1.0.2 || 2": 1201 | version "2.1.1" 1202 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" 1203 | integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== 1204 | dependencies: 1205 | is-fullwidth-code-point "^2.0.0" 1206 | strip-ansi "^4.0.0" 1207 | 1208 | string-width@^3.0.0, string-width@^3.1.0: 1209 | version "3.1.0" 1210 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" 1211 | integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== 1212 | dependencies: 1213 | emoji-regex "^7.0.1" 1214 | is-fullwidth-code-point "^2.0.0" 1215 | strip-ansi "^5.1.0" 1216 | 1217 | string-width@^4.1.0: 1218 | version "4.2.2" 1219 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" 1220 | integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== 1221 | dependencies: 1222 | emoji-regex "^8.0.0" 1223 | is-fullwidth-code-point "^3.0.0" 1224 | strip-ansi "^6.0.0" 1225 | 1226 | strip-ansi@^4.0.0: 1227 | version "4.0.0" 1228 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" 1229 | dependencies: 1230 | ansi-regex "^3.0.0" 1231 | 1232 | strip-ansi@^5.0.0, strip-ansi@^5.1.0: 1233 | version "5.1.0" 1234 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.1.0.tgz#55aaa54e33b4c0649a7338a43437b1887d153ec4" 1235 | integrity sha512-TjxrkPONqO2Z8QDCpeE2j6n0M6EwxzyDgzEeGp+FbdvaJAt//ClYi6W5my+3ROlC/hZX2KACUwDfK49Ka5eDvg== 1236 | dependencies: 1237 | ansi-regex "^4.1.0" 1238 | 1239 | strip-ansi@^5.2.0: 1240 | version "5.2.0" 1241 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" 1242 | integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== 1243 | dependencies: 1244 | ansi-regex "^4.1.0" 1245 | 1246 | strip-ansi@^6.0.0: 1247 | version "6.0.0" 1248 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" 1249 | integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== 1250 | dependencies: 1251 | ansi-regex "^5.0.0" 1252 | 1253 | strip-final-newline@^2.0.0: 1254 | version "2.0.0" 1255 | resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" 1256 | integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== 1257 | 1258 | strip-json-comments@2.0.1: 1259 | version "2.0.1" 1260 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 1261 | integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= 1262 | 1263 | strip-json-comments@^3.0.1: 1264 | version "3.1.1" 1265 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" 1266 | integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== 1267 | 1268 | supports-color@6.0.0: 1269 | version "6.0.0" 1270 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a" 1271 | integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg== 1272 | dependencies: 1273 | has-flag "^3.0.0" 1274 | 1275 | supports-color@^5.3.0: 1276 | version "5.5.0" 1277 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 1278 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 1279 | dependencies: 1280 | has-flag "^3.0.0" 1281 | 1282 | supports-color@^7.1.0: 1283 | version "7.2.0" 1284 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" 1285 | integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== 1286 | dependencies: 1287 | has-flag "^4.0.0" 1288 | 1289 | table@^5.2.3: 1290 | version "5.2.3" 1291 | resolved "https://registry.yarnpkg.com/table/-/table-5.2.3.tgz#cde0cc6eb06751c009efab27e8c820ca5b67b7f2" 1292 | integrity sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ== 1293 | dependencies: 1294 | ajv "^6.9.1" 1295 | lodash "^4.17.11" 1296 | slice-ansi "^2.1.0" 1297 | string-width "^3.0.0" 1298 | 1299 | text-table@^0.2.0: 1300 | version "0.2.0" 1301 | resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" 1302 | integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= 1303 | 1304 | through@^2.3.6: 1305 | version "2.3.8" 1306 | resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" 1307 | 1308 | tmp@0.1.0: 1309 | version "0.1.0" 1310 | resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877" 1311 | integrity sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw== 1312 | dependencies: 1313 | rimraf "^2.6.3" 1314 | 1315 | tmp@^0.0.33: 1316 | version "0.0.33" 1317 | resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" 1318 | integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== 1319 | dependencies: 1320 | os-tmpdir "~1.0.2" 1321 | 1322 | tmpl@1.0.x: 1323 | version "1.0.4" 1324 | resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" 1325 | 1326 | to-regex-range@^5.0.1: 1327 | version "5.0.1" 1328 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 1329 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 1330 | dependencies: 1331 | is-number "^7.0.0" 1332 | 1333 | tslib@^1.9.0: 1334 | version "1.9.3" 1335 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" 1336 | integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== 1337 | 1338 | type-check@~0.3.2: 1339 | version "0.3.2" 1340 | resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" 1341 | dependencies: 1342 | prelude-ls "~1.1.2" 1343 | 1344 | type-fest@^0.21.3: 1345 | version "0.21.3" 1346 | resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" 1347 | integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== 1348 | 1349 | type-fest@^0.8.1: 1350 | version "0.8.1" 1351 | resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" 1352 | integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== 1353 | 1354 | uri-js@^4.2.2: 1355 | version "4.2.2" 1356 | resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" 1357 | integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== 1358 | dependencies: 1359 | punycode "^2.1.0" 1360 | 1361 | v8-compile-cache@^2.0.3: 1362 | version "2.3.0" 1363 | resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" 1364 | integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== 1365 | 1366 | walker@~1.0.5: 1367 | version "1.0.7" 1368 | resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" 1369 | dependencies: 1370 | makeerror "1.0.x" 1371 | 1372 | which-module@^2.0.0: 1373 | version "2.0.0" 1374 | resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" 1375 | integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= 1376 | 1377 | which@1.3.1, which@^1.2.9: 1378 | version "1.3.1" 1379 | resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" 1380 | dependencies: 1381 | isexe "^2.0.0" 1382 | 1383 | which@^2.0.1: 1384 | version "2.0.2" 1385 | resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" 1386 | integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== 1387 | dependencies: 1388 | isexe "^2.0.0" 1389 | 1390 | wide-align@1.1.3: 1391 | version "1.1.3" 1392 | resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" 1393 | integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== 1394 | dependencies: 1395 | string-width "^1.0.2 || 2" 1396 | 1397 | word-wrap@~1.2.3: 1398 | version "1.2.3" 1399 | resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" 1400 | integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== 1401 | 1402 | wrap-ansi@^5.1.0: 1403 | version "5.1.0" 1404 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" 1405 | integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== 1406 | dependencies: 1407 | ansi-styles "^3.2.0" 1408 | string-width "^3.0.0" 1409 | strip-ansi "^5.0.0" 1410 | 1411 | wrappy@1: 1412 | version "1.0.2" 1413 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 1414 | 1415 | write@1.0.3: 1416 | version "1.0.3" 1417 | resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" 1418 | integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== 1419 | dependencies: 1420 | mkdirp "^0.5.1" 1421 | 1422 | y18n@^4.0.0: 1423 | version "4.0.3" 1424 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" 1425 | integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== 1426 | 1427 | yargs-parser@13.1.2, yargs-parser@^13.1.2: 1428 | version "13.1.2" 1429 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" 1430 | integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== 1431 | dependencies: 1432 | camelcase "^5.0.0" 1433 | decamelize "^1.2.0" 1434 | 1435 | yargs-unparser@1.6.0: 1436 | version "1.6.0" 1437 | resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" 1438 | integrity sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw== 1439 | dependencies: 1440 | flat "^4.1.0" 1441 | lodash "^4.17.15" 1442 | yargs "^13.3.0" 1443 | 1444 | yargs@13.3.2, yargs@^13.3.0: 1445 | version "13.3.2" 1446 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" 1447 | integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== 1448 | dependencies: 1449 | cliui "^5.0.0" 1450 | find-up "^3.0.0" 1451 | get-caller-file "^2.0.1" 1452 | require-directory "^2.1.1" 1453 | require-main-filename "^2.0.0" 1454 | set-blocking "^2.0.0" 1455 | string-width "^3.0.0" 1456 | which-module "^2.0.0" 1457 | y18n "^4.0.0" 1458 | yargs-parser "^13.1.2" 1459 | --------------------------------------------------------------------------------