├── .eslintignore ├── .eslintrc ├── .gitignore ├── README.md ├── index.js ├── package.json ├── src ├── AddressCache.js ├── Lookup.js ├── ResolveTask.js └── TasksManager.js ├── tests ├── .eslintrc ├── Functional │ ├── Lookup │ │ └── _innerResolve.js │ └── lookup.js ├── Unit │ ├── AddressCache │ │ ├── _isExpired.js │ │ ├── find.js │ │ └── set.js │ ├── Lookup │ │ ├── _innerResolve.js │ │ ├── _makeNotFoundError.js │ │ ├── _resolve.js │ │ ├── _resolveBoth.js │ │ ├── _resolveTaskBuilder.js │ │ └── run.js │ ├── ResolveTask │ │ ├── _resolved.js │ │ ├── addResolvedCallback.js │ │ ├── constructor.js │ │ └── run.js │ └── TasksManager │ │ ├── add.js │ │ ├── done.js │ │ └── find.js ├── addresses.js └── mocha.opts └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "parserOptions": { 8 | "ecmaVersion": 8, 9 | "sourceType": "module" 10 | }, 11 | "rules": { 12 | "no-console": 0, 13 | "no-case-declarations": 0, 14 | "new-cap": 0, 15 | "no-unused-vars": [ 16 | "error", 17 | { 18 | "args": "none" 19 | } 20 | ], 21 | "indent": [ 22 | "error", 23 | 4, 24 | { 25 | "SwitchCase": 1, 26 | "ObjectExpression": 1, 27 | "CallExpression": { 28 | "arguments": 1 29 | } 30 | } 31 | ], 32 | "linebreak-style": [ 33 | "error", 34 | "unix" 35 | ], 36 | "quotes": [ 37 | "error", 38 | "single", 39 | { 40 | "avoidEscape": true 41 | } 42 | ], 43 | "semi": [ 44 | "error", 45 | "always" 46 | ], 47 | "arrow-spacing": [ 48 | "error" 49 | ], 50 | "curly": [ 51 | "error" 52 | ], 53 | "no-use-before-define": [ 54 | "off" 55 | ], 56 | "no-caller": [ 57 | "error" 58 | ], 59 | "no-trailing-spaces": [ 60 | "error" 61 | ], 62 | "guard-for-in": [ 63 | "off" 64 | ], 65 | "eqeqeq": [ 66 | "error" 67 | ], 68 | "no-var": [ 69 | "error" 70 | ], 71 | "no-bitwise": [ 72 | "error" 73 | ], 74 | "camelcase": [ 75 | "error", 76 | { 77 | "properties": "never" 78 | } 79 | ], 80 | "no-proto": [ 81 | "error" 82 | ], 83 | "no-new-wrappers": [ 84 | "error" 85 | ], 86 | "space-before-function-paren": [ 87 | "error", 88 | { 89 | "anonymous": "always", 90 | "named": "never", 91 | "asyncArrow": "always" 92 | } 93 | ], 94 | "func-call-spacing": [ 95 | "error", 96 | "never" 97 | ], 98 | "array-bracket-spacing": [ 99 | "error" 100 | ], 101 | "space-in-parens": [ 102 | "error" 103 | ], 104 | "quote-props": [ 105 | "error", 106 | "as-needed", 107 | { 108 | "keywords": false, 109 | "unnecessary": false 110 | } 111 | ], 112 | "space-unary-ops": [ 113 | "error" 114 | ], 115 | "space-infix-ops": [ 116 | "error" 117 | ], 118 | "comma-spacing": [ 119 | "error" 120 | ], 121 | "yoda": [ 122 | "error", 123 | "never", 124 | { 125 | "exceptRange": true 126 | } 127 | ], 128 | "no-with": [ 129 | "error" 130 | ], 131 | "brace-style": [ 132 | "error", 133 | "1tbs" 134 | ], 135 | "no-multi-str": [ 136 | "error" 137 | ], 138 | "no-multi-spaces": [ 139 | "error", 140 | { 141 | "exceptions": { 142 | "VariableDeclarator": true, 143 | "AssignmentExpression": true 144 | } 145 | } 146 | ], 147 | "one-var": [ 148 | "error", 149 | "never" 150 | ], 151 | "semi-spacing": [ 152 | "error" 153 | ], 154 | "space-before-blocks": [ 155 | "error" 156 | ], 157 | "wrap-iife": [ 158 | "error" 159 | ], 160 | "comma-style": [ 161 | "error" 162 | ], 163 | "eol-last": [ 164 | "error" 165 | ], 166 | "dot-notation": [ 167 | "error" 168 | ], 169 | "max-len": [ 170 | "error", 171 | 120 172 | ], 173 | "spaced-comment": [ 174 | "error" 175 | ] 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /coverage 3 | .idea 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # lookup-dns-cache - DNS cache to replace NodeJS `dns.lookup` standard method 3 | 4 | ## Super simple to use 5 | 6 | ```js 7 | const request = require('request'); 8 | const {lookup} = require('lookup-dns-cache'); 9 | 10 | // With "request" module 11 | 12 | request({ 13 | url: 'http://google.com', 14 | method: 'GET', 15 | lookup: lookup 16 | }, (error, response, body) => { 17 | // ... 18 | }); 19 | 20 | // Direct usage 21 | 22 | lookup('google.com', {}, (error, address, family) => { 23 | // ... 24 | }); 25 | ``` 26 | 27 | ## Table of contents 28 | 29 | - [Motivation](#motivation) 30 | - [How to use](#howtouse) 31 | - [Inner implementation](#implementation) 32 | - [Examples](#examples) 33 | 34 | --- 35 | 36 | ## Motivation 37 | 38 | The main idea behind this package is eliminate NodeJS event loop usage when you do network request. 39 | See [NodeJS DNS implementation](https://nodejs.org/api/dns.html#dns_implementation_considerations) to understand the problem with `dns.lookup`. 40 | 41 | 42 | [back to top](#table-of-contents) 43 | 44 | --- 45 | 46 | ## How to use 47 | 48 | This module supports almost the same params as `dns.lookup` does. Concretely, you can pass `options` object as a second param, and 49 | set: 50 | - `family` to `4` or `6` 51 | - `all` flag to `true`/`false` if you want/don't want get all IP addresses at once. 52 | 53 | Because this implementation does not use `getaddrinfo` method, the `hints` param is not supported. 54 | 55 | The `verbatim` param is not supported for now. If you will not specify any family you will get IPv4 addresses first and IPv6 addresses second. 56 | 57 | The `callback` function works the same way as a standard method. 58 | 59 | The `error` object would have all fields the standard implementation's error object has. 60 | 61 | NodeJS `dns.lookup`: 62 | ```javascript 63 | > const dns = require('dns'); 64 | > dns.lookup('host-doesnot-support-ipv6', {family: 6}, console.log) 65 | 66 | > { Error: getaddrinfo ENOTFOUND vss-cc-ha.hwtool.net 67 | at errnoException (dns.js:55:10) 68 | at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:97:26) 69 | code: 'ENOTFOUND', 70 | errno: 'ENOTFOUND', 71 | syscall: 'getaddrinfo', 72 | hostname: 'vss-cc-ha.hwtool.net' } 73 | 74 | ``` 75 | 76 | `lookup-dns-cache` 77 | ```javascript 78 | > const {lookup} = require('lookup-dns-cache'); 79 | > lookup('host-doesnot-support-ipv6', {family: 6}, console.log) 80 | 81 | > { Error: queryAaaa ENOTFOUND vss-cc-ha.hwtool.net 82 | at makeNotFoundError (/path/lookup-dns-cache/src/Lookup.js:182:19) 83 | at ipv6AddressesTable.resolve (/path/lookup-dns-cache/src/Lookup.js:147:37) 84 | at Immediate.setImmediate [as _onImmediate] (/path/lookup-dns-cache/src/IpAddressesTable.js:70:48) 85 | at runCallback (timers.js:773:18) 86 | at tryOnImmediate (timers.js:734:5) 87 | at processImmediate [as _immediateCallback] (timers.js:711:5) 88 | hostname: 'vss-cc-ha.hwtool.net', 89 | syscall: 'queryAaaa', 90 | code: 'ENOTFOUND', 91 | errno: 'ENOTFOUND' } 92 | ``` 93 | 94 | If you are looking for `IPv4` addresses only, explicitly specify param `{family: 4}`. In that case, you will avoid 95 | spending time on useless searching for `IPv6`. Apply the same technique if you are looking for `IPv6` addresses only. 96 | 97 | Under the hood, `lookup` method has Round-robin algorithm. It means that if particular hostname resolves to several addresses 98 | it will return new address every time you call that function. For example: 99 | ```javascript 100 | // hostname: example.com 101 | // resolves to: [1.2.3.4, 5.6.7.8, 9.10.11.12] 102 | 103 | lookup('example.com', {family: 4}, (error, address, family) => { 104 | // address === "1.2.3.4" 105 | // family === 4 106 | }); 107 | 108 | lookup('example.com', {family: 4}, (error, address, family) => { 109 | // address === "5.6.7.8" 110 | // family === 4 111 | }); 112 | 113 | lookup('example.com', {family: 4}, (error, address, family) => { 114 | // address === "9.10.11.12" 115 | // family === 4 116 | }); 117 | 118 | lookup('example.com', {family: 4}, (error, address, family) => { 119 | // address === "1.2.3.4" 120 | // family === 4 121 | }); 122 | ``` 123 | 124 | [back to top](#table-of-contents) 125 | 126 | --- 127 | ## Implementation 128 | 129 | Under the hood, this package uses `dns.resolve4` and `dns.resolve6` methods with `{ttl: true}` param. 130 | It caches addresses for that particular hostname for DNS TTL time and returns one address if you specified `{all: false}` (default value) 131 | and array of addresses if `{all: true}`. 132 | 133 | If you didn't specify family type (`{family: 4}` or `{family: 6}`) the method searches for addresses of `{family: 4}` and `{family: 6}` in parallel. 134 | After that, if you specified `{all: true}` it returns an array in form `[[...IPv4],[...IPv6]]`, in other case it returns `IPv4` or `IPv6` address. 135 | (`IPv4` has more priority). 136 | 137 | [back to top](#table-of-contents) 138 | 139 | --- 140 | ## Examples 141 | 142 | ```javascript 143 | lookup('hostname', {all: true}, (error, results) => { 144 | // results is an array that contains both IPv4 and IPv6 addresses (Ipv4 first). 145 | // 146 | // error - null 147 | // results - [ 148 | // { address: '1.2.3.4', family: 4 }, 149 | // { address: '5.6.7.8', family: 4 } 150 | // ] 151 | }); 152 | ``` 153 | 154 | ```javascript 155 | lookup('hostname', {all: false}, (error, address, family) => { 156 | // address and family of the first resolved IP (IPv4 or IPv6 if supported). 157 | // error - null 158 | // address - '1.2.3.4' 159 | // family - 4 160 | }); 161 | ``` 162 | 163 | ```javascript 164 | lookup('hostname', {all: false, family: 4}, (error, address, family) => { 165 | // address and family of the first resolved IP (IPv4 only). 166 | }); 167 | ``` 168 | 169 | ```javascript 170 | lookup('hostname', {all: false, family: 6}, (error, address, family) => { 171 | // address and family of the first resolved IP (IPv6 only). 172 | // will return an error if IPv6 is not supported. See NodeJS dns.lookup doc. 173 | }); 174 | ``` 175 | 176 | [back to top](#table-of-contents) 177 | 178 | --- 179 | ## Similar packages 180 | 181 | Yahoo tried to solve this problem in own way https://github.com/yahoo/dnscache. 182 | 183 | The big disadvantages if this package are: 184 | - monkey patching dns module 185 | - does not support DNS TTL 186 | - cache just one IP address and use it for every request (no advantage of round-robin if you have dns resolver that returns several addresses) 187 | 188 | [back to top](#table-of-contents) 189 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Lookup = require('./src/Lookup'); 4 | 5 | const _lookup = new Lookup(); 6 | 7 | const lookup = _lookup.run.bind(_lookup); 8 | 9 | module.exports = { 10 | lookup 11 | }; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lookup-dns-cache", 3 | "version": "2.1.0", 4 | "description": "An implementation to speed up the nodejs `dns.lookup` method by avoiding thread pool and using resolve4/resolve6 with DNS TTL values", 5 | "main": "index.js", 6 | "engines": { 7 | "node": ">=6.0" 8 | }, 9 | "scripts": { 10 | "prettier": "./node_modules/.bin/prettier --single-quote --tab-width 4 --write \"**/*.js\"", 11 | "lint": "./node_modules/.bin/eslint ./", 12 | "test": "yarn run unit-test && yarn run func-test", 13 | "unit-test": "NODE_ENV=test ./node_modules/.bin/mocha --opts tests/mocha.opts -R spec ./tests/Unit/**", 14 | "func-test": "NODE_ENV=test ./node_modules/.bin/mocha --opts tests/mocha.opts --timeout 15000 -R spec ./tests/Functional/**", 15 | "test:coverage": "NODE_ENV=test ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --print both -- --opts tests/mocha.opts -R spec ./tests" 16 | }, 17 | "dependencies": { 18 | "async": "2.6.0", 19 | "lodash": "^4.17.10", 20 | "rr": "0.1.0" 21 | }, 22 | "devDependencies": { 23 | "chai": "4.1.2", 24 | "eslint": "4.15.0", 25 | "istanbul": "0.4.5", 26 | "mocha": "4.1.0", 27 | "prettier": "1.9.2", 28 | "proxyquire": "1.8.0", 29 | "sinon": "4.1.4" 30 | }, 31 | "author": { 32 | "name": "Eduard", 33 | "email": "eduardbcom@gmail.com" 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "https://github.com/eduardbcom/lookup-dns-cache" 38 | }, 39 | "keywords": [ 40 | "dns", 41 | "lookup", 42 | "resolve4", 43 | "resolve6", 44 | "cache", 45 | "ipv4", 46 | "ipv6" 47 | ], 48 | "homepage": "https://github.com/eduardbcom/lookup-dns-cache", 49 | "license": "MIT" 50 | } 51 | -------------------------------------------------------------------------------- /src/AddressCache.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const _ = require('lodash'); 4 | 5 | class AddressCache { 6 | constructor() { 7 | this._cache = new Map(); 8 | } 9 | 10 | /** 11 | * @param {string} key 12 | * @returns {Address[]|undefined} 13 | */ 14 | find(key) { 15 | if (!this._cache.has(key)) { 16 | return; 17 | } 18 | 19 | const addresses = this._cache.get(key); 20 | 21 | if (this._isExpired(addresses)) { 22 | return; 23 | } 24 | 25 | return addresses; 26 | } 27 | 28 | /** 29 | * @param {string} key 30 | * @param {Address[]} addresses 31 | */ 32 | set(key, addresses) { 33 | this._cache.set(key, addresses); 34 | } 35 | 36 | /** 37 | * @param {Address[]} addresses 38 | * @returns {boolean} 39 | * @private 40 | */ 41 | _isExpired(addresses) { 42 | if (_.isEmpty(addresses)) { 43 | return true; 44 | } 45 | 46 | return addresses.some(address => address.expiredTime <= Date.now()); 47 | } 48 | } 49 | 50 | module.exports = AddressCache; 51 | -------------------------------------------------------------------------------- /src/Lookup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const dns = require('dns'); 4 | 5 | const _ = require('lodash'); 6 | const async = require('async'); 7 | const rr = require('rr'); 8 | 9 | const AddressCache = require('./AddressCache'); 10 | const TasksManager = require('./TasksManager'); 11 | const ResolveTask = require('./ResolveTask'); 12 | 13 | class Lookup { 14 | /** 15 | * @returns {number} 16 | */ 17 | static get IPv4() { 18 | return 4; 19 | } 20 | 21 | /** 22 | * @returns {number} 23 | */ 24 | static get IPv6() { 25 | return 6; 26 | } 27 | 28 | /** 29 | * @returns {number} 30 | */ 31 | static get MAX_AMOUNT_OF_RESOLVE_TRIES() { 32 | return 100; 33 | } 34 | 35 | constructor() { 36 | this._addressCache = new AddressCache(); 37 | this._tasksManager = new TasksManager(); 38 | 39 | this._amountOfResolveTries = {}; 40 | } 41 | 42 | /** 43 | * Lookup method that uses IP cache(and DNS TTL) to resolve hostname avoiding system call via thread pool. 44 | * 45 | * @param {string} hostname 46 | * @param {Object} options 47 | * @param {number} options.family 48 | * @param {boolean} options.all 49 | * @param {Function} callback 50 | * @throws {Error} 51 | * @returns {{}|undefined} 52 | */ 53 | run(hostname, options, callback) { 54 | if (_.isFunction(options)) { 55 | callback = options; 56 | options = {}; 57 | } else if (_.isNumber(options)) { 58 | options = { family: options }; 59 | } else if (!_.isPlainObject(options)) { 60 | throw new Error( 61 | 'options must be an object or an ip version number' 62 | ); 63 | } 64 | 65 | if (!_.isFunction(callback)) { 66 | throw new Error('callback param must be a function'); 67 | } 68 | 69 | if (!hostname) { 70 | if (options.all) { 71 | process.nextTick(callback, null, []); 72 | } else { 73 | process.nextTick( 74 | callback, 75 | null, 76 | null, 77 | options.family === Lookup.IPv6 ? Lookup.IPv6 : Lookup.IPv4 78 | ); 79 | } 80 | 81 | return {}; 82 | } 83 | 84 | if (!_.isString(hostname)) { 85 | throw new Error('hostname must be a string'); 86 | } 87 | 88 | switch (options.family) { 89 | case Lookup.IPv4: 90 | case Lookup.IPv6: 91 | return this._resolve(hostname, options, callback); 92 | case undefined: 93 | return this._resolveBoth(hostname, options, callback); 94 | default: 95 | throw new Error( 96 | 'invalid family number, must be one of the {4, 6} or undefined' 97 | ); 98 | } 99 | } 100 | 101 | /** 102 | * @param {string} hostname 103 | * @param {Object} options 104 | * @param {number} options.family 105 | * @param {boolean} options.all 106 | * @param {Function} callback 107 | * @private 108 | */ 109 | _resolve(hostname, options, callback) { 110 | this._amountOfResolveTries[hostname] = 111 | this._amountOfResolveTries[hostname] || 0; 112 | 113 | this._innerResolve(hostname, options.family, (error, records) => { 114 | if (error) { 115 | this._amountOfResolveTries[hostname] = 0; 116 | 117 | if (error.code === dns.NODATA) { 118 | return callback( 119 | this._makeNotFoundError(hostname, error.syscall) 120 | ); 121 | } 122 | 123 | return callback(error); 124 | } 125 | 126 | // Corner case branch. 127 | // 128 | // Intensively calling `lookup` method in parallel can produce situations 129 | // when DNS TTL for particular IP has been exhausted, 130 | // but task queue within NodeJS is full of `resolved` callbacks. 131 | // No way to skip them or update DNS cache before them. 132 | // 133 | // So the work around is return undefined for that callbacks and client code should repeat `lookup` call. 134 | if (!records) { 135 | if ( 136 | this._amountOfResolveTries[hostname] >= 137 | Lookup.MAX_AMOUNT_OF_RESOLVE_TRIES 138 | ) { 139 | this._amountOfResolveTries[hostname] = 0; 140 | 141 | return callback( 142 | new Error( 143 | `Cannot resolve host '${hostname}'. Too deep recursion.` 144 | ) 145 | ); 146 | } 147 | 148 | this._amountOfResolveTries[hostname] += 1; 149 | 150 | return this._resolve(hostname, options, callback); 151 | } 152 | 153 | this._amountOfResolveTries[hostname] = 0; 154 | 155 | if (options.all) { 156 | const result = records.map(record => { 157 | return { 158 | address: record.address, 159 | family: record.family 160 | }; 161 | }); 162 | 163 | return callback(null, result); 164 | } else { 165 | if (_.isEmpty(records)) { 166 | const error = this._makeNotFoundError( 167 | hostname, 168 | options.family === 4 ? 'queryA' : 'queryAaaa' 169 | ); 170 | 171 | return callback(error); 172 | } 173 | 174 | const record = rr(records); 175 | 176 | return callback(null, record.address, record.family); 177 | } 178 | }); 179 | } 180 | 181 | /** 182 | * @param {string} hostname 183 | * @param {number} ipVersion 184 | * @param {Function} callback 185 | * @private 186 | */ 187 | _innerResolve(hostname, ipVersion, callback) { 188 | const key = `${hostname}_${ipVersion}`; 189 | 190 | const cachedAddresses = this._addressCache.find(key); 191 | 192 | if (cachedAddresses) { 193 | setImmediate(() => { 194 | callback(null, cachedAddresses); 195 | }); 196 | 197 | return; 198 | } 199 | 200 | let task = this._tasksManager.find(key); 201 | 202 | if (task) { 203 | task.addResolvedCallback(callback); 204 | } else { 205 | task = new ResolveTask(hostname, ipVersion); 206 | 207 | this._tasksManager.add(key, task); 208 | 209 | task.on('addresses', addresses => { 210 | this._addressCache.set(key, addresses); 211 | }); 212 | 213 | task.on('done', () => { 214 | this._tasksManager.done(key); 215 | }); 216 | 217 | task.addResolvedCallback(callback); 218 | task.run(); 219 | } 220 | } 221 | 222 | /** 223 | * @param {string} hostname 224 | * @param {Object} options 225 | * @param {number} options.family 226 | * @param {boolean} options.all 227 | * @param {Function} callback 228 | * @private 229 | */ 230 | _resolveBoth(hostname, options, callback) { 231 | async.parallel( 232 | [ 233 | this._resolveTaskBuilder( 234 | hostname, 235 | Object.assign({}, options, { family: Lookup.IPv4 }) 236 | ), 237 | this._resolveTaskBuilder( 238 | hostname, 239 | Object.assign({}, options, { family: Lookup.IPv6 }) 240 | ) 241 | ], 242 | (error, records) => { 243 | if (error) { 244 | return callback(error); 245 | } 246 | 247 | const [ipv4records, ipv6records] = records; 248 | 249 | if (options.all) { 250 | const result = ipv4records.concat(ipv6records); 251 | 252 | if (_.isEmpty(result)) { 253 | return callback(this._makeNotFoundError(hostname)); 254 | } 255 | 256 | return callback(null, result); 257 | } else if (!_.isEmpty(ipv4records)) { 258 | return callback(null, ...ipv4records); 259 | } else if (!_.isEmpty(ipv6records)) { 260 | return callback(null, ...ipv6records); 261 | } 262 | 263 | return callback(this._makeNotFoundError(hostname)); 264 | } 265 | ); 266 | } 267 | 268 | /** 269 | * @param {string} hostname 270 | * @param {Object} options 271 | * @returns {Function} 272 | * @private 273 | */ 274 | _resolveTaskBuilder(hostname, options) { 275 | return cb => { 276 | this._resolve(hostname, options, (error, ...records) => { 277 | if (error) { 278 | if (error.code === dns.NOTFOUND) { 279 | return cb(null, []); 280 | } 281 | 282 | return cb(error); 283 | } 284 | 285 | cb(null, ...records); 286 | }); 287 | }; 288 | } 289 | 290 | // noinspection JSMethodCanBeStatic 291 | /** 292 | * @param {string} hostname 293 | * @param {string} [syscall] 294 | * @returns {Error} 295 | */ 296 | _makeNotFoundError(hostname, syscall) { 297 | const errorMessage = 298 | (syscall ? syscall + ' ' : '') + `${dns.NOTFOUND} ${hostname}`; 299 | const error = new Error(errorMessage); 300 | 301 | error.hostname = hostname; 302 | error.code = dns.NOTFOUND; 303 | error.errno = dns.NOTFOUND; 304 | 305 | if (syscall) { 306 | error.syscall = syscall; 307 | } 308 | 309 | return error; 310 | } 311 | } 312 | 313 | module.exports = Lookup; 314 | -------------------------------------------------------------------------------- /src/ResolveTask.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @typedef {Object} Address 5 | * @property {string} address - IPv4 or IPv6 address 6 | * @property {number} ttl - IP DNS TTL 7 | * @property {number} family - IP family 8 | * @property {number} expiredTime - DNS TTL expiration timestamp 9 | */ 10 | 11 | const assert = require('assert'); 12 | const dns = require('dns'); 13 | const { EventEmitter } = require('events'); 14 | 15 | const _ = require('lodash'); 16 | 17 | class ResolveTask extends EventEmitter { 18 | /** 19 | * @returns {number} 20 | */ 21 | static get IPv4() { 22 | return 4; 23 | } 24 | 25 | /** 26 | * @returns {number} 27 | */ 28 | static get IPv6() { 29 | return 6; 30 | } 31 | 32 | /** 33 | * @param {string} hostname 34 | * @param {number} ipVersion 35 | */ 36 | constructor(hostname, ipVersion) { 37 | super(); 38 | 39 | assert( 40 | _.isString(hostname) && hostname.length > 0, 41 | `hostname must be not empty string, '${hostname}' has been provided.` 42 | ); 43 | 44 | assert( 45 | ipVersion === ResolveTask.IPv4 || ipVersion === ResolveTask.IPv6, 46 | `ipVersion must be 4 or 6. '${ipVersion}' has been provided.` 47 | ); 48 | 49 | this._callbacks = []; 50 | this._hostname = hostname; 51 | this._ipVersion = ipVersion; 52 | this._resolver = 53 | ipVersion === ResolveTask.IPv4 ? dns.resolve4 : dns.resolve6; 54 | 55 | this._resolved = this._resolved.bind(this); 56 | } 57 | 58 | /** 59 | * @param {Function} callback 60 | */ 61 | addResolvedCallback(callback) { 62 | this._callbacks.push(callback); 63 | } 64 | 65 | run() { 66 | this._resolver(this._hostname, { ttl: true }, this._resolved); 67 | } 68 | 69 | /** 70 | * @param {Error} error 71 | * @param {Address[]} addresses 72 | * @emits ResolveTask#addresses array of addresses 73 | * @emits ResolveTask#done notification about completion 74 | * @private 75 | */ 76 | _resolved(error, addresses) { 77 | assert(this._callbacks.length > 0, 'callbacks array cannot be empty.'); 78 | 79 | if (!error) { 80 | assert(Array.isArray(addresses), 'addresses must be an array.'); 81 | 82 | addresses.forEach(address => { 83 | address.family = this._ipVersion; 84 | address.expiredTime = Date.now() + address.ttl * 1000; 85 | }); 86 | 87 | this.emit('addresses', addresses); 88 | } 89 | 90 | this._callbacks.forEach(callback => { 91 | setImmediate(() => callback(error, addresses)); 92 | }); 93 | 94 | this._callbacks = []; 95 | 96 | this.emit('done'); 97 | } 98 | } 99 | 100 | module.exports = ResolveTask; 101 | -------------------------------------------------------------------------------- /src/TasksManager.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | class TasksManager { 6 | constructor() { 7 | this._tasks = new Map(); 8 | } 9 | 10 | /** 11 | * @param {string} key 12 | * @returns {ResolveTask} 13 | */ 14 | find(key) { 15 | return this._tasks.get(key); 16 | } 17 | 18 | /** 19 | * @param {string} key 20 | * @param {ResolveTask} task 21 | */ 22 | add(key, task) { 23 | this._tasks.set(key, task); 24 | } 25 | 26 | /** 27 | * @param {string} key 28 | */ 29 | done(key) { 30 | assert( 31 | this._tasks.has(key), 32 | 'task cannot be done, cuz it does not exist.' 33 | ); 34 | 35 | this._tasks.delete(key); 36 | } 37 | } 38 | 39 | module.exports = TasksManager; 40 | -------------------------------------------------------------------------------- /tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true, 5 | "mocha": true 6 | }, 7 | "globals": { 8 | "sinon": true, 9 | "assert": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/Functional/Lookup/_innerResolve.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const async = require('async'); 4 | const sinon = require('sinon'); 5 | const { assert } = require('chai'); 6 | 7 | const Lookup = require('../../../src/Lookup'); 8 | const ResolveTask = require('../../../src/ResolveTask'); 9 | const addresses = require('../../addresses'); 10 | 11 | describe('Func: Lookup::_innerResolve', () => { 12 | it('must has correct behavior for parallel requests with for different hosts', done => { 13 | const ipVersion = 4; 14 | 15 | const lookup = new Lookup(); 16 | 17 | const addressCacheFindSpy = sinon.spy(lookup._addressCache, 'find'); 18 | const addressCacheSetSpy = sinon.spy(lookup._addressCache, 'set'); 19 | 20 | const tasksManagerFindSpy = sinon.spy(lookup._tasksManager, 'find'); 21 | const tasksManagerAddSpy = sinon.spy(lookup._tasksManager, 'add'); 22 | const tasksManagerDoneSpy = sinon.spy(lookup._tasksManager, 'done'); 23 | 24 | const taskAddResolvedCallbackSpy = sinon.spy( 25 | ResolveTask.prototype, 26 | 'addResolvedCallback' 27 | ); 28 | const taskRunSpy = sinon.spy(ResolveTask.prototype, 'run'); 29 | 30 | async.parallel( 31 | [ 32 | cb => lookup._innerResolve(addresses.INET_HOST1, ipVersion, cb), 33 | cb => lookup._innerResolve(addresses.INET_HOST2, ipVersion, cb) 34 | ], 35 | (error, results) => { 36 | assert.ifError(error); 37 | 38 | assert.isTrue(addressCacheFindSpy.calledTwice); 39 | 40 | assert.isTrue(addressCacheSetSpy.calledTwice); 41 | 42 | assert.isTrue(tasksManagerFindSpy.calledTwice); 43 | 44 | assert.isTrue(tasksManagerAddSpy.calledTwice); 45 | 46 | assert.isTrue(tasksManagerDoneSpy.calledTwice); 47 | 48 | assert.isTrue(taskAddResolvedCallbackSpy.calledTwice); 49 | assert.isTrue(taskRunSpy.calledTwice); 50 | 51 | addressCacheFindSpy.restore(); 52 | addressCacheSetSpy.restore(); 53 | 54 | tasksManagerFindSpy.restore(); 55 | tasksManagerAddSpy.restore(); 56 | tasksManagerDoneSpy.restore(); 57 | 58 | taskAddResolvedCallbackSpy.restore(); 59 | taskRunSpy.restore(); 60 | 61 | done(); 62 | } 63 | ); 64 | }); 65 | 66 | it('must has correct behavior for parallel requests with the same hostname', done => { 67 | const ipVersion = 4; 68 | 69 | const lookup = new Lookup(); 70 | 71 | const addressCacheFindSpy = sinon.spy(lookup._addressCache, 'find'); 72 | const addressCacheSetSpy = sinon.spy(lookup._addressCache, 'set'); 73 | 74 | const tasksManagerFindSpy = sinon.spy(lookup._tasksManager, 'find'); 75 | const tasksManagerAddSpy = sinon.spy(lookup._tasksManager, 'add'); 76 | const tasksManagerDoneSpy = sinon.spy(lookup._tasksManager, 'done'); 77 | 78 | const taskAddResolvedCallbackSpy = sinon.spy( 79 | ResolveTask.prototype, 80 | 'addResolvedCallback' 81 | ); 82 | const taskRunSpy = sinon.spy(ResolveTask.prototype, 'run'); 83 | 84 | async.parallel( 85 | [ 86 | cb => lookup._innerResolve(addresses.INET_HOST1, ipVersion, cb), 87 | cb => lookup._innerResolve(addresses.INET_HOST1, ipVersion, cb) 88 | ], 89 | error => { 90 | assert.ifError(error); 91 | 92 | assert.isTrue(addressCacheFindSpy.calledTwice); 93 | assert.isTrue( 94 | addressCacheFindSpy.calledWithExactly( 95 | `${addresses.INET_HOST1}_${ipVersion}` 96 | ) 97 | ); 98 | 99 | assert.isTrue(addressCacheSetSpy.calledOnce); 100 | assert.strictEqual( 101 | addressCacheSetSpy.getCall(0).args[0], 102 | `${addresses.INET_HOST1}_${ipVersion}` 103 | ); 104 | assert.instanceOf(addressCacheSetSpy.getCall(0).args[1], Array); 105 | 106 | assert.isTrue(tasksManagerFindSpy.calledTwice); 107 | assert.isTrue( 108 | tasksManagerFindSpy.calledWithExactly( 109 | `${addresses.INET_HOST1}_${ipVersion}` 110 | ) 111 | ); 112 | 113 | assert.isTrue(tasksManagerAddSpy.calledOnce); 114 | assert.strictEqual( 115 | tasksManagerAddSpy.getCall(0).args[0], 116 | `${addresses.INET_HOST1}_${ipVersion}` 117 | ); 118 | assert.instanceOf( 119 | tasksManagerAddSpy.getCall(0).args[1], 120 | ResolveTask 121 | ); 122 | 123 | assert.isTrue(tasksManagerDoneSpy.calledOnce); 124 | assert.isTrue( 125 | tasksManagerDoneSpy.calledWithExactly( 126 | `${addresses.INET_HOST1}_${ipVersion}` 127 | ) 128 | ); 129 | 130 | assert.isTrue(taskAddResolvedCallbackSpy.calledTwice); 131 | assert.isTrue(taskRunSpy.calledOnce); 132 | 133 | addressCacheFindSpy.restore(); 134 | addressCacheSetSpy.restore(); 135 | 136 | tasksManagerFindSpy.restore(); 137 | tasksManagerAddSpy.restore(); 138 | tasksManagerDoneSpy.restore(); 139 | 140 | taskAddResolvedCallbackSpy.restore(); 141 | taskRunSpy.restore(); 142 | 143 | done(); 144 | } 145 | ); 146 | }); 147 | }); 148 | -------------------------------------------------------------------------------- /tests/Functional/lookup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const dns = require('dns'); 4 | const net = require('net'); 5 | 6 | const { assert } = require('chai'); 7 | 8 | const { lookup } = require('../../'); 9 | 10 | const addresses = require('../addresses'); 11 | 12 | describe("must correct process 'hostname' param", () => { 13 | const invalidData = [true, 1, [], {}, () => {}, Buffer.alloc(0)]; 14 | 15 | invalidData.forEach(invalidHostname => { 16 | it(`must throw an exception, cuz 'hostname' param has type ${Object.prototype.toString.call( 17 | invalidHostname 18 | )}`, () => { 19 | assert.throws( 20 | () => { 21 | lookup(invalidHostname, {}, () => {}); 22 | }, 23 | Error, 24 | 'hostname must be a string' 25 | ); 26 | }); 27 | }); 28 | 29 | it('must throw an error, cuz `hostname` param has invalid value', done => { 30 | lookup(addresses.INVALID_HOST, error => { 31 | assert.instanceOf(error, Error); 32 | assert.strictEqual(error.code, dns.NOTFOUND); 33 | assert.strictEqual(error.hostname, addresses.INVALID_HOST); 34 | assert.match( 35 | error.message, 36 | new RegExp(`${dns.NOTFOUND} ${addresses.INVALID_HOST}`) 37 | ); 38 | 39 | done(); 40 | }); 41 | }); 42 | 43 | it('must work just fine for correct hostname param', done => { 44 | const expectedIpFamily = 4; 45 | 46 | lookup( 47 | addresses.INET_HOST1, 48 | expectedIpFamily, 49 | (error, address, family) => { 50 | assert.ifError(error); 51 | assert.isTrue(net.isIPv4(address)); 52 | assert.strictEqual(family, expectedIpFamily); 53 | 54 | done(); 55 | } 56 | ); 57 | }); 58 | 59 | const falsyValues = [false, null, undefined, 0, NaN, '']; 60 | 61 | const optionsValues = [ 62 | { 63 | options: {}, 64 | expectedError: null, 65 | expectedAddress: null, 66 | expectedFamily: 4 67 | }, 68 | { 69 | options: { all: false }, 70 | expectedError: null, 71 | expectedAddress: null, 72 | expectedFamily: 4 73 | }, 74 | { 75 | options: { all: false, family: 6 }, 76 | expectedError: null, 77 | expectedAddress: null, 78 | expectedFamily: 6 79 | }, 80 | { 81 | options: { all: true }, 82 | expectedError: null, 83 | expectedAddress: [], 84 | expectedFamily: undefined 85 | }, 86 | { 87 | options: { all: true, family: 6 }, 88 | expectedError: null, 89 | expectedAddress: [], 90 | expectedFamily: undefined 91 | } 92 | ]; 93 | 94 | falsyValues.forEach(hostname => { 95 | optionsValues.forEach(optionsValue => { 96 | const options = optionsValue.options; 97 | 98 | it(`must return correct value for hostname === ${hostname}, and options === ${JSON.stringify( 99 | options 100 | )}`, done => { 101 | lookup(hostname, options, (error, address, family) => { 102 | assert.strictEqual(error, optionsValue.expectedError); 103 | assert.deepEqual(address, optionsValue.expectedAddress); 104 | assert.strictEqual(family, optionsValue.expectedFamily); 105 | 106 | done(); 107 | }); 108 | }); 109 | }); 110 | }); 111 | }); 112 | 113 | describe('must correct process `options` param', () => { 114 | const invalidOptions = [undefined, null, false, '1', [], Buffer.alloc(0)]; 115 | 116 | invalidOptions.forEach(invalidOption => { 117 | it(`must throw an exception if 'options' param has type ${Object.prototype.toString.call( 118 | invalidOption 119 | )}`, () => { 120 | assert.throws( 121 | () => { 122 | lookup(addresses.INET_HOST1, invalidOption, () => {}); 123 | }, 124 | Error, 125 | 'options must be an object or an ip version number' 126 | ); 127 | }); 128 | }); 129 | 130 | const validFamilyNumbers = [4, 6]; 131 | 132 | validFamilyNumbers.forEach(ipFamily => { 133 | it(`must correct call lookup method if 'options' param has value - ${ipFamily}`, () => { 134 | lookup(addresses.INET_HOST1, ipFamily, (error, address, family) => { 135 | assert.ifError(error); 136 | 137 | ipFamily === 4 && assert.isTrue(net.isIPv4(address)); 138 | ipFamily === 6 && assert.isTrue(net.isIPv6(address)); 139 | 140 | assert.strictEqual(family, ipFamily); 141 | }); 142 | }); 143 | }); 144 | 145 | const invalidFamilyNumbers = [-1, 0, 3, 5, 7]; 146 | 147 | invalidFamilyNumbers.forEach(invalidIpFamily => { 148 | it(`must correct call lookup method if 'options' param has invalid value - ${invalidIpFamily}`, () => { 149 | assert.throws( 150 | () => { 151 | lookup(addresses.INET_HOST1, invalidIpFamily, () => {}); 152 | }, 153 | Error, 154 | 'invalid family number, must be one of the {4, 6} or undefined' 155 | ); 156 | }); 157 | }); 158 | 159 | it('must correct call lookup method if `options` param is omitted', done => { 160 | const expectedError = null; 161 | const expectedAddress = null; 162 | const expectedIpFamily = 4; 163 | 164 | lookup(null, (error, address, family) => { 165 | assert.strictEqual(error, expectedError); 166 | assert.deepEqual(address, expectedAddress); 167 | assert.strictEqual(family, expectedIpFamily); 168 | 169 | done(); 170 | }); 171 | }); 172 | }); 173 | 174 | describe('must correct process `callback` param', () => { 175 | const hostname = null; 176 | const options = {}; 177 | const invalidCallbacks = [ 178 | undefined, 179 | null, 180 | false, 181 | '1', 182 | [], 183 | {}, 184 | Buffer.alloc(0) 185 | ]; 186 | 187 | invalidCallbacks.forEach(invalidCallback => { 188 | it(`must throw an error if callback param has type ${Object.prototype.toString.call( 189 | invalidCallback 190 | )}`, () => { 191 | assert.throws( 192 | () => { 193 | lookup(hostname, options, invalidCallback); 194 | }, 195 | Error, 196 | 'callback param must be a function' 197 | ); 198 | }); 199 | }); 200 | 201 | const expectedError = null; 202 | const expectedAddress = null; 203 | const expectedFamily = 4; 204 | 205 | it('must okay process, cuz callback param is a function', done => { 206 | lookup(hostname, options, (error, address, family) => { 207 | assert.strictEqual(error, expectedError); 208 | assert.strictEqual(address, expectedAddress); 209 | assert.strictEqual(family, expectedFamily); 210 | 211 | done(); 212 | }); 213 | }); 214 | }); 215 | 216 | describe('must correct lookup for all IPv4 and IPv6 addresses', () => { 217 | const testCases = [ 218 | { 219 | title: 'must correct lookup all IPv4 addresses', 220 | family: 4, 221 | expectedAddressIps: [net.isIPv4], 222 | expectedIpFamilies: [4] 223 | }, 224 | { 225 | title: 'must correct lookup all IPv6 addresses', 226 | family: 6, 227 | expectedAddressIps: [net.isIPv6], 228 | expectedIpFamilies: [6] 229 | }, 230 | { 231 | title: 'must correct lookup all IPv4 and IPv6 addresses', 232 | family: undefined, 233 | expectedAddressIps: [net.isIPv4, net.isIPv6], 234 | expectedIpFamilies: [4, 6] 235 | } 236 | ]; 237 | 238 | testCases.forEach(testCase => { 239 | it(testCase.title, done => { 240 | lookup( 241 | addresses.INET_HOST1, 242 | { all: true, family: testCase.family }, 243 | (err, ips) => { 244 | assert.ifError(err); 245 | 246 | assert.isTrue(Array.isArray(ips)); 247 | 248 | assert.isTrue( 249 | ips.every(ip => { 250 | return ( 251 | testCase.expectedAddressIps.some(func => 252 | func(ip.address) 253 | ) && 254 | testCase.expectedIpFamilies.includes(ip.family) 255 | ); 256 | }) 257 | ); 258 | 259 | done(); 260 | } 261 | ); 262 | }); 263 | }); 264 | }); 265 | 266 | describe('must correct lookup for one IPv4/IPv6 address', () => { 267 | const testCases = [ 268 | { 269 | host: addresses.INET_HOST1, 270 | title: 'must correct lookup IPv4 address', 271 | family: 4, 272 | expectedAddressIp: net.isIPv4, 273 | expectedIpFamily: 4, 274 | checkResult(done, err, address, family) { 275 | assert.ifError(err); 276 | 277 | assert.isTrue(this.expectedAddressIp(address)); 278 | assert.strictEqual(this.expectedIpFamily, family); 279 | 280 | done(); 281 | } 282 | }, 283 | { 284 | host: addresses.INET_HOST1, 285 | title: 'must correct lookup IPv6 address', 286 | family: 6, 287 | expectedAddressIp: net.isIPv6, 288 | expectedIpFamily: 6, 289 | checkResult(done, err, address, family) { 290 | assert.ifError(err); 291 | 292 | assert.isTrue(this.expectedAddressIp(address)); 293 | assert.strictEqual(this.expectedIpFamily, family); 294 | 295 | done(); 296 | } 297 | }, 298 | { 299 | host: addresses.INET_HOST2, 300 | title: 'must correct lookup IPv4 address', 301 | family: 4, 302 | expectedAddressIp: net.isIPv4, 303 | expectedIpFamily: 4, 304 | checkResult(done, err, address, family) { 305 | assert.ifError(err); 306 | 307 | assert.isTrue(this.expectedAddressIp(address)); 308 | assert.strictEqual(this.expectedIpFamily, family); 309 | 310 | done(); 311 | } 312 | }, 313 | { 314 | host: addresses.INET_HOST2, 315 | title: 'must correct lookup IPv6 address', 316 | family: 6, 317 | expectedAddressIp: net.isIPv6, 318 | expectedIpFamily: 6, 319 | checkResult(done, err, address, family) { 320 | assert.ifError(err); 321 | 322 | assert.isTrue(this.expectedAddressIp(address)); 323 | assert.strictEqual(this.expectedIpFamily, family); 324 | 325 | done(); 326 | } 327 | }, 328 | { 329 | host: addresses.INET_HOST3, 330 | title: 'must correct lookup IPv4 address', 331 | family: 4, 332 | expectedAddressIp: net.isIPv4, 333 | expectedIpFamily: 4, 334 | checkResult(done, err, address, family) { 335 | assert.ifError(err); 336 | 337 | assert.isTrue(this.expectedAddressIp(address)); 338 | assert.strictEqual(this.expectedIpFamily, family); 339 | 340 | done(); 341 | } 342 | }, 343 | { 344 | host: addresses.WWWINET_HOST3, 345 | title: 'must correct lookup IPv4 address', 346 | family: 4, 347 | expectedAddressIp: net.isIPv4, 348 | expectedIpFamily: 4, 349 | checkResult(done, err, address, family) { 350 | assert.ifError(err); 351 | 352 | assert.isTrue(this.expectedAddressIp(address)); 353 | assert.strictEqual(this.expectedIpFamily, family); 354 | 355 | done(); 356 | } 357 | }, 358 | { 359 | host: addresses.INET_HOST3, 360 | title: 'must correct notify that no IPv6 address was found', 361 | family: 6, 362 | expectedAddressIp: net.isIPv4, 363 | expectedIpFamily: 4, 364 | checkResult(done, err, address, family) { 365 | const expectedSysCall = 'queryAaaa'; 366 | const expectedCode = 'ENOTFOUND'; 367 | const expectedErrNo = 'ENOTFOUND'; 368 | const expectedHostName = this.host; 369 | 370 | const expectedMessage = `${expectedSysCall} ${expectedErrNo} ${expectedHostName}`; 371 | 372 | assert.instanceOf(err, Error); 373 | 374 | assert.strictEqual(err.message, expectedMessage); 375 | assert.strictEqual(err.syscall, expectedSysCall); 376 | assert.strictEqual(err.code, expectedCode); 377 | assert.strictEqual(err.errno, expectedErrNo); 378 | assert.strictEqual(err.hostname, expectedHostName); 379 | 380 | assert.isUndefined(address); 381 | assert.isUndefined(family); 382 | 383 | done(); 384 | } 385 | }, 386 | { 387 | host: addresses.WWWINET_HOST3, 388 | title: 'must correct notify that no IPv6 address was found', 389 | family: 6, 390 | expectedAddressIp: net.isIPv4, 391 | expectedIpFamily: 4, 392 | checkResult(done, err, address, family) { 393 | const expectedSysCall = 'queryAaaa'; 394 | const expectedCode = 'ENOTFOUND'; 395 | const expectedErrNo = 'ENOTFOUND'; 396 | const expectedHostName = this.host; 397 | 398 | const expectedMessage = `${expectedSysCall} ${expectedErrNo} ${expectedHostName}`; 399 | 400 | assert.instanceOf(err, Error); 401 | 402 | assert.strictEqual(err.message, expectedMessage); 403 | assert.strictEqual(err.syscall, expectedSysCall); 404 | assert.strictEqual(err.code, expectedCode); 405 | assert.strictEqual(err.errno, expectedErrNo); 406 | assert.strictEqual(err.hostname, expectedHostName); 407 | 408 | assert.isUndefined(address); 409 | assert.isUndefined(family); 410 | 411 | done(); 412 | } 413 | } 414 | ]; 415 | 416 | testCases.forEach(testCase => { 417 | it(`${testCase.title} - ${testCase.host}`, done => { 418 | lookup( 419 | testCase.host, 420 | { family: testCase.family }, 421 | testCase.checkResult.bind(testCase, done) 422 | ); 423 | }); 424 | }); 425 | }); 426 | 427 | describe('must behaves like dns.lookup method', () => { 428 | const testCases = [ 429 | { 430 | host: addresses.INET_HOST1, 431 | title: 'must correct lookup IPv4 address', 432 | options: { family: 4 } 433 | }, 434 | { 435 | host: addresses.INET_HOST1, 436 | title: 'must correct lookup IPv6 address', 437 | options: { family: 6 } 438 | }, 439 | { 440 | host: addresses.INET_HOST2, 441 | title: 'must correct lookup IPv4 address', 442 | options: { family: 4 } 443 | }, 444 | { 445 | host: addresses.INET_HOST2, 446 | title: 'must correct lookup IPv6 address', 447 | options: { family: 6 } 448 | }, 449 | { 450 | host: addresses.INET_HOST3, 451 | title: 'must correct lookup IPv4 address', 452 | options: { family: 4 } 453 | }, 454 | { 455 | host: addresses.WWWINET_HOST3, 456 | title: 'must correct lookup IPv4 address', 457 | options: { family: 4 } 458 | }, 459 | { 460 | host: addresses.INET_HOST3, 461 | title: 'must correct notify that no IPv6 address was found', 462 | options: { family: 6 } 463 | }, 464 | { 465 | host: addresses.WWWINET_HOST3, 466 | title: 'must correct notify that no IPv6 address was found', 467 | options: { family: 6 } 468 | }, 469 | { 470 | host: addresses.INET_HOST3, 471 | title: 'must correct lookup all IPv4 and IPv6 adresses', 472 | options: { all: true } 473 | }, 474 | { 475 | host: addresses.WWWINET_HOST3, 476 | title: 'must correct lookup all IPv4 and IPv6 adresses', 477 | options: { all: true } 478 | }, 479 | { 480 | host: addresses.INET_HOST3, 481 | title: 'must correct lookup all IPv4 and IPv6 adresses', 482 | options: { all: true } 483 | }, 484 | { 485 | host: addresses.WWWINET_HOST3, 486 | title: 'must correct lookup all IPv4 and IPv6 adresses', 487 | options: { all: true } 488 | } 489 | ]; 490 | 491 | testCases.forEach(testCase => { 492 | it(`${testCase.title} - ${testCase.host}`, done => { 493 | dns.lookup( 494 | testCase.host, 495 | testCase.options, 496 | (errDnsLookup, addressesDnsLookup, familyDnsLookup) => { 497 | lookup( 498 | testCase.host, 499 | testCase.options, 500 | (errLookup, addressesLookup, familyLookup) => { 501 | if (errDnsLookup) { 502 | assert.instanceOf(errLookup, Error); 503 | 504 | assert.strictEqual( 505 | addressesDnsLookup, 506 | addressesLookup 507 | ); 508 | assert.strictEqual( 509 | familyDnsLookup, 510 | familyLookup 511 | ); 512 | 513 | return done(); 514 | } 515 | 516 | assert.isDefined(addressesLookup); 517 | 518 | assert.strictEqual( 519 | typeof addressesLookup, 520 | typeof addressesDnsLookup 521 | ); 522 | assert.strictEqual(familyLookup, familyDnsLookup); 523 | 524 | done(); 525 | } 526 | ); 527 | } 528 | ); 529 | }); 530 | }); 531 | }); 532 | -------------------------------------------------------------------------------- /tests/Unit/AddressCache/_isExpired.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { assert } = require('chai'); 4 | 5 | const AddressCache = require('../../../src/AddressCache'); 6 | 7 | describe('Unit: AddressCache::_isExpired', () => { 8 | const addressCache = new AddressCache(); 9 | 10 | it('must return false, cuz addresses array is empty', () => { 11 | const addresses = []; 12 | 13 | const isExpired = addressCache._isExpired(addresses); 14 | 15 | assert.isTrue(isExpired); 16 | }); 17 | 18 | it('must return true, cuz all addresses within an array is expired', () => { 19 | const addresses = [{ expiredTime: Date.now() - 1 }]; 20 | 21 | const isExpired = addressCache._isExpired(addresses); 22 | 23 | assert.isTrue(isExpired); 24 | }); 25 | 26 | it('must return false, cuz array does not have expired elements', () => { 27 | const addresses = [{ expiredTime: 2 * Date.now() }]; 28 | 29 | const isExpired = addressCache._isExpired(addresses); 30 | 31 | assert.isFalse(isExpired); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /tests/Unit/AddressCache/find.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { assert } = require('chai'); 4 | 5 | const AddressCache = require('../../../src/AddressCache'); 6 | const addresses = require('../../addresses'); 7 | 8 | describe('Unit: AddressCache::find', () => { 9 | let addressCache; 10 | 11 | beforeEach(() => { 12 | addressCache = new AddressCache(); 13 | }); 14 | 15 | it('must return undefined, cuz there are no addresses for this hostname key', () => { 16 | const notExistedKey = addresses.INET_HOST1; 17 | 18 | const result = addressCache.find(notExistedKey); 19 | 20 | assert.isUndefined(result); 21 | }); 22 | 23 | it('must return undefined, cuz there are only expired addresses within cache for particular host', () => { 24 | const cachedAddresses = [ 25 | { 26 | expiredTime: Date.now() - 1 27 | } 28 | ]; 29 | 30 | addressCache._cache.set(addresses.INET_HOST1, cachedAddresses); 31 | 32 | const result = addressCache.find(addresses.INET_HOST1); 33 | 34 | assert.isUndefined(result); 35 | }); 36 | 37 | it('must return addresses array, cuz there are only not-expired addresses within cache for particular host', () => { 38 | const cachedAddress = { 39 | expiredTime: Date.now() * 2 40 | }; 41 | 42 | const cachedAddresses = [cachedAddress]; 43 | 44 | addressCache._cache.set(addresses.INET_HOST1, cachedAddresses); 45 | 46 | const result = addressCache.find(addresses.INET_HOST1); 47 | 48 | assert.deepEqual(result, cachedAddresses); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /tests/Unit/AddressCache/set.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { assert } = require('chai'); 4 | 5 | const AddressCache = require('../../../src/AddressCache'); 6 | const addresses = require('../../addresses'); 7 | 8 | describe('Unit: AddressCache::set', () => { 9 | it('must correct set addressed for particular key', () => { 10 | const setOfAddresses1 = [{ address: '1.2.3.4' }]; 11 | 12 | const setOfAddresses2 = [{ address: '5.6.7.8' }]; 13 | 14 | const addressesCache = new AddressCache(); 15 | 16 | addressesCache.set(addresses.INET_HOST1, setOfAddresses1); 17 | addressesCache.set(addresses.INET_HOST2, setOfAddresses2); 18 | 19 | assert.deepEqual( 20 | addressesCache._cache.get(addresses.INET_HOST1), 21 | setOfAddresses1 22 | ); 23 | assert.deepEqual( 24 | addressesCache._cache.get(addresses.INET_HOST2), 25 | setOfAddresses2 26 | ); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /tests/Unit/Lookup/_innerResolve.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { EventEmitter } = require('events'); 4 | 5 | const { assert } = require('chai'); 6 | const proxyquire = require('proxyquire'); 7 | const sinon = require('sinon'); 8 | 9 | const addresses = require('../../addresses'); 10 | 11 | describe('Unit: Lookup::_innerResolve', () => { 12 | const ipVersions = [4, 6]; 13 | 14 | ipVersions.forEach(ipVersion => { 15 | it(`must correct return cached value for IPv${ipVersion}`, done => { 16 | const cachedAddress = {}; 17 | 18 | const addressCacheFindSpy = sinon.spy(() => cachedAddress); 19 | const addressCacheSetSpy = sinon.spy(); 20 | 21 | const resolveTaskRunSpy = sinon.spy(); 22 | 23 | const tasksManagerFindSpy = sinon.spy(); 24 | const tasksManagerAddSpy = sinon.spy(); 25 | const tasksManagerDoneSpy = sinon.spy(); 26 | 27 | class AddressCache {} 28 | AddressCache.prototype.find = addressCacheFindSpy; 29 | AddressCache.prototype.set = addressCacheSetSpy; 30 | 31 | class ResolveTask {} 32 | ResolveTask.prototype.run = resolveTaskRunSpy; 33 | 34 | class TasksManager {} 35 | TasksManager.prototype.find = tasksManagerFindSpy; 36 | TasksManager.prototype.add = tasksManagerAddSpy; 37 | TasksManager.prototype.done = tasksManagerDoneSpy; 38 | 39 | const Lookup = proxyquire('../../../src/Lookup', { 40 | './AddressCache': AddressCache, 41 | './TasksManager': TasksManager, 42 | './ResolveTask': ResolveTask 43 | }); 44 | 45 | const callbackSpy = sinon.spy(); 46 | 47 | const lookup = new Lookup(); 48 | 49 | lookup._innerResolve(addresses.INET_HOST1, ipVersion, callbackSpy); 50 | 51 | setImmediate(() => { 52 | assert.isTrue(addressCacheFindSpy.calledOnce); 53 | assert.isTrue( 54 | addressCacheFindSpy.calledWithExactly( 55 | `${addresses.INET_HOST1}_${ipVersion}` 56 | ) 57 | ); 58 | assert.isTrue(addressCacheSetSpy.notCalled); 59 | 60 | assert.isTrue(resolveTaskRunSpy.notCalled); 61 | 62 | assert.isTrue(tasksManagerFindSpy.notCalled); 63 | assert.isTrue(tasksManagerAddSpy.notCalled); 64 | assert.isTrue(tasksManagerDoneSpy.notCalled); 65 | 66 | assert.isTrue(callbackSpy.calledOnce); 67 | assert.isTrue( 68 | callbackSpy.calledWithExactly(null, cachedAddress) 69 | ); 70 | 71 | done(); 72 | }); 73 | }); 74 | }); 75 | 76 | ipVersions.forEach(ipVersion => { 77 | it(`must correct add callback for the found task for IPv${ipVersion}`, done => { 78 | const resolveTaskRunSpy = sinon.spy(); 79 | const taskAddResolvedCallbackSpy = sinon.spy(); 80 | 81 | class ResolveTask {} 82 | ResolveTask.prototype.run = resolveTaskRunSpy; 83 | ResolveTask.prototype.addResolvedCallback = taskAddResolvedCallbackSpy; 84 | 85 | const addressCacheFindSpy = sinon.spy(() => undefined); 86 | const addressCacheSetSpy = sinon.spy(); 87 | 88 | const tasksManagerFindSpy = sinon.spy(() => new ResolveTask()); 89 | const tasksManagerAddSpy = sinon.spy(); 90 | const tasksManagerDoneSpy = sinon.spy(); 91 | 92 | class AddressCache {} 93 | AddressCache.prototype.find = addressCacheFindSpy; 94 | AddressCache.prototype.set = addressCacheSetSpy; 95 | 96 | class TasksManager {} 97 | TasksManager.prototype.find = tasksManagerFindSpy; 98 | TasksManager.prototype.add = tasksManagerAddSpy; 99 | TasksManager.prototype.done = tasksManagerDoneSpy; 100 | 101 | const Lookup = proxyquire('../../../src/Lookup', { 102 | './AddressCache': AddressCache, 103 | './TasksManager': TasksManager, 104 | './ResolveTask': ResolveTask 105 | }); 106 | 107 | const callbackSpy = sinon.spy(); 108 | 109 | const lookup = new Lookup(); 110 | 111 | lookup._innerResolve(addresses.INET_HOST1, ipVersion, callbackSpy); 112 | 113 | setImmediate(() => { 114 | assert.isTrue(addressCacheFindSpy.calledOnce); 115 | assert.isTrue( 116 | addressCacheFindSpy.calledWithExactly( 117 | `${addresses.INET_HOST1}_${ipVersion}` 118 | ) 119 | ); 120 | 121 | assert.isTrue(tasksManagerFindSpy.calledOnce); 122 | assert.isTrue( 123 | tasksManagerFindSpy.calledWithExactly( 124 | `${addresses.INET_HOST1}_${ipVersion}` 125 | ) 126 | ); 127 | assert.isTrue(tasksManagerAddSpy.notCalled); 128 | assert.isTrue(tasksManagerDoneSpy.notCalled); 129 | 130 | assert.isTrue(resolveTaskRunSpy.notCalled); 131 | 132 | assert.isTrue(taskAddResolvedCallbackSpy.calledOnce); 133 | assert.isTrue( 134 | taskAddResolvedCallbackSpy.calledWithExactly(callbackSpy) 135 | ); 136 | 137 | assert.isTrue(callbackSpy.notCalled); 138 | 139 | done(); 140 | }); 141 | }); 142 | }); 143 | 144 | ipVersions.forEach(ipVersion => { 145 | it(`must correct create task, add callback to it, run for IPv${ipVersion} and correct handle events`, done => { 146 | const expectedAddresses = Symbol(); 147 | const resolveTaskRunSpy = sinon.spy(); 148 | const taskAddResolvedCallbackSpy = sinon.spy(); 149 | 150 | const task = new EventEmitter(); 151 | 152 | task.run = resolveTaskRunSpy; 153 | task.addResolvedCallback = taskAddResolvedCallbackSpy; 154 | 155 | const resolveTaskOnSpy = sinon.spy(task, 'on'); 156 | 157 | class ResolveTask { 158 | constructor() { 159 | return task; 160 | } 161 | } 162 | 163 | const addressCacheFindSpy = sinon.spy(() => undefined); 164 | const addressCacheSetSpy = sinon.spy(); 165 | 166 | const tasksManagerFindSpy = sinon.spy(() => undefined); 167 | const tasksManagerAddSpy = sinon.spy(); 168 | const tasksManagerDoneSpy = sinon.spy(); 169 | 170 | class AddressCache {} 171 | AddressCache.prototype.find = addressCacheFindSpy; 172 | AddressCache.prototype.set = addressCacheSetSpy; 173 | 174 | class TasksManager {} 175 | TasksManager.prototype.find = tasksManagerFindSpy; 176 | TasksManager.prototype.add = tasksManagerAddSpy; 177 | TasksManager.prototype.done = tasksManagerDoneSpy; 178 | 179 | const Lookup = proxyquire('../../../src/Lookup', { 180 | './AddressCache': AddressCache, 181 | './TasksManager': TasksManager, 182 | './ResolveTask': ResolveTask 183 | }); 184 | 185 | const callbackSpy = sinon.spy(); 186 | 187 | const lookup = new Lookup(); 188 | 189 | lookup._innerResolve(addresses.INET_HOST1, ipVersion, callbackSpy); 190 | 191 | setImmediate(() => { 192 | task.emit('addresses', expectedAddresses); 193 | task.emit('done'); 194 | }); 195 | 196 | setImmediate(() => { 197 | assert.isTrue(addressCacheFindSpy.calledOnce); 198 | assert.isTrue( 199 | addressCacheFindSpy.calledWithExactly( 200 | `${addresses.INET_HOST1}_${ipVersion}` 201 | ) 202 | ); 203 | 204 | assert.isTrue(addressCacheSetSpy.calledOnce); 205 | assert.strictEqual( 206 | addressCacheSetSpy.getCall(0).args[0], 207 | `${addresses.INET_HOST1}_${ipVersion}` 208 | ); 209 | assert.strictEqual( 210 | addressCacheSetSpy.getCall(0).args[1], 211 | expectedAddresses 212 | ); 213 | 214 | assert.isTrue(tasksManagerFindSpy.calledOnce); 215 | assert.isTrue( 216 | tasksManagerFindSpy.calledWithExactly( 217 | `${addresses.INET_HOST1}_${ipVersion}` 218 | ) 219 | ); 220 | 221 | assert.isTrue(tasksManagerAddSpy.calledOnce); 222 | assert.strictEqual( 223 | tasksManagerAddSpy.getCall(0).args[0], 224 | `${addresses.INET_HOST1}_${ipVersion}` 225 | ); 226 | assert.strictEqual(tasksManagerAddSpy.getCall(0).args[1], task); 227 | 228 | assert.isTrue(tasksManagerDoneSpy.calledOnce); 229 | assert.isTrue( 230 | tasksManagerDoneSpy.calledWithExactly( 231 | `${addresses.INET_HOST1}_${ipVersion}` 232 | ) 233 | ); 234 | 235 | assert.isTrue(resolveTaskRunSpy.calledOnce); 236 | assert.isTrue(resolveTaskRunSpy.calledWithExactly()); 237 | 238 | assert.isTrue(taskAddResolvedCallbackSpy.calledOnce); 239 | assert.isTrue( 240 | taskAddResolvedCallbackSpy.calledWithExactly(callbackSpy) 241 | ); 242 | 243 | assert.isTrue(callbackSpy.notCalled); 244 | 245 | assert.isTrue(resolveTaskOnSpy.calledTwice); 246 | 247 | assert.strictEqual( 248 | resolveTaskOnSpy.getCall(0).args[0], 249 | 'addresses' 250 | ); 251 | assert.instanceOf( 252 | resolveTaskOnSpy.getCall(0).args[1], 253 | Function 254 | ); 255 | 256 | assert.strictEqual(resolveTaskOnSpy.getCall(1).args[0], 'done'); 257 | assert.instanceOf( 258 | resolveTaskOnSpy.getCall(1).args[1], 259 | Function 260 | ); 261 | 262 | done(); 263 | }); 264 | }); 265 | }); 266 | }); 267 | -------------------------------------------------------------------------------- /tests/Unit/Lookup/_makeNotFoundError.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const dns = require('dns'); 4 | 5 | const { assert } = require('chai'); 6 | 7 | const Lookup = require('../../../src/Lookup'); 8 | const addresses = require('../../addresses'); 9 | 10 | describe('Unit: Lookup::_makeNotFoundError', () => { 11 | let lookup; 12 | 13 | beforeEach(() => { 14 | lookup = new Lookup(); 15 | }); 16 | 17 | it('must correct create error object with syscall', () => { 18 | const expectedSysCall = 'queryA'; 19 | 20 | const error = lookup._makeNotFoundError( 21 | addresses.INET_HOST1, 22 | expectedSysCall 23 | ); 24 | 25 | assert.instanceOf(error, Error); 26 | assert.strictEqual( 27 | error.message, 28 | `${expectedSysCall} ${dns.NOTFOUND} ${addresses.INET_HOST1}` 29 | ); 30 | assert.strictEqual(error.hostname, addresses.INET_HOST1); 31 | assert.strictEqual(error.code, dns.NOTFOUND); 32 | assert.strictEqual(error.errno, dns.NOTFOUND); 33 | assert.strictEqual(error.syscall, expectedSysCall); 34 | }); 35 | 36 | it('must correct create error object without syscall', () => { 37 | const error = lookup._makeNotFoundError(addresses.INET_HOST1); 38 | 39 | assert.instanceOf(error, Error); 40 | assert.strictEqual( 41 | error.message, 42 | `${dns.NOTFOUND} ${addresses.INET_HOST1}` 43 | ); 44 | assert.strictEqual(error.hostname, addresses.INET_HOST1); 45 | assert.strictEqual(error.code, dns.NOTFOUND); 46 | assert.strictEqual(error.errno, dns.NOTFOUND); 47 | assert.notProperty(error, 'syscall'); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /tests/Unit/Lookup/_resolve.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const dns = require('dns'); 4 | 5 | const { assert } = require('chai'); 6 | const sinon = require('sinon'); 7 | 8 | const Lookup = require('../../../src/Lookup'); 9 | const addresses = require('../../addresses'); 10 | 11 | describe('Unit: Lookup::_resolve', () => { 12 | const hostname = addresses.INET_HOST1; 13 | 14 | let makeNotFoundErrorStub; 15 | let innerResolveStub; 16 | 17 | let lookup; 18 | 19 | beforeEach(() => { 20 | lookup = new Lookup(); 21 | }); 22 | 23 | afterEach(() => { 24 | makeNotFoundErrorStub.restore(); 25 | innerResolveStub.restore(); 26 | }); 27 | 28 | it('must correct call callback with adjusted error if got NODATA error', () => { 29 | const expectedErrorMessage = new Error('expected error message'); 30 | 31 | const expectedNumberOfResolveTriesAfterError = 0; 32 | 33 | const error = new Error('some error'); 34 | error.syscall = 'some-sys-call'; 35 | error.code = dns.NODATA; 36 | 37 | const callbackStub = sinon.stub(); 38 | 39 | makeNotFoundErrorStub = sinon 40 | .stub(Lookup.prototype, '_makeNotFoundError') 41 | .returns(expectedErrorMessage); 42 | innerResolveStub = sinon 43 | .stub(Lookup.prototype, '_innerResolve') 44 | .callsFake((hostname, family, callback) => { 45 | callback(error); 46 | }); 47 | 48 | lookup._resolve(hostname, { family: 4 }, callbackStub); 49 | 50 | assert.isTrue(callbackStub.calledOnce); 51 | assert.strictEqual( 52 | callbackStub.getCall(0).args[0], 53 | expectedErrorMessage 54 | ); 55 | 56 | assert.isTrue(makeNotFoundErrorStub.calledOnce); 57 | assert.isTrue( 58 | makeNotFoundErrorStub.calledWithExactly(hostname, error.syscall) 59 | ); 60 | 61 | assert.strictEqual( 62 | lookup._amountOfResolveTries[hostname], 63 | expectedNumberOfResolveTriesAfterError 64 | ); 65 | }); 66 | 67 | const testCases = [ 68 | { 69 | syscall: 'queryA', 70 | family: 4 71 | }, 72 | { 73 | syscall: 'queryAaaa', 74 | family: 6 75 | } 76 | ]; 77 | 78 | testCases.forEach(testCase => { 79 | it(`must correct call callback with adjusted NODATA error if got empty result with no error for family - ${ 80 | testCase.family 81 | }`, () => { 82 | const expectedErrorMessage = new Error('expected error message'); 83 | const expectedNumberOfResolveTriesAfterError = 0; 84 | const expectedSysCall = testCase.syscall; 85 | 86 | const error = null; 87 | const emptyResult = []; 88 | 89 | const callbackStub = sinon.stub(); 90 | 91 | makeNotFoundErrorStub = sinon 92 | .stub(Lookup.prototype, '_makeNotFoundError') 93 | .returns(expectedErrorMessage); 94 | innerResolveStub = sinon 95 | .stub(Lookup.prototype, '_innerResolve') 96 | .callsFake((hostname, family, callback) => { 97 | callback(error, emptyResult); 98 | }); 99 | 100 | lookup._resolve( 101 | hostname, 102 | { family: testCase.family }, 103 | callbackStub 104 | ); 105 | 106 | assert.isTrue(callbackStub.calledOnce); 107 | assert.strictEqual( 108 | callbackStub.getCall(0).args[0], 109 | expectedErrorMessage 110 | ); 111 | 112 | assert.isTrue(makeNotFoundErrorStub.calledOnce); 113 | assert.isTrue( 114 | makeNotFoundErrorStub.calledWithExactly( 115 | hostname, 116 | expectedSysCall 117 | ) 118 | ); 119 | 120 | assert.strictEqual( 121 | lookup._amountOfResolveTries[hostname], 122 | expectedNumberOfResolveTriesAfterError 123 | ); 124 | }); 125 | }); 126 | 127 | it('must correct call callback with original error', () => { 128 | const expectedErrorMessage = new Error('expected error message'); 129 | 130 | const expectedNumberOfResolveTriesAfterError = 0; 131 | 132 | const callbackStub = sinon.stub(); 133 | 134 | makeNotFoundErrorStub = sinon.spy( 135 | Lookup.prototype, 136 | '_makeNotFoundError' 137 | ); 138 | innerResolveStub = sinon 139 | .stub(Lookup.prototype, '_innerResolve') 140 | .callsFake((hostname, family, callback) => { 141 | callback(expectedErrorMessage); 142 | }); 143 | 144 | lookup._resolve(hostname, { family: 4 }, callbackStub); 145 | 146 | assert.isTrue(callbackStub.calledOnce); 147 | assert.strictEqual( 148 | callbackStub.getCall(0).args[0], 149 | expectedErrorMessage 150 | ); 151 | 152 | assert.isTrue(makeNotFoundErrorStub.notCalled); 153 | 154 | assert.strictEqual( 155 | lookup._amountOfResolveTries[hostname], 156 | expectedNumberOfResolveTriesAfterError 157 | ); 158 | }); 159 | 160 | it('must go into recursion if there is no error and records param equals to undefined', () => { 161 | const error = null; 162 | const records = undefined; 163 | 164 | const expectedNumberOfResolveTriesAfterRecursion = 0; 165 | 166 | const callbackStub = sinon.stub(); 167 | 168 | makeNotFoundErrorStub = sinon.spy( 169 | Lookup.prototype, 170 | '_makeNotFoundError' 171 | ); 172 | innerResolveStub = sinon 173 | .stub(Lookup.prototype, '_innerResolve') 174 | .callsFake((hostname, family, callback) => { 175 | callback(error, records); 176 | }); 177 | 178 | lookup._resolve(hostname, { family: 4 }, callbackStub); 179 | 180 | assert.isTrue(callbackStub.calledOnce); 181 | assert.instanceOf(callbackStub.getCall(0).args[0], Error); 182 | assert.strictEqual( 183 | callbackStub.getCall(0).args[0].message, 184 | `Cannot resolve host '${hostname}'. Too deep recursion.` 185 | ); 186 | 187 | assert.isTrue(makeNotFoundErrorStub.notCalled); 188 | 189 | assert.strictEqual( 190 | lookup._amountOfResolveTries[hostname], 191 | expectedNumberOfResolveTriesAfterRecursion 192 | ); 193 | }); 194 | 195 | it('must return next IP address every call (RR algorithm) ({all: false} option has been provided)', () => { 196 | const error = null; 197 | const records = [{ address: 1, family: 4 }, { address: 2, family: 4 }]; 198 | 199 | const expectedNumberOfResolveTriesAfterCorrectResolve = 0; 200 | 201 | const callbackStub = sinon.stub(); 202 | 203 | makeNotFoundErrorStub = sinon.spy( 204 | Lookup.prototype, 205 | '_makeNotFoundError' 206 | ); 207 | innerResolveStub = sinon 208 | .stub(Lookup.prototype, '_innerResolve') 209 | .callsFake((hostname, family, callback) => { 210 | callback(error, records); 211 | }); 212 | 213 | lookup._resolve(hostname, { family: 4 }, callbackStub); 214 | lookup._resolve(hostname, { family: 4 }, callbackStub); 215 | lookup._resolve(hostname, { family: 4 }, callbackStub); 216 | 217 | assert.isTrue(callbackStub.calledThrice); 218 | 219 | assert.deepEqual(callbackStub.getCall(0).args[0], null); 220 | assert.deepEqual(callbackStub.getCall(0).args[1], records[0].address); 221 | assert.deepEqual(callbackStub.getCall(0).args[2], records[0].family); 222 | 223 | assert.deepEqual(callbackStub.getCall(1).args[0], null); 224 | assert.deepEqual(callbackStub.getCall(1).args[1], records[1].address); 225 | assert.deepEqual(callbackStub.getCall(1).args[2], records[1].family); 226 | 227 | assert.deepEqual(callbackStub.getCall(2).args[0], null); 228 | assert.deepEqual(callbackStub.getCall(2).args[1], records[0].address); 229 | assert.deepEqual(callbackStub.getCall(2).args[2], records[0].family); 230 | 231 | assert.isTrue(makeNotFoundErrorStub.notCalled); 232 | 233 | assert.strictEqual( 234 | lookup._amountOfResolveTries[hostname], 235 | expectedNumberOfResolveTriesAfterCorrectResolve 236 | ); 237 | }); 238 | 239 | it('must return all IP addresses ({all: true} options has been provided)', () => { 240 | const error = null; 241 | const records = [ 242 | { address: 1, family: 4, expiredTime: Date.now() }, 243 | { address: 2, family: 4, expiredTime: Date.now() } 244 | ]; 245 | const expectedRecords = records.map(record => { 246 | return { 247 | address: record.address, 248 | family: record.family 249 | }; 250 | }); 251 | 252 | const expectedNumberOfResolveTriesAfterCorrectResolve = 0; 253 | 254 | const callbackStub = sinon.stub(); 255 | 256 | makeNotFoundErrorStub = sinon.spy( 257 | Lookup.prototype, 258 | '_makeNotFoundError' 259 | ); 260 | innerResolveStub = sinon 261 | .stub(Lookup.prototype, '_innerResolve') 262 | .callsFake((hostname, family, callback) => { 263 | callback(error, records); 264 | }); 265 | 266 | lookup._resolve(hostname, { all: true, family: 4 }, callbackStub); 267 | 268 | assert.isTrue(callbackStub.calledOnce); 269 | 270 | assert.deepEqual(callbackStub.getCall(0).args[0], null); 271 | assert.deepEqual(callbackStub.getCall(0).args[1], expectedRecords); 272 | 273 | assert.isTrue(makeNotFoundErrorStub.notCalled); 274 | 275 | assert.strictEqual( 276 | lookup._amountOfResolveTries[hostname], 277 | expectedNumberOfResolveTriesAfterCorrectResolve 278 | ); 279 | }); 280 | }); 281 | -------------------------------------------------------------------------------- /tests/Unit/Lookup/_resolveBoth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const dns = require('dns'); 4 | 5 | const async = require('async'); 6 | const { assert } = require('chai'); 7 | const sinon = require('sinon'); 8 | 9 | const Lookup = require('../../../src/Lookup'); 10 | const addresses = require('../../addresses'); 11 | 12 | describe('Unit: Lookup::_resolveBoth', () => { 13 | let lookup; 14 | 15 | const hostname = addresses.INET_HOST1; 16 | 17 | beforeEach(() => { 18 | lookup = new Lookup(); 19 | }); 20 | 21 | it('must run two resolve tasks for IPv4 and IPv6 in parallel', () => { 22 | const options = {}; 23 | 24 | const resolveTaskBuilderStub = sinon 25 | .stub(lookup, '_resolveTaskBuilder') 26 | .callsFake(() => { 27 | return cb => {}; 28 | }); 29 | 30 | const asyncParallelSpy = sinon.spy(async, 'parallel'); 31 | 32 | lookup._resolveBoth(hostname, options); 33 | 34 | assert.isTrue(asyncParallelSpy.calledOnce); 35 | assert.isTrue(resolveTaskBuilderStub.calledTwice); 36 | 37 | assert.strictEqual(resolveTaskBuilderStub.getCall(0).args[0], hostname); 38 | assert.strictEqual(resolveTaskBuilderStub.getCall(1).args[0], hostname); 39 | 40 | assert.deepEqual( 41 | resolveTaskBuilderStub.getCall(0).args[1], 42 | Object.assign({}, options, { family: Lookup.IPv4 }) 43 | ); 44 | assert.deepEqual( 45 | resolveTaskBuilderStub.getCall(1).args[1], 46 | Object.assign({}, options, { family: Lookup.IPv6 }) 47 | ); 48 | }); 49 | 50 | it('must run callback with error in case we got error during resolve on first call', done => { 51 | const cbSpy = sinon.stub(); 52 | 53 | const error = new Error('some error'); 54 | const options = {}; 55 | 56 | const resolveTaskBuilderStub = sinon.stub( 57 | lookup, 58 | '_resolveTaskBuilder' 59 | ); 60 | resolveTaskBuilderStub.onCall(0).callsFake(() => { 61 | return cb => { 62 | setImmediate(() => { 63 | cb(error); 64 | }); 65 | }; 66 | }); 67 | resolveTaskBuilderStub.onCall(1).callsFake(() => { 68 | return cb => {}; 69 | }); 70 | 71 | const makeNotFoundErrorSpy = sinon.spy(lookup, '_makeNotFoundError'); 72 | 73 | lookup._resolveBoth(hostname, options, cbSpy); 74 | 75 | setImmediate(() => { 76 | assert.isTrue(resolveTaskBuilderStub.calledTwice); 77 | 78 | assert.strictEqual( 79 | resolveTaskBuilderStub.getCall(0).args[0], 80 | hostname 81 | ); 82 | assert.strictEqual( 83 | resolveTaskBuilderStub.getCall(1).args[0], 84 | hostname 85 | ); 86 | 87 | assert.deepEqual( 88 | resolveTaskBuilderStub.getCall(0).args[1], 89 | Object.assign({}, options, { family: Lookup.IPv4 }) 90 | ); 91 | assert.deepEqual( 92 | resolveTaskBuilderStub.getCall(1).args[1], 93 | Object.assign({}, options, { family: Lookup.IPv6 }) 94 | ); 95 | 96 | assert.isTrue(cbSpy.calledOnce); 97 | 98 | assert.strictEqual(cbSpy.getCall(0).args.length, 1); 99 | assert.instanceOf(cbSpy.getCall(0).args[0], Error); 100 | assert.strictEqual(cbSpy.getCall(0).args[0].message, error.message); 101 | 102 | assert.isTrue(makeNotFoundErrorSpy.notCalled); 103 | 104 | done(); 105 | }); 106 | }); 107 | 108 | it('must run callback with error in case we got error during resolve on second call', done => { 109 | const cbSpy = sinon.stub(); 110 | 111 | const error = new Error('some error'); 112 | const options = {}; 113 | 114 | const resolveTaskBuilderStub = sinon.stub( 115 | lookup, 116 | '_resolveTaskBuilder' 117 | ); 118 | resolveTaskBuilderStub.onCall(0).callsFake(() => { 119 | return cb => {}; 120 | }); 121 | resolveTaskBuilderStub.onCall(1).callsFake(() => { 122 | return cb => { 123 | setImmediate(() => { 124 | cb(error); 125 | }); 126 | }; 127 | }); 128 | 129 | const makeNotFoundErrorSpy = sinon.spy(lookup, '_makeNotFoundError'); 130 | 131 | lookup._resolveBoth(hostname, options, cbSpy); 132 | 133 | setImmediate(() => { 134 | assert.isTrue(resolveTaskBuilderStub.calledTwice); 135 | 136 | assert.strictEqual( 137 | resolveTaskBuilderStub.getCall(0).args[0], 138 | hostname 139 | ); 140 | assert.strictEqual( 141 | resolveTaskBuilderStub.getCall(1).args[0], 142 | hostname 143 | ); 144 | 145 | assert.deepEqual( 146 | resolveTaskBuilderStub.getCall(0).args[1], 147 | Object.assign({}, options, { family: Lookup.IPv4 }) 148 | ); 149 | assert.deepEqual( 150 | resolveTaskBuilderStub.getCall(1).args[1], 151 | Object.assign({}, options, { family: Lookup.IPv6 }) 152 | ); 153 | 154 | assert.isTrue(cbSpy.calledOnce); 155 | 156 | assert.strictEqual(cbSpy.getCall(0).args.length, 1); 157 | assert.instanceOf(cbSpy.getCall(0).args[0], Error); 158 | assert.strictEqual(cbSpy.getCall(0).args[0].message, error.message); 159 | 160 | assert.isTrue(makeNotFoundErrorSpy.notCalled); 161 | 162 | done(); 163 | }); 164 | }); 165 | 166 | const setOfOptions = [{ all: false }, { all: true }]; 167 | 168 | setOfOptions.forEach(options => { 169 | it(`must return error, cuz no data were found with ${JSON.stringify( 170 | options 171 | )} options`, done => { 172 | const expectedError = new Error(`${dns.NOTFOUND} ${hostname}`); 173 | expectedError.hostname = hostname; 174 | expectedError.code = dns.NOTFOUND; 175 | expectedError.errno = dns.NOTFOUND; 176 | 177 | const cbSpy = sinon.stub(); 178 | 179 | const resolveTaskBuilderStub = sinon 180 | .stub(lookup, '_resolveTaskBuilder') 181 | .callsFake(() => { 182 | return cb => { 183 | setImmediate(() => { 184 | cb(null, []); 185 | }); 186 | }; 187 | }); 188 | 189 | const makeNotFoundErrorSpy = sinon.spy( 190 | lookup, 191 | '_makeNotFoundError' 192 | ); 193 | 194 | lookup._resolveBoth(hostname, options, cbSpy); 195 | 196 | setImmediate(() => { 197 | assert.isTrue(resolveTaskBuilderStub.calledTwice); 198 | 199 | assert.strictEqual( 200 | resolveTaskBuilderStub.getCall(0).args[0], 201 | hostname 202 | ); 203 | assert.strictEqual( 204 | resolveTaskBuilderStub.getCall(1).args[0], 205 | hostname 206 | ); 207 | 208 | assert.deepEqual( 209 | resolveTaskBuilderStub.getCall(0).args[1], 210 | Object.assign({}, options, { family: Lookup.IPv4 }) 211 | ); 212 | 213 | assert.deepEqual( 214 | resolveTaskBuilderStub.getCall(1).args[1], 215 | Object.assign({}, options, { family: Lookup.IPv6 }) 216 | ); 217 | 218 | assert.isTrue(cbSpy.calledOnce); 219 | 220 | assert.isTrue(makeNotFoundErrorSpy.calledOnce); 221 | assert.isTrue(makeNotFoundErrorSpy.calledWithExactly(hostname)); 222 | 223 | assert.strictEqual(cbSpy.getCall(0).args.length, 1); 224 | assert.instanceOf(cbSpy.getCall(0).args[0], Error); 225 | assert.strictEqual( 226 | cbSpy.getCall(0).args[0].message, 227 | expectedError.message 228 | ); 229 | assert.strictEqual( 230 | cbSpy.getCall(0).args[0].hostname, 231 | expectedError.hostname 232 | ); 233 | assert.strictEqual( 234 | cbSpy.getCall(0).args[0].code, 235 | expectedError.code 236 | ); 237 | assert.strictEqual( 238 | cbSpy.getCall(0).args[0].errno, 239 | expectedError.errno 240 | ); 241 | 242 | done(); 243 | }); 244 | }); 245 | }); 246 | 247 | it('must return addresses that was found with {all: true} option. IPv4 should be listed first', done => { 248 | const options = { all: true }; 249 | 250 | const cbSpy = sinon.stub(); 251 | 252 | const ipv4records = [{ address: '1.2.3.4' }, { address: '5.6.7.8' }]; 253 | 254 | const ipv6records = [ 255 | { address: '2001:0db8:85a3:0000:0000:8a2e:0370:7334' }, 256 | { address: '2001:0db8:85a3:0000:0000:8a2e:0370:7335' } 257 | ]; 258 | 259 | const resolveTaskBuilderStub = sinon.stub( 260 | lookup, 261 | '_resolveTaskBuilder' 262 | ); 263 | resolveTaskBuilderStub.onCall(0).callsFake(() => { 264 | return cb => { 265 | setTimeout(() => { 266 | cb(null, ipv4records); 267 | }, 20); 268 | }; 269 | }); 270 | 271 | resolveTaskBuilderStub.onCall(1).callsFake(() => { 272 | return cb => { 273 | setTimeout(() => { 274 | cb(null, ipv6records); 275 | }, 10); 276 | }; 277 | }); 278 | 279 | const makeNotFoundErrorSpy = sinon.spy(lookup, '_makeNotFoundError'); 280 | 281 | lookup._resolveBoth(hostname, options, cbSpy); 282 | 283 | setTimeout(() => { 284 | assert.isTrue(resolveTaskBuilderStub.calledTwice); 285 | 286 | assert.strictEqual( 287 | resolveTaskBuilderStub.getCall(0).args[0], 288 | hostname 289 | ); 290 | assert.strictEqual( 291 | resolveTaskBuilderStub.getCall(1).args[0], 292 | hostname 293 | ); 294 | 295 | assert.deepEqual( 296 | resolveTaskBuilderStub.getCall(0).args[1], 297 | Object.assign({}, options, { family: Lookup.IPv4 }) 298 | ); 299 | 300 | assert.deepEqual( 301 | resolveTaskBuilderStub.getCall(1).args[1], 302 | Object.assign({}, options, { family: Lookup.IPv6 }) 303 | ); 304 | 305 | assert.isTrue(cbSpy.calledOnce); 306 | 307 | assert.strictEqual(cbSpy.getCall(0).args.length, 2); 308 | assert.isNull(cbSpy.getCall(0).args[0]); 309 | assert.deepEqual( 310 | cbSpy.getCall(0).args[1], 311 | ipv4records.concat(ipv6records) 312 | ); 313 | 314 | assert.isTrue(makeNotFoundErrorSpy.notCalled); 315 | 316 | done(); 317 | }, 30); 318 | }); 319 | 320 | it('must return IPv4 address that was found with {all: false} option', done => { 321 | const options = { all: false }; 322 | 323 | const cbSpy = sinon.stub(); 324 | 325 | const ipv4records = ['1.2.3.4', 4]; 326 | const ipv6records = []; 327 | 328 | const resolveTaskBuilderStub = sinon.stub( 329 | lookup, 330 | '_resolveTaskBuilder' 331 | ); 332 | resolveTaskBuilderStub.onCall(0).callsFake(() => { 333 | return cb => { 334 | setImmediate(() => { 335 | cb(null, ipv4records); 336 | }); 337 | }; 338 | }); 339 | 340 | resolveTaskBuilderStub.onCall(1).callsFake(() => { 341 | return cb => { 342 | setImmediate(() => { 343 | cb(null, ipv6records); 344 | }); 345 | }; 346 | }); 347 | 348 | const makeNotFoundErrorSpy = sinon.spy(lookup, '_makeNotFoundError'); 349 | 350 | lookup._resolveBoth(hostname, options, cbSpy); 351 | 352 | setTimeout(() => { 353 | assert.isTrue(resolveTaskBuilderStub.calledTwice); 354 | 355 | assert.strictEqual( 356 | resolveTaskBuilderStub.getCall(0).args[0], 357 | hostname 358 | ); 359 | assert.strictEqual( 360 | resolveTaskBuilderStub.getCall(1).args[0], 361 | hostname 362 | ); 363 | 364 | assert.deepEqual( 365 | resolveTaskBuilderStub.getCall(0).args[1], 366 | Object.assign({}, options, { family: Lookup.IPv4 }) 367 | ); 368 | 369 | assert.deepEqual( 370 | resolveTaskBuilderStub.getCall(1).args[1], 371 | Object.assign({}, options, { family: Lookup.IPv6 }) 372 | ); 373 | 374 | assert.isTrue(cbSpy.calledOnce); 375 | 376 | assert.strictEqual(cbSpy.getCall(0).args.length, 3); 377 | assert.isNull(cbSpy.getCall(0).args[0]); 378 | assert.deepEqual(cbSpy.getCall(0).args.slice(1), ipv4records); 379 | 380 | assert.isTrue(makeNotFoundErrorSpy.notCalled); 381 | 382 | done(); 383 | }, 30); 384 | }); 385 | 386 | it('must return IPv6 address that was found with {all: false} option', done => { 387 | const options = { all: false }; 388 | 389 | const cbSpy = sinon.stub(); 390 | 391 | const ipv4records = []; 392 | const ipv6records = ['2001:0db8:85a3:0000:0000:8a2e:0370:7334', 6]; 393 | 394 | const resolveTaskBuilderStub = sinon.stub( 395 | lookup, 396 | '_resolveTaskBuilder' 397 | ); 398 | resolveTaskBuilderStub.onCall(0).callsFake(() => { 399 | return cb => { 400 | setImmediate(() => { 401 | cb(null, ipv4records); 402 | }); 403 | }; 404 | }); 405 | 406 | resolveTaskBuilderStub.onCall(1).callsFake(() => { 407 | return cb => { 408 | setImmediate(() => { 409 | cb(null, ipv6records); 410 | }); 411 | }; 412 | }); 413 | 414 | const makeNotFoundErrorSpy = sinon.spy(lookup, '_makeNotFoundError'); 415 | 416 | lookup._resolveBoth(hostname, options, cbSpy); 417 | 418 | setTimeout(() => { 419 | assert.isTrue(resolveTaskBuilderStub.calledTwice); 420 | 421 | assert.strictEqual( 422 | resolveTaskBuilderStub.getCall(0).args[0], 423 | hostname 424 | ); 425 | assert.strictEqual( 426 | resolveTaskBuilderStub.getCall(1).args[0], 427 | hostname 428 | ); 429 | 430 | assert.deepEqual( 431 | resolveTaskBuilderStub.getCall(0).args[1], 432 | Object.assign({}, options, { family: Lookup.IPv4 }) 433 | ); 434 | 435 | assert.deepEqual( 436 | resolveTaskBuilderStub.getCall(1).args[1], 437 | Object.assign({}, options, { family: Lookup.IPv6 }) 438 | ); 439 | 440 | assert.isTrue(cbSpy.calledOnce); 441 | 442 | assert.strictEqual(cbSpy.getCall(0).args.length, 3); 443 | assert.isNull(cbSpy.getCall(0).args[0]); 444 | assert.deepEqual(cbSpy.getCall(0).args.slice(1), ipv6records); 445 | 446 | assert.isTrue(makeNotFoundErrorSpy.notCalled); 447 | 448 | done(); 449 | }, 30); 450 | }); 451 | }); 452 | -------------------------------------------------------------------------------- /tests/Unit/Lookup/_resolveTaskBuilder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const dns = require('dns'); 4 | 5 | const { assert } = require('chai'); 6 | const sinon = require('sinon'); 7 | 8 | const Lookup = require('../../../src/Lookup'); 9 | 10 | describe('Unit: Lookup::_resolveTaskBuilder', () => { 11 | const hostname = Symbol(); 12 | const options = Symbol(); 13 | 14 | let lookup; 15 | 16 | beforeEach(() => { 17 | lookup = new Lookup(); 18 | }); 19 | 20 | it('must return new function every call', () => { 21 | const expectedNumberOfArguments = 1; 22 | 23 | const resolveTask1 = lookup._resolveTaskBuilder(); 24 | const resolveTask2 = lookup._resolveTaskBuilder(); 25 | 26 | assert.instanceOf(resolveTask1, Function); 27 | assert.instanceOf(resolveTask2, Function); 28 | 29 | assert.strictEqual(resolveTask1.length, expectedNumberOfArguments); 30 | assert.strictEqual(resolveTask2.length, expectedNumberOfArguments); 31 | 32 | assert.notStrictEqual(resolveTask1, resolveTask2); 33 | }); 34 | 35 | it(`must return function that calls '_resolve' under the hood and for ${ 36 | dns.NOTFOUND 37 | } error returns empty array`, () => { 38 | const error = new Error('some error'); 39 | error.code = dns.NOTFOUND; 40 | 41 | const cbSpy = sinon.spy(); 42 | const resolveSpy = sinon 43 | .stub(lookup, '_resolve') 44 | .callsFake((hostname, options, cb) => { 45 | assert.strictEqual(resolveSpy.getCall(0).args[0], hostname); 46 | assert.strictEqual(resolveSpy.getCall(0).args[1], options); 47 | 48 | cb(error); 49 | }); 50 | 51 | const resolveTask = lookup._resolveTaskBuilder(hostname, options); 52 | 53 | resolveTask(cbSpy); 54 | 55 | assert.isTrue(resolveSpy.calledOnce); 56 | assert.strictEqual(resolveSpy.getCall(0).args[0], hostname); 57 | assert.strictEqual(resolveSpy.getCall(0).args[1], options); 58 | assert.instanceOf(resolveSpy.getCall(0).args[2], Function); 59 | 60 | assert.isTrue(cbSpy.calledOnce); 61 | assert.isTrue(cbSpy.calledWithExactly(null, [])); 62 | }); 63 | 64 | it("must return function that calls '_resolve' under the hood and in case error calls callback with an error", () => { 65 | const error = new Error('some error'); 66 | 67 | const cbSpy = sinon.spy(); 68 | const resolveSpy = sinon 69 | .stub(lookup, '_resolve') 70 | .callsFake((hostname, options, cb) => { 71 | assert.strictEqual(resolveSpy.getCall(0).args[0], hostname); 72 | assert.strictEqual(resolveSpy.getCall(0).args[1], options); 73 | 74 | cb(error); 75 | }); 76 | 77 | const resolveTask = lookup._resolveTaskBuilder(hostname, options); 78 | 79 | resolveTask(cbSpy); 80 | 81 | assert.isTrue(resolveSpy.calledOnce); 82 | assert.strictEqual(resolveSpy.getCall(0).args[0], hostname); 83 | assert.strictEqual(resolveSpy.getCall(0).args[1], options); 84 | assert.instanceOf(resolveSpy.getCall(0).args[2], Function); 85 | 86 | assert.isTrue(cbSpy.calledOnce); 87 | assert.strictEqual(cbSpy.getCall(0).args.length, 1); 88 | assert.instanceOf(cbSpy.getCall(0).args[0], Error); 89 | assert.strictEqual(cbSpy.getCall(0).args[0].message, error.message); 90 | }); 91 | 92 | it("must return function that calls '_resolve' under the hood and in case no error calls callback with results", () => { 93 | const error = null; 94 | const results = [Symbol(), Symbol()]; 95 | 96 | const cbSpy = sinon.spy(); 97 | const resolveSpy = sinon 98 | .stub(lookup, '_resolve') 99 | .callsFake((hostname, options, cb) => { 100 | assert.strictEqual(resolveSpy.getCall(0).args[0], hostname); 101 | assert.strictEqual(resolveSpy.getCall(0).args[1], options); 102 | 103 | cb(error, ...results); 104 | }); 105 | 106 | const resolveTask = lookup._resolveTaskBuilder(hostname, options); 107 | 108 | resolveTask(cbSpy); 109 | 110 | assert.isTrue(resolveSpy.calledOnce); 111 | assert.strictEqual(resolveSpy.getCall(0).args[0], hostname); 112 | assert.strictEqual(resolveSpy.getCall(0).args[1], options); 113 | assert.instanceOf(resolveSpy.getCall(0).args[2], Function); 114 | 115 | assert.isTrue(cbSpy.calledOnce); 116 | assert.strictEqual(cbSpy.getCall(0).args.length, 3); 117 | assert.isNull(cbSpy.getCall(0).args[0]); 118 | assert.deepEqual(cbSpy.getCall(0).args.slice(1), results); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /tests/Unit/Lookup/run.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { assert } = require('chai'); 4 | const sinon = require('sinon'); 5 | 6 | const Lookup = require('../../../src/Lookup'); 7 | const addresses = require('../../addresses'); 8 | 9 | describe('Unit: Lookup::run', () => { 10 | let lookup; 11 | 12 | const hostname = addresses.INET_HOST1; 13 | 14 | beforeEach(() => { 15 | lookup = new Lookup(); 16 | }); 17 | 18 | const invalidSetOfOptions = [ 19 | { family: -1 }, 20 | { family: 0 }, 21 | { family: 3 }, 22 | { family: 5 }, 23 | { family: 7 } 24 | ]; 25 | 26 | invalidSetOfOptions.forEach(invalidOptions => { 27 | it(`must throw exception, cuz family option is invalid ${JSON.stringify( 28 | invalidOptions 29 | )}`, () => { 30 | const cb = () => {}; 31 | 32 | const resolveStub = sinon.stub(lookup, '_resolve'); 33 | const resolveBothStub = sinon.stub(lookup, '_resolveBoth'); 34 | 35 | assert.throws( 36 | () => { 37 | lookup.run(hostname, invalidOptions, cb); 38 | }, 39 | Error, 40 | 'invalid family number, must be one of the {4, 6} or undefined' 41 | ); 42 | 43 | assert.isTrue(resolveStub.notCalled); 44 | assert.isTrue(resolveBothStub.notCalled); 45 | }); 46 | }); 47 | 48 | const setOfOptions = [{ family: 4 }, { family: 6 }]; 49 | 50 | setOfOptions.forEach(options => { 51 | it(`must correct call _resolve method with appropriate params and options ${JSON.stringify( 52 | options 53 | )}`, () => { 54 | const cb = () => {}; 55 | 56 | const resolveStub = sinon.stub(lookup, '_resolve'); 57 | const resolveBothStub = sinon.stub(lookup, '_resolveBoth'); 58 | 59 | lookup.run(hostname, options, cb); 60 | 61 | assert.isTrue(resolveStub.calledOnce); 62 | assert.isTrue(resolveStub.calledWithExactly(hostname, options, cb)); 63 | 64 | assert.isTrue(resolveBothStub.notCalled); 65 | }); 66 | }); 67 | 68 | it('must correct call _resolveBoth method with appropriate params, cuz family value is undefined', () => { 69 | const options = { family: undefined }; 70 | const cb = () => {}; 71 | 72 | const resolveStub = sinon.stub(lookup, '_resolve'); 73 | const resolveBothStub = sinon.stub(lookup, '_resolveBoth'); 74 | 75 | lookup.run(hostname, options, cb); 76 | 77 | assert.isTrue(resolveStub.notCalled); 78 | 79 | assert.isTrue(resolveBothStub.calledOnce); 80 | assert.isTrue(resolveBothStub.calledWithExactly(hostname, options, cb)); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /tests/Unit/ResolveTask/_resolved.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { assert } = require('chai'); 4 | const sinon = require('sinon'); 5 | 6 | const ResolveTask = require('../../../src/ResolveTask'); 7 | 8 | const addresses = require('../../addresses'); 9 | 10 | describe('Unit: ResolveTask::_resolved', () => { 11 | const error = new Error('error'); 12 | 13 | const hostname = addresses.INET_HOST1; 14 | const ipVersion = 4; 15 | 16 | let onAddressesSpy; 17 | let onDoneSpy; 18 | 19 | beforeEach(() => { 20 | onAddressesSpy = sinon.spy(); 21 | onDoneSpy = sinon.spy(); 22 | }); 23 | 24 | it(`must correct call callbacks with appropriate error object, IPv${ipVersion} version`, done => { 25 | const expectedError = error; 26 | const expectedAddresses = undefined; 27 | 28 | const task = new ResolveTask(hostname, ipVersion); 29 | 30 | task.on('addresses', onAddressesSpy); 31 | task.on('done', onDoneSpy); 32 | 33 | const resolvedCallback = sinon.spy(); 34 | 35 | task._callbacks.push(resolvedCallback); 36 | 37 | task._resolved(error); 38 | 39 | assert.isTrue(onDoneSpy.calledOnce); 40 | assert.isTrue(onDoneSpy.calledWithExactly()); 41 | 42 | assert.isEmpty(task._callbacks); 43 | 44 | setImmediate(() => { 45 | assert.isTrue(resolvedCallback.calledOnce); 46 | assert.isTrue( 47 | resolvedCallback.calledWithExactly( 48 | expectedError, 49 | expectedAddresses 50 | ) 51 | ); 52 | 53 | assert.isTrue(onAddressesSpy.notCalled); 54 | 55 | done(); 56 | }); 57 | }); 58 | 59 | it(`must correct emit all events and run callbacks for IPv${ipVersion}`, done => { 60 | const task = new ResolveTask(hostname, ipVersion); 61 | 62 | task.on('addresses', onAddressesSpy); 63 | task.on('done', onDoneSpy); 64 | 65 | const addresses = [{}]; 66 | const resolvedCallback = sinon.spy(); 67 | 68 | task._callbacks.push(resolvedCallback); 69 | 70 | task._resolved(null, addresses); 71 | 72 | assert.isTrue(onAddressesSpy.calledOnce); 73 | assert.isTrue(onAddressesSpy.calledWithExactly(addresses)); 74 | 75 | assert.isTrue(onDoneSpy.calledOnce); 76 | assert.isTrue(onDoneSpy.calledWithExactly()); 77 | 78 | assert.isEmpty(task._callbacks); 79 | 80 | setImmediate(() => { 81 | assert.isTrue(resolvedCallback.calledOnce); 82 | assert.isTrue(resolvedCallback.calledWithExactly(null, addresses)); 83 | 84 | done(); 85 | }); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /tests/Unit/ResolveTask/addResolvedCallback.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { assert } = require('chai'); 4 | 5 | const ResolveTask = require('../../../src/ResolveTask'); 6 | const addresses = require('../../addresses'); 7 | 8 | describe('Unit: ResolveTask::addResolveCallback', () => { 9 | let task; 10 | 11 | const hostname = addresses.INET_HOST1; 12 | const ipVersion = 4; 13 | 14 | beforeEach(() => { 15 | task = new ResolveTask(hostname, ipVersion); 16 | }); 17 | 18 | it('must correct add callbacks to run when task has been resolved', () => { 19 | const resolvedCallback = () => {}; 20 | const expectedCallback = [resolvedCallback]; 21 | 22 | assert.isEmpty(task._callbacks); 23 | 24 | task.addResolvedCallback(resolvedCallback); 25 | 26 | assert.isTrue(task._callbacks.length === 1); 27 | assert.deepEqual(task._callbacks, expectedCallback); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /tests/Unit/ResolveTask/constructor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const dns = require('dns'); 4 | 5 | const { assert } = require('chai'); 6 | 7 | const ResolveTask = require('../../../src/ResolveTask'); 8 | const addresses = require('../../addresses'); 9 | 10 | describe('Unit: ResolveTask::constructor', () => { 11 | const hostname = addresses.INET_HOST1; 12 | const ipVersions = [4, 6]; 13 | 14 | ipVersions.forEach(ipVersion => { 15 | it(`must correct create resolve task for ipVersion === ${ipVersion}`, () => { 16 | const task = new ResolveTask(hostname, ipVersion); 17 | 18 | assert.strictEqual(task._hostname, hostname); 19 | assert.strictEqual(task._ipVersion, ipVersion); 20 | 21 | ipVersion === 4 && assert.strictEqual(task._resolver, dns.resolve4); 22 | ipVersion !== 4 && assert.strictEqual(task._resolver, dns.resolve6); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /tests/Unit/ResolveTask/run.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { assert } = require('chai'); 4 | const sinon = require('sinon'); 5 | 6 | const ResolveTask = require('../../../src/ResolveTask'); 7 | const addresses = require('../../addresses'); 8 | 9 | describe('Unit: ResolveTask::run', () => { 10 | const hostname = addresses.INET_HOST1; 11 | const ipVersion = 4; 12 | 13 | it('must run resolver with correct set of params', () => { 14 | const resolverSpy = sinon.spy(); 15 | 16 | const task = new ResolveTask(hostname, ipVersion); 17 | 18 | task._resolver = resolverSpy; 19 | 20 | task.run(); 21 | 22 | assert.isTrue(resolverSpy.calledOnce); 23 | assert.isTrue( 24 | resolverSpy.calledWithExactly( 25 | hostname, 26 | { ttl: true }, 27 | task._resolved 28 | ) 29 | ); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /tests/Unit/TasksManager/add.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { assert } = require('chai'); 4 | 5 | const TasksManager = require('../../../src/TasksManager'); 6 | const addresses = require('../../addresses'); 7 | 8 | describe('Unit: TasksManager::add', () => { 9 | it('must correct add task for particular hostname key', () => { 10 | const tasksManager = new TasksManager(); 11 | const task = {}; 12 | 13 | tasksManager.add(addresses.INET_HOST1, task); 14 | 15 | assert.strictEqual(tasksManager._tasks.get(addresses.INET_HOST1), task); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /tests/Unit/TasksManager/done.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { assert } = require('chai'); 4 | 5 | const TasksManager = require('../../../src/TasksManager'); 6 | const addresses = require('../../addresses'); 7 | 8 | describe('Unit: TasksManager::done', () => { 9 | it('must correct done task for particular hostname key', () => { 10 | const tasksManager = new TasksManager(); 11 | const task = {}; 12 | 13 | tasksManager._tasks.set(addresses.INET_HOST1, task); 14 | 15 | tasksManager.done(addresses.INET_HOST1); 16 | 17 | assert.isUndefined(tasksManager._tasks.get(addresses.INET_HOST1)); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/Unit/TasksManager/find.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { assert } = require('chai'); 4 | 5 | const TasksManager = require('../../../src/TasksManager'); 6 | const addresses = require('../../addresses'); 7 | 8 | describe('Unit: TasksManager::find', () => { 9 | let tasksManager; 10 | 11 | beforeEach(() => { 12 | tasksManager = new TasksManager(); 13 | }); 14 | 15 | it('must return undefined, cuz there is no task for such key', () => { 16 | const task = tasksManager.find(addresses.INET_HOST1); 17 | 18 | assert.isUndefined(task); 19 | }); 20 | 21 | it('must return task for particular key', () => { 22 | const task = {}; 23 | 24 | tasksManager._tasks.set(addresses.INET_HOST1, task); 25 | 26 | assert.strictEqual(tasksManager.find(addresses.INET_HOST1), task); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /tests/addresses.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const addresses = { 4 | INET_HOST1: 'nodejs.org', 5 | INET_HOST2: 'google.com', 6 | INET_HOST3: 'github.com', 7 | WWWINET_HOST3: 'www.github.com', 8 | INVALID_HOST: 'something.invalid' 9 | }; 10 | 11 | module.exports = addresses; 12 | -------------------------------------------------------------------------------- /tests/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter list 2 | --slow 50 3 | --check-leaks 4 | --recursive 5 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | abbrev@1: 6 | version "1.1.1" 7 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" 8 | 9 | abbrev@1.0.x: 10 | version "1.0.9" 11 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" 12 | 13 | acorn-jsx@^3.0.0: 14 | version "3.0.1" 15 | resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" 16 | dependencies: 17 | acorn "^3.0.4" 18 | 19 | acorn@^3.0.4: 20 | version "3.3.0" 21 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" 22 | 23 | acorn@^5.2.1: 24 | version "5.3.0" 25 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.3.0.tgz#7446d39459c54fb49a80e6ee6478149b940ec822" 26 | 27 | ajv-keywords@^2.1.0: 28 | version "2.1.1" 29 | resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" 30 | 31 | ajv@^5.2.3, ajv@^5.3.0: 32 | version "5.5.2" 33 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" 34 | dependencies: 35 | co "^4.6.0" 36 | fast-deep-equal "^1.0.0" 37 | fast-json-stable-stringify "^2.0.0" 38 | json-schema-traverse "^0.3.0" 39 | 40 | align-text@^0.1.1, align-text@^0.1.3: 41 | version "0.1.4" 42 | resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" 43 | dependencies: 44 | kind-of "^3.0.2" 45 | longest "^1.0.1" 46 | repeat-string "^1.5.2" 47 | 48 | amdefine@>=0.0.4: 49 | version "1.0.1" 50 | resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" 51 | 52 | ansi-escapes@^3.0.0: 53 | version "3.0.0" 54 | resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92" 55 | 56 | ansi-regex@^2.0.0: 57 | version "2.1.1" 58 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 59 | 60 | ansi-regex@^3.0.0: 61 | version "3.0.0" 62 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" 63 | 64 | ansi-styles@^2.2.1: 65 | version "2.2.1" 66 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" 67 | 68 | ansi-styles@^3.1.0: 69 | version "3.2.0" 70 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" 71 | dependencies: 72 | color-convert "^1.9.0" 73 | 74 | argparse@^1.0.7: 75 | version "1.0.9" 76 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" 77 | dependencies: 78 | sprintf-js "~1.0.2" 79 | 80 | array-union@^1.0.1: 81 | version "1.0.2" 82 | resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" 83 | dependencies: 84 | array-uniq "^1.0.1" 85 | 86 | array-uniq@^1.0.1: 87 | version "1.0.3" 88 | resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" 89 | 90 | arrify@^1.0.0: 91 | version "1.0.1" 92 | resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" 93 | 94 | assertion-error@^1.0.1: 95 | version "1.1.0" 96 | resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" 97 | 98 | async@1.x, async@^1.4.0: 99 | version "1.5.2" 100 | resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" 101 | 102 | async@2.6.0: 103 | version "2.6.0" 104 | resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" 105 | dependencies: 106 | lodash "^4.14.0" 107 | 108 | babel-code-frame@^6.22.0: 109 | version "6.26.0" 110 | resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" 111 | dependencies: 112 | chalk "^1.1.3" 113 | esutils "^2.0.2" 114 | js-tokens "^3.0.2" 115 | 116 | balanced-match@^1.0.0: 117 | version "1.0.0" 118 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 119 | 120 | brace-expansion@^1.1.7: 121 | version "1.1.8" 122 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" 123 | dependencies: 124 | balanced-match "^1.0.0" 125 | concat-map "0.0.1" 126 | 127 | browser-stdout@1.3.0: 128 | version "1.3.0" 129 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" 130 | 131 | caller-path@^0.1.0: 132 | version "0.1.0" 133 | resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" 134 | dependencies: 135 | callsites "^0.2.0" 136 | 137 | callsites@^0.2.0: 138 | version "0.2.0" 139 | resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" 140 | 141 | camelcase@^1.0.2: 142 | version "1.2.1" 143 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" 144 | 145 | center-align@^0.1.1: 146 | version "0.1.3" 147 | resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" 148 | dependencies: 149 | align-text "^0.1.3" 150 | lazy-cache "^1.0.3" 151 | 152 | chai@4.1.2: 153 | version "4.1.2" 154 | resolved "https://registry.yarnpkg.com/chai/-/chai-4.1.2.tgz#0f64584ba642f0f2ace2806279f4f06ca23ad73c" 155 | dependencies: 156 | assertion-error "^1.0.1" 157 | check-error "^1.0.1" 158 | deep-eql "^3.0.0" 159 | get-func-name "^2.0.0" 160 | pathval "^1.0.0" 161 | type-detect "^4.0.0" 162 | 163 | chalk@^1.1.3: 164 | version "1.1.3" 165 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" 166 | dependencies: 167 | ansi-styles "^2.2.1" 168 | escape-string-regexp "^1.0.2" 169 | has-ansi "^2.0.0" 170 | strip-ansi "^3.0.0" 171 | supports-color "^2.0.0" 172 | 173 | chalk@^2.0.0, chalk@^2.1.0: 174 | version "2.3.0" 175 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" 176 | dependencies: 177 | ansi-styles "^3.1.0" 178 | escape-string-regexp "^1.0.5" 179 | supports-color "^4.0.0" 180 | 181 | chardet@^0.4.0: 182 | version "0.4.2" 183 | resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" 184 | 185 | check-error@^1.0.1: 186 | version "1.0.2" 187 | resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" 188 | 189 | circular-json@^0.3.1: 190 | version "0.3.3" 191 | resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" 192 | 193 | cli-cursor@^2.1.0: 194 | version "2.1.0" 195 | resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" 196 | dependencies: 197 | restore-cursor "^2.0.0" 198 | 199 | cli-width@^2.0.0: 200 | version "2.2.0" 201 | resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" 202 | 203 | cliui@^2.1.0: 204 | version "2.1.0" 205 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" 206 | dependencies: 207 | center-align "^0.1.1" 208 | right-align "^0.1.1" 209 | wordwrap "0.0.2" 210 | 211 | co@^4.6.0: 212 | version "4.6.0" 213 | resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" 214 | 215 | color-convert@^1.9.0: 216 | version "1.9.1" 217 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" 218 | dependencies: 219 | color-name "^1.1.1" 220 | 221 | color-name@^1.1.1: 222 | version "1.1.3" 223 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 224 | 225 | commander@2.11.0: 226 | version "2.11.0" 227 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" 228 | 229 | concat-map@0.0.1: 230 | version "0.0.1" 231 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 232 | 233 | concat-stream@^1.6.0: 234 | version "1.6.0" 235 | resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" 236 | dependencies: 237 | inherits "^2.0.3" 238 | readable-stream "^2.2.2" 239 | typedarray "^0.0.6" 240 | 241 | core-util-is@~1.0.0: 242 | version "1.0.2" 243 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 244 | 245 | cross-spawn@^5.1.0: 246 | version "5.1.0" 247 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" 248 | dependencies: 249 | lru-cache "^4.0.1" 250 | shebang-command "^1.2.0" 251 | which "^1.2.9" 252 | 253 | debug@3.1.0, debug@^3.1.0: 254 | version "3.1.0" 255 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" 256 | dependencies: 257 | ms "2.0.0" 258 | 259 | decamelize@^1.0.0: 260 | version "1.2.0" 261 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" 262 | 263 | deep-eql@^3.0.0: 264 | version "3.0.1" 265 | resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" 266 | dependencies: 267 | type-detect "^4.0.0" 268 | 269 | deep-is@~0.1.3: 270 | version "0.1.3" 271 | resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" 272 | 273 | del@^2.0.2: 274 | version "2.2.2" 275 | resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" 276 | dependencies: 277 | globby "^5.0.0" 278 | is-path-cwd "^1.0.0" 279 | is-path-in-cwd "^1.0.0" 280 | object-assign "^4.0.1" 281 | pify "^2.0.0" 282 | pinkie-promise "^2.0.0" 283 | rimraf "^2.2.8" 284 | 285 | diff@3.3.1: 286 | version "3.3.1" 287 | resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" 288 | 289 | diff@^3.1.0: 290 | version "3.4.0" 291 | resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c" 292 | 293 | doctrine@^2.0.2: 294 | version "2.1.0" 295 | resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" 296 | dependencies: 297 | esutils "^2.0.2" 298 | 299 | escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: 300 | version "1.0.5" 301 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 302 | 303 | escodegen@1.8.x: 304 | version "1.8.1" 305 | resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018" 306 | dependencies: 307 | esprima "^2.7.1" 308 | estraverse "^1.9.1" 309 | esutils "^2.0.2" 310 | optionator "^0.8.1" 311 | optionalDependencies: 312 | source-map "~0.2.0" 313 | 314 | eslint-scope@^3.7.1: 315 | version "3.7.1" 316 | resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" 317 | dependencies: 318 | esrecurse "^4.1.0" 319 | estraverse "^4.1.1" 320 | 321 | eslint-visitor-keys@^1.0.0: 322 | version "1.0.0" 323 | resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" 324 | 325 | eslint@4.15.0: 326 | version "4.15.0" 327 | resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.15.0.tgz#89ab38c12713eec3d13afac14e4a89e75ef08145" 328 | dependencies: 329 | ajv "^5.3.0" 330 | babel-code-frame "^6.22.0" 331 | chalk "^2.1.0" 332 | concat-stream "^1.6.0" 333 | cross-spawn "^5.1.0" 334 | debug "^3.1.0" 335 | doctrine "^2.0.2" 336 | eslint-scope "^3.7.1" 337 | eslint-visitor-keys "^1.0.0" 338 | espree "^3.5.2" 339 | esquery "^1.0.0" 340 | esutils "^2.0.2" 341 | file-entry-cache "^2.0.0" 342 | functional-red-black-tree "^1.0.1" 343 | glob "^7.1.2" 344 | globals "^11.0.1" 345 | ignore "^3.3.3" 346 | imurmurhash "^0.1.4" 347 | inquirer "^3.0.6" 348 | is-resolvable "^1.0.0" 349 | js-yaml "^3.9.1" 350 | json-stable-stringify-without-jsonify "^1.0.1" 351 | levn "^0.3.0" 352 | lodash "^4.17.4" 353 | minimatch "^3.0.2" 354 | mkdirp "^0.5.1" 355 | natural-compare "^1.4.0" 356 | optionator "^0.8.2" 357 | path-is-inside "^1.0.2" 358 | pluralize "^7.0.0" 359 | progress "^2.0.0" 360 | require-uncached "^1.0.3" 361 | semver "^5.3.0" 362 | strip-ansi "^4.0.0" 363 | strip-json-comments "~2.0.1" 364 | table "^4.0.1" 365 | text-table "~0.2.0" 366 | 367 | espree@^3.5.2: 368 | version "3.5.2" 369 | resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.2.tgz#756ada8b979e9dcfcdb30aad8d1a9304a905e1ca" 370 | dependencies: 371 | acorn "^5.2.1" 372 | acorn-jsx "^3.0.0" 373 | 374 | esprima@2.7.x, esprima@^2.7.1: 375 | version "2.7.3" 376 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" 377 | 378 | esprima@^4.0.0: 379 | version "4.0.0" 380 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" 381 | 382 | esquery@^1.0.0: 383 | version "1.0.0" 384 | resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa" 385 | dependencies: 386 | estraverse "^4.0.0" 387 | 388 | esrecurse@^4.1.0: 389 | version "4.2.0" 390 | resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163" 391 | dependencies: 392 | estraverse "^4.1.0" 393 | object-assign "^4.0.1" 394 | 395 | estraverse@^1.9.1: 396 | version "1.9.3" 397 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" 398 | 399 | estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1: 400 | version "4.2.0" 401 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" 402 | 403 | esutils@^2.0.2: 404 | version "2.0.2" 405 | resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" 406 | 407 | external-editor@^2.0.4: 408 | version "2.1.0" 409 | resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.1.0.tgz#3d026a21b7f95b5726387d4200ac160d372c3b48" 410 | dependencies: 411 | chardet "^0.4.0" 412 | iconv-lite "^0.4.17" 413 | tmp "^0.0.33" 414 | 415 | fast-deep-equal@^1.0.0: 416 | version "1.0.0" 417 | resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" 418 | 419 | fast-json-stable-stringify@^2.0.0: 420 | version "2.0.0" 421 | resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" 422 | 423 | fast-levenshtein@~2.0.4: 424 | version "2.0.6" 425 | resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" 426 | 427 | figures@^2.0.0: 428 | version "2.0.0" 429 | resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" 430 | dependencies: 431 | escape-string-regexp "^1.0.5" 432 | 433 | file-entry-cache@^2.0.0: 434 | version "2.0.0" 435 | resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" 436 | dependencies: 437 | flat-cache "^1.2.1" 438 | object-assign "^4.0.1" 439 | 440 | fill-keys@^1.0.2: 441 | version "1.0.2" 442 | resolved "https://registry.yarnpkg.com/fill-keys/-/fill-keys-1.0.2.tgz#9a8fa36f4e8ad634e3bf6b4f3c8882551452eb20" 443 | dependencies: 444 | is-object "~1.0.1" 445 | merge-descriptors "~1.0.0" 446 | 447 | flat-cache@^1.2.1: 448 | version "1.3.0" 449 | resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.0.tgz#d3030b32b38154f4e3b7e9c709f490f7ef97c481" 450 | dependencies: 451 | circular-json "^0.3.1" 452 | del "^2.0.2" 453 | graceful-fs "^4.1.2" 454 | write "^0.2.1" 455 | 456 | formatio@1.2.0, formatio@^1.2.0: 457 | version "1.2.0" 458 | resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.2.0.tgz#f3b2167d9068c4698a8d51f4f760a39a54d818eb" 459 | dependencies: 460 | samsam "1.x" 461 | 462 | fs.realpath@^1.0.0: 463 | version "1.0.0" 464 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 465 | 466 | functional-red-black-tree@^1.0.1: 467 | version "1.0.1" 468 | resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" 469 | 470 | get-func-name@^2.0.0: 471 | version "2.0.0" 472 | resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" 473 | 474 | glob@7.1.2, glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: 475 | version "7.1.2" 476 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" 477 | dependencies: 478 | fs.realpath "^1.0.0" 479 | inflight "^1.0.4" 480 | inherits "2" 481 | minimatch "^3.0.4" 482 | once "^1.3.0" 483 | path-is-absolute "^1.0.0" 484 | 485 | glob@^5.0.15: 486 | version "5.0.15" 487 | resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" 488 | dependencies: 489 | inflight "^1.0.4" 490 | inherits "2" 491 | minimatch "2 || 3" 492 | once "^1.3.0" 493 | path-is-absolute "^1.0.0" 494 | 495 | globals@^11.0.1: 496 | version "11.1.0" 497 | resolved "https://registry.yarnpkg.com/globals/-/globals-11.1.0.tgz#632644457f5f0e3ae711807183700ebf2e4633e4" 498 | 499 | globby@^5.0.0: 500 | version "5.0.0" 501 | resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" 502 | dependencies: 503 | array-union "^1.0.1" 504 | arrify "^1.0.0" 505 | glob "^7.0.3" 506 | object-assign "^4.0.1" 507 | pify "^2.0.0" 508 | pinkie-promise "^2.0.0" 509 | 510 | graceful-fs@^4.1.2: 511 | version "4.1.11" 512 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" 513 | 514 | growl@1.10.3: 515 | version "1.10.3" 516 | resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f" 517 | 518 | handlebars@^4.0.1: 519 | version "4.0.11" 520 | resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" 521 | dependencies: 522 | async "^1.4.0" 523 | optimist "^0.6.1" 524 | source-map "^0.4.4" 525 | optionalDependencies: 526 | uglify-js "^2.6" 527 | 528 | has-ansi@^2.0.0: 529 | version "2.0.0" 530 | resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" 531 | dependencies: 532 | ansi-regex "^2.0.0" 533 | 534 | has-flag@^1.0.0: 535 | version "1.0.0" 536 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" 537 | 538 | has-flag@^2.0.0: 539 | version "2.0.0" 540 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" 541 | 542 | he@1.1.1: 543 | version "1.1.1" 544 | resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" 545 | 546 | iconv-lite@^0.4.17: 547 | version "0.4.19" 548 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" 549 | 550 | ignore@^3.3.3: 551 | version "3.3.7" 552 | resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021" 553 | 554 | imurmurhash@^0.1.4: 555 | version "0.1.4" 556 | resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" 557 | 558 | inflight@^1.0.4: 559 | version "1.0.6" 560 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 561 | dependencies: 562 | once "^1.3.0" 563 | wrappy "1" 564 | 565 | inherits@2, inherits@^2.0.3, inherits@~2.0.3: 566 | version "2.0.3" 567 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 568 | 569 | inquirer@^3.0.6: 570 | version "3.3.0" 571 | resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" 572 | dependencies: 573 | ansi-escapes "^3.0.0" 574 | chalk "^2.0.0" 575 | cli-cursor "^2.1.0" 576 | cli-width "^2.0.0" 577 | external-editor "^2.0.4" 578 | figures "^2.0.0" 579 | lodash "^4.3.0" 580 | mute-stream "0.0.7" 581 | run-async "^2.2.0" 582 | rx-lite "^4.0.8" 583 | rx-lite-aggregates "^4.0.8" 584 | string-width "^2.1.0" 585 | strip-ansi "^4.0.0" 586 | through "^2.3.6" 587 | 588 | is-buffer@^1.1.5: 589 | version "1.1.6" 590 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" 591 | 592 | is-fullwidth-code-point@^2.0.0: 593 | version "2.0.0" 594 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" 595 | 596 | is-object@~1.0.1: 597 | version "1.0.1" 598 | resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" 599 | 600 | is-path-cwd@^1.0.0: 601 | version "1.0.0" 602 | resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" 603 | 604 | is-path-in-cwd@^1.0.0: 605 | version "1.0.0" 606 | resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" 607 | dependencies: 608 | is-path-inside "^1.0.0" 609 | 610 | is-path-inside@^1.0.0: 611 | version "1.0.1" 612 | resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" 613 | dependencies: 614 | path-is-inside "^1.0.1" 615 | 616 | is-promise@^2.1.0: 617 | version "2.1.0" 618 | resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" 619 | 620 | is-resolvable@^1.0.0: 621 | version "1.0.1" 622 | resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.1.tgz#acca1cd36dbe44b974b924321555a70ba03b1cf4" 623 | 624 | isarray@0.0.1: 625 | version "0.0.1" 626 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" 627 | 628 | isarray@~1.0.0: 629 | version "1.0.0" 630 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 631 | 632 | isexe@^2.0.0: 633 | version "2.0.0" 634 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 635 | 636 | istanbul@0.4.5: 637 | version "0.4.5" 638 | resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.4.5.tgz#65c7d73d4c4da84d4f3ac310b918fb0b8033733b" 639 | dependencies: 640 | abbrev "1.0.x" 641 | async "1.x" 642 | escodegen "1.8.x" 643 | esprima "2.7.x" 644 | glob "^5.0.15" 645 | handlebars "^4.0.1" 646 | js-yaml "3.x" 647 | mkdirp "0.5.x" 648 | nopt "3.x" 649 | once "1.x" 650 | resolve "1.1.x" 651 | supports-color "^3.1.0" 652 | which "^1.1.1" 653 | wordwrap "^1.0.0" 654 | 655 | js-tokens@^3.0.2: 656 | version "3.0.2" 657 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" 658 | 659 | js-yaml@3.x, js-yaml@^3.9.1: 660 | version "3.10.0" 661 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" 662 | dependencies: 663 | argparse "^1.0.7" 664 | esprima "^4.0.0" 665 | 666 | json-schema-traverse@^0.3.0: 667 | version "0.3.1" 668 | resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" 669 | 670 | json-stable-stringify-without-jsonify@^1.0.1: 671 | version "1.0.1" 672 | resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" 673 | 674 | just-extend@^1.1.26: 675 | version "1.1.27" 676 | resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-1.1.27.tgz#ec6e79410ff914e472652abfa0e603c03d60e905" 677 | 678 | kind-of@^3.0.2: 679 | version "3.2.2" 680 | resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" 681 | dependencies: 682 | is-buffer "^1.1.5" 683 | 684 | lazy-cache@^1.0.3: 685 | version "1.0.4" 686 | resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" 687 | 688 | levn@^0.3.0, levn@~0.3.0: 689 | version "0.3.0" 690 | resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" 691 | dependencies: 692 | prelude-ls "~1.1.2" 693 | type-check "~0.3.2" 694 | 695 | lodash.get@^4.4.2: 696 | version "4.4.2" 697 | resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" 698 | 699 | lodash@^4.14.0, lodash@^4.17.4, lodash@^4.3.0: 700 | version "4.17.4" 701 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" 702 | 703 | lodash@^4.17.10: 704 | version "4.17.10" 705 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" 706 | 707 | lolex@^1.6.0: 708 | version "1.6.0" 709 | resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.6.0.tgz#3a9a0283452a47d7439e72731b9e07d7386e49f6" 710 | 711 | lolex@^2.2.0: 712 | version "2.3.1" 713 | resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.3.1.tgz#3d2319894471ea0950ef64692ead2a5318cff362" 714 | 715 | longest@^1.0.1: 716 | version "1.0.1" 717 | resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" 718 | 719 | lru-cache@^4.0.1: 720 | version "4.1.1" 721 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" 722 | dependencies: 723 | pseudomap "^1.0.2" 724 | yallist "^2.1.2" 725 | 726 | merge-descriptors@~1.0.0: 727 | version "1.0.1" 728 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 729 | 730 | mimic-fn@^1.0.0: 731 | version "1.1.0" 732 | resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" 733 | 734 | "minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.4: 735 | version "3.0.4" 736 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 737 | dependencies: 738 | brace-expansion "^1.1.7" 739 | 740 | minimist@0.0.8: 741 | version "0.0.8" 742 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 743 | 744 | minimist@~0.0.1: 745 | version "0.0.10" 746 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" 747 | 748 | mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@^0.5.1: 749 | version "0.5.1" 750 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 751 | dependencies: 752 | minimist "0.0.8" 753 | 754 | mocha@4.1.0: 755 | version "4.1.0" 756 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-4.1.0.tgz#7d86cfbcf35cb829e2754c32e17355ec05338794" 757 | dependencies: 758 | browser-stdout "1.3.0" 759 | commander "2.11.0" 760 | debug "3.1.0" 761 | diff "3.3.1" 762 | escape-string-regexp "1.0.5" 763 | glob "7.1.2" 764 | growl "1.10.3" 765 | he "1.1.1" 766 | mkdirp "0.5.1" 767 | supports-color "4.4.0" 768 | 769 | module-not-found-error@^1.0.0: 770 | version "1.0.1" 771 | resolved "https://registry.yarnpkg.com/module-not-found-error/-/module-not-found-error-1.0.1.tgz#cf8b4ff4f29640674d6cdd02b0e3bc523c2bbdc0" 772 | 773 | ms@2.0.0: 774 | version "2.0.0" 775 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 776 | 777 | mute-stream@0.0.7: 778 | version "0.0.7" 779 | resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" 780 | 781 | natural-compare@^1.4.0: 782 | version "1.4.0" 783 | resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" 784 | 785 | nise@^1.2.0: 786 | version "1.2.0" 787 | resolved "https://registry.yarnpkg.com/nise/-/nise-1.2.0.tgz#079d6cadbbcb12ba30e38f1c999f36ad4d6baa53" 788 | dependencies: 789 | formatio "^1.2.0" 790 | just-extend "^1.1.26" 791 | lolex "^1.6.0" 792 | path-to-regexp "^1.7.0" 793 | text-encoding "^0.6.4" 794 | 795 | nopt@3.x: 796 | version "3.0.6" 797 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" 798 | dependencies: 799 | abbrev "1" 800 | 801 | object-assign@^4.0.1: 802 | version "4.1.1" 803 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 804 | 805 | once@1.x, once@^1.3.0: 806 | version "1.4.0" 807 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 808 | dependencies: 809 | wrappy "1" 810 | 811 | onetime@^2.0.0: 812 | version "2.0.1" 813 | resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" 814 | dependencies: 815 | mimic-fn "^1.0.0" 816 | 817 | optimist@^0.6.1: 818 | version "0.6.1" 819 | resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" 820 | dependencies: 821 | minimist "~0.0.1" 822 | wordwrap "~0.0.2" 823 | 824 | optionator@^0.8.1, optionator@^0.8.2: 825 | version "0.8.2" 826 | resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" 827 | dependencies: 828 | deep-is "~0.1.3" 829 | fast-levenshtein "~2.0.4" 830 | levn "~0.3.0" 831 | prelude-ls "~1.1.2" 832 | type-check "~0.3.2" 833 | wordwrap "~1.0.0" 834 | 835 | os-tmpdir@~1.0.2: 836 | version "1.0.2" 837 | resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" 838 | 839 | path-is-absolute@^1.0.0: 840 | version "1.0.1" 841 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 842 | 843 | path-is-inside@^1.0.1, path-is-inside@^1.0.2: 844 | version "1.0.2" 845 | resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" 846 | 847 | path-to-regexp@^1.7.0: 848 | version "1.7.0" 849 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" 850 | dependencies: 851 | isarray "0.0.1" 852 | 853 | pathval@^1.0.0: 854 | version "1.1.0" 855 | resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" 856 | 857 | pify@^2.0.0: 858 | version "2.3.0" 859 | resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" 860 | 861 | pinkie-promise@^2.0.0: 862 | version "2.0.1" 863 | resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" 864 | dependencies: 865 | pinkie "^2.0.0" 866 | 867 | pinkie@^2.0.0: 868 | version "2.0.4" 869 | resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" 870 | 871 | pluralize@^7.0.0: 872 | version "7.0.0" 873 | resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" 874 | 875 | prelude-ls@~1.1.2: 876 | version "1.1.2" 877 | resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" 878 | 879 | prettier@1.9.2: 880 | version "1.9.2" 881 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.9.2.tgz#96bc2132f7a32338e6078aeb29727178c6335827" 882 | 883 | process-nextick-args@~1.0.6: 884 | version "1.0.7" 885 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" 886 | 887 | progress@^2.0.0: 888 | version "2.0.0" 889 | resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" 890 | 891 | proxyquire@1.8.0: 892 | version "1.8.0" 893 | resolved "https://registry.yarnpkg.com/proxyquire/-/proxyquire-1.8.0.tgz#02d514a5bed986f04cbb2093af16741535f79edc" 894 | dependencies: 895 | fill-keys "^1.0.2" 896 | module-not-found-error "^1.0.0" 897 | resolve "~1.1.7" 898 | 899 | pseudomap@^1.0.2: 900 | version "1.0.2" 901 | resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" 902 | 903 | readable-stream@^2.2.2: 904 | version "2.3.3" 905 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" 906 | dependencies: 907 | core-util-is "~1.0.0" 908 | inherits "~2.0.3" 909 | isarray "~1.0.0" 910 | process-nextick-args "~1.0.6" 911 | safe-buffer "~5.1.1" 912 | string_decoder "~1.0.3" 913 | util-deprecate "~1.0.1" 914 | 915 | repeat-string@^1.5.2: 916 | version "1.6.1" 917 | resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" 918 | 919 | require-uncached@^1.0.3: 920 | version "1.0.3" 921 | resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" 922 | dependencies: 923 | caller-path "^0.1.0" 924 | resolve-from "^1.0.0" 925 | 926 | resolve-from@^1.0.0: 927 | version "1.0.1" 928 | resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" 929 | 930 | resolve@1.1.x, resolve@~1.1.7: 931 | version "1.1.7" 932 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" 933 | 934 | restore-cursor@^2.0.0: 935 | version "2.0.0" 936 | resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" 937 | dependencies: 938 | onetime "^2.0.0" 939 | signal-exit "^3.0.2" 940 | 941 | right-align@^0.1.1: 942 | version "0.1.3" 943 | resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" 944 | dependencies: 945 | align-text "^0.1.1" 946 | 947 | rimraf@^2.2.8: 948 | version "2.6.2" 949 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" 950 | dependencies: 951 | glob "^7.0.5" 952 | 953 | rr@0.1.0: 954 | version "0.1.0" 955 | resolved "https://registry.yarnpkg.com/rr/-/rr-0.1.0.tgz#a18ec25ec94a67c35f210bb3a85d17914e79cd1e" 956 | 957 | run-async@^2.2.0: 958 | version "2.3.0" 959 | resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" 960 | dependencies: 961 | is-promise "^2.1.0" 962 | 963 | rx-lite-aggregates@^4.0.8: 964 | version "4.0.8" 965 | resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" 966 | dependencies: 967 | rx-lite "*" 968 | 969 | rx-lite@*, rx-lite@^4.0.8: 970 | version "4.0.8" 971 | resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" 972 | 973 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: 974 | version "5.1.1" 975 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" 976 | 977 | samsam@1.x: 978 | version "1.3.0" 979 | resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50" 980 | 981 | semver@^5.3.0: 982 | version "5.4.1" 983 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" 984 | 985 | shebang-command@^1.2.0: 986 | version "1.2.0" 987 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" 988 | dependencies: 989 | shebang-regex "^1.0.0" 990 | 991 | shebang-regex@^1.0.0: 992 | version "1.0.0" 993 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" 994 | 995 | signal-exit@^3.0.2: 996 | version "3.0.2" 997 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" 998 | 999 | sinon@4.1.4: 1000 | version "4.1.4" 1001 | resolved "https://registry.yarnpkg.com/sinon/-/sinon-4.1.4.tgz#36bb237bae38ddf9cc92dcc1b16c51e7785bbc9c" 1002 | dependencies: 1003 | diff "^3.1.0" 1004 | formatio "1.2.0" 1005 | lodash.get "^4.4.2" 1006 | lolex "^2.2.0" 1007 | nise "^1.2.0" 1008 | supports-color "^4.4.0" 1009 | type-detect "^4.0.5" 1010 | 1011 | slice-ansi@1.0.0: 1012 | version "1.0.0" 1013 | resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" 1014 | dependencies: 1015 | is-fullwidth-code-point "^2.0.0" 1016 | 1017 | source-map@^0.4.4: 1018 | version "0.4.4" 1019 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" 1020 | dependencies: 1021 | amdefine ">=0.0.4" 1022 | 1023 | source-map@~0.2.0: 1024 | version "0.2.0" 1025 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" 1026 | dependencies: 1027 | amdefine ">=0.0.4" 1028 | 1029 | source-map@~0.5.1: 1030 | version "0.5.7" 1031 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" 1032 | 1033 | sprintf-js@~1.0.2: 1034 | version "1.0.3" 1035 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 1036 | 1037 | string-width@^2.1.0, string-width@^2.1.1: 1038 | version "2.1.1" 1039 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" 1040 | dependencies: 1041 | is-fullwidth-code-point "^2.0.0" 1042 | strip-ansi "^4.0.0" 1043 | 1044 | string_decoder@~1.0.3: 1045 | version "1.0.3" 1046 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" 1047 | dependencies: 1048 | safe-buffer "~5.1.0" 1049 | 1050 | strip-ansi@^3.0.0: 1051 | version "3.0.1" 1052 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 1053 | dependencies: 1054 | ansi-regex "^2.0.0" 1055 | 1056 | strip-ansi@^4.0.0: 1057 | version "4.0.0" 1058 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" 1059 | dependencies: 1060 | ansi-regex "^3.0.0" 1061 | 1062 | strip-json-comments@~2.0.1: 1063 | version "2.0.1" 1064 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 1065 | 1066 | supports-color@4.4.0: 1067 | version "4.4.0" 1068 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" 1069 | dependencies: 1070 | has-flag "^2.0.0" 1071 | 1072 | supports-color@^2.0.0: 1073 | version "2.0.0" 1074 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" 1075 | 1076 | supports-color@^3.1.0: 1077 | version "3.2.3" 1078 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" 1079 | dependencies: 1080 | has-flag "^1.0.0" 1081 | 1082 | supports-color@^4.0.0, supports-color@^4.4.0: 1083 | version "4.5.0" 1084 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" 1085 | dependencies: 1086 | has-flag "^2.0.0" 1087 | 1088 | table@^4.0.1: 1089 | version "4.0.2" 1090 | resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" 1091 | dependencies: 1092 | ajv "^5.2.3" 1093 | ajv-keywords "^2.1.0" 1094 | chalk "^2.1.0" 1095 | lodash "^4.17.4" 1096 | slice-ansi "1.0.0" 1097 | string-width "^2.1.1" 1098 | 1099 | text-encoding@^0.6.4: 1100 | version "0.6.4" 1101 | resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19" 1102 | 1103 | text-table@~0.2.0: 1104 | version "0.2.0" 1105 | resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" 1106 | 1107 | through@^2.3.6: 1108 | version "2.3.8" 1109 | resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" 1110 | 1111 | tmp@^0.0.33: 1112 | version "0.0.33" 1113 | resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" 1114 | dependencies: 1115 | os-tmpdir "~1.0.2" 1116 | 1117 | type-check@~0.3.2: 1118 | version "0.3.2" 1119 | resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" 1120 | dependencies: 1121 | prelude-ls "~1.1.2" 1122 | 1123 | type-detect@^4.0.0, type-detect@^4.0.5: 1124 | version "4.0.6" 1125 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.6.tgz#88cbce3d13bc675a63f840b3225c180f870786d7" 1126 | 1127 | typedarray@^0.0.6: 1128 | version "0.0.6" 1129 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" 1130 | 1131 | uglify-js@^2.6: 1132 | version "2.8.29" 1133 | resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" 1134 | dependencies: 1135 | source-map "~0.5.1" 1136 | yargs "~3.10.0" 1137 | optionalDependencies: 1138 | uglify-to-browserify "~1.0.0" 1139 | 1140 | uglify-to-browserify@~1.0.0: 1141 | version "1.0.2" 1142 | resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" 1143 | 1144 | util-deprecate@~1.0.1: 1145 | version "1.0.2" 1146 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 1147 | 1148 | which@^1.1.1, which@^1.2.9: 1149 | version "1.3.0" 1150 | resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" 1151 | dependencies: 1152 | isexe "^2.0.0" 1153 | 1154 | window-size@0.1.0: 1155 | version "0.1.0" 1156 | resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" 1157 | 1158 | wordwrap@0.0.2: 1159 | version "0.0.2" 1160 | resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" 1161 | 1162 | wordwrap@^1.0.0, wordwrap@~1.0.0: 1163 | version "1.0.0" 1164 | resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" 1165 | 1166 | wordwrap@~0.0.2: 1167 | version "0.0.3" 1168 | resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" 1169 | 1170 | wrappy@1: 1171 | version "1.0.2" 1172 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 1173 | 1174 | write@^0.2.1: 1175 | version "0.2.1" 1176 | resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" 1177 | dependencies: 1178 | mkdirp "^0.5.1" 1179 | 1180 | yallist@^2.1.2: 1181 | version "2.1.2" 1182 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" 1183 | 1184 | yargs@~3.10.0: 1185 | version "3.10.0" 1186 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" 1187 | dependencies: 1188 | camelcase "^1.0.2" 1189 | cliui "^2.1.0" 1190 | decamelize "^1.0.0" 1191 | window-size "0.1.0" 1192 | --------------------------------------------------------------------------------