From c233e2c88c947fe9b95320211e0af35e7b7b7243 Mon Sep 17 00:00:00 2001 From: Steve Hetzel Date: Tue, 8 Aug 2023 11:03:39 -0600 Subject: [PATCH 1/5] fix: prefer const --- src/shared/populateFilePaths.ts | 4 +- src/shared/remoteSourceTrackingService.ts | 31 ++++- test/unit/remoteSourceTracking.test.ts | 148 ++++++++++++++++++++++ 3 files changed, 180 insertions(+), 3 deletions(-) diff --git a/src/shared/populateFilePaths.ts b/src/shared/populateFilePaths.ts index d272718d..446f4634 100644 --- a/src/shared/populateFilePaths.ts +++ b/src/shared/populateFilePaths.ts @@ -77,7 +77,9 @@ export const populateFilePaths = (elements: ChangeResult[], packageDirPaths: str matchingComponent.xml } and maybe ${matchingComponent.walkContent().toString()}` ); - const key = getMetadataKey(matchingComponent.type.name, matchingComponent.fullName); + // Decode the key since local components can have encoded fullNames, but results from querying + // SourceMembers have fullNames that are not encoded. See: https://github.com/forcedotcom/cli/issues/1683 + const key = decodeURI(getMetadataKey(matchingComponent.type.name, matchingComponent.fullName)); elementMap.set(key, { ...elementMap.get(key), modified: true, diff --git a/src/shared/remoteSourceTrackingService.ts b/src/shared/remoteSourceTrackingService.ts index 7b23929b..ed67459e 100644 --- a/src/shared/remoteSourceTrackingService.ts +++ b/src/shared/remoteSourceTrackingService.ts @@ -500,11 +500,38 @@ export class RemoteSourceTrackingService extends ConfigFile { - return this.getSourceMembers()[key]; + const sourceMembers = this.getSourceMembers(); + let sm = sourceMembers[key]; + if (!sm) { + // Get all SourceMember keys in maxRevision, then iterate over them + // and compare their decoded value with the decoded key. + Object.keys(sourceMembers).some((memberKey) => { + if (decodeURI(memberKey) === decodeURI(key)) { + sm = sourceMembers[memberKey]; + this.logger.debug(`${key} matches already tracked member: ${memberKey}`); + return true; + } + }); + } + return sm; } private setMemberRevision(key: string, sourceMember: MemberRevision): void { - this.getContents().sourceMembers[key] = sourceMember; + const sourceMembers = this.getSourceMembers(); + const sm = sourceMembers[key]; + let matchingKey = key; + if (!sm) { + // Get all SourceMember keys in maxRevision, then iterate over them + // and compare their decoded value with the decoded key. + Object.keys(sourceMembers).some((memberKey) => { + if (decodeURI(memberKey) === decodeURI(key)) { + matchingKey = memberKey; + this.logger.debug(`${key} matches already tracked member: ${memberKey}`); + return true; + } + }); + } + this.getContents().sourceMembers[matchingKey] = sourceMember; } private calculateTimeout(memberCount: number): Duration { diff --git a/test/unit/remoteSourceTracking.test.ts b/test/unit/remoteSourceTracking.test.ts index bc8e641f..35d9396d 100644 --- a/test/unit/remoteSourceTracking.test.ts +++ b/test/unit/remoteSourceTracking.test.ts @@ -166,6 +166,154 @@ describe('remoteSourceTrackingService', () => { deleted: true, }); }); + + it('will match decoded SourceMember keys on get', async () => { + const maxJson = { + serverMaxRevisionCounter: 2, + sourceMembers: { + 'Layout__Broker__c-Broker Layout': { + serverRevisionCounter: 1, + lastRetrievedFromServer: 1, + memberType: 'Layout', + isNameObsolete: false, + }, + 'Layout__Broker__c-v1.1 Broker Layout': { + serverRevisionCounter: 2, + lastRetrievedFromServer: 2, + memberType: 'Layout', + isNameObsolete: false, + }, + }, + }; + await remoteSourceTrackingService.setContentsFromObject(maxJson); + + // @ts-ignore getSourceMember is private + expect(remoteSourceTrackingService.getSourceMember('Layout__Broker__c-v1%2E1 Broker Layout')).to.deep.equal({ + serverRevisionCounter: 2, + lastRetrievedFromServer: 2, + memberType: 'Layout', + isNameObsolete: false, + }); + }); + + it('will match encoded SourceMember keys on get', async () => { + const maxJson = { + serverMaxRevisionCounter: 2, + sourceMembers: { + 'Layout__Broker__c-Broker Layout': { + serverRevisionCounter: 1, + lastRetrievedFromServer: 1, + memberType: 'Layout', + isNameObsolete: false, + }, + 'Layout__Broker__c-v1%2E1 Broker Layout': { + serverRevisionCounter: 2, + lastRetrievedFromServer: 2, + memberType: 'Layout', + isNameObsolete: false, + }, + }, + }; + await remoteSourceTrackingService.setContentsFromObject(maxJson); + + // @ts-ignore getSourceMember is private + expect(remoteSourceTrackingService.getSourceMember('Layout__Broker__c-v1.1 Broker Layout')).to.deep.equal({ + serverRevisionCounter: 2, + lastRetrievedFromServer: 2, + memberType: 'Layout', + isNameObsolete: false, + }); + }); + + it('will match/update decoded SourceMember keys on set', async () => { + const maxJson = { + serverMaxRevisionCounter: 2, + sourceMembers: { + 'Layout__Broker__c-Broker Layout': { + serverRevisionCounter: 1, + lastRetrievedFromServer: 1, + memberType: 'Layout', + isNameObsolete: false, + }, + 'Layout__Broker__c-v1.1 Broker Layout': { + serverRevisionCounter: 2, + lastRetrievedFromServer: 2, + memberType: 'Layout', + isNameObsolete: false, + }, + }, + }; + await remoteSourceTrackingService.setContentsFromObject(maxJson); + + // @ts-ignore setMemberRevision is private + remoteSourceTrackingService.setMemberRevision('Layout__Broker__c-v1%2E1 Broker Layout', { + serverRevisionCounter: 3, + lastRetrievedFromServer: 3, + memberType: 'Layout', + isNameObsolete: false, + }); + + // @ts-ignore getSourceMembers is private + expect(remoteSourceTrackingService.getSourceMembers()).to.deep.equal({ + 'Layout__Broker__c-Broker Layout': { + serverRevisionCounter: 1, + lastRetrievedFromServer: 1, + memberType: 'Layout', + isNameObsolete: false, + }, + 'Layout__Broker__c-v1.1 Broker Layout': { + serverRevisionCounter: 3, + lastRetrievedFromServer: 3, + memberType: 'Layout', + isNameObsolete: false, + }, + }); + }); + + it('will match/update encoded SourceMember keys on set', async () => { + const maxJson = { + serverMaxRevisionCounter: 2, + sourceMembers: { + 'Layout__Broker__c-Broker Layout': { + serverRevisionCounter: 1, + lastRetrievedFromServer: 1, + memberType: 'Layout', + isNameObsolete: false, + }, + 'Layout__Broker__c-v1%2E1 Broker Layout': { + serverRevisionCounter: 2, + lastRetrievedFromServer: 2, + memberType: 'Layout', + isNameObsolete: false, + }, + }, + }; + await remoteSourceTrackingService.setContentsFromObject(maxJson); + + // @ts-ignore setMemberRevision is private + remoteSourceTrackingService.setMemberRevision('Layout__Broker__c-v1.1 Broker Layout', { + serverRevisionCounter: 3, + lastRetrievedFromServer: 3, + memberType: 'Layout', + isNameObsolete: false, + }); + + // @ts-ignore getSourceMembers is private + expect(remoteSourceTrackingService.getSourceMembers()).to.deep.equal({ + 'Layout__Broker__c-Broker Layout': { + serverRevisionCounter: 1, + lastRetrievedFromServer: 1, + memberType: 'Layout', + isNameObsolete: false, + }, + 'Layout__Broker__c-v1%2E1 Broker Layout': { + serverRevisionCounter: 3, + lastRetrievedFromServer: 3, + memberType: 'Layout', + isNameObsolete: false, + }, + }); + }); }); describe('setServerMaxRevision', () => { From d24e9b5ceffc17926338af20261774d36ae39d59 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 9 Aug 2023 19:31:11 -0500 Subject: [PATCH 2/5] refactor: pr feedback --- src/shared/remoteSourceTrackingService.ts | 55 ++++++++++++----------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/src/shared/remoteSourceTrackingService.ts b/src/shared/remoteSourceTrackingService.ts index ed67459e..582c67ed 100644 --- a/src/shared/remoteSourceTrackingService.ts +++ b/src/shared/remoteSourceTrackingService.ts @@ -501,36 +501,16 @@ export class RemoteSourceTrackingService extends ConfigFile { const sourceMembers = this.getSourceMembers(); - let sm = sourceMembers[key]; - if (!sm) { - // Get all SourceMember keys in maxRevision, then iterate over them - // and compare their decoded value with the decoded key. - Object.keys(sourceMembers).some((memberKey) => { - if (decodeURI(memberKey) === decodeURI(key)) { - sm = sourceMembers[memberKey]; - this.logger.debug(`${key} matches already tracked member: ${memberKey}`); - return true; - } - }); - } - return sm; + return ( + sourceMembers[key] ?? sourceMembers[getDecodedKeyIfSourceMembersHas({ sourceMembers, key, logger: this.logger })] + ); } private setMemberRevision(key: string, sourceMember: MemberRevision): void { const sourceMembers = this.getSourceMembers(); - const sm = sourceMembers[key]; - let matchingKey = key; - if (!sm) { - // Get all SourceMember keys in maxRevision, then iterate over them - // and compare their decoded value with the decoded key. - Object.keys(sourceMembers).some((memberKey) => { - if (decodeURI(memberKey) === decodeURI(key)) { - matchingKey = memberKey; - this.logger.debug(`${key} matches already tracked member: ${memberKey}`); - return true; - } - }); - } + const matchingKey = sourceMembers[key] + ? key + : getDecodedKeyIfSourceMembersHas({ sourceMembers, key, logger: this.logger }); this.getContents().sourceMembers[matchingKey] = sourceMember; } @@ -615,3 +595,26 @@ const convertRevisionToChange = (memberKey: string, memberRevision: MemberRevisi name: memberKey.replace(`${memberRevision.memberType}__`, ''), deleted: memberRevision.isNameObsolete, }); + +/** + * + * iterate SourceMember keys and compare their decoded value with the decoded key. + * if there's a match, return the matching decoded key, otherwise, return the original key + */ +function getDecodedKeyIfSourceMembersHas({ + key, + sourceMembers, + logger, +}: { + sourceMembers: Dictionary; + key: string; + logger: Logger; +}): string { + const originalKeyDecoded = decodeURI(key); + const match = Object.keys(sourceMembers).find((memberKey) => decodeURI(memberKey) === originalKeyDecoded); + if (match) { + logger.debug(`${match} matches already tracked member: ${key}`); + return match; + } + return key; +} From 2d42c6fd895fda140014696c794f855b1921f7dc Mon Sep 17 00:00:00 2001 From: Steve Hetzel Date: Fri, 11 Aug 2023 13:21:27 -0600 Subject: [PATCH 3/5] fix: use decodeURIComponent() rather than decodeURI() --- src/shared/populateFilePaths.ts | 2 +- src/shared/remoteSourceTrackingService.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/shared/populateFilePaths.ts b/src/shared/populateFilePaths.ts index 446f4634..dbff4be1 100644 --- a/src/shared/populateFilePaths.ts +++ b/src/shared/populateFilePaths.ts @@ -79,7 +79,7 @@ export const populateFilePaths = (elements: ChangeResult[], packageDirPaths: str ); // Decode the key since local components can have encoded fullNames, but results from querying // SourceMembers have fullNames that are not encoded. See: https://github.com/forcedotcom/cli/issues/1683 - const key = decodeURI(getMetadataKey(matchingComponent.type.name, matchingComponent.fullName)); + const key = decodeURIComponent(getMetadataKey(matchingComponent.type.name, matchingComponent.fullName)); elementMap.set(key, { ...elementMap.get(key), modified: true, diff --git a/src/shared/remoteSourceTrackingService.ts b/src/shared/remoteSourceTrackingService.ts index 582c67ed..c58f43e3 100644 --- a/src/shared/remoteSourceTrackingService.ts +++ b/src/shared/remoteSourceTrackingService.ts @@ -610,8 +610,8 @@ function getDecodedKeyIfSourceMembersHas({ key: string; logger: Logger; }): string { - const originalKeyDecoded = decodeURI(key); - const match = Object.keys(sourceMembers).find((memberKey) => decodeURI(memberKey) === originalKeyDecoded); + const originalKeyDecoded = decodeURIComponent(key); + const match = Object.keys(sourceMembers).find((memberKey) => decodeURIComponent(memberKey) === originalKeyDecoded); if (match) { logger.debug(`${match} matches already tracked member: ${key}`); return match; From 5ce7285b981a5797aa82af131c9c034518a660cd Mon Sep 17 00:00:00 2001 From: Steve Hetzel Date: Mon, 14 Aug 2023 09:57:01 -0600 Subject: [PATCH 4/5] fix: use latest SDR lib and bump cli-plugins-testkit --- package.json | 4 ++-- yarn.lock | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index ca0fbce0..ed35611f 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "dependencies": { "@salesforce/core": "^5.2.0", "@salesforce/kit": "^3.0.9", - "@salesforce/source-deploy-retrieve": "^9.7.2", + "@salesforce/source-deploy-retrieve": "^9.7.4", "@salesforce/ts-types": "^2.0.6", "fast-xml-parser": "^4.2.5", "graceful-fs": "^4.2.11", @@ -56,7 +56,7 @@ "ts-retry-promise": "^0.7.0" }, "devDependencies": { - "@salesforce/cli-plugins-testkit": "^4.2.9", + "@salesforce/cli-plugins-testkit": "^4.3.0", "@salesforce/dev-config": "^4.0.1", "@salesforce/dev-scripts": "^5.5.0", "@salesforce/prettier-config": "^0.0.3", diff --git a/yarn.lock b/yarn.lock index dfe146f9..ec1a1cb9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -547,14 +547,14 @@ mv "~2" safe-json-stringify "~1" -"@salesforce/cli-plugins-testkit@^4.2.9": - version "4.2.9" - resolved "https://registry.yarnpkg.com/@salesforce/cli-plugins-testkit/-/cli-plugins-testkit-4.2.9.tgz#813bbb6a7926d17d5d0becd664e08f1c115680d1" - integrity sha512-v71dFmhlgwtmehK2QJ8+toeCYc39WQFu7R2wmhsHQeVA1UuYcx3uue5JnC392PIXddxQtjdR6eazk52tQg4nyA== +"@salesforce/cli-plugins-testkit@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@salesforce/cli-plugins-testkit/-/cli-plugins-testkit-4.3.0.tgz#253079d476569fbc4977c8100b3bf201b3e4a788" + integrity sha512-kW59i4muO49evQ76waWTfDUXpTfyBUQ0N1QIF4ZFB+aSAdVGG5N/oTMYz882ScS/SwB0XP0H119wT4cLoJI+Mw== dependencies: "@salesforce/core" "^5.2.0" "@salesforce/kit" "^3.0.9" - "@salesforce/ts-types" "^2.0.2" + "@salesforce/ts-types" "^2.0.6" "@types/shelljs" "^0.8.12" debug "^4.3.1" jszip "^3.10.1" @@ -669,16 +669,16 @@ resolved "https://registry.yarnpkg.com/@salesforce/schemas/-/schemas-1.6.0.tgz#14505ebad2fb2d4f7b14837545d662766d293561" integrity sha512-SwhDTLucj/GRbPpxlEoDZeqlX22o+G6fiebTXTu1cZKmd1oE0W2L7SlTTgJnWck8bhTeBIgQi9cpD8c2t5ISKA== -"@salesforce/source-deploy-retrieve@^9.7.2": - version "9.7.2" - resolved "https://registry.yarnpkg.com/@salesforce/source-deploy-retrieve/-/source-deploy-retrieve-9.7.2.tgz#b04b946ea226b21971f4cb94337ab6d8c2cd8b9f" - integrity sha512-RJUSjeNgIpijT0BFEEnEsbOCbkWo7AJaWIN5IHyTzBlExNnTfnze3zq5hOvhuzu2UDWgD4LdQsovnhk8i0i75A== +"@salesforce/source-deploy-retrieve@^9.7.4": + version "9.7.4" + resolved "https://registry.yarnpkg.com/@salesforce/source-deploy-retrieve/-/source-deploy-retrieve-9.7.4.tgz#d1fb390e3be3d245b7b0fae5b803d946cc653f9a" + integrity sha512-Y+/93VvRT9YkP368s+K9WrZrSwoo8Y0e2iVlrxj2WVLkOacue0clWZZizrkqXMxD0s4xnStk9be8cKyyWbKh1w== dependencies: "@salesforce/core" "^5.2.0" "@salesforce/kit" "^3.0.9" - "@salesforce/ts-types" "^2.0.5" + "@salesforce/ts-types" "^2.0.6" fast-levenshtein "^3.0.0" - fast-xml-parser "^4.2.6" + fast-xml-parser "^4.2.7" got "^11.8.6" graceful-fs "^4.2.11" ignore "^5.2.4" @@ -2491,7 +2491,7 @@ fast-safe-stringify@^2.1.1: resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== -fast-xml-parser@^4.2.5, fast-xml-parser@^4.2.6: +fast-xml-parser@^4.2.5, fast-xml-parser@^4.2.7: version "4.2.7" resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.2.7.tgz#871f2ca299dc4334b29f8da3658c164e68395167" integrity sha512-J8r6BriSLO1uj2miOk1NW0YVm8AGOOu3Si2HQp/cSmo6EA4m3fcwu2WKjJ4RK9wMLBtg69y1kS8baDiQBR41Ig== From 3d62d11ac29ded0b5b915b6fb9487a3dd4f79fb6 Mon Sep 17 00:00:00 2001 From: Steve Hetzel Date: Mon, 21 Aug 2023 10:28:34 -0600 Subject: [PATCH 5/5] fix: bump to lastest SDR and merge main --- package.json | 2 +- yarn.lock | 47 +++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index ed35611f..52e5e470 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "dependencies": { "@salesforce/core": "^5.2.0", "@salesforce/kit": "^3.0.9", - "@salesforce/source-deploy-retrieve": "^9.7.4", + "@salesforce/source-deploy-retrieve": "^9.7.6", "@salesforce/ts-types": "^2.0.6", "fast-xml-parser": "^4.2.5", "graceful-fs": "^4.2.11", diff --git a/yarn.lock b/yarn.lock index ec1a1cb9..bac48796 100644 --- a/yarn.lock +++ b/yarn.lock @@ -607,6 +607,29 @@ proper-lockfile "^4.1.2" ts-retry-promise "^0.7.0" +"@salesforce/core@^5.2.1": + version "5.2.1" + resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-5.2.1.tgz#299bddae7d0705c773b194be8994e5730735e2b4" + integrity sha512-QMx11A0KA/Vl+Ckmz83cw+fiUCMOmsUD8CA3987wAkfJOgxtqyhT4R1R8tj7ad8flQydKUyL3o4UE/2u82tXOw== + dependencies: + "@salesforce/kit" "^3.0.9" + "@salesforce/schemas" "^1.6.0" + "@salesforce/ts-types" "^2.0.5" + "@types/semver" "^7.5.0" + ajv "^8.12.0" + change-case "^4.1.2" + faye "^1.4.0" + form-data "^4.0.0" + js2xmlparser "^4.0.1" + jsforce "^2.0.0-beta.27" + jsonwebtoken "9.0.1" + jszip "3.10.1" + pino "^8.14.2" + pino-abstract-transport "^1.0.0" + pino-pretty "^10.2.0" + proper-lockfile "^4.1.2" + ts-retry-promise "^0.7.0" + "@salesforce/dev-config@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@salesforce/dev-config/-/dev-config-4.0.1.tgz#662ffaa4409713553aaf68eed93e7d2429c3ff0e" @@ -669,14 +692,14 @@ resolved "https://registry.yarnpkg.com/@salesforce/schemas/-/schemas-1.6.0.tgz#14505ebad2fb2d4f7b14837545d662766d293561" integrity sha512-SwhDTLucj/GRbPpxlEoDZeqlX22o+G6fiebTXTu1cZKmd1oE0W2L7SlTTgJnWck8bhTeBIgQi9cpD8c2t5ISKA== -"@salesforce/source-deploy-retrieve@^9.7.4": - version "9.7.4" - resolved "https://registry.yarnpkg.com/@salesforce/source-deploy-retrieve/-/source-deploy-retrieve-9.7.4.tgz#d1fb390e3be3d245b7b0fae5b803d946cc653f9a" - integrity sha512-Y+/93VvRT9YkP368s+K9WrZrSwoo8Y0e2iVlrxj2WVLkOacue0clWZZizrkqXMxD0s4xnStk9be8cKyyWbKh1w== +"@salesforce/source-deploy-retrieve@^9.7.6": + version "9.7.6" + resolved "https://registry.yarnpkg.com/@salesforce/source-deploy-retrieve/-/source-deploy-retrieve-9.7.6.tgz#331d2b6287fa9a5a19fe6e00abe3146002503e1c" + integrity sha512-U6hDvVd/zU4NJJMpaskXVaPOb9RDFjz6fvLWzSjcbVPRbn4QOppi9geAAoDn494Cnspo5pG9/DNoSQ2aKcVK2w== dependencies: - "@salesforce/core" "^5.2.0" + "@salesforce/core" "^5.2.1" "@salesforce/kit" "^3.0.9" - "@salesforce/ts-types" "^2.0.6" + "@salesforce/ts-types" "^2.0.7" fast-levenshtein "^3.0.0" fast-xml-parser "^4.2.7" got "^11.8.6" @@ -704,6 +727,13 @@ dependencies: tslib "^2.6.1" +"@salesforce/ts-types@^2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@salesforce/ts-types/-/ts-types-2.0.7.tgz#02a6999d0b0e7bcd6c6d8ce621c79fa61af24701" + integrity sha512-8csXgstPuy6QXL3JavkIi/f8DOWHBNCvWeszrFu5sbVlcKO3YqOOCE+rDFGPkrZsYv5OywV6H8kEi877bWOz6Q== + dependencies: + tslib "^2.6.2" + "@sindresorhus/is@^4.0.0": version "4.6.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" @@ -5429,6 +5459,11 @@ tslib@^2.0.1, tslib@^2.0.3, tslib@^2.5.3, tslib@^2.6.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.1.tgz#fd8c9a0ff42590b25703c0acb3de3d3f4ede0410" integrity sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig== +tslib@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"