From b93d30e7d1811118e603a9529e5fde0ccb66bda6 Mon Sep 17 00:00:00 2001 From: Nathan Walters Date: Wed, 1 Dec 2021 15:53:16 -0800 Subject: [PATCH] feat: GitHub request throttling (#551) --- package.json | 1 + src/adapters/github.test.ts | 107 ++++++++++-------- src/adapters/index.ts | 2 +- src/services/github.test.ts | 25 +++-- src/services/github.ts | 25 +++-- yarn.lock | 216 +++++++++++++++++++++--------------- 6 files changed, 221 insertions(+), 155 deletions(-) diff --git a/package.json b/package.json index f419d4e9..53681c19 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ ], "dependencies": { "@octokit/plugin-retry": "^3.0.9", + "@octokit/plugin-throttling": "^3.5.2", "@octokit/rest": "^18.1.0", "@types/js-yaml": "^3.12.6", "chalk": "^4.1.0", diff --git a/src/adapters/github.test.ts b/src/adapters/github.test.ts index 1969e914..712c4c41 100644 --- a/src/adapters/github.test.ts +++ b/src/adapters/github.test.ts @@ -12,7 +12,7 @@ const mockMigrationContext = () => ({ title: 'Test migration' }, }, -}); +} as IMigrationContext); describe('GithubAdapter', () => { describe('reposEqual', () => { @@ -21,8 +21,9 @@ describe('GithubAdapter', () => { const repo1 = { owner: 'NerdWallet', name: 'shepherd' }; const repo2 = { owner: 'NerdWallet', name: 'shepherd' }; - const service = new GithubService(mocktokit); - const adapter = new GithubAdapter(mockMigrationContext() as IMigrationContext, service); + const context = mockMigrationContext(); + const service = new GithubService(context, mocktokit); + const adapter = new GithubAdapter(context, service); expect(adapter.reposEqual(repo1, repo2)).toBe(true); }); @@ -31,8 +32,9 @@ describe('GithubAdapter', () => { const repo1 = { owner: 'NerdWallet', name: 'shepherd', defaultBranch: 'master' }; const repo2 = { owner: 'NerdWallet', name: 'shepherd' }; - const service = new GithubService(mocktokit); - const adapter = new GithubAdapter(mockMigrationContext() as IMigrationContext, service); + const context = mockMigrationContext(); + const service = new GithubService(context, mocktokit); + const adapter = new GithubAdapter(context, service); expect(adapter.reposEqual(repo1, repo2)).toBe(true); }); }); @@ -40,15 +42,15 @@ describe('GithubAdapter', () => { describe('getCandidateRepos', () => { it('validates search_query is not specified with org search', () => { const mocktokit = ({} as any as Octokit); - const migrationCtx: any = mockMigrationContext(); - migrationCtx.migration.spec.adapter = { + const context = mockMigrationContext(); + context.migration.spec.adapter = { org: 'testOrg', type: 'github', search_query: 'topics:test' }; - const service = new GithubService(mocktokit); - const adapter = new GithubAdapter(migrationCtx, service); + const service = new GithubService(context, mocktokit); + const adapter = new GithubAdapter(context, service); return expect(adapter.getCandidateRepos()) .rejects @@ -57,15 +59,16 @@ describe('GithubAdapter', () => { it('performs org search if specified and returns expected result', async () => { const mocktokit = ({} as any as Octokit); - const migrationCtx: any = mockMigrationContext(); - migrationCtx.migration.spec.adapter = { + const context = mockMigrationContext(); + context.migration.spec.adapter = { type: 'github', org: 'testOrg' }; - const service: any = new GithubService(mocktokit); + const service: any = new GithubService(context, mocktokit); service.getActiveReposForOrg.mockResolvedValue(['testOrg/test-repo']); - const adapter = new GithubAdapter(migrationCtx, service); + + const adapter = new GithubAdapter(context, service); const result = await adapter.getCandidateRepos(); expect(service.getActiveReposForOrg).toBeCalledWith({ org: 'testOrg' }); @@ -74,16 +77,17 @@ describe('GithubAdapter', () => { it(`performs repository search and returns expected result if 'respositories' is specified for search_type`, async () => { const mocktokit = ({} as any as Octokit); - const migrationCtx: any = mockMigrationContext(); - migrationCtx.migration.spec.adapter = { + const context = mockMigrationContext(); + context.migration.spec.adapter = { type: 'github', search_type: 'repositories', search_query: 'topics:test' }; - const service: any = new GithubService(mocktokit); + const service: any = new GithubService(context, mocktokit); service.getActiveReposForSearchTypeAndQuery.mockResolvedValue(['repoownername/test-repo']); - const adapter = new GithubAdapter(migrationCtx, service); + + const adapter = new GithubAdapter(context, service); const result = await adapter.getCandidateRepos(); expect(service.getActiveReposForSearchTypeAndQuery).toBeCalledWith({ @@ -93,48 +97,57 @@ describe('GithubAdapter', () => { expect(result).toStrictEqual([ { owner: 'repoownername', name: 'test-repo' } ]); }); - it(`performs code search and returns expected result if search_type is 'code' or is not provided`, async () => { + it(`performs code search and returns expected result if search_type is 'code'`, async () => { const mocktokit = ({} as any as Octokit); - const migrationCtx: any = mockMigrationContext(); - migrationCtx.migration.spec.adapter = { + const context = mockMigrationContext(); + context.migration.spec.adapter = { type: 'github', search_type: 'code', search_query: 'path:/ filename:package.json in:path' }; - const migrationCtxWithoutSearchType: any = mockMigrationContext(); - migrationCtxWithoutSearchType.migration.spec.adapter = { + const service: any = new GithubService(context, mocktokit); + service.getActiveReposForSearchTypeAndQuery.mockResolvedValue(['repoownername/test-repo']); + const adapter = new GithubAdapter(context, service); + + const result = await adapter.getCandidateRepos(); + + expect(service.getActiveReposForSearchTypeAndQuery).toBeCalledTimes(1); + expect(service.getActiveReposForSearchTypeAndQuery).toBeCalledWith({ + search_type: 'code', + search_query: 'path:/ filename:package.json in:path' + }); + expect(result).toStrictEqual([ { owner: 'repoownername', name: 'test-repo' } ]); + }); + + it(`performs code search and returns expected result if search_type is not provided`, async () => { + const mocktokit = ({} as any as Octokit); + const context = mockMigrationContext(); + context.migration.spec.adapter = { type: 'github', search_query: 'path:/ filename:package.json in:path' }; - const service: any = new GithubService(mocktokit); + const service: any = new GithubService(context, mocktokit); service.getActiveReposForSearchTypeAndQuery.mockResolvedValue(['repoownername/test-repo']); - const adapterWithSearchType = new GithubAdapter(migrationCtx, service); - const adapterWithoutSearchType = new GithubAdapter(migrationCtxWithoutSearchType, service); + const adapter = new GithubAdapter(context, service); - const getCandidateRepos = [ - adapterWithSearchType.getCandidateRepos(), - adapterWithoutSearchType.getCandidateRepos() - ]; + const result = await adapter.getCandidateRepos(); - const results = await Promise.all(getCandidateRepos); - expect(service.getActiveReposForSearchTypeAndQuery).toBeCalledTimes(2); + expect(service.getActiveReposForSearchTypeAndQuery).toBeCalledTimes(1); expect(service.getActiveReposForSearchTypeAndQuery).toBeCalledWith({ - search_type: 'code', search_query: 'path:/ filename:package.json in:path' }); - expect(results[0]).toStrictEqual([ { owner: 'repoownername', name: 'test-repo' } ]); - expect(results[1]).toStrictEqual([ { owner: 'repoownername', name: 'test-repo' } ]); + expect(result).toStrictEqual([ { owner: 'repoownername', name: 'test-repo' } ]); }); }); describe('parseRepo', () => { it('throws if owner or name not found in repo string', () => { + const context = mockMigrationContext(); const mocktokit = ({} as any as Octokit); - const migrationCtx: any = mockMigrationContext(); - const service = new GithubService(mocktokit); - const adapter = new GithubAdapter(migrationCtx, service); + const service = new GithubService(context, mocktokit); + const adapter = new GithubAdapter(context, service); const calledWithoutName = adapter.parseRepo.bind(null, 'ownerbutnoname/'); const calledWithoutOwner = adapter.parseRepo.bind(null, '/namebutnoowner'); const calledWithNeither = adapter.parseRepo.bind(null, ''); @@ -147,14 +160,15 @@ describe('GithubAdapter', () => { describe('mapRepoAfterCheckout', () => { it('saves the default branch', async () => { + const context = mockMigrationContext(); const mocktokit = ({} as any as Octokit); - const service: any = new GithubService(mocktokit); + const service: any = new GithubService(context, mocktokit); const repo = { owner: 'NerdWallet', name: 'test', }; service.getDefaultBranchForRepo.mockResolvedValue('develop'); - const adapter = new GithubAdapter(mockMigrationContext() as IMigrationContext, service); + const adapter = new GithubAdapter(context, service); const mappedRepo = await adapter.mapRepoAfterCheckout(repo); expect(service.getDefaultBranchForRepo).toBeCalledTimes(1); @@ -174,10 +188,11 @@ describe('GithubAdapter', () => { }; it('creates a new PR if one does not exist', async () => { + const context = mockMigrationContext(); const octokit = ({} as any as Octokit); - const service: any = new GithubService(octokit); + const service: any = new GithubService(context, octokit); service.listPullRequests.mockResolvedValue([]) - const adapter = new GithubAdapter(mockMigrationContext() as IMigrationContext, service); + const adapter = new GithubAdapter(context, service); await adapter.createPullRequest(REPO, 'Test PR message'); @@ -197,13 +212,14 @@ describe('GithubAdapter', () => { }); it('updates a PR if one exists and is open', async () => { + const context = mockMigrationContext(); const octokit = ({} as any as Octokit); - const service: any = new GithubService(octokit); + const service: any = new GithubService(context, octokit); service.listPullRequests.mockResolvedValue([{ number: 1234, state: 'open', }]); - const adapter = new GithubAdapter(mockMigrationContext() as IMigrationContext, service); + const adapter = new GithubAdapter(context, service); await adapter.createPullRequest(REPO, 'Test PR message, part 2'); expect(service.updatePullRequest).toBeCalledWith({ @@ -216,13 +232,14 @@ describe('GithubAdapter', () => { }); it('does not update a closed PR', async () => { + const context = mockMigrationContext(); const octokit = ({} as any as Octokit); - const service: any = new GithubService(octokit); + const service: any = new GithubService(context, octokit); service.listPullRequests.mockResolvedValue([{ number: 1234, state: 'closed', }]); - const adapter = new GithubAdapter(mockMigrationContext() as IMigrationContext, service); + const adapter = new GithubAdapter(context, service); await expect(adapter.createPullRequest(REPO, 'Test PR message, part 2')).rejects.toThrow(); expect(service.updatePullRequest).not.toBeCalled() }); diff --git a/src/adapters/index.ts b/src/adapters/index.ts index ffbd1174..b7858ca4 100644 --- a/src/adapters/index.ts +++ b/src/adapters/index.ts @@ -6,7 +6,7 @@ import GithubService from '../services/github'; export function adapterForName(name: string, context: IMigrationContext): IRepoAdapter { switch (name) { case 'github': { - const githubService = new GithubService(); + const githubService = new GithubService(context); return new GithubAdapter(context, githubService); } default: diff --git a/src/services/github.test.ts b/src/services/github.test.ts index 6a950ad5..25018f32 100644 --- a/src/services/github.test.ts +++ b/src/services/github.test.ts @@ -1,6 +1,9 @@ import type { Octokit } from '@octokit/rest'; +import { IMigrationContext } from '../migration-context'; import GithubService from './github'; +const mockMigrationContext = () => ({} as IMigrationContext); + describe('GithubService', () => { describe('getDefaultBranchForRepo', () => { it('calls repos.get with provided criteria & returns default branch', async () => { @@ -13,7 +16,7 @@ describe('GithubService', () => { }), }, } as any as Octokit; - const service = new GithubService(mocktokit); + const service = new GithubService(mockMigrationContext(), mocktokit); const searchCriteria = { owner: 'NerdwalletOSS', repo: 'shepherd', @@ -54,7 +57,7 @@ describe('GithubService', () => { }, } as any as Octokit; - const service = new GithubService(mocktokit); + const service = new GithubService(mockMigrationContext(), mocktokit); const searchCriteria = { org: 'testOrg' }; const result = await service.getActiveReposForOrg(searchCriteria); @@ -81,7 +84,7 @@ describe('GithubService', () => { }, } as any as Octokit; - const service = new GithubService(mocktokit); + const service = new GithubService(mockMigrationContext(), mocktokit); const searchCriteria = { owner: 'testOrg', repo: 'test-repo', @@ -112,7 +115,7 @@ describe('GithubService', () => { }, } as any as Octokit; - const service = new GithubService(mocktokit); + const service = new GithubService(mockMigrationContext(), mocktokit); const searchCriteria: any = { owner: 'testOrg', repo: 'test-repo', @@ -140,7 +143,7 @@ describe('GithubService', () => { }, } as any as Octokit; - const service = new GithubService(mocktokit); + const service = new GithubService(mockMigrationContext(), mocktokit); const prCreateParams = { owner: 'testOrg', repo: 'test-repo', @@ -170,7 +173,7 @@ describe('GithubService', () => { }, } as any as Octokit; - const service = new GithubService(mocktokit); + const service = new GithubService(mockMigrationContext(), mocktokit); const prUpdateParams = { owner: 'testOrg', repo: 'test-repo', @@ -202,7 +205,7 @@ describe('GithubService', () => { } } as any as Octokit; - const service = new GithubService(mocktokit); + const service = new GithubService(mockMigrationContext(), mocktokit); const criteria = { owner: 'testOrg', repo: 'test-repo', @@ -230,7 +233,7 @@ describe('GithubService', () => { } } as any as Octokit; - const service = new GithubService(mocktokit); + const service = new GithubService(mockMigrationContext(), mocktokit); const criteria = { owner: 'testOrg', repo: 'test-repo', @@ -247,7 +250,7 @@ describe('GithubService', () => { it('validates search_type is valid & throws if not', async () => { const mocktokit = {} as any as Octokit; - const service = new GithubService(mocktokit); + const service = new GithubService(mockMigrationContext(), mocktokit); const criteria = { search_type: 'invalid_search_type', search_query: 'any' @@ -276,7 +279,7 @@ describe('GithubService', () => { } } as any as Octokit; - const service = new GithubService(mocktokit); + const service = new GithubService(mockMigrationContext(), mocktokit); const criteria = { search_type: 'repositories', search_query: 'topics:test' @@ -314,7 +317,7 @@ describe('GithubService', () => { } } as any as Octokit; - const service = new GithubService(mocktokit); + const service = new GithubService(mockMigrationContext(), mocktokit); const criteria1 = { search_type: 'code', search_query: 'org:testOrg path:/ filename:package.json in:path' diff --git a/src/services/github.ts b/src/services/github.ts index 2ab97dff..398cafbd 100644 --- a/src/services/github.ts +++ b/src/services/github.ts @@ -2,12 +2,15 @@ import { Octokit } from '@octokit/rest'; import { retry } from '@octokit/plugin-retry'; import type { RestEndpointMethodTypes } from '@octokit/plugin-rest-endpoint-methods' +import { throttling } from '@octokit/plugin-throttling'; import _ from 'lodash'; import netrc from 'netrc'; +import { IMigrationContext } from '../migration-context'; + const VALID_SEARCH_TYPES: ReadonlyArray = ['code', 'repositories'] as const; -const RetryableOctokit = Octokit.plugin(retry); +const RetryableThrottledOctokit = Octokit.plugin(throttling, retry); interface SearchTypeAndQueryParams { search_type?: string @@ -17,7 +20,7 @@ interface SearchTypeAndQueryParams { export default class GithubService { private octokit: Octokit; - constructor(octokit?: Octokit) { + constructor(context: IMigrationContext, octokit?: Octokit) { if (octokit) { this.octokit = octokit; } else { @@ -29,12 +32,20 @@ export default class GithubService { set a token on the 'password' field in ~/.netrc for api.github.com`); } - this.octokit = new RetryableOctokit({ + this.octokit = new RetryableThrottledOctokit({ auth: token, - retry: { - // By default the doNotRetry setting includes 403, which means we face secondary rate limits - doNotRetry: [400, 401, 404, 422], - } + throttle: { + onRateLimit: (retryAfter: number, options: any) => { + context.logger.warn(`Hit rate limit for ${options.method} ${options.url}`); + context.logger.warn(`Retrying in ${retryAfter} second(s)`); + return options.request.retryCount < 5; + }, + onAbuseLimit: (retryAfter: number, options: any) => { + context.logger.warn(`Hit abuse limit for ${options.method} ${options.url}`); + context.logger.warn(`Retrying in ${retryAfter} second(s)`); + return options.request.retryCount < 5; + }, + }, }); } } diff --git a/yarn.lock b/yarn.lock index 2a79fba4..b74172bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -622,9 +622,9 @@ integrity sha512-3BGrt6FLjqM6br5AhWRKTr3u5GIVkjRYeAFrMp3HjnfICrg4xOrVRwFavKT6tsp++bq5dluL5t8ME/Nha/6c1Q== "@npmcli/config@*": - version "2.3.1" - resolved "https://registry.yarnpkg.com/@npmcli/config/-/config-2.3.1.tgz#41d80ce272831461b5cb158afa110525d4be0fed" - integrity sha512-F/8R/Zqun8682TgaCILUNoaVfd1LVaYZ/jcVt9KWzfKpzcPus1zEApAl54PqVqVJbNq6f01QTDQHD6L/n56BXw== + version "2.3.2" + resolved "https://registry.yarnpkg.com/@npmcli/config/-/config-2.3.2.tgz#6027efc132fcc809abef749c2f2e13dc4dcd6e0b" + integrity sha512-2/9dj143BFgQR8qxJbYptd8k+4+Po2uHYq3H6498ynZcRu4LrsDlngov5HGrvo2+f0pe0fBJwDEP2rRtaW8bkw== dependencies: ini "^2.0.0" mkdirp-infer-owner "^2.0.0" @@ -669,7 +669,7 @@ npm-bundled "^1.1.1" npm-normalize-package-bin "^1.0.1" -"@npmcli/map-workspaces@*", "@npmcli/map-workspaces@^2.0.0": +"@npmcli/map-workspaces@*": version "2.0.0" resolved "https://registry.yarnpkg.com/@npmcli/map-workspaces/-/map-workspaces-2.0.0.tgz#e342efbbdd0dad1bba5d7723b674ca668bf8ac5a" integrity sha512-QBJfpCY1NOAkkW3lFfru9VTdqvMB2TN0/vrevl5xBCv5Fi0XDVcA6rqqSau4Ysi4Iw3fBzyXV7hzyTBDfadf7g== @@ -679,14 +679,23 @@ minimatch "^3.0.4" read-package-json-fast "^2.0.1" -"@npmcli/metavuln-calculator@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@npmcli/metavuln-calculator/-/metavuln-calculator-2.0.0.tgz#70937b8b5a5cad5c588c8a7b38c4a8bd6f62c84c" - integrity sha512-VVW+JhWCKRwCTE+0xvD6p3uV4WpqocNYYtzyvenqL/u1Q3Xx6fGTJ+6UoIoii07fbuEO9U3IIyuGY0CYHDv1sg== +"@npmcli/map-workspaces@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@npmcli/map-workspaces/-/map-workspaces-1.0.4.tgz#915708b55afa25e20bc2c14a766c124c2c5d4cab" + integrity sha512-wVR8QxhyXsFcD/cORtJwGQodeeaDf0OxcHie8ema4VgFeqwYkFsDPnSrIRSytX8xR6nKPAH89WnwTcaU608b/Q== + dependencies: + "@npmcli/name-from-folder" "^1.0.1" + glob "^7.1.6" + minimatch "^3.0.4" + read-package-json-fast "^2.0.1" + +"@npmcli/metavuln-calculator@^1.1.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@npmcli/metavuln-calculator/-/metavuln-calculator-1.1.1.tgz#2f95ff3c6d88b366dd70de1c3f304267c631b458" + integrity sha512-9xe+ZZ1iGVaUovBVFI9h3qW+UuECUzhvZPxK9RaEA2mjU26o5D0JloGYWwLYvQELJNmBdQB6rrpuN8jni6LwzQ== dependencies: cacache "^15.0.5" - json-parse-even-better-errors "^2.3.1" - pacote "^12.0.0" + pacote "^11.1.11" semver "^7.3.2" "@npmcli/move-file@^1.0.1", "@npmcli/move-file@^1.1.0": @@ -812,6 +821,14 @@ "@octokit/types" "^6.0.3" bottleneck "^2.15.3" +"@octokit/plugin-throttling@^3.5.2": + version "3.5.2" + resolved "https://registry.yarnpkg.com/@octokit/plugin-throttling/-/plugin-throttling-3.5.2.tgz#8b1797a5f14edbca0b8af619394056ed0ed5c9b5" + integrity sha512-Eu7kfJxU8vmHqWGNszWpg+GVp2tnAfax3XQV5CkYPEE69C+KvInJXW9WajgSeW+cxYe0UVdouzCtcreGNuJo7A== + dependencies: + "@octokit/types" "^6.0.1" + bottleneck "^2.15.3" + "@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677" @@ -843,7 +860,7 @@ "@octokit/plugin-request-log" "^1.0.4" "@octokit/plugin-rest-endpoint-methods" "^5.12.0" -"@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.34.0", "@octokit/types@^6.8.3": +"@octokit/types@^6.0.1", "@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.34.0", "@octokit/types@^6.8.3": version "6.34.0" resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.34.0.tgz#c6021333334d1ecfb5d370a8798162ddf1ae8218" integrity sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw== @@ -1572,7 +1589,7 @@ before-after-hook@^2.2.0: resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e" integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ== -bin-links@^2.3.0: +bin-links@^2.2.1: version "2.3.0" resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-2.3.0.tgz#1ff241c86d2c29b24ae52f49544db5d78a4eb967" integrity sha512-JzrOLHLwX2zMqKdyYZjkDgQGT+kHDkIhv2/IK2lJ00qLxV4TmFoHi8drDBb6H5Zrz1YfgHkai4e2MGPqnoUhqA== @@ -1740,13 +1757,10 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chalk@*, chalk@^4.0.0, chalk@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" +chalk@*: + version "5.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.0.0.tgz#bd96c6bb8e02b96e08c0c3ee2a9d90e050c7b832" + integrity sha512-/duVOqst+luxCQRKEo4bNxinsOQtMP80ZYm7mMqzuh5PociNL0PvmHFvREJ9ueYL2TxlHjBcmLCdmocx9Vg+IQ== chalk@^2.0.0, chalk@^2.3.2: version "2.4.2" @@ -1757,6 +1771,14 @@ chalk@^2.0.0, chalk@^2.3.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^4.0.0, chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + char-regex@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" @@ -4731,7 +4753,19 @@ npm-profile@*: dependencies: npm-registry-fetch "^11.0.0" -npm-registry-fetch@*, npm-registry-fetch@^11.0.0: +npm-registry-fetch@*: + version "12.0.0" + resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-12.0.0.tgz#53d8c94f7c37293707b23728864710b76d3a3ca5" + integrity sha512-nd1I90UHoETjgWpo3GbcoM1l2S4JCUpzDcahU4x/GVCiDQ6yRiw2KyDoPVD8+MqODbPtWwHHGiyc4O5sgdEqPQ== + dependencies: + make-fetch-happen "^9.0.1" + minipass "^3.1.3" + minipass-fetch "^1.3.0" + minipass-json-stream "^1.0.1" + minizlib "^2.0.0" + npm-package-arg "^8.0.0" + +npm-registry-fetch@^11.0.0: version "11.0.0" resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-11.0.0.tgz#68c1bb810c46542760d62a6a965f85a702d43a76" integrity sha512-jmlgSxoDNuhAtxUIG6pVwwtz840i994dL14FoNVZisrmZW5kWd63IUTNv1m/hyRSGSqWjCUp/YZlS1BJyNp9XA== @@ -4760,76 +4794,76 @@ npm@^7.0.0: resolved "https://registry.yarnpkg.com/npm/-/npm-7.24.2.tgz#861117af8241bea592289f22407230e5300e59ca" integrity sha512-120p116CE8VMMZ+hk8IAb1inCPk4Dj3VZw29/n2g6UI77urJKVYb7FZUDW8hY+EBnfsjI/2yrobBgFyzo7YpVQ== dependencies: - "@isaacs/string-locale-compare" "*" - "@npmcli/arborist" "*" - "@npmcli/ci-detect" "*" - "@npmcli/config" "*" - "@npmcli/map-workspaces" "*" - "@npmcli/package-json" "*" - "@npmcli/run-script" "*" - abbrev "*" - ansicolors "*" - ansistyles "*" - archy "*" - cacache "*" - chalk "*" - chownr "*" - cli-columns "*" - cli-table3 "*" - columnify "*" - fastest-levenshtein "*" - glob "*" - graceful-fs "*" - hosted-git-info "*" - ini "*" - init-package-json "*" - is-cidr "*" - json-parse-even-better-errors "*" - libnpmaccess "*" - libnpmdiff "*" - libnpmexec "*" - libnpmfund "*" - libnpmhook "*" - libnpmorg "*" - libnpmpack "*" - libnpmpublish "*" - libnpmsearch "*" - libnpmteam "*" - libnpmversion "*" - make-fetch-happen "*" - minipass "*" - minipass-pipeline "*" - mkdirp "*" - mkdirp-infer-owner "*" - ms "*" - node-gyp "*" - nopt "*" - npm-audit-report "*" - npm-install-checks "*" - npm-package-arg "*" - npm-pick-manifest "*" - npm-profile "*" - npm-registry-fetch "*" - npm-user-validate "*" - npmlog "*" - opener "*" - pacote "*" - parse-conflict-json "*" - qrcode-terminal "*" - read "*" - read-package-json "*" - read-package-json-fast "*" - readdir-scoped-modules "*" - rimraf "*" - semver "*" - ssri "*" - tar "*" - text-table "*" - tiny-relative-date "*" - treeverse "*" - validate-npm-package-name "*" - which "*" - write-file-atomic "*" + "@isaacs/string-locale-compare" "^1.1.0" + "@npmcli/arborist" "^2.9.0" + "@npmcli/ci-detect" "^1.2.0" + "@npmcli/config" "^2.3.0" + "@npmcli/map-workspaces" "^1.0.4" + "@npmcli/package-json" "^1.0.1" + "@npmcli/run-script" "^1.8.6" + abbrev "~1.1.1" + ansicolors "~0.3.2" + ansistyles "~0.1.3" + archy "~1.0.0" + cacache "^15.3.0" + chalk "^4.1.2" + chownr "^2.0.0" + cli-columns "^3.1.2" + cli-table3 "^0.6.0" + columnify "~1.5.4" + fastest-levenshtein "^1.0.12" + glob "^7.2.0" + graceful-fs "^4.2.8" + hosted-git-info "^4.0.2" + ini "^2.0.0" + init-package-json "^2.0.5" + is-cidr "^4.0.2" + json-parse-even-better-errors "^2.3.1" + libnpmaccess "^4.0.2" + libnpmdiff "^2.0.4" + libnpmexec "^2.0.1" + libnpmfund "^1.1.0" + libnpmhook "^6.0.2" + libnpmorg "^2.0.2" + libnpmpack "^2.0.1" + libnpmpublish "^4.0.1" + libnpmsearch "^3.1.1" + libnpmteam "^2.0.3" + libnpmversion "^1.2.1" + make-fetch-happen "^9.1.0" + minipass "^3.1.3" + minipass-pipeline "^1.2.4" + mkdirp "^1.0.4" + mkdirp-infer-owner "^2.0.0" + ms "^2.1.2" + node-gyp "^7.1.2" + nopt "^5.0.0" + npm-audit-report "^2.1.5" + npm-install-checks "^4.0.0" + npm-package-arg "^8.1.5" + npm-pick-manifest "^6.1.1" + npm-profile "^5.0.3" + npm-registry-fetch "^11.0.0" + npm-user-validate "^1.0.1" + npmlog "^5.0.1" + opener "^1.5.2" + pacote "^11.3.5" + parse-conflict-json "^1.1.1" + qrcode-terminal "^0.12.0" + read "~1.0.7" + read-package-json "^4.1.1" + read-package-json-fast "^2.0.3" + readdir-scoped-modules "^1.1.0" + rimraf "^3.0.2" + semver "^7.3.5" + ssri "^8.0.1" + tar "^6.1.11" + text-table "~0.2.0" + tiny-relative-date "^1.3.0" + treeverse "^1.0.4" + validate-npm-package-name "~3.0.0" + which "^2.0.2" + write-file-atomic "^3.0.3" npmlog@*, npmlog@^6.0.0: version "6.0.0" @@ -5063,7 +5097,7 @@ pacote@*, pacote@^12.0.0, pacote@^12.0.2: ssri "^8.0.1" tar "^6.1.0" -pacote@^11.3.0: +pacote@^11.1.11, pacote@^11.3.0: version "11.3.5" resolved "https://registry.yarnpkg.com/pacote/-/pacote-11.3.5.tgz#73cf1fc3772b533f575e39efa96c50be8c3dc9d2" integrity sha512-fT375Yczn4zi+6Hkk2TBe1x1sP8FgFsEIZ2/iWaXY2r/NkhDJfxbcn5paz1+RTFCyNf+dPnaoBDJoAxXSU8Bkg==