From ecfd4a8aaf4ca9e39f5f8f8de9c61b9d6f9acae6 Mon Sep 17 00:00:00 2001 From: Choro Abdymanapov Date: Wed, 9 Dec 2020 13:27:53 +0300 Subject: [PATCH] feat: add more useful output on batch actions, some minor fixes (#10) --- packages/cli-api/jest.config.json | 3 +- .../src/main/ts/executors/deprecation.ts | 79 ++++++- packages/cli-api/src/main/ts/interfaces.ts | 3 +- packages/cli-api/src/main/ts/runner.ts | 4 +- .../src/test/ts/executors/deprecation.ts | 214 ++++++++++++++---- packages/cli-api/src/test/ts/runner.ts | 5 + packages/cli-api/src/test/ts/utils.ts | 6 + packages/cli-pipe/src/main/ts/index.ts | 13 +- packages/cli-pipe/src/test/ts/index.ts | 2 +- packages/cli/README.md | 46 +++- packages/cli/package.json | 2 +- packages/cli/src/main/ts/index.ts | 1 - packages/cli/src/test/ts/index.ts | 10 +- yarn.lock | 167 +++++++------- 14 files changed, 388 insertions(+), 167 deletions(-) create mode 100644 packages/cli-api/src/test/ts/utils.ts diff --git a/packages/cli-api/jest.config.json b/packages/cli-api/jest.config.json index 4eb84a5..ae5fa02 100644 --- a/packages/cli-api/jest.config.json +++ b/packages/cli-api/jest.config.json @@ -15,7 +15,8 @@ "/src/test/ts/**/*.ts" ], "testPathIgnorePatterns": [ - "/node_modules/" + "/node_modules/", + "/src/test/ts/utils.ts" ], "moduleFileExtensions": [ "ts", diff --git a/packages/cli-api/src/main/ts/executors/deprecation.ts b/packages/cli-api/src/main/ts/executors/deprecation.ts index 49abd79..abbd9a1 100644 --- a/packages/cli-api/src/main/ts/executors/deprecation.ts +++ b/packages/cli-api/src/main/ts/executors/deprecation.ts @@ -1,15 +1,88 @@ -import { NpmRegClientWrapper, RegClient } from '@qiwi/npm-batch-client' +import { IDeprecatePackageParams, NpmRegClientWrapper, RegClient } from '@qiwi/npm-batch-client' import { defaultRateLimit } from '../default' import { TDeprecationConfig } from '../interfaces' import { withRateLimit } from '../utils' -export const performDeprecation = async (config: TDeprecationConfig): Promise => { +export const performDeprecation = async (config: TDeprecationConfig): Promise => { const regClient = new RegClient() const batchClient = new NpmRegClientWrapper( config.registryUrl, config.auth, withRateLimit(regClient, config.batch?.ratelimit || defaultRateLimit, ['deprecate']) ) - return batchClient.deprecateBatch(config.data, config.batch?.skipErrors) + + return batchClient + .deprecateBatch(config.data, config.batch?.skipErrors) + .then(data => processResults(data, config)) +} + +export const processResults = (results: any[], config: TDeprecationConfig): void => { + const normalizedResults = config.batch?.skipErrors ? handleSettledResults(results) : results + const enrichedResults = enrichResults(normalizedResults, config.data) + + printResults( + getSuccessfulResults(enrichedResults), + getFailedResults(enrichedResults), + config.batch?.jsonOutput + ) +} + +type TEnrichedBatchResult = { + result: any + packageInfo: IDeprecatePackageParams +} + +export const enrichResults = (normalizedResults: any[], data: IDeprecatePackageParams[]): TEnrichedBatchResult[] => + normalizedResults.map((result, i) => ({ result, packageInfo: data[i] })) + +export const handleSettledResults = (results: PromiseSettledResult[]): any[] => + results.map(result => result.status === 'rejected' + ? result.reason + : result.value + ) + +export const getSuccessfulResults = ( + results: TEnrichedBatchResult[] +): IDeprecatePackageParams[] => + results + .filter(item => item.result === null) + .map(item => item.packageInfo) + +export const getFailedResults = ( + results: TEnrichedBatchResult[] +): Array => + results + .filter(item => item.result !== null) + .map(item => ({ ...item.packageInfo, error: item.result.message || item.result })) + +export const printResults = ( + successfulPackages: Array, + failedPackages: Array, + jsonOutput?: boolean, + logger = console +): void => { + if (jsonOutput) { + logger.log( + JSON.stringify( + { + successfulPackages, + failedPackages + }, + null, // eslint-disable-line unicorn/no-null + '\t' + ) + ) + return + } + + if (successfulPackages.length > 0) { + logger.log('Following packages are deprecated successfully:') + logger.table(successfulPackages, ['packageName', 'version', 'message']) + } + + if (failedPackages.length > 0) { + logger.error('Following packages are not deprecated due to errors:') + logger.table(failedPackages, ['packageName', 'version', 'message', 'error']) + } } diff --git a/packages/cli-api/src/main/ts/interfaces.ts b/packages/cli-api/src/main/ts/interfaces.ts index 58bdebf..b5b99d4 100644 --- a/packages/cli-api/src/main/ts/interfaces.ts +++ b/packages/cli-api/src/main/ts/interfaces.ts @@ -12,7 +12,8 @@ export interface IBaseConfig { batch?: { ratelimit?: TRateLimit, skipErrors?: boolean, - } + jsonOutput?: boolean + }, data: T, } diff --git a/packages/cli-api/src/main/ts/runner.ts b/packages/cli-api/src/main/ts/runner.ts index f386b96..ccb4eb9 100644 --- a/packages/cli-api/src/main/ts/runner.ts +++ b/packages/cli-api/src/main/ts/runner.ts @@ -2,7 +2,7 @@ import { performDeprecation } from './executors' import { ICliArgs } from './interfaces' import { readFileToString, validateBaseConfig, validateDeprecationConfig } from './utils' -export const run = (configString: string): Promise => { +export const run = (configString: string): Promise => { const rawConfig = JSON.parse(configString) const validatedConfig = validateBaseConfig(rawConfig) switch (validatedConfig.action) { @@ -16,7 +16,7 @@ export const run = (configString: string): Promise => { } } -export const readConfigAndRun = (args: ICliArgs): Promise => { +export const readConfigAndRun = (args: ICliArgs): Promise => { const config = readFileToString(args.config) return run(config) } diff --git a/packages/cli-api/src/test/ts/executors/deprecation.ts b/packages/cli-api/src/test/ts/executors/deprecation.ts index 985112d..edfd000 100644 --- a/packages/cli-api/src/test/ts/executors/deprecation.ts +++ b/packages/cli-api/src/test/ts/executors/deprecation.ts @@ -1,54 +1,70 @@ import nock from 'nock' -import { performDeprecation, TDeprecationConfig } from '../../../main/ts' +import { + getFailedResults, + handleSettledResults, + performDeprecation, + printResults, + processResults, + TDeprecationConfig +} from '../../../main/ts' +import * as deprecation from '../../../main/ts/executors/deprecation' +import { mockOutput } from '../utils' -describe('performDeprecation', () => { - it ('makes API requests with rate limit', async () => { - const registryUrl = 'http://localhost' - const config: TDeprecationConfig = { - registryUrl, - auth: { - username: 'username', - password: 'password', - }, - batch: { - ratelimit: { - period: 500, - count: 2 - } - }, - action: 'deprecate', - data: [ - { - packageName: 'foo', - version: '<1.2.0', - message: 'foo is deprecated', - }, - { - packageName: 'bar', - version: '<1.3.0', - message: 'bar is deprecated', - }, - { - packageName: 'baz', - version: '<1.4.0', - message: 'baz is deprecated', - }, - { - packageName: 'bat', - version: '<1.5.0', - message: 'bat is deprecated', - }, - ] +const registryUrl = 'http://localhost' +const config: TDeprecationConfig = { + registryUrl, + auth: { + username: 'username', + password: 'password', + }, + batch: { + ratelimit: { + period: 500, + count: 2 } - const mocks = config.data.map(data => ({ - info: nock(registryUrl) - .get(`/${data.packageName}?write=true`) - .reply(200, { versions: { '0.1.0': {} }}), - deprecate: nock(registryUrl) - .put(`/${data.packageName}`) - .reply(200) - })) + }, + action: 'deprecate', + data: [ + { + packageName: 'foo', + version: '<1.2.0', + message: 'foo is deprecated', + }, + { + packageName: 'bar', + version: '<1.3.0', + message: 'bar is deprecated', + }, + { + packageName: 'baz', + version: '<1.4.0', + message: 'baz is deprecated', + }, + { + packageName: 'bat', + version: '<1.5.0', + message: 'bat is deprecated', + }, + ] +} + +const createMocks = () => + config.data.map(data => ({ + info: nock(registryUrl) + .get(`/${data.packageName}?write=true`) + .reply(200, { versions: { '0.1.0': {} } }), + deprecate: nock(registryUrl) + .put(`/${data.packageName}`) + .reply(200) + })) + +describe('performDeprecation', () => { + beforeEach(() => jest.restoreAllMocks()) + + it('makes API requests with rate limit', async () => { + mockOutput() + const mocks = createMocks() const startTime = Date.now() await performDeprecation(config) const endTime = Date.now() - startTime @@ -57,3 +73,107 @@ describe('performDeprecation', () => { expect(endTime).toBeGreaterThanOrEqual(500) }) }) + +describe('processResults', () => { + it('calls necessary util functions', () => { + mockOutput() + const getSuccessfulResults = jest.spyOn(deprecation, 'getSuccessfulResults') + const getFailedResults = jest.spyOn(deprecation, 'getFailedResults') + + processResults([], config) + + expect(getSuccessfulResults).toHaveBeenCalled() + expect(getFailedResults).toHaveBeenCalled() + }) + + it('normalizes settled results', () => { + mockOutput() + const handleSettledResultsSpy = jest.spyOn(deprecation, 'handleSettledResults') + + // eslint-disable-next-line unicorn/no-null + processResults([{ status: 'fulfilled', value: null }, { status: 'fulfilled', value: null }], { + ...config, + batch: { + ...config.batch, + skipErrors: true + } + }) + + expect(handleSettledResultsSpy).toHaveBeenCalled() + }) +}) + +describe('printResults', function () { + it('prints successful results only when they are presented', () => { + const consoleMock = { + log: jest.fn(), + table: jest.fn(), + error: jest.fn() + } + + const failedPackages: any[] = [] + printResults(config.data, failedPackages, false, consoleMock as any) + expect(consoleMock.log).toHaveBeenCalledWith(expect.stringContaining('success')) + expect(consoleMock.table).toHaveBeenCalledWith(config.data, expect.any(Array)) + expect(consoleMock.error).not.toHaveBeenCalledWith(expect.stringContaining('errors')) + expect(consoleMock.table).not.toHaveBeenCalledWith(failedPackages, expect.any(Array)) + }) + + it('does not print successful results when they are not presented', () => { + const consoleMock = { + log: jest.fn(), + table: jest.fn(), + error: jest.fn() + } + + const successfulPackages: any[] = [] + const failedPackages = config.data.map(item => ({ ...item, error: 'error' })) + printResults(successfulPackages, failedPackages, false, consoleMock as any) + expect(consoleMock.log).not.toHaveBeenCalledWith(expect.stringContaining('success')) + expect(consoleMock.table).not.toHaveBeenCalledWith(successfulPackages, expect.any(Array)) + expect(consoleMock.error).toHaveBeenCalledWith(expect.stringContaining('errors')) + expect(consoleMock.table).toHaveBeenCalledWith(failedPackages, expect.any(Array)) + }) + + it('prints results in JSON when appropriate flag is presented', () => { + const consoleMock = { + log: jest.fn(), + table: jest.fn(), + error: jest.fn() + } + const successfulPackages: any[] = [] + const failedPackages: any[] = [] + printResults(successfulPackages, failedPackages, true, consoleMock as any) + expect(consoleMock.log).toHaveBeenCalledWith(JSON.stringify( + { successfulPackages, failedPackages }, + null, // eslint-disable-line unicorn/no-null + '\t' + )) + + expect(consoleMock.error).not.toHaveBeenCalled() + expect(consoleMock.table).not.toHaveBeenCalled() + }) +}) + +describe('utils', () => { + test('getFailedResults handles different types of error', () => { + const data = getFailedResults( + config.data.map( + (packageInfo, i) => ({ + packageInfo, + result: i % 2 === 0 ? 'error' : new Error('error') + })) + ) + expect(data).toEqual(config.data.map(item => ({ ...item, error: 'error' }))) + }) + + test('handleSettledResults normalizes fulfilled and rejected results', () => { + expect(handleSettledResults([ + { status: 'fulfilled', value: 'value' }, + { status: 'rejected', reason: 'reason' }, + ])).toEqual([ + 'value', + 'reason' + ]) + }) +}); diff --git a/packages/cli-api/src/test/ts/runner.ts b/packages/cli-api/src/test/ts/runner.ts index 3f0692a..5f0960a 100644 --- a/packages/cli-api/src/test/ts/runner.ts +++ b/packages/cli-api/src/test/ts/runner.ts @@ -2,6 +2,7 @@ import { IBaseConfig, readConfigAndRun } from '../../main/ts' import * as deprecation from '../../main/ts/executors/deprecation' import * as utils from '../../main/ts/utils/misc' import * as validators from '../../main/ts/utils/validators' +import { mockOutput } from './utils' const config: IBaseConfig = { registryUrl: 'http://localhost', @@ -17,6 +18,7 @@ beforeEach(() => jest.restoreAllMocks()) describe('readConfigAndRun', () => { it('reads and validates config', () => { + mockOutput() const readSpy = jest.spyOn(utils, 'readFileToString') .mockImplementation(() => JSON.stringify(config)) const baseValidatorSpy = jest.spyOn(validators, 'validateBaseConfig') @@ -26,6 +28,7 @@ describe('readConfigAndRun', () => { }) it('calls performDeprecation for deprecate', () => { + mockOutput() jest.spyOn(utils, 'readFileToString') .mockImplementation(() => JSON.stringify(config)) const spy = jest.spyOn(deprecation, 'performDeprecation') @@ -34,6 +37,7 @@ describe('readConfigAndRun', () => { }) it('calls performDeprecation for un-deprecate', () => { + mockOutput() jest.spyOn(utils, 'readFileToString') .mockImplementation(() => JSON.stringify({ ...config, action: 'un-deprecate' })) const spy = jest.spyOn(deprecation, 'performDeprecation') @@ -42,6 +46,7 @@ describe('readConfigAndRun', () => { }) it('throws an error when action is not recognized', () => { + mockOutput() jest.spyOn(utils, 'readFileToString') .mockImplementation(() => JSON.stringify({ ...config, action: 'foo' })) expect(() => readConfigAndRun({ config: 'foo' })).toThrow() diff --git a/packages/cli-api/src/test/ts/utils.ts b/packages/cli-api/src/test/ts/utils.ts new file mode 100644 index 0000000..bd06fcd --- /dev/null +++ b/packages/cli-api/src/test/ts/utils.ts @@ -0,0 +1,6 @@ +export const mockOutput = (): void => { + jest.spyOn(console, 'log') + .mockImplementation(() => { /* noop */ }) + jest.spyOn(console, 'error') + .mockImplementation(() => { /* noop */ }) +} diff --git a/packages/cli-pipe/src/main/ts/index.ts b/packages/cli-pipe/src/main/ts/index.ts index b8b33e2..f88c6b1 100644 --- a/packages/cli-pipe/src/main/ts/index.ts +++ b/packages/cli-pipe/src/main/ts/index.ts @@ -1,5 +1,6 @@ #!/usr/bin/env node import { run } from '@qiwi/npm-batch-cli-api' + const stdin = process.openStdin() let configRaw = '' @@ -8,7 +9,11 @@ stdin.on('data', (chunk) => { configRaw += chunk }) -stdin.on('end', async () => { - await run(configRaw) - process.exit() -}) +stdin.on('end', () => + run(configRaw) + .then(() => process.exit()) + .catch(e => { + console.error(e) + process.exit(1) + }) +) diff --git a/packages/cli-pipe/src/test/ts/index.ts b/packages/cli-pipe/src/test/ts/index.ts index 42178c2..d7b1ca0 100644 --- a/packages/cli-pipe/src/test/ts/index.ts +++ b/packages/cli-pipe/src/test/ts/index.ts @@ -4,7 +4,7 @@ describe('cli', () => { it('gets data from stdin correctly ', () => { const stdin = process.openStdin() const runSpy = jest.spyOn(runner, 'run') - .mockImplementation(() => Promise.resolve([])) + .mockImplementation(() => Promise.resolve()) const openStdinSpy = jest.spyOn(process, 'openStdin') .mockImplementation(() => stdin) const exitSpy = jest.spyOn(process, 'exit') diff --git a/packages/cli/README.md b/packages/cli/README.md index 94c622a..dbd9930 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -13,10 +13,36 @@ You can call thus utility without installation via `npx` ```shell script npx @qiwi/npm-batch-cli --config=some/path/config.json ``` +Output: +```text +info attempt registry request try #1 at 8:28:25 PM +http request GET https://registry.npmjs.org/gen-tree-lib?write=true +info attempt registry request try #1 at 8:28:25 PM +http request GET https://registry.npmjs.org/foobarbazbat?write=true +http 404 https://registry.npmjs.org/foobarbazbat?write=true +http 200 https://registry.npmjs.org/gen-tree-lib?write=true +info attempt registry request try #1 at 8:28:26 PM +http request PUT https://registry.npmjs.org/gen-tree-lib +http 200 https://registry.npmjs.org/gen-tree-lib +Following packages are deprecated successfully: +┌─────────┬────────────────┬─────────┬──────────────────────────────┐ +│ (index) │ packageName │ version │ message │ +├─────────┼────────────────┼─────────┼──────────────────────────────┤ +│ 0 │ 'gen-tree-lib' │ '*' │ 'gen-tree-lib is deprecated' │ +└─────────┴────────────────┴─────────┴──────────────────────────────┘ + +Following packages are not deprecated due to errors: +┌─────────┬────────────────┬──────────┬─────────────────────────────────────────────────────────┬───────────────────────────────────────────────────────────────────────────────────────┐ +│ (index) │ packageName │ version │ message │ error │ +├─────────┼────────────────┼──────────┼─────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┤ +│ 0 │ 'foobarbazbat' │ '<1.3.2' │ 'foobarbazbat@<1.3.2 contains critical vulnerabilities' │ 'Registry returned 404 for GET on https://registry.npmjs.org/foobarbazbat?write=true' │ +└─────────┴────────────────┴──────────┴─────────────────────────────────────────────────────────┴───────────────────────────────────────────────────────────────────────────────────────┘ +``` # Config file example ## Deprecation/Un-deprecation If you want to un-deprecate package, pass empty message. +In this example all versions of `gen-tree-lib` and `foobarbazbat@<1.3.2` will be deprecated, `baz@<1.2.2` will be un-deprecated. ```json { "registryUrl": "https://registry.npmjs.org", @@ -26,23 +52,26 @@ If you want to un-deprecate package, pass empty message. "action": "deprecate", "data": [ { - "packageName": "foo", + "packageName": "gen-tree-lib", "version": "*", - "message": "foo is deprecated" + "message": "gen-tree-lib is deprecated" }, { - "packageName": "bar", + "packageName": "foobarbazbat", "version": "<1.3.2", - "message": "bar@<1.3.2 contains critical vulnerabilities" + "message": "foobarbazbat@<1.3.2 contains critical vulnerabilities" }, { - "packageName": "baz", // baz@<1.2.2 will be un-deprecated + "packageName": "baz", "version": "<1.2.2", "message": "" } ] } + ``` +Output: + You can use authorization via token as in example above, or simple username and password pair: ```json { @@ -83,7 +112,7 @@ You can specify several rate limits: "password": "password" }, "batch": { - "skipErrors": true, // pass this, if you want utility to continue when an error occurs + "skipErrors": true, "ratelimit": [ { "period": 500, @@ -93,8 +122,11 @@ You can specify several rate limits: "period": 10000, "count": 17 } - ] + ], + "jsonOutput": true }, ... } ``` +Flag `skipErrors` allows utility to continue on errors. +Flag `jsonOutput` prints result in JSON format. diff --git a/packages/cli/package.json b/packages/cli/package.json index ab02d79..54b3977 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -28,7 +28,7 @@ ], "scripts": { "test": "yarn lint && yarn jest", - "jest": "jest -w=1 --config=jest.config.json", + "jest": "jest -w=1", "lint": "eslint src/**/*.ts", "lint:fix": "yarn lint --fix", "clean": "rimraf target typings flow-typed buildcache coverage docs", diff --git a/packages/cli/src/main/ts/index.ts b/packages/cli/src/main/ts/index.ts index f1b4552..bd7ae44 100644 --- a/packages/cli/src/main/ts/index.ts +++ b/packages/cli/src/main/ts/index.ts @@ -22,7 +22,6 @@ const cli = meow( readConfigAndRun(cli.flags) .then(() => { - console.log('Done') process.exit(0) }) .catch(e => { diff --git a/packages/cli/src/test/ts/index.ts b/packages/cli/src/test/ts/index.ts index be03526..528ccbf 100644 --- a/packages/cli/src/test/ts/index.ts +++ b/packages/cli/src/test/ts/index.ts @@ -3,23 +3,17 @@ import * as cliApi from '@qiwi/npm-batch-cli-api/target/es5/runner' describe('cli', () => { it('calls readConfigAndRun and exits', async () => { const spy = jest.spyOn(cliApi, 'readConfigAndRun') - .mockImplementation(() => Promise.resolve([])) + .mockImplementation(() => Promise.resolve()) const exitSpy = jest.spyOn(process, 'exit') .mockImplementation(() => undefined as never) - const logSpy = jest.spyOn(console, 'log') - .mockImplementation(() => { /* noop */ }) - // local script `jest` is called with --config=jest.config.json flag, and CLI script requires --config flag, - // that's why now we don't need to add it to process.argv - // process.argv.push('--config=config.json') + process.argv.push('--config=config.json') require('../../main/ts') await expect(spy).toHaveBeenCalled() - expect(logSpy).toHaveBeenCalled() expect(exitSpy).toHaveBeenCalledWith(0) spy.mockReset() - logSpy.mockReset() exitSpy.mockReset() }) }) diff --git a/yarn.lock b/yarn.lock index d85f84f..1a2379e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -316,10 +316,10 @@ exec-sh "^0.3.2" minimist "^1.2.0" -"@eslint/eslintrc@^0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.2.1.tgz#f72069c330461a06684d119384435e12a5d76e3c" - integrity sha512-XRUeBZ5zBWLYgSANMpThFddrZZkEbGHgUdt5UJjZfnlN9BGCiUBrf+nvbRupSjMvqzwnQN0qwCmOxITt1cfywA== +"@eslint/eslintrc@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.2.2.tgz#d01fc791e2fc33e88a29d6f3dc7e93d0cd784b76" + integrity sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ== dependencies: ajv "^6.12.4" debug "^4.1.1" @@ -1740,60 +1740,60 @@ "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^4.6.0": - version "4.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.9.0.tgz#8fde15743413661fdc086c9f1f5d74a80b856113" - integrity sha512-WrVzGMzzCrgrpnQMQm4Tnf+dk+wdl/YbgIgd5hKGa2P+lnJ2MON+nQnbwgbxtN9QDLi8HO+JAq0/krMnjQK6Cw== + version "4.9.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.9.1.tgz#66758cbe129b965fe9c63b04b405d0cf5280868b" + integrity sha512-QRLDSvIPeI1pz5tVuurD+cStNR4sle4avtHhxA+2uyixWGFjKzJ+EaFVRW6dA/jOgjV5DTAjOxboQkRDE8cRlQ== dependencies: - "@typescript-eslint/experimental-utils" "4.9.0" - "@typescript-eslint/scope-manager" "4.9.0" + "@typescript-eslint/experimental-utils" "4.9.1" + "@typescript-eslint/scope-manager" "4.9.1" debug "^4.1.1" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@4.9.0": - version "4.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.9.0.tgz#23a296b85d243afba24e75a43fd55aceda5141f0" - integrity sha512-0p8GnDWB3R2oGhmRXlEnCvYOtaBCijtA5uBfH5GxQKsukdSQyI4opC4NGTUb88CagsoNQ4rb/hId2JuMbzWKFQ== +"@typescript-eslint/experimental-utils@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.9.1.tgz#86633e8395191d65786a808dc3df030a55267ae2" + integrity sha512-c3k/xJqk0exLFs+cWSJxIjqLYwdHCuLWhnpnikmPQD2+NGAx9KjLYlBDcSI81EArh9FDYSL6dslAUSwILeWOxg== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/scope-manager" "4.9.0" - "@typescript-eslint/types" "4.9.0" - "@typescript-eslint/typescript-estree" "4.9.0" + "@typescript-eslint/scope-manager" "4.9.1" + "@typescript-eslint/types" "4.9.1" + "@typescript-eslint/typescript-estree" "4.9.1" eslint-scope "^5.0.0" eslint-utils "^2.0.0" "@typescript-eslint/parser@^4.6.0": - version "4.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.9.0.tgz#bb65f1214b5e221604996db53ef77c9d62b09249" - integrity sha512-QRSDAV8tGZoQye/ogp28ypb8qpsZPV6FOLD+tbN4ohKUWHD2n/u0Q2tIBnCsGwQCiD94RdtLkcqpdK4vKcLCCw== + version "4.9.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.9.1.tgz#2d74c4db5dd5117379a9659081a4d1ec02629055" + integrity sha512-Gv2VpqiomvQ2v4UL+dXlQcZ8zCX4eTkoIW+1aGVWT6yTO+6jbxsw7yQl2z2pPl/4B9qa5JXeIbhJpONKjXIy3g== dependencies: - "@typescript-eslint/scope-manager" "4.9.0" - "@typescript-eslint/types" "4.9.0" - "@typescript-eslint/typescript-estree" "4.9.0" + "@typescript-eslint/scope-manager" "4.9.1" + "@typescript-eslint/types" "4.9.1" + "@typescript-eslint/typescript-estree" "4.9.1" debug "^4.1.1" -"@typescript-eslint/scope-manager@4.9.0": - version "4.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.9.0.tgz#5eefe305d6b71d1c85af6587b048426bfd4d3708" - integrity sha512-q/81jtmcDtMRE+nfFt5pWqO0R41k46gpVLnuefqVOXl4QV1GdQoBWfk5REcipoJNQH9+F5l+dwa9Li5fbALjzg== +"@typescript-eslint/scope-manager@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.9.1.tgz#cc2fde310b3f3deafe8436a924e784eaab265103" + integrity sha512-sa4L9yUfD/1sg9Kl8OxPxvpUcqxKXRjBeZxBuZSSV1v13hjfEJkn84n0An2hN8oLQ1PmEl2uA6FkI07idXeFgQ== dependencies: - "@typescript-eslint/types" "4.9.0" - "@typescript-eslint/visitor-keys" "4.9.0" + "@typescript-eslint/types" "4.9.1" + "@typescript-eslint/visitor-keys" "4.9.1" -"@typescript-eslint/types@4.9.0": - version "4.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.9.0.tgz#3fe8c3632abd07095c7458f7451bd14c85d0033c" - integrity sha512-luzLKmowfiM/IoJL/rus1K9iZpSJK6GlOS/1ezKplb7MkORt2dDcfi8g9B0bsF6JoRGhqn0D3Va55b+vredFHA== +"@typescript-eslint/types@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.9.1.tgz#a1a7dd80e4e5ac2c593bc458d75dd1edaf77faa2" + integrity sha512-fjkT+tXR13ks6Le7JiEdagnwEFc49IkOyys7ueWQ4O8k4quKPwPJudrwlVOJCUQhXo45PrfIvIarcrEjFTNwUA== -"@typescript-eslint/typescript-estree@4.9.0": - version "4.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.9.0.tgz#38a98df6ee281cfd6164d6f9d91795b37d9e508c" - integrity sha512-rmDR++PGrIyQzAtt3pPcmKWLr7MA+u/Cmq9b/rON3//t5WofNR4m/Ybft2vOLj0WtUzjn018ekHjTsnIyBsQug== +"@typescript-eslint/typescript-estree@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.9.1.tgz#6e5b86ff5a5f66809e1f347469fadeec69ac50bf" + integrity sha512-bzP8vqwX6Vgmvs81bPtCkLtM/Skh36NE6unu6tsDeU/ZFoYthlTXbBmpIrvosgiDKlWTfb2ZpPELHH89aQjeQw== dependencies: - "@typescript-eslint/types" "4.9.0" - "@typescript-eslint/visitor-keys" "4.9.0" + "@typescript-eslint/types" "4.9.1" + "@typescript-eslint/visitor-keys" "4.9.1" debug "^4.1.1" globby "^11.0.1" is-glob "^4.0.1" @@ -1801,12 +1801,12 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/visitor-keys@4.9.0": - version "4.9.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.9.0.tgz#f284e9fac43f2d6d35094ce137473ee321f266c8" - integrity sha512-sV45zfdRqQo1A97pOSx3fsjR+3blmwtdCt8LDrXgCX36v4Vmz4KHrhpV6Fo2cRdXmyumxx11AHw0pNJqCNpDyg== +"@typescript-eslint/visitor-keys@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.9.1.tgz#d76374a58c4ead9e92b454d186fea63487b25ae1" + integrity sha512-9gspzc6UqLQHd7lXQS7oWs+hrYggspv/rk6zzEMhCbYwPE/sF7oxo7GAjkS35Tdlt7wguIG+ViWCPtVZHz/ybQ== dependencies: - "@typescript-eslint/types" "4.9.0" + "@typescript-eslint/types" "4.9.1" eslint-visitor-keys "^2.0.0" "@zkochan/cmd-shim@^3.1.0": @@ -1844,7 +1844,7 @@ acorn-globals@^6.0.0: acorn "^7.1.1" acorn-walk "^7.1.1" -acorn-jsx@^5.2.0: +acorn-jsx@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== @@ -3524,12 +3524,12 @@ eslint-visitor-keys@^2.0.0: integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== eslint@^7.14.0: - version "7.14.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.14.0.tgz#2d2cac1d28174c510a97b377f122a5507958e344" - integrity sha512-5YubdnPXrlrYAFCKybPuHIAH++PINe1pmKNc5wQRB9HSbqIK1ywAnntE3Wwua4giKu0bjligf1gLF6qxMGOYRA== + version "7.15.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.15.0.tgz#eb155fb8ed0865fcf5d903f76be2e5b6cd7e0bc7" + integrity sha512-Vr64xFDT8w30wFll643e7cGrIkPEU50yIiI36OdSIDoSGguIeaLzBo0vpGvzo9RECUqq7htURfwEtKqwytkqzA== dependencies: "@babel/code-frame" "^7.0.0" - "@eslint/eslintrc" "^0.2.1" + "@eslint/eslintrc" "^0.2.2" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" @@ -3539,10 +3539,10 @@ eslint@^7.14.0: eslint-scope "^5.1.1" eslint-utils "^2.1.0" eslint-visitor-keys "^2.0.0" - espree "^7.3.0" + espree "^7.3.1" esquery "^1.2.0" esutils "^2.0.2" - file-entry-cache "^5.0.1" + file-entry-cache "^6.0.0" functional-red-black-tree "^1.0.1" glob-parent "^5.0.0" globals "^12.1.0" @@ -3566,13 +3566,13 @@ eslint@^7.14.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^7.3.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.0.tgz#dc30437cf67947cf576121ebd780f15eeac72348" - integrity sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw== +espree@^7.3.0, espree@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== dependencies: acorn "^7.4.0" - acorn-jsx "^5.2.0" + acorn-jsx "^5.3.1" eslint-visitor-keys "^1.3.0" esprima@^4.0.0, esprima@^4.0.1: @@ -3795,12 +3795,12 @@ figures@^2.0.0: dependencies: escape-string-regexp "^1.0.5" -file-entry-cache@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" - integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== +file-entry-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.0.tgz#7921a89c391c6d93efec2169ac6bf300c527ea0a" + integrity sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA== dependencies: - flat-cache "^2.0.1" + flat-cache "^3.0.4" fill-range@^4.0.0: version "4.0.0" @@ -3871,19 +3871,18 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -flat-cache@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" - integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== dependencies: - flatted "^2.0.0" - rimraf "2.6.3" - write "1.0.3" + flatted "^3.1.0" + rimraf "^3.0.2" -flatted@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" - integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== +flatted@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.0.tgz#a5d06b4a8b01e3a63771daa5cb7a1903e2e57067" + integrity sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA== flowgen@1.12.0: version "1.12.0" @@ -4711,9 +4710,9 @@ is-glob@^4.0.0, is-glob@^4.0.1: is-extglob "^2.1.1" is-negative-zero@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" - integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== is-number@^3.0.0: version "3.0.0" @@ -7337,13 +7336,6 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@2.6.3: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== - dependencies: - glob "^7.1.3" - rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -8481,9 +8473,9 @@ uuid@^3.0.1, uuid@^3.3.2: integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== uuid@^8.3.0: - version "8.3.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31" - integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg== + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== v8-compile-cache@^2.0.3: version "2.2.0" @@ -8713,13 +8705,6 @@ write-pkg@^3.1.0: sort-keys "^2.0.0" write-json-file "^2.2.0" -write@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" - integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== - dependencies: - mkdirp "^0.5.1" - ws@^7.2.3: version "7.4.0" resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.0.tgz#a5dd76a24197940d4a8bb9e0e152bb4503764da7"