From d9b6fd88cfa5806b1e37de1bddc4bc79b9053430 Mon Sep 17 00:00:00 2001 From: nikolay-borzov Date: Mon, 20 Apr 2020 23:09:00 +0400 Subject: [PATCH] Remember last torrent ID - Implement remembering last torrent ID for the specified directory. Setting `rememberLastTorrent: true` makes `torrent-clean` save last torrent ID value to `lastTorrent` property inside config file. `lastTorrent` value is used when `--torrent` argument is not set. - Make `--torrent` and `torrentId` optional. Throw error when `torrentId` cannot be received from `lastTorrent` property - Add some spacing in CLI output - Return "Parsed TORRENT_NAME" CLI output - Add `nyc` to collect coverage - Update dependencies --- .gitignore | 4 + README.md | 17 +- bin/torrent-clean.js | 36 +- lib/api.js | 69 +- lib/config.js | 145 ++++ lib/helpers.js | 50 ++ lib/load-config.js | 118 --- lib/parse-torrent.js | 7 +- package-lock.json | 1256 ++++++++++++++++++++++++--- package.json | 22 +- tests/api.test.js | 188 +++- tests/config.test.js | 225 +++++ tests/load-config.test.js | 87 -- tests/snapshots/config.test.js.md | 61 ++ tests/snapshots/config.test.js.snap | Bin 0 -> 313 bytes 15 files changed, 1897 insertions(+), 388 deletions(-) create mode 100644 lib/config.js create mode 100644 lib/helpers.js delete mode 100644 lib/load-config.js create mode 100644 tests/config.test.js delete mode 100644 tests/load-config.test.js create mode 100644 tests/snapshots/config.test.js.md create mode 100644 tests/snapshots/config.test.js.snap diff --git a/.gitignore b/.gitignore index 6a37ef8..e454479 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ node_modules/ # Optional eslint cache .eslintcache + +# nyc test coverage +.nyc_output +coverage diff --git a/README.md b/README.md index 74fd1bb..dee1d1a 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ gets files' paths from `nature-pack.torrent` and compares them with files from ` ## Arguments -`--torrent` (or `-t`) - Torrent id (as described in [webtorrent api](https://github.com/webtorrent/webtorrent/blob/master/docs/api.md#clientaddtorrentid-opts-function-ontorrent-torrent-)) +`--torrent` (or `-t`) - Torrent ID (as described in [webtorrent api](https://github.com/webtorrent/webtorrent/blob/master/docs/api.md#clientaddtorrentid-opts-function-ontorrent-torrent-)) - Magnet URI (e.g. `magnet:?xt=urn:btih:d2474e86c95b19b8bcfdb92bc12c9d44667cfa36`) - Info Hash (e.g. `d2474e86c95b19b8bcfdb92bc12c9d44667cfa36`) - http/https URL to a torrent file @@ -52,10 +52,11 @@ gets files' paths from `nature-pack.torrent` and compares them with files from ` ## Config files -`torrent-clean` allows specifying some parameters via config file (`.torrent-cleanrc`, `.torrent-cleanrc.json`, `.torrent-cleanrc.yaml`, `.torrent-cleanrc.yml` or `.torrent-cleanrc.js`). There are might be many files - `torrent-clean` will collect and merge all files up to root directory. The closer config to the directory is, the higher its priority +`torrent-clean` allows specifying some parameters via config file (`.torrent-cleanrc`, `.torrent-cleanrc.json`, `.torrent-cleanrc.yaml`, `.torrent-cleanrc.yml`). There might be many files - `torrent-clean` will collect and merge all files up to root directory. The closer config to the directory is, the higher its priority. Parameter are: - - `ignore` - an array of globs (picomatch) or filenames that will be excluded from the list of extra files. + - `ignore: string[]` - an array of globs (picomatch) or filenames that will be excluded from the list of extra files. + - `rememberLastTorrent: boolean` - Enable remembering last specified torrent ID for specified directory to `lastTorrent` config parameter. Only string values are saved. `lastTorrent` is used when `--torrent` argument is not set. ## API `cleanTorrentDir` accepts options object: @@ -63,11 +64,13 @@ Parameter are: { torrentId: '6a9759bffd5c0af65319979fb7832189f4f3c35d', // Directory to clean - directoryPath: 'C:/Downloads/wallpapers/nature', + dirPath: 'C:/Downloads/wallpapers/nature', // Do not delete files immediately. Instead return `deleteFiles` function dryRun: true, // Config with highest priority - customConfig: { ignore: ['**/*\(edited\)*'] } + customConfig: { ignore: ['**/*\(edited\)*'] }, + // Called when config is loaded + onConfigLoaded({ torrent }) { console.log(`Parsed ${torrent}`) } } ``` @@ -77,7 +80,7 @@ const cleanTorrentDir = require('torrent-clean') const { extraFiles } = await cleanTorrentDir({ torrentId: 'C:/Downloads/torrents/nature wallpapers.torrent', - directoryPath: 'C:/Downloads/wallpapers/nature' + dirPath: 'C:/Downloads/wallpapers/nature' }) console.log('Removed', extraFiles) @@ -88,7 +91,7 @@ const cleanTorrentDir = require('torrent-clean') const { extraFiles, deleteFiles } = await cleanTorrentDir({ torrentId: '6a9759bffd5c0af65319979fb7832189f4f3c35d', - directoryPath: 'C:/Downloads/wallpapers/nature', + dirPath: 'C:/Downloads/wallpapers/nature', dryRun: true, customConfig: { ignore: ['**/*\(edited\)*'] } }) diff --git a/bin/torrent-clean.js b/bin/torrent-clean.js index 06ef849..d926dcc 100644 --- a/bin/torrent-clean.js +++ b/bin/torrent-clean.js @@ -20,30 +20,34 @@ const argv = require('minimist')(process.argv.slice(2), { if (argv.version) { console.log(packageJson.version) } else { - console.log(logColor.info.bold('dir:'.padEnd(10)), argv.dir) - console.log(logColor.info.bold('torrent:'.padEnd(10)), argv.torrent, os.EOL) - - if (!argv.torrent) { - throw new Error( - logColor.error(`${chalk.bold('torrent')} argument is required`) - ) - } - const torrentId = argv.torrent - const directoryPath = path.resolve(argv.dir) - - console.log(logColor.info('Parsing torrent file...')) + const dirPath = path.resolve(argv.dir) + + cleanTorrentDir({ + torrentId, + dirPath, + dryRun: true, + onConfigLoaded(torrentId) { + console.log(logColor.info.bold('directory:'.padEnd(10)), argv.dir) + console.log(logColor.info.bold('torrent:'.padEnd(10)), torrentId, os.EOL) + + console.log(logColor.info('Parsing torrent file...'), os.EOL) + }, + }) + .then(async ({ torrentName, extraFiles, deleteFiles }) => { + console.log(`Parsed ${chalk.bold(torrentName)}`) - cleanTorrentDir({ torrentId, directoryPath, dryRun: true }) - .then(async ({ extraFiles, deleteFiles }) => { if (extraFiles.length === 0) { console.log('No extra files found!') return } - console.log(`Found ${chalk.bold(extraFiles.length)} extra file(s).`) + console.log( + `Found ${chalk.bold(extraFiles.length)} extra file(s).`, + os.EOL + ) - const dirRoot = `${directoryPath}${path.sep}` + const dirRoot = `${dirPath}${path.sep}` const filesChoices = extraFiles.map((filename) => ({ name: filename.replace(dirRoot, ''), diff --git a/lib/api.js b/lib/api.js index 84163e7..bc10a04 100644 --- a/lib/api.js +++ b/lib/api.js @@ -1,58 +1,81 @@ const path = require('path') -const { loadConfig } = require('./load-config') +const { loadConfig, saveConfig } = require('./config') const { getTorrentMetadata } = require('./parse-torrent') const { readDir } = require('./read-dir') const { deleteFilesAndEmptyDirs } = require('./delete-files') /** - * @typedef {import('./load-config').TorrentCleanConfig} TorrentCleanConfig + * @typedef {import('./config').TorrentCleanConfig} TorrentCleanConfig + * @typedef {import('./config').LoadConfigResult} LoadConfigResult * @typedef {import('./parse-torrent').TorrentId} TorrentId */ /** * @typedef {object} TorrentCleanOptions - * @property {TorrentId} torrentId Torrent id - * (as described in {@link https://github.com/webtorrent/webtorrent/blob/master/docs/api.md#clientaddtorrentid-opts-function-ontorrent-torrent-|webtorrent api}) - * @property {string} directoryPath Path to directory with downloaded files + * @property {TorrentId} [torrentId] Torrent ID + * (as described in {@link https://github.com/webtorrent/webtorrent/blob/master/docs/api.md#clientaddtorrentid-opts-function-ontorrent-torrent-|webtorrent api}). + * If empty `lastTorrent` value from [config]{@link TorrentCleanConfig} is used + * (only when `rememberLastTorrent` is `true`) + * @property {string} dirPath Path to directory with downloaded files * @property {boolean} [dryRun] Postpone deleting files until confirm function * is called * @property {TorrentCleanConfig} [customConfig] Config that will be merged with * configs collected from files + * @property {(torrentId: TorrentId) => void} [onConfigLoaded] Called when + * config is parsed */ /** * @typedef {object} TorrentCleanResult + * @property {string} torrentName Parsed torrent name * @property {string[]} extraFiles Extra files' paths * @property {(files: string[]) => Promise} [deleteFiles] Function to * delete files in the directory. Only set if `dryRun` is set to `true` */ /** - * Parses `torrentId` and finds files inside `directoryPath` not listed in the torrent. + * Parses `torrentId`, finds and deletes files inside `dirPath` not listed + * in the torrent. * * @param {TorrentCleanOptions} options * @returns {Promise} */ async function cleanTorrentDir({ torrentId, - directoryPath, + dirPath, customConfig, dryRun, + onConfigLoaded, }) { - /** @type {TorrentCleanConfig} */ - let config + /** @type {LoadConfigResult} */ + let loadConfigResult try { - config = loadConfig(directoryPath, customConfig) + loadConfigResult = await loadConfig(dirPath, customConfig) } catch (error) { error.message = `Cannot parse config file ${error.message}` throw error } + const { config, searchFromCosmiconfigResult } = loadConfigResult + + // Use `lastTorrent` only if `rememberLastTorrent` is enabled + if (!torrentId && config.rememberLastTorrent) { + torrentId = config.lastTorrent + } + + if (!torrentId) { + throw new Error(`'torrentId' is required`) + } + + if (onConfigLoaded) { + onConfigLoaded(torrentId) + } + return Promise.all([ getTorrentMetadata(torrentId), - readDir(directoryPath, config.ignore), + readDir(dirPath, config.ignore), ]).then(async ([parseResult, dirFiles]) => { if (!parseResult) { throw new Error('Unable to parse torrent') @@ -60,11 +83,8 @@ async function cleanTorrentDir({ const { name, files } = parseResult - const rootDir = `${name}${path.sep}` // Get absolute paths of torrent files - const torrentFiles = files.map((file) => - path.join(directoryPath, file.replace(rootDir, '')) - ) + const torrentFiles = files.map((file) => path.join(dirPath, file)) // Get files not listed in the torrent const extraFiles = dirFiles.filter( @@ -72,13 +92,28 @@ async function cleanTorrentDir({ ) if (!dryRun) { - await deleteFilesAndEmptyDirs(directoryPath, extraFiles) + await deleteFilesAndEmptyDirs(dirPath, extraFiles) + } + + if (config.rememberLastTorrent && typeof torrentId === 'string') { + const { config: searchFromConfig = {}, filepath } = + searchFromCosmiconfigResult || {} + + saveConfig({ + config: { + ...searchFromConfig, + lastTorrent: torrentId, + }, + saveDirPath: dirPath, + ...(filepath && { existingConfigPath: filepath }), + }) } return { + torrentName: name, extraFiles, ...(dryRun && { - deleteFiles: deleteFilesAndEmptyDirs.bind(null, directoryPath), + deleteFiles: deleteFilesAndEmptyDirs.bind(null, dirPath), }), } }) diff --git a/lib/config.js b/lib/config.js new file mode 100644 index 0000000..c2580e4 --- /dev/null +++ b/lib/config.js @@ -0,0 +1,145 @@ +const path = require('path') +const fs = require('fs') +const { cosmiconfig } = require('cosmiconfig') +const YAML = require('yaml') + +const { merge } = require('./helpers') + +/** + * @typedef {import('cosmiconfig/dist/types').CosmiconfigResult} CosmiconfigResult + */ + +/** + * @typedef {object} TorrentCleanConfig + * @property {string[]} [ignore] Globs to ignore files + * @property {boolean} [rememberLastTorrent] Save last specified torrent ID to config + * @property {string} [lastTorrent] Last specified torrent ID + */ + +/** + * @typedef {object} LoadConfigResult + * @property {TorrentCleanConfig} config Merged config + * @property {CosmiconfigResult} [searchFromCosmiconfigResult] `cosmiconfig` + * search result inside `searchFrom` + */ + +/** @typedef {'json' | 'yaml'} FileType */ + +const MODULE_NAME = 'torrent-clean' + +const COSMICONFIG_SEARCH_PLACES = [ + `.${MODULE_NAME}rc`, + `.${MODULE_NAME}rc.json`, + `.${MODULE_NAME}rc.yaml`, + `.${MODULE_NAME}rc.yml`, +] +/** Default ignore patterns */ +const IGNORE_GLOBS = ['~uTorrentPartFile*', `.${MODULE_NAME}rc*`] + +/** + * Loads and merges configs starting from `searchFrom` directory + * + * @param {string} searchFrom Config search start directory + * @param {TorrentCleanConfig} [customConfig] Config with highest priority + * @returns {Promise} + */ +exports.loadConfig = async function loadConfig(searchFrom, customConfig) { + const explorer = cosmiconfig(MODULE_NAME, { + searchPlaces: COSMICONFIG_SEARCH_PLACES, + }) + + /** @type {CosmiconfigResult} */ + let searchFromCosmiconfigResult + + /** @type {CosmiconfigResult[]} */ + const results = [] + /** @type {CosmiconfigResult} */ + let result = await explorer.search(searchFrom) + + // Remember `startFrom` search config result + if (result && path.dirname(result.filepath) === searchFrom) { + searchFromCosmiconfigResult = result + } + + // Collect all configs up to root directory + while (result) { + results.push(result) + + const configDirName = path.dirname(result.filepath) + const parentDir = path.resolve(configDirName, '..') + + result = await explorer.search(parentDir) + } + + /** @type {TorrentCleanConfig} */ + const mergedConfig = [ + // The closer config to `searchFrom` directory is, the higher its priority + ...results.map((result) => result.config).reverse(), + customConfig, + ] + .filter(Boolean) + .reduce(merge, {}) + + if (mergedConfig.ignore) { + mergedConfig.ignore = [...IGNORE_GLOBS, ...mergedConfig.ignore] + } else { + mergedConfig.ignore = IGNORE_GLOBS + } + + return { + config: mergedConfig, + ...(searchFromCosmiconfigResult && { searchFromCosmiconfigResult }), + } +} + +/** + * Detects file type by file path + * + * @param {string} filepath + * @returns {FileType} + */ +function getFileType(filepath) { + const extension = path.extname(filepath) + + if (extension === '.json') { + return 'json' + } else if (['.yaml', '.yml'].includes(extension)) { + return 'yaml' + } else { + const content = fs.readFileSync(filepath).toString() + + try { + JSON.parse(content) + return 'json' + } catch { + return 'yaml' + } + } +} + +/** + * Saves config + * + * @param {object} params + * @param {TorrentCleanConfig} params.config + * @param {string} params.saveDirPath + * @param {string} [params.existingConfigPath] Path to existing config file + */ +exports.saveConfig = function saveConfig({ + config, + saveDirPath, + existingConfigPath, +}) { + /** @type {FileType} */ + const fileType = existingConfigPath ? getFileType(existingConfigPath) : 'yaml' + + const fileContent = + fileType === 'json' + ? JSON.stringify(config, null, 2) + : YAML.stringify(config) + + const filename = + existingConfigPath || path.join(saveDirPath, `.${MODULE_NAME}rc`) + + fs.writeFileSync(filename, fileContent) +} diff --git a/lib/helpers.js b/lib/helpers.js new file mode 100644 index 0000000..32a0d36 --- /dev/null +++ b/lib/helpers.js @@ -0,0 +1,50 @@ +/** + * Naive check for Object + * + * @param {object} value + * @returns {boolean} + */ +function isObject(value) { + return typeof value === 'object' && !Array.isArray(value) +} + +/** + * Checks if array consists of unique elements + * + * @param {*} value + * @param {number} index `value` index in `array` + * @param {Array<*>} array + * @returns {boolean} + */ +function isUnique(value, index, array) { + return array.indexOf(value) === index +} + +/** + * Merges two objects recursively + * + * @param {object} a + * @param {object} b + * @returns {object} Merged object + */ +exports.merge = function merge(a, b) { + return Object.keys(b).reduce((target, prop) => { + const srcValue = b[prop] + const targetValue = target[prop] + let value + + if (targetValue === undefined) { + value = srcValue + } else if (isObject(targetValue)) { + value = merge(targetValue, srcValue) + } else if (Array.isArray(targetValue)) { + value = [...srcValue, ...targetValue].filter(isUnique) + } else { + value = srcValue + } + + target[prop] = value + + return target + }, a) +} diff --git a/lib/load-config.js b/lib/load-config.js deleted file mode 100644 index 7855541..0000000 --- a/lib/load-config.js +++ /dev/null @@ -1,118 +0,0 @@ -const path = require('path') -const { cosmiconfigSync } = require('cosmiconfig') - -/** - * @typedef {import('cosmiconfig/dist/types').CosmiconfigResult} CosmiconfigResult - */ - -/** - * @typedef {object} TorrentCleanConfig - * @property {string[]} [ignore] Globs to ignore files - * @property {boolean} [rememberLastTorrent] Save last specified torrent ID to config - */ - -const MODULE_NAME = 'torrent-clean' -/** Default ignore patterns */ -const IGNORE_GLOBS = ['~uTorrentPartFile*', `.${MODULE_NAME}rc*`] - -/** - * Merges two objects recursively - * - * @param {object} a - * @param {object} b - * @returns {object} Merged object - */ -function merge(a, b) { - return Object.keys(b).reduce((target, prop) => { - const srcValue = b[prop] - const targetValue = target[prop] - let value - - if (targetValue === undefined) { - value = srcValue - } else if (isObject(targetValue)) { - value = merge(targetValue, srcValue) - } else if (Array.isArray(targetValue)) { - value = [...srcValue, ...targetValue].filter(isUnique) - } else { - value = srcValue - } - - target[prop] = value - - return target - }, a) -} - -/** - * Naive check for Object - * - * @param {object} value - * @returns {boolean} - */ -function isObject(value) { - return typeof value === 'object' && !Array.isArray(value) -} - -/** - * Checks if array consists of unique elements - * - * @param {*} value - * @param {number} index `value` index in `array` - * @param {Array<*>} array - * @returns {boolean} - */ -function isUnique(value, index, array) { - return array.indexOf(value) === index -} - -/** - * Loads and merges configs starting from `searchFrom` directory - * - * @param {string} searchFrom - * @param {TorrentCleanConfig} [customConfig] Config with highest priority - * @returns {TorrentCleanConfig} - */ -exports.loadConfig = function loadConfig(searchFrom, customConfig) { - const explorer = cosmiconfigSync(MODULE_NAME, { - searchPlaces: [ - `.${MODULE_NAME}rc`, - `.${MODULE_NAME}rc.json`, - `.${MODULE_NAME}rc.yaml`, - `.${MODULE_NAME}rc.yml`, - `.${MODULE_NAME}rc.js`, - ], - }) - - /** @type {CosmiconfigResult[]} */ - const results = [] - /** @type {CosmiconfigResult} */ - let result = explorer.search(searchFrom) - - // Collect all configs up to root directory - while (result) { - results.push(result) - - const configDirName = path.dirname(result.filepath) - const parentDir = path.resolve(configDirName, '..') - - result = explorer.search(parentDir) - } - - /** @type {TorrentCleanConfig} */ - const mergedConfig = [ - // The closer config to `searchFrom` directory is, the higher its priority - ...results.map((result) => result.config).reverse(), - customConfig, - ] - .filter(Boolean) - .reduce(merge, {}) - - if (mergedConfig.ignore) { - mergedConfig.ignore = [...IGNORE_GLOBS, ...mergedConfig.ignore] - } else { - mergedConfig.ignore = IGNORE_GLOBS - } - - return mergedConfig -} diff --git a/lib/parse-torrent.js b/lib/parse-torrent.js index 8a31a95..64b8413 100644 --- a/lib/parse-torrent.js +++ b/lib/parse-torrent.js @@ -1,5 +1,6 @@ const WebTorrent = require('webtorrent') const memoryChunkStore = require('memory-chunk-store') +const path = require('path') /** * Magnet URI, .torrent file, info hash @@ -10,7 +11,7 @@ const memoryChunkStore = require('memory-chunk-store') /** * @typedef {object} TorrentParseResult * @property {string} name Torrent name - * @property {string[]} files Torrent files' paths + * @property {string[]} files Torrent relative filenames */ /** @@ -43,9 +44,11 @@ function parseTorrent(torrentId, onDone, onError) { }) torrent.on('metadata', () => { + const rootDir = `${torrent.name}${path.sep}` + onDone({ name: torrent.name, - files: torrent.files.map((file) => file.path), + files: torrent.files.map((file) => file.path.replace(rootDir, '')), }) client.destroy() diff --git a/package-lock.json b/package-lock.json index fec0abf..4cb8273 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@nikolay-borzov/torrent-clean", - "version": "1.6.0", + "version": "1.7.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -12,6 +12,165 @@ "@babel/highlight": "^7.0.0" } }, + "@babel/core": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz", + "integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.0", + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helpers": "^7.9.0", + "@babel/parser": "^7.9.0", + "@babel/template": "^7.8.6", + "@babel/traverse": "^7.9.0", + "@babel/types": "^7.9.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/generator": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.5.tgz", + "integrity": "sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ==", + "dev": true, + "requires": { + "@babel/types": "^7.9.5", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz", + "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.9.5" + } + }, + "@babel/highlight": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", + "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==", + "dev": true + }, + "@babel/traverse": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.5.tgz", + "integrity": "sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.5", + "@babel/helper-function-name": "^7.9.5", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.9.0", + "@babel/types": "^7.9.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.5.tgz", + "integrity": "sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.5", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "@babel/generator": { "version": "7.8.8", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.8.tgz", @@ -44,6 +203,83 @@ "@babel/types": "^7.8.3" } }, + "@babel/helper-member-expression-to-functions": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", + "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-module-imports": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", + "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-module-transforms": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz", + "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.6", + "@babel/helper-simple-access": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/template": "^7.8.6", + "@babel/types": "^7.9.0", + "lodash": "^4.17.13" + }, + "dependencies": { + "@babel/types": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.5.tgz", + "integrity": "sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.5", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", + "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-replace-supers": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz", + "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.8.3", + "@babel/helper-optimise-call-expression": "^7.8.3", + "@babel/traverse": "^7.8.6", + "@babel/types": "^7.8.6" + } + }, + "@babel/helper-simple-access": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", + "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", + "dev": true, + "requires": { + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, "@babel/helper-split-export-declaration": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", @@ -53,6 +289,152 @@ "@babel/types": "^7.8.3" } }, + "@babel/helper-validator-identifier": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", + "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==", + "dev": true + }, + "@babel/helpers": { + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.2.tgz", + "integrity": "sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==", + "dev": true, + "requires": { + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.9.0", + "@babel/types": "^7.9.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/generator": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.5.tgz", + "integrity": "sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ==", + "dev": true, + "requires": { + "@babel/types": "^7.9.5", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz", + "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.9.5" + } + }, + "@babel/highlight": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", + "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==", + "dev": true + }, + "@babel/traverse": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.5.tgz", + "integrity": "sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.5", + "@babel/helper-function-name": "^7.9.5", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.9.0", + "@babel/types": "^7.9.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.5.tgz", + "integrity": "sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.5", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "@babel/highlight": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", @@ -116,9 +498,9 @@ "dev": true }, "@babel/runtime": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz", - "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==", + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", + "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -323,6 +705,81 @@ } } }, + "@istanbuljs/load-nyc-config": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz", + "integrity": "sha512-ZR0rq/f/E4f4XcgnDvtMWXCUJpi8eO0rssVhmztsZqLIEFA9UUP9zmpE0VxlM+kv/E1ul2I876Fwil2ayptDVg==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", + "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", + "dev": true + }, "@nodelib/fs.scandir": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", @@ -402,9 +859,9 @@ "dev": true }, "@types/node": { - "version": "13.11.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.11.1.tgz", - "integrity": "sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g==", + "version": "13.13.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.1.tgz", + "integrity": "sha512-uysqysLJ+As9jqI5yqjwP3QJrhOcUwBjHUlUxPxjbplwKoILvXVsmYWEhfmAQlrPfbRZmhJB007o4L9sKqtHqQ==", "dev": true }, "@types/normalize-package-data": { @@ -544,6 +1001,21 @@ "picomatch": "^2.0.4" } }, + "append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "requires": { + "default-require-extensions": "^3.0.0" + } + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -605,9 +1077,9 @@ "dev": true }, "ava": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/ava/-/ava-3.7.0.tgz", - "integrity": "sha512-SV1oqRpZ00qevETsNzcqTqaTnspJZZ1wBGOjyQzcLOMChnUF+17/RS4YiieClaV0eCFULLU/roICpJoQlNLHZw==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/ava/-/ava-3.7.1.tgz", + "integrity": "sha512-UX7RSenUgFPhxe866doqOJy6tQZAXAVAU4yufYeBAcnEjnS/plIcG6lE2yGIqgjk5cIMpSi+sP4f6EsornlsuA==", "dev": true, "requires": { "@concordance/react": "^2.0.0", @@ -961,6 +1433,18 @@ } } }, + "caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "requires": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1208,9 +1692,9 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.0.0.tgz", + "integrity": "sha512-JrDGPAKjMGSP1G0DUoaceEJ3DZgAfr/q6X7FVk4+U5KxUSKviYGM2k6zWkfyyBHy5rAtzgYJFa1ro2O9PtoxwQ==", "dev": true }, "comment-parser": { @@ -1225,6 +1709,12 @@ "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", "dev": true }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, "compact2string": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/compact2string/-/compact2string-1.4.1.tgz", @@ -1502,6 +1992,23 @@ "core-assert": "^0.2.0" } }, + "default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "dev": true, + "requires": { + "strip-bom": "^4.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + } + } + }, "defaults": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", @@ -1730,6 +2237,12 @@ "is-symbol": "^1.0.2" } }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, "escape-goat": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", @@ -2085,9 +2598,9 @@ } }, "eslint-plugin-jsdoc": { - "version": "23.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-23.0.0.tgz", - "integrity": "sha512-zj5ZephjKkFU/J9hEw3RcjwpuywChvwNMgHs2DTgOuKarpJ65SJU3JGgx/K4y9l8iFw0ysrk6NlAKDX88ZwZdw==", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-24.0.0.tgz", + "integrity": "sha512-AGAc9PYpramsJGVmqtxnXBYlq+AMh+hIZdbJ52OLvyJS3f+PaT/PzuckRFOLnth2uhCDv4IjgsB3r5jUFWqUnw==", "dev": true, "requires": { "comment-parser": "^0.7.2", @@ -2229,9 +2742,9 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" }, "execa": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz", - "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.0.tgz", + "integrity": "sha512-JbDUxwV3BoT5ZVXQrSVbAiaXhXUkIwvbhPIwZ0N13kX+5yCzOhUNdocxB/UQRuYOHRYYwAxKYwJYc0T4D12pDA==", "dev": true, "requires": { "cross-spawn": "^7.0.0", @@ -2241,7 +2754,6 @@ "merge-stream": "^2.0.0", "npm-run-path": "^4.0.0", "onetime": "^5.1.0", - "p-finally": "^2.0.0", "signal-exit": "^3.0.2", "strip-final-newline": "^2.0.0" }, @@ -2392,6 +2904,77 @@ "to-regex-range": "^5.0.1" } }, + "find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + } + } + }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -2438,11 +3021,70 @@ "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", "dev": true }, + "foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", + "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "freelist": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/freelist/-/freelist-1.0.3.tgz", "integrity": "sha1-AGd1UJ85NXAXhNPtL8nxLJ3xurI=" }, + "fromentries": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.2.0.tgz", + "integrity": "sha512-33X7H/wdfO99GdRLLgkjUrD4geAFdq/Uv0kl3HD4da6HDixd2GUg8Mw7dahLCV9r/EARkmtYBB6Tch4EEokFTQ==", + "dev": true + }, "fs-chunk-store": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fs-chunk-store/-/fs-chunk-store-2.0.1.tgz", @@ -2489,6 +3131,12 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "gensync": { + "version": "1.0.0-beta.1", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", + "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", + "dev": true + }, "get-browser-rtc": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.0.2.tgz", @@ -2664,12 +3312,28 @@ "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", "dev": true }, + "hasha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.0.tgz", + "integrity": "sha512-2W+jKdQbAdSIrggA8Q35Br8qKadTrqCTC8+XZvBWepKDK6m9XkX6Iz1a2yh2KP01kzAR/dpuMeUnocoLYDcskw==", + "dev": true, + "requires": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + } + }, "hosted-git-info": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", "dev": true }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, "http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", @@ -3119,86 +3783,238 @@ "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", "dev": true }, - "is-plain-object": { + "is-plain-object": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz", + "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", + "dev": true, + "requires": { + "isobject": "^4.0.0" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", + "dev": true + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", + "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "requires": { + "append-transform": "^2.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.1.tgz", + "integrity": "sha512-imIchxnodll7pvQBYOqUu88EufLCU56LMeFPZZM/fJZ1irYcYdqroaV+ACK1Ila8ls09iEYArp+nqyC6lW1Vfg==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@babel/parser": "^7.7.5", + "@babel/template": "^7.7.4", + "@babel/traverse": "^7.7.4", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + } + }, + "istanbul-lib-processinfo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", + "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.0", + "istanbul-lib-coverage": "^3.0.0-alpha.1", + "make-dir": "^3.0.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^3.3.3" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", + "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "istanbul-lib-report": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz", - "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", "dev": true, "requires": { - "isobject": "^4.0.0" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" } }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", "dev": true, "requires": { - "has": "^1.0.3" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, - "is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", - "dev": true - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "dev": true - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", "dev": true, "requires": { - "has-symbols": "^1.0.1" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" } }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", - "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", - "dev": true - }, "js-string-escape": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", @@ -3255,6 +4071,15 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "junk": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", @@ -3328,17 +4153,17 @@ "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" }, "lint-staged": { - "version": "10.1.5", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.1.5.tgz", - "integrity": "sha512-Etn83be+CiG674kIkE4pOyjtpssXfdmuAPhOehy5w4bOVMnQJ5DsiWq5CShpZj38NA+UdJSsMMkqhb3hB72kOg==", + "version": "10.1.6", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.1.6.tgz", + "integrity": "sha512-45zaGxf4XZuwdUk87yRFE/1b4vTZmH2UnYmUPmindsgdAljOFpWWb0yEjxngmqERUS/MGauJexFF6BjLVg9VMA==", "dev": true, "requires": { - "chalk": "^3.0.0", - "commander": "^4.0.1", + "chalk": "^4.0.0", + "commander": "^5.0.0", "cosmiconfig": "^6.0.0", "debug": "^4.1.1", "dedent": "^0.7.0", - "execa": "^3.4.0", + "execa": "^4.0.0", "listr": "^0.14.3", "log-symbols": "^3.0.0", "micromatch": "^4.0.2", @@ -3346,18 +4171,6 @@ "please-upgrade-node": "^3.2.0", "string-argv": "0.3.1", "stringify-object": "^3.3.0" - }, - "dependencies": { - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } } }, "listr": { @@ -4078,6 +4891,15 @@ "integrity": "sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w==", "optional": true }, + "node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "requires": { + "process-on-spawn": "^1.0.0" + } + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -4133,6 +4955,129 @@ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, + "nyc": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.0.1.tgz", + "integrity": "sha512-n0MBXYBYRqa67IVt62qW1r/d9UH/Qtr7SF1w/nQLJ9KxvWF6b2xCHImRAixHN9tnMMYHC2P14uo6KddNGwMgGg==", + "dev": true, + "requires": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4267,12 +5212,6 @@ "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", "dev": true }, - "p-finally": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", - "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", - "dev": true - }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -4306,6 +5245,18 @@ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, + "package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } + }, "package-json": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", @@ -4573,6 +5524,15 @@ "parse-ms": "^2.1.0" } }, + "process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "requires": { + "fromentries": "^1.2.0" + } + }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -4750,6 +5710,15 @@ "rc": "^1.2.8" } }, + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } + }, "render-media": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/render-media/-/render-media-3.4.0.tgz", @@ -5051,9 +6020,9 @@ "dev": true }, "source-map-support": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", - "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.17.tgz", + "integrity": "sha512-bwdKOBZ5L0gFRh4KOxNap/J/MpvX9Yxsq9lFDx65s3o7F/NiHy7JRaGIS8MwW6tZPAq9UXE207Il0cfcb5yu/Q==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -5068,6 +6037,40 @@ } } }, + "spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "requires": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", @@ -5403,6 +6406,17 @@ "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==", "dev": true }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -5629,6 +6643,12 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + }, "v8-compile-cache": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", @@ -5823,11 +6843,11 @@ "dev": true }, "yaml": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.8.2.tgz", - "integrity": "sha512-omakb0d7FjMo3R1D2EbTKVIk6dAVLRxFXdLZMEUToeAvuqgG/YuHMuQOZ5fgk+vQ8cx+cnGKwyg+8g8PNT0xQg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.9.2.tgz", + "integrity": "sha512-HPT7cGGI0DuRcsO51qC1j9O16Dh1mZ2bnXwsi0jrSpsLz0WxOLSLXfkABVl6bZO629py3CU+OMJtpNHDLB97kg==", "requires": { - "@babel/runtime": "^7.8.7" + "@babel/runtime": "^7.9.2" } }, "yargs": { diff --git a/package.json b/package.json index 9faa5ad..d770393 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@nikolay-borzov/torrent-clean", - "version": "1.6.0", + "version": "1.7.0", "description": "Deletes files that are not listed in selected torrent file", "keywords": [ "torrent", @@ -25,7 +25,9 @@ "homepage": "https://github.com/nikolay-borzov/torrent-clean#readme", "scripts": { "test": "ava", - "lint": "eslint **/*.js", + "test:coverage": "nyc ava", + "test:update-snapshot": "ava --update-snapshots", + "lint": "eslint \"**/*.js\"", "lint-staged": "lint-staged" }, "files": [ @@ -41,6 +43,12 @@ ], "verbose": true }, + "nyc": { + "reporter": [ + "text", + "lcov" + ] + }, "husky": { "hooks": { "pre-commit": "lint-staged" @@ -55,10 +63,11 @@ "minimist": "^1.2.5", "picomatch": "^2.2.2", "readdirp": "^3.4.0", - "webtorrent": "^0.108.1" + "webtorrent": "^0.108.1", + "yaml": "^1.9.2" }, "devDependencies": { - "ava": "^3.7.0", + "ava": "^3.7.1", "babel-eslint": "^10.1.0", "create-temp-directory": "^1.1.1", "create-torrent": "^4.4.1", @@ -67,13 +76,14 @@ "eslint-config-standard": "^14.1.1", "eslint-plugin-ava": "^10.2.0", "eslint-plugin-import": "^2.20.2", - "eslint-plugin-jsdoc": "^23.0.0", + "eslint-plugin-jsdoc": "^24.0.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^3.1.3", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-standard": "^4.0.1", "husky": "^4.2.5", - "lint-staged": "^10.1.5", + "lint-staged": "^10.1.6", + "nyc": "^15.0.1", "prettier": "^2.0.4" } } diff --git a/tests/api.test.js b/tests/api.test.js index 46ecfc5..1ef98ed 100644 --- a/tests/api.test.js +++ b/tests/api.test.js @@ -4,10 +4,15 @@ const path = require('path') const { createTempDirectory } = require('create-temp-directory') const util = require('util') const createTorrent = util.promisify(require('create-torrent')) +const YAML = require('yaml') const cleanTorrentDir = require('../lib/api') const { createFiles } = require('./utils') +/** + * @typedef {import('../lib/config').TorrentCleanConfig} TorrentCleanConfig + */ + test.beforeEach('Create temp directory', async (t) => { t.context.tempDir = await createTempDirectory() }) @@ -16,14 +21,21 @@ test.afterEach.always('Remove temp directory', async (t) => { await t.context.tempDir.remove() }) -test('cleanTorrentDir » should clean directory from extra files', async (t) => { - const { tempDir } = t.context - - const directoryPath = path.resolve(tempDir.path, 'download/images/nature') +test('cleanTorrentDir » should throw if `torrentId` is not set', async (t) => { + await t.throwsAsync(async () => cleanTorrentDir({ dirPath: './downloads' }), { + message: `'torrentId' is required`, + }) +}) +/** + * Creates test torrent directory with files and configs + * + * @param {string} tempDirPath + */ +function createTestDir(tempDirPath) { createFiles( { - download: { + downloads: { images: { nature: { set1: { @@ -42,41 +54,183 @@ test('cleanTorrentDir » should clean directory from extra files', async (t) => }, }, }, - tempDir.path + tempDirPath ) +} +/** + * Creates test torrent file + */ +async function createStubTorrent() { const file1 = Buffer.from('[binary]') file1.name = 'set1/image1.jpg' const file2 = Buffer.from('[binary]') file2.name = 'set2/image2.jpg' - const torrentId = await createTorrent([file1, file2], { + return createTorrent([file1, file2], { name: 'Nature Wallpapers', }) +} + +/** + * @param {string} tempDirPath + */ +async function createTestContext(tempDirPath) { + createTestDir(tempDirPath) + + const torrentId = await createStubTorrent() + + const dirPath = path.resolve(tempDirPath, 'downloads/images/nature') + + return { + torrentId, + dirPath, + expectDeleted: ['set2/image2 (Copy).jpg'].map((filename) => + path.join(dirPath, filename) + ), + expectKept: [ + 'set1/image1.jpg', + 'set1/~image1.jpg', + 'set2/image2.jpg', + 'edited/image1.jpg', + ].map((filename) => path.join(dirPath, filename)), + } +} + +test('cleanTorrentDir » should clean directory from extra files', async (t) => { + const { tempDir } = t.context + + const { + torrentId, + dirPath, + expectDeleted, + expectKept, + } = await createTestContext(tempDir.path) await cleanTorrentDir({ torrentId, - directoryPath, + dirPath, customConfig: { ignore: ['**/~*'] }, }) - const expectDeleted = ['set2/image2 (Copy).jpg'].map((filename) => - path.join(directoryPath, filename) - ) + expectDeleted.forEach((filename) => { + t.false(fs.existsSync(filename), `"${filename}" should not exist`) + }) + + expectKept.forEach((filename) => { + t.true(fs.existsSync(filename), `"${filename}" should exist`) + }) +}) + +test('cleanTorrentDir » should postpone files deleting if `dryRun` is set to `true`', async (t) => { + const { tempDir } = t.context + + const { + torrentId, + dirPath, + expectDeleted, + expectKept, + } = await createTestContext(tempDir.path) + + const { extraFiles, deleteFiles } = await cleanTorrentDir({ + torrentId, + dirPath, + dryRun: true, + customConfig: { ignore: ['**/~*'] }, + }) + + expectDeleted.forEach((filename) => { + t.true(fs.existsSync(filename), `"${filename}" should exist`) + }) + + expectKept.forEach((filename) => { + t.true(fs.existsSync(filename), `"${filename}" should exist`) + }) + + await deleteFiles(extraFiles) expectDeleted.forEach((filename) => { t.false(fs.existsSync(filename), `"${filename}" should not exist`) }) - const expectKept = [ - 'set1/image1.jpg', - 'set1/~image1.jpg', - 'set2/image2.jpg', - 'edited/image1.jpg', - ].map((filename) => path.join(directoryPath, filename)) + expectKept.forEach((filename) => { + t.true(fs.existsSync(filename), `"${filename}" should exist`) + }) +}) + +test('cleanTorrentDir » should get `torrentId` from `lastTorrent` config property if `rememberLastTorrent` is set to `true`', async (t) => { + const { tempDir } = t.context + + const { + torrentId, + dirPath, + expectDeleted, + expectKept, + } = await createTestContext(tempDir.path) + + const torrentFilePath = path.join( + tempDir.path, + 'downloads/nature-pack.torrent' + ) + + fs.writeFileSync(torrentFilePath, torrentId) + + fs.writeFileSync( + path.join(tempDir.path, 'downloads', '.torrent-cleanrc'), + JSON.stringify({ + rememberLastTorrent: true, + }) + ) + + fs.writeFileSync( + path.join(dirPath, '.torrent-cleanrc'), + JSON.stringify({ ignore: ['**/edited/*'], lastTorrent: torrentFilePath }) + ) + + await cleanTorrentDir({ + dirPath, + customConfig: { ignore: ['**/~*'] }, + }) + + expectDeleted.forEach((filename) => { + t.false(fs.existsSync(filename), `"${filename}" should not exist`) + }) expectKept.forEach((filename) => { t.true(fs.existsSync(filename), `"${filename}" should exist`) }) }) + +test('cleanTorrentDir » should save `torrentId` to `lastTorrent` config property if `rememberLastTorrent` is set to `true`', async (t) => { + const { tempDir } = t.context + + const { torrentId, dirPath } = await createTestContext(tempDir.path) + + const torrentFilePath = path.join( + tempDir.path, + 'downloads/nature-pack.torrent' + ) + + fs.writeFileSync(torrentFilePath, torrentId) + + fs.writeFileSync( + path.join(tempDir.path, 'downloads', '.torrent-cleanrc'), + JSON.stringify({ + rememberLastTorrent: true, + }) + ) + + await cleanTorrentDir({ + torrentId: torrentFilePath, + dirPath, + customConfig: { ignore: ['**/~*'] }, + }) + + /** @type {TorrentCleanConfig} */ + const savedConfig = YAML.parse( + fs.readFileSync(path.resolve(dirPath, '.torrent-cleanrc')).toString() + ) + + t.assert(savedConfig.lastTorrent === torrentFilePath) +}) diff --git a/tests/config.test.js b/tests/config.test.js new file mode 100644 index 0000000..52838e6 --- /dev/null +++ b/tests/config.test.js @@ -0,0 +1,225 @@ +const test = require('ava') +const { createTempDirectory } = require('create-temp-directory') +const path = require('path') +const fs = require('fs') +const YAML = require('yaml') + +const { loadConfig, saveConfig } = require('../lib/config') +const { createFiles } = require('./utils') + +/** + * @typedef {import('ava').ExecutionContext} ExecutionContext + * @typedef {import('../lib/config').TorrentCleanConfig} TorrentCleanConfig + */ + +test.beforeEach('Create temp directory', async (t) => { + t.context.tempDir = await createTempDirectory() +}) + +test.afterEach.always('Remove temp directory', async (t) => { + await t.context.tempDir.remove() +}) + +/** @type {TorrentCleanConfig} */ +const DEFAULT_CONFIG = { + ignore: ['~uTorrentPartFile*', '.torrent-cleanrc*'], +} + +test('loadConfig » should return default config if no config files found', async (t) => { + const { tempDir } = t.context + + const actual = await loadConfig(tempDir.path) + + t.deepEqual(actual, { config: DEFAULT_CONFIG }) +}) + +test('loadConfig » should collect and merge configs up to root directory', async (t) => { + const { tempDir } = t.context + + const expected = { + config: { + ignore: [...DEFAULT_CONFIG.ignore, '*(Copy)*', 'Thumbs.db', '~*'], + rememberLastTorrent: true, + }, + searchFromCosmiconfigResult: { + config: { ignore: ['*(Copy)*'], rememberLastTorrent: true }, + filepath: path.join( + tempDir.path, + 'download/images/nature/.torrent-cleanrc' + ), + }, + } + + createFiles( + { + download: { + images: { + nature: { + 'image1.jpg': '[binary]', + 'image2.jpg': '[binary]', + 'image2 (Copy).jpg': '[binary]', + 'Thumbs.db': '[binary]', + '.torrent-cleanrc': + '{ "ignore": ["*(Copy)*"], "rememberLastTorrent": true }', + }, + '.torrent-cleanrc.json': + '{ "ignore": ["Thumbs.db"], "rememberLastTorrent": false }', + }, + '.torrent-cleanrc': + '{ "ignore": ["~*"], "rememberLastTorrent": false }', + }, + }, + tempDir.path + ) + + const actual = await loadConfig( + path.join(tempDir.path, '/download/images/nature') + ) + + t.deepEqual(actual, expected) +}) + +test('loadConfig » should accept config object', async (t) => { + const { tempDir } = t.context + + const expected = { + config: { + ignore: [...DEFAULT_CONFIG.ignore, 'walkthrough.txt', '~*'], + rememberLastTorrent: true, + }, + searchFromCosmiconfigResult: { + config: { ignore: ['~*'], rememberLastTorrent: false }, + filepath: path.join(tempDir.path, 'game/.torrent-cleanrc'), + }, + } + + createFiles( + { + game: { + 'game.exe': '[binary]', + 'data.dat': '[binary]', + '.torrent-cleanrc': + '{ "ignore": ["~*"], "rememberLastTorrent": false }', + }, + }, + tempDir.path + ) + + const actual = await loadConfig(path.join(tempDir.path, 'game'), { + ignore: ['walkthrough.txt'], + rememberLastTorrent: true, + }) + + t.deepEqual(actual, expected) +}) + +test('saveConfig » should create config at `saveDirPath` if it does not exist', (t) => { + const { tempDir } = t.context + + /** @type {TorrentCleanConfig} */ + const config = { lastTorrent: 'C:/downloads/nature-pack.torrent' } + const saveDirPath = path.join(tempDir.path, 'path/to/dir') + const filename = path.join(saveDirPath, '.torrent-cleanrc') + + createFiles( + { + path: { + to: { + dir: {}, + }, + }, + }, + tempDir.path + ) + + saveConfig({ + config, + saveDirPath, + }) + + const fileContent = fs.readFileSync(filename).toString() + + t.snapshot(fileContent) +}) + +/** + * @param {ExecutionContext} t + * @param {object} params + * @param {string} params.filename + * @param {(config: TorrentCleanConfig) => string} params.getFileContent + */ +function saveConfigUpdateFileMacro(t, { filename, getFileContent }) { + const { tempDir } = t.context + + /** @type {TorrentCleanConfig} */ + const config = { + ignore: ['~*'], + lastTorrent: 'C:/downloads/nature-pack.torrent', + } + const saveDirPath = path.join(tempDir.path, 'downloads') + const existingConfigPath = path.join(saveDirPath, filename) + + createFiles( + { + downloads: { + [filename]: getFileContent(config), + }, + }, + tempDir.path + ) + + saveConfig({ + config, + saveDirPath, + existingConfigPath, + }) + + const fileContent = fs.readFileSync(existingConfigPath).toString() + + t.snapshot(fileContent) +} + +test( + 'saveConfig » should update existing config `.json` file', + saveConfigUpdateFileMacro, + { + filename: '.torrent-cleanrc.json', + getFileContent: (config) => JSON.stringify(config), + } +) + +test( + 'saveConfig » should update existing config `.yaml` file', + saveConfigUpdateFileMacro, + { + filename: '.torrent-cleanrc.yaml', + getFileContent: (config) => YAML.stringify(config), + } +) + +test( + 'saveConfig » should update existing config `.yml` file', + saveConfigUpdateFileMacro, + { + filename: '.torrent-cleanrc.yml', + getFileContent: (config) => YAML.stringify(config), + } +) + +test( + 'saveConfig » should update existing config JSON file w/o extension', + saveConfigUpdateFileMacro, + { + filename: '.torrent-cleanrc', + getFileContent: (config) => JSON.stringify(config), + } +) + +test( + 'saveConfig » should update existing config YAML file w/o extension', + saveConfigUpdateFileMacro, + { + filename: '.torrent-cleanrc', + getFileContent: (config) => YAML.stringify(config), + } +) diff --git a/tests/load-config.test.js b/tests/load-config.test.js deleted file mode 100644 index 245f07b..0000000 --- a/tests/load-config.test.js +++ /dev/null @@ -1,87 +0,0 @@ -const test = require('ava') -const { createTempDirectory } = require('create-temp-directory') -const path = require('path') - -const { loadConfig } = require('../lib/load-config') -const { createFiles } = require('./utils') - -test.beforeEach('Create temp directory', async (t) => { - t.context.tempDir = await createTempDirectory() -}) - -test.afterEach.always('Remove temp directory', async (t) => { - await t.context.tempDir.remove() -}) - -/** @type {import('../lib/load-config').TorrentCleanConfig} */ -const DEFAULT_CONFIG = { - ignore: ['~uTorrentPartFile*', '.torrent-cleanrc*'], -} - -test('loadConfig » should return default config if no config files found', (t) => { - const { tempDir } = t.context - - const actual = loadConfig(tempDir.path) - - t.deepEqual(actual, DEFAULT_CONFIG) -}) - -test('loadConfig » should collect and merge configs up to root directory', (t) => { - const expected = { - ignore: [...DEFAULT_CONFIG.ignore, '*(Copy)*', 'Thumbs.db', '~*'], - dryRun: true, - } - - const { tempDir } = t.context - - createFiles( - { - download: { - images: { - nature: { - 'image1.jpg': '[binary]', - 'image2.jpg': '[binary]', - 'image2 (Copy).jpg': '[binary]', - 'Thumbs.db': '[binary]', - '.torrent-cleanrc': '{ "ignore": ["*(Copy)*"], "dryRun": true }', - }, - '.torrent-cleanrc.json': - '{ "ignore": ["Thumbs.db"], "dryRun": false }', - }, - '.torrent-cleanrc': '{ "ignore": ["~*"], "dryRun": false }', - }, - }, - tempDir.path - ) - - const actual = loadConfig(path.join(tempDir.path, '/download/images/nature')) - - t.deepEqual(actual, expected) -}) - -test('loadConfig » should accept config object', (t) => { - const expected = { - ignore: [...DEFAULT_CONFIG.ignore, 'walkthrough.txt', '~*'], - dryRun: true, - } - - const { tempDir } = t.context - - createFiles( - { - game: { - 'game.exe': '[binary]', - 'data.dat': '[binary]', - '.torrent-cleanrc': '{ "ignore": ["~*"], "dryRun": false }', - }, - }, - tempDir.path - ) - - const actual = loadConfig(path.join(tempDir.path, 'game'), { - ignore: ['walkthrough.txt'], - dryRun: true, - }) - - t.deepEqual(actual, expected) -}) diff --git a/tests/snapshots/config.test.js.md b/tests/snapshots/config.test.js.md new file mode 100644 index 0000000..c535ff9 --- /dev/null +++ b/tests/snapshots/config.test.js.md @@ -0,0 +1,61 @@ +# Snapshot report for `tests/config.test.js` + +The actual snapshot is saved in `config.test.js.snap`. + +Generated by [AVA](https://avajs.dev). + +## saveConfig » should create config at `saveDirPath` if it does not exist + +> Snapshot 1 + + `lastTorrent: C:/downloads/nature-pack.torrent␊ + ` + +## saveConfig » should update existing config JSON file w/o extension + +> Snapshot 1 + + `{␊ + "ignore": [␊ + "~*"␊ + ],␊ + "lastTorrent": "C:/downloads/nature-pack.torrent"␊ + }` + +## saveConfig » should update existing config YAML file w/o extension + +> Snapshot 1 + + `ignore:␊ + - ~*␊ + lastTorrent: C:/downloads/nature-pack.torrent␊ + ` + +## saveConfig » should update existing config `.json` file + +> Snapshot 1 + + `{␊ + "ignore": [␊ + "~*"␊ + ],␊ + "lastTorrent": "C:/downloads/nature-pack.torrent"␊ + }` + +## saveConfig » should update existing config `.yaml` file + +> Snapshot 1 + + `ignore:␊ + - ~*␊ + lastTorrent: C:/downloads/nature-pack.torrent␊ + ` + +## saveConfig » should update existing config `.yml` file + +> Snapshot 1 + + `ignore:␊ + - ~*␊ + lastTorrent: C:/downloads/nature-pack.torrent␊ + ` diff --git a/tests/snapshots/config.test.js.snap b/tests/snapshots/config.test.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..467d19ceb9e3ad9c6c1ca95067375c48409e3611 GIT binary patch literal 313 zcmV-90mlA8RzVV+4!70OH3_T8s3mPbt3L`h9Ao)y7whU{PbBp-;=6HTnN~ zSHAH;nZ@E}{_TulQJ|p=3!FKwOgcF0YP8?7(;0ue(-^^`_koz1fgS7`MpgzvM!lTG z;*ya3qN3Ei5-SB~EB%!G^1Ph<#FS$Fyu^~yqEy|2#N=$f5{L>e6urUKTnY+Gndy1? zMX5?w3ei9wkYA^z1Z2eOfaH)22P#k^WV{krEs6!U5VNg-M(HZlX>n2DRw^1b$h{2! LcV~}9fdT*kkjj~& literal 0 HcmV?d00001