Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into sh/remove-unzipper
Browse files Browse the repository at this point in the history
  • Loading branch information
mshanemc committed Aug 30, 2023
2 parents 2973e82 + f288319 commit a987037
Show file tree
Hide file tree
Showing 7 changed files with 609 additions and 1,148 deletions.
1,398 changes: 303 additions & 1,095 deletions CHANGELOG.md

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@salesforce/source-deploy-retrieve",
"version": "9.7.4",
"version": "9.7.8",
"description": "JavaScript library to run Salesforce metadata deploys and retrieves",
"main": "lib/src/index.js",
"author": "Salesforce",
Expand All @@ -26,7 +26,7 @@
},
"dependencies": {
"@salesforce/core": "^5.2.3",
"@salesforce/kit": "^3.0.9",
"@salesforce/kit": "^3.0.11",
"@salesforce/ts-types": "^2.0.7",
"fast-levenshtein": "^3.0.0",
"fast-xml-parser": "^4.2.7",
Expand All @@ -43,7 +43,7 @@
"@salesforce/dev-config": "^4.0.1",
"@salesforce/dev-scripts": "^5.7.0",
"@salesforce/prettier-config": "^0.0.3",
"@salesforce/ts-sinon": "^1.4.14",
"@salesforce/ts-sinon": "^1.4.15",
"@types/deep-equal-in-any-order": "^1.0.1",
"@types/fast-levenshtein": "^0.0.2",
"@types/graceful-fs": "^4.1.6",
Expand All @@ -53,18 +53,18 @@
"@types/shelljs": "^0.8.12",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"chai": "^4.3.7",
"chai": "^4.3.8",
"deep-equal-in-any-order": "^1.1.19",
"deepmerge": "^4.3.1",
"eslint": "^8.47.0",
"eslint": "^8.48.0",
"eslint-config-prettier": "^8.10.0",
"eslint-config-salesforce": "^2.0.2",
"eslint-config-salesforce-license": "^0.2.0",
"eslint-config-salesforce-typescript": "^1.1.2",
"eslint-plugin-header": "^3.1.1",
"eslint-plugin-import": "^2.28.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-jsdoc": "^44.2.7",
"eslint-plugin-sf-plugin": "^1.16.2",
"eslint-plugin-sf-plugin": "^1.16.3",
"husky": "^7.0.4",
"jsforce": "^2.0.0-beta.27",
"mocha": "^9.2.2",
Expand Down
23 changes: 11 additions & 12 deletions src/collections/componentSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
PackageTypeMembers,
} from './types';
import { LazyCollection } from './lazyCollection';
import { DecodeableMap } from './decodeableMap';

Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@salesforce/source-deploy-retrieve', 'sdr');
Expand Down Expand Up @@ -73,14 +74,14 @@ export class ComponentSet extends LazyCollection<MetadataComponent> {
public forceIgnoredPaths?: Set<string>;
private logger: Logger;
private registry: RegistryAccess;
private components = new Map<string, Map<string, SourceComponent>>();
private components = new DecodeableMap<string, DecodeableMap<string, SourceComponent>>();

// internal component maps used by this.getObject() when building manifests.
private destructiveComponents = {
[DestructiveChangesType.PRE]: new Map<string, Map<string, SourceComponent>>(),
[DestructiveChangesType.POST]: new Map<string, Map<string, SourceComponent>>(),
[DestructiveChangesType.PRE]: new DecodeableMap<string, DecodeableMap<string, SourceComponent>>(),
[DestructiveChangesType.POST]: new DecodeableMap<string, DecodeableMap<string, SourceComponent>>(),
};
private manifestComponents = new Map<string, Map<string, SourceComponent>>();
private manifestComponents = new DecodeableMap<string, DecodeableMap<string, SourceComponent>>();

private destructiveChangesType = DestructiveChangesType.POST;

Expand Down Expand Up @@ -112,11 +113,11 @@ export class ComponentSet extends LazyCollection<MetadataComponent> {
return size;
}

public get destructiveChangesPre(): Map<string, Map<string, SourceComponent>> {
public get destructiveChangesPre(): DecodeableMap<string, DecodeableMap<string, SourceComponent>> {
return this.destructiveComponents[DestructiveChangesType.PRE];
}

public get destructiveChangesPost(): Map<string, Map<string, SourceComponent>> {
public get destructiveChangesPost(): DecodeableMap<string, DecodeableMap<string, SourceComponent>> {
return this.destructiveComponents[DestructiveChangesType.POST];
}

Expand Down Expand Up @@ -485,7 +486,7 @@ export class ComponentSet extends LazyCollection<MetadataComponent> {
public add(component: ComponentLike, deletionType?: DestructiveChangesType): void {
const key = simpleKey(component);
if (!this.components.has(key)) {
this.components.set(key, new Map<string, SourceComponent>());
this.components.set(key, new DecodeableMap<string, SourceComponent>());
}

if (!(component instanceof SourceComponent)) {
Expand All @@ -502,12 +503,12 @@ export class ComponentSet extends LazyCollection<MetadataComponent> {
this.logger.debug(`Marking component for delete: ${component.fullName}`);
const deletions = this.destructiveComponents[deletionType];
if (!deletions.has(key)) {
deletions.set(key, new Map<string, SourceComponent>());
deletions.set(key, new DecodeableMap<string, SourceComponent>());
}
deletions.get(key)?.set(sourceKey(component), component);
} else {
if (!this.manifestComponents.has(key)) {
this.manifestComponents.set(key, new Map<string, SourceComponent>());
this.manifestComponents.set(key, new DecodeableMap<string, SourceComponent>());
}
this.manifestComponents.get(key)?.set(sourceKey(component), component);
}
Expand Down Expand Up @@ -542,10 +543,8 @@ export class ComponentSet extends LazyCollection<MetadataComponent> {
* @returns `true` if the component is in the set
*/
public has(component: ComponentLike): boolean {
// Compare the component key as is and decoded. Decoding the key before comparing can solve some edge cases
// in component fullNames such as Layouts. See: https://github.com/forcedotcom/cli/issues/1683
const key = simpleKey(component);
if (this.components.has(key) || this.components.has(decodeURIComponent(key))) {
if (this.components.has(key)) {
return true;
}

Expand Down
78 changes: 78 additions & 0 deletions src/collections/decodeableMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright (c) 2023, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

/**
* This is an extension of the Map class that treats keys as the same by matching first normally,
* then decoded. Decoding the key before comparing can solve some edge cases in component fullNames
* such as Layouts. See: https://github.com/forcedotcom/cli/issues/1683
*
* Examples:
*
* Given a map with entries:
* ```javascript
* 'layout#Layout__Broker__c-v1%2E1 Broker Layout' : {...}
* 'layout#Layout__Broker__c-v9.2 Broker Layout' : {...}
* ```
*
* `decodeableMap.has('layout#Layout__Broker__c-v1.1 Broker Layout')` --> returns `true`
* `decodeableMap.has('layout#Layout__Broker__c-v9%2E2 Broker Layout')` --> returns `true`
*/
export class DecodeableMap<K extends string, V> extends Map<string, V> {
/**
* boolean indicating whether an element with the specified key (matching decoded) exists or not.
*/
public has(key: K): boolean {
return super.has(key) || this.hasDecoded(key);
}

/**
* Returns a specified element from the Map object. If the value that is associated to
* the provided key (matching decoded) is an object, then you will get a reference to
* that object and any change made to that object will effectively modify it inside the Map.
*/
public get(key: K): V | undefined {
return super.get(key) ?? this.getDecoded(key);
}

/**
* Adds a new element with a specified key and value to the Map. If an element with the
* same key (matching decoded) already exists, the element will be updated.
*/
public set(key: K, value: V): this {
const sKey = this.getExistingKey(key) ?? key;
return super.set(sKey, value);
}

/**
* true if an element in the Map existed (matching decoded) and has been removed, or false
* if the element does not exist.
*/
public delete(key: K): boolean {
const sKey = this.getExistingKey(key) ?? key;
return super.delete(sKey);
}

// Returns true if the passed `key` matches an existing key entry when both keys are decoded.
private hasDecoded(key: string): boolean {
return !!this.getExistingKey(key);
}

// Returns the value of an entry matching on decoded keys.
private getDecoded(key: string): V | undefined {
const existingKey = this.getExistingKey(key);
return existingKey ? super.get(existingKey) : undefined;
}

// Returns the key as it is in the map, matching on decoded keys.
private getExistingKey(key: string): string | undefined {
for (const compKey of this.keys()) {
if (decodeURIComponent(compKey) === decodeURIComponent(key)) {
return compKey;
}
}
}
}
11 changes: 10 additions & 1 deletion src/registry/metadataRegistry.json
Original file line number Diff line number Diff line change
Expand Up @@ -3523,6 +3523,14 @@
"directoryName": "SearchableObjDataSyncInfoSettings",
"inFolder": false,
"strictDirectoryName": false
},
"fundraisingconfig": {
"id": "fundraisingconfig",
"name": "FundraisingConfig",
"suffix": "fundraisingConfig",
"directoryName": "fundraisingConfigs",
"inFolder": false,
"strictDirectoryName": false
}
},
"suffixes": {
Expand Down Expand Up @@ -3909,7 +3917,8 @@
"serviceprocess": "serviceprocess",
"processflowmigration": "processflowmigration",
"SearchableObjDataSyncInfoSetting": "searchableobjdatasyncinfo",
"SearchCriteriaConfigurationSetting": "searchcriteriaconfiguration"
"SearchCriteriaConfigurationSetting": "searchcriteriaconfiguration",
"fundraisingConfig": "fundraisingconfig"
},
"strictDirectoryNames": {
"experiences": "experiencebundle",
Expand Down
168 changes: 168 additions & 0 deletions test/collections/decodeableMap.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
* Copyright (c) 2020, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import { expect } from 'chai';
import * as sinon from 'sinon';
import { DecodeableMap } from '../../src/collections/decodeableMap';

describe('DecodeableMap', () => {
let dMap: DecodeableMap<string, string>;
const ENCODED_KEY = 'encodedKey';
const DECODED_KEY = 'decodedKey';

const sandbox = sinon.createSandbox();
let hasDecodedSpy: sinon.SinonSpy;
let getDecodedSpy: sinon.SinonSpy;
let hasMapSpy: sinon.SinonSpy;
let getMapSpy: sinon.SinonSpy;
let setMapSpy: sinon.SinonSpy;
let deleteMapSpy: sinon.SinonSpy;

beforeEach(() => {
dMap = new DecodeableMap([
['Layout-v1%2E1 Layout', ENCODED_KEY],
['Layout-v9.2 Layout', DECODED_KEY],
]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
hasDecodedSpy = sandbox.spy(dMap, 'hasDecoded' as any);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getDecodedSpy = sandbox.spy(dMap, 'getDecoded' as any);
hasMapSpy = sandbox.spy(Map.prototype, 'has');
getMapSpy = sandbox.spy(Map.prototype, 'get');
setMapSpy = sandbox.spy(Map.prototype, 'set');
deleteMapSpy = sandbox.spy(Map.prototype, 'delete');
});

afterEach(() => {
sandbox.restore();
});

describe('has()', () => {
it('should match on exact key without decoding', () => {
expect(dMap.has('Layout-v1%2E1 Layout')).to.be.true;
expect(hasMapSpy.called).to.be.true;
expect(hasDecodedSpy.called).to.be.false;
});

it('should match encoded key with decoded value', () => {
expect(dMap.has('Layout-v1.1 Layout')).to.be.true;
expect(hasMapSpy.called).to.be.true;
expect(hasDecodedSpy.called).to.be.true;
});

it('should match decoded key with encoded value', () => {
expect(dMap.has('Layout-v9%2E2 Layout')).to.be.true;
expect(hasMapSpy.called).to.be.true;
expect(hasDecodedSpy.called).to.be.true;
});

it('should not match on no existing key', () => {
expect(dMap.has('Layout-MISSING Layout')).to.be.false;
expect(hasMapSpy.called).to.be.true;
expect(hasDecodedSpy.called).to.be.true;
});
});

describe('get()', () => {
it('should get value with exact key without decoding', () => {
expect(dMap.get('Layout-v1%2E1 Layout')).to.equal(ENCODED_KEY);
expect(getMapSpy.calledOnce).to.be.true;
expect(getDecodedSpy.called).to.be.false;
});

it('should get value of encoded key using decoded key', () => {
expect(dMap.get('Layout-v1.1 Layout')).to.equal(ENCODED_KEY);
expect(getMapSpy.calledTwice).to.be.true;
expect(getDecodedSpy.calledOnce).to.be.true;
});

it('should get value of decoded key using encoded key', () => {
expect(dMap.get('Layout-v9%2E2 Layout')).to.equal(DECODED_KEY);
expect(getMapSpy.calledTwice).to.be.true;
expect(getDecodedSpy.calledOnce).to.be.true;
});

it('should return undefined on no existing key', () => {
expect(dMap.get('Layout-MISSING Layout')).to.be.undefined;
expect(getMapSpy.calledOnce).to.be.true;
expect(getDecodedSpy.called).to.be.true;
});
});

describe('set()', () => {
const NEW_VALUE = 'new value from set';

it('should set value with exact key', () => {
expect(dMap.set('Layout-v1%2E1 Layout', NEW_VALUE)).to.equal(dMap);
expect(setMapSpy.called).to.be.true;
expect(setMapSpy.lastCall.args[0]).to.equal('Layout-v1%2E1 Layout');
expect(setMapSpy.lastCall.args[1]).to.equal(NEW_VALUE);
expect(dMap.size).to.equal(2);
expect(dMap.get('Layout-v1%2E1 Layout')).to.equal(NEW_VALUE);
});

it('should set value of encoded key using decoded key', () => {
expect(dMap.set('Layout-v1.1 Layout', NEW_VALUE)).to.equal(dMap);
expect(setMapSpy.called).to.be.true;
expect(setMapSpy.lastCall.args[0]).to.equal('Layout-v1%2E1 Layout');
expect(setMapSpy.lastCall.args[1]).to.equal(NEW_VALUE);
expect(dMap.size).to.equal(2);
expect(dMap.get('Layout-v1%2E1 Layout')).to.equal(NEW_VALUE);
});

it('should set value of decoded key using encoded key', () => {
expect(dMap.set('Layout-v9%2E2 Layout', NEW_VALUE)).to.equal(dMap);
expect(setMapSpy.called).to.be.true;
expect(setMapSpy.lastCall.args[0]).to.equal('Layout-v9.2 Layout');
expect(setMapSpy.lastCall.args[1]).to.equal(NEW_VALUE);
expect(dMap.size).to.equal(2);
expect(dMap.get('Layout-v9.2 Layout')).to.equal(NEW_VALUE);
});

it('should set new entry on no existing key', () => {
expect(dMap.set('Layout-MISSING Layout', NEW_VALUE)).to.equal(dMap);
expect(setMapSpy.called).to.be.true;
expect(setMapSpy.lastCall.args[0]).to.equal('Layout-MISSING Layout');
expect(setMapSpy.lastCall.args[1]).to.equal(NEW_VALUE);
expect(dMap.size).to.equal(3);
expect(dMap.get('Layout-MISSING Layout')).to.equal(NEW_VALUE);
});
});

describe('delete()', () => {
it('should delete using exact key', () => {
expect(dMap.delete('Layout-v1%2E1 Layout')).to.be.true;
expect(deleteMapSpy.calledOnce).to.be.true;
expect(deleteMapSpy.firstCall.args[0]).to.equal('Layout-v1%2E1 Layout');
expect(dMap.size).to.equal(1);
expect(dMap.has('Layout-v1%2E1 Layout')).to.be.false;
});

it('should delete the encoded key using decoded value', () => {
expect(dMap.delete('Layout-v1.1 Layout')).to.be.true;
expect(deleteMapSpy.calledOnce).to.be.true;
expect(deleteMapSpy.firstCall.args[0]).to.equal('Layout-v1%2E1 Layout');
expect(dMap.size).to.equal(1);
expect(dMap.has('Layout-v1.1 Layout')).to.be.false;
});

it('should delete the decoded key using encoded value', () => {
expect(dMap.delete('Layout-v9%2E2 Layout')).to.be.true;
expect(deleteMapSpy.calledOnce).to.be.true;
expect(deleteMapSpy.firstCall.args[0]).to.equal('Layout-v9.2 Layout');
expect(dMap.size).to.equal(1);
expect(dMap.has('Layout-v9%2E2 Layout')).to.be.false;
});

it('should not delete on no existing key', () => {
expect(dMap.delete('Layout-MISSING Layout')).to.be.false;
expect(deleteMapSpy.calledOnce).to.be.true;
expect(deleteMapSpy.firstCall.args[0]).to.equal('Layout-MISSING Layout');
expect(dMap.size).to.equal(2);
expect(dMap.has('Layout-MISSING Layout')).to.be.false;
});
});
});
Loading

2 comments on commit a987037

@svc-cli-bot
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Benchmark suite Current: a987037 Previous: 2973e82 Ratio
eda-componentSetCreate-linux 3097 ms 341 ms 9.08
eda-sourceToMdapi-linux 10755 ms 8527 ms 1.26
eda-sourceToZip-linux 9599 ms 5973 ms 1.61
eda-mdapiToSource-linux 8081 ms 5257 ms 1.54
lotsOfClasses-componentSetCreate-linux 23970 ms 681 ms 35.20
lotsOfClasses-sourceToMdapi-linux 34661 ms 11682 ms 2.97
lotsOfClasses-sourceToZip-linux 31935 ms 9140 ms 3.49
lotsOfClasses-mdapiToSource-linux 29111 ms 6011 ms 4.84
lotsOfClassesOneDir-componentSetCreate-linux 86237 ms 900 ms 95.82
lotsOfClassesOneDir-sourceToMdapi-linux 101128 ms 15846 ms 6.38
lotsOfClassesOneDir-sourceToZip-linux 95651 ms 15459 ms 6.19
lotsOfClassesOneDir-mdapiToSource-linux 94881 ms 11322 ms 8.38

This comment was automatically generated by workflow using github-action-benchmark.

@svc-cli-bot
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 2.

Benchmark suite Current: a987037 Previous: 2973e82 Ratio
eda-componentSetCreate-linux 3097 ms 341 ms 9.08
lotsOfClasses-componentSetCreate-linux 23970 ms 681 ms 35.20
lotsOfClasses-sourceToMdapi-linux 34661 ms 11682 ms 2.97
lotsOfClasses-sourceToZip-linux 31935 ms 9140 ms 3.49
lotsOfClasses-mdapiToSource-linux 29111 ms 6011 ms 4.84
lotsOfClassesOneDir-componentSetCreate-linux 86237 ms 900 ms 95.82
lotsOfClassesOneDir-sourceToMdapi-linux 101128 ms 15846 ms 6.38
lotsOfClassesOneDir-sourceToZip-linux 95651 ms 15459 ms 6.19
lotsOfClassesOneDir-mdapiToSource-linux 94881 ms 11322 ms 8.38

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.