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', () => {