From 2e865e97a200cd4e493bde577970fb4f49862e7f Mon Sep 17 00:00:00 2001 From: Steve Hetzel Date: Fri, 9 Dec 2022 14:36:45 -0700 Subject: [PATCH] fix: correct apiVersion and sourceApiVersion values before transfer and send events (#791) * fix: correct apiVersion and sourceApiVersion values before transfer and send events * test: record perf * fix: add unit tests and make options take precendence over Connection * chore: auto-update metadata coverage in METADATA_SUPPORT.md * test: record perf * fix: use specific directory for ComponentSetBuilder * test: record perf Co-authored-by: svc-cli-bot --- METADATA_SUPPORT.md | 61 ++-- package.json | 4 +- src/client/metadataApiDeploy.ts | 25 +- src/client/metadataApiRetrieve.ts | 17 +- src/collections/componentSet.ts | 29 +- src/collections/componentSetBuilder.ts | 11 +- test/client/metadataApiRetrieve.test.ts | 3 +- test/collections/componentSet.test.ts | 300 +++++++++++++++++- .../eda.json | 8 +- .../lotsOfClasses.json | 10 +- .../lotsOfClassesOneDir.json | 8 +- .../eda.json | 8 +- .../lotsOfClasses.json | 8 +- .../lotsOfClassesOneDir.json | 8 +- yarn.lock | 35 +- 15 files changed, 457 insertions(+), 78 deletions(-) diff --git a/METADATA_SUPPORT.md b/METADATA_SUPPORT.md index dec2356e51..1d627e4b51 100644 --- a/METADATA_SUPPORT.md +++ b/METADATA_SUPPORT.md @@ -1,10 +1,10 @@ # Supported CLI Metadata Types -This list compares metadata types found in Salesforce v56 with the [metadata registry file](./src/registry/metadataRegistry.json) included in this repository. +This list compares metadata types found in Salesforce v57 with the [metadata registry file](./src/registry/metadataRegistry.json) included in this repository. This repository is used by both the Salesforce CLIs and Salesforce's VSCode Extensions. -Currently, there are 482/511 supported metadata types. +Currently, there are 495/534 supported metadata types. For status on any existing gaps, please search or file an issue in the [Salesforce CLI issues only repo](https://github.com/forcedotcom/cli/issues). To contribute a new metadata type, please see the [Contributing Metadata Types to the Registry](./contributing/metadata.md) @@ -23,8 +23,10 @@ To contribute a new metadata type, please see the [Contributing Metadata Types t |AccountingModelConfig|❌|Not supported, but support could be added| |AccountingSettings|✅|| |AcctMgrTargetSettings|✅|| +|ActionLauncherItemDef|❌|Not supported, but support could be added| |ActionLinkGroupTemplate|✅|| |ActionPlanTemplate|✅|| +|ActionableListDefinition|❌|Not supported, but support could be added| |ActionsSettings|✅|| |ActivationPlatform|✅|| |ActivitiesSettings|✅|| @@ -44,6 +46,7 @@ To contribute a new metadata type, please see the [Contributing Metadata Types t |ApexTrigger|✅|| |AppAnalyticsSettings|✅|| |AppExperienceSettings|✅|| +|AppExplorationDataConsent|❌|Not supported, but support could be added| |AppMenu|✅|| |ApplicationRecordTypeConfig|✅|| |ApplicationSubtypeDefinition|✅|| @@ -106,6 +109,7 @@ To contribute a new metadata type, please see the [Contributing Metadata Types t |ChatterEmailsMDSettings|✅|| |ChatterExtension|✅|| |ChatterSettings|✅|| +|ClauseCatgConfiguration|✅|| |CleanDataService|✅|| |CollectionsDashboardSettings|✅|| |CommandAction|✅|| @@ -169,9 +173,12 @@ To contribute a new metadata type, please see the [Contributing Metadata Types t |DelegateGroup|✅|| |DeploymentSettings|✅|| |DevHubSettings|✅|| -|DigitalExperience|⚠️|Supports deploy/retrieve but not source tracking| -|DigitalExperienceBundle|⚠️|Supports deploy/retrieve but not source tracking| -|DigitalExperienceConfig|⚠️|Supports deploy/retrieve but not source tracking| +|DigitalExperience|✅|| +|DigitalExperienceBundle|✅|| +|DigitalExperienceConfig|✅|| +|DisclosureDefinition|✅|| +|DisclosureDefinitionVersion|✅|| +|DisclosureType|✅|| |DiscoveryAIModel|✅|| |DiscoveryGoal|✅|| |DiscoverySettings|✅|| @@ -222,6 +229,8 @@ To contribute a new metadata type, please see the [Contributing Metadata Types t |ExpressionSetDefinitionVersion|✅|| |ExpressionSetObjectAlias|❌|Not supported, but support could be added| |ExternalAIModel|❌|Not supported, but support could be added| +|ExternalClientAppSettings|✅|| +|ExternalClientApplication|✅|| |ExternalCredential|✅|| |ExternalDataConnector|✅|| |ExternalDataSource|✅|| @@ -229,6 +238,10 @@ To contribute a new metadata type, please see the [Contributing Metadata Types t |ExternalDataTranField|❌|Not supported, but support could be added| |ExternalDataTranObject|❌|Not supported, but support could be added| |ExternalServiceRegistration|✅|| +|ExtlClntAppMobileConfigurablePolicies|✅|| +|ExtlClntAppMobileSettings|✅|| +|ExtlClntAppOauthConfigurablePolicies|✅|| +|ExtlClntAppOauthSettings|✅|| |FeatureParameterBoolean|✅|| |FeatureParameterDate|✅|| |FeatureParameterInteger|✅|| @@ -267,6 +280,7 @@ To contribute a new metadata type, please see the [Contributing Metadata Types t |IPAddressRange|✅|| |Icon|✅|| |IdeasSettings|✅|| +|IdentityProviderSettings|✅|| |IdentityVerificationProcDef|✅|| |IframeWhiteListUrlSettings|✅|| |InboundCertificate|✅|| @@ -279,6 +293,7 @@ To contribute a new metadata type, please see the [Contributing Metadata Types t |IndustriesManufacturingSettings|✅|| |IndustriesSettings|✅|| |InstalledPackage|⚠️|Supports deploy/retrieve but not source tracking| +|IntegrationProviderDef|❌|Not supported, but support could be added| |InterestTaggingSettings|✅|| |InternalDataConnector|✅|| |InvLatePymntRiskCalcSettings|✅|| @@ -305,6 +320,7 @@ To contribute a new metadata type, please see the [Contributing Metadata Types t |LiveChatDeployment|✅|| |LiveChatSensitiveDataRule|✅|| |LiveMessageSettings|✅|| +|LocationUse|❌|Not supported, but support could be added| |LoyaltyProgramSetup|⚠️|Supports deploy/retrieve but not source tracking| |MLDataDefinition|✅|| |MLPredictionDefinition|✅|| @@ -356,6 +372,7 @@ To contribute a new metadata type, please see the [Contributing Metadata Types t |OmniInteractionAccessConfig|⚠️|Supports deploy/retrieve but not source tracking| |OmniInteractionConfig|⚠️|Supports deploy/retrieve but not source tracking| |OmniScript|⚠️|Supports deploy/retrieve but not source tracking| +|OmniSupervisorConfig|✅|| |OmniUiCard|⚠️|Supports deploy/retrieve but not source tracking| |OnlineSalesSettings|✅|| |OpportunityInsightsSettings|✅|| @@ -372,13 +389,16 @@ To contribute a new metadata type, please see the [Contributing Metadata Types t |PathAssistant|✅|| |PathAssistantSettings|✅|| |PaymentGatewayProvider|✅|| +|PaymentsIngestEnabledSettings|✅|| |PaymentsManagementEnabledSettings|✅|| |PaymentsSettings|✅|| |PermissionSet|✅|| |PermissionSetGroup|✅|| |PermissionSetLicenseDefinition|✅|| +|PersonAccountOwnerPowerUser|❌|Not supported, but support could be added| |PicklistSettings|✅|| |PicklistValue|❌|Not supported, but support could be added| +|PipelineInspMetricConfig|❌|Not supported, but support could be added| |PlatformCachePartition|✅|| |PlatformEventChannel|✅|| |PlatformEventChannelMember|✅|| @@ -393,6 +413,7 @@ To contribute a new metadata type, please see the [Contributing Metadata Types t |PrivacySettings|✅|| |ProductAttributeSet|✅|| |ProductSettings|✅|| +|ProductSpecificationTypeDefinition|❌|Not supported, but support could be added| |Profile|✅|| |ProfilePasswordPolicy|✅|| |ProfileSessionSetting|✅|| @@ -437,6 +458,7 @@ To contribute a new metadata type, please see the [Contributing Metadata Types t |ServiceChannel|✅|| |ServiceCloudVoiceSettings|✅|| |ServicePresenceStatus|✅|| +|ServiceProcess|❌|Not supported, but support could be added| |ServiceSetupAssistantSettings|✅|| |SharingCriteriaRule|✅|| |SharingGuestRule|✅|| @@ -497,6 +519,7 @@ To contribute a new metadata type, please see the [Contributing Metadata Types t |VirtualVisitConfig|❌|Not supported, but support could be added| |VoiceSettings|✅|| |WarrantyLifecycleMgmtSettings|✅|| +|WaveAnalyticAssetCollection|❌|Not supported, but support could be added| |WaveApplication|✅|| |WaveComponent|✅|| |WaveDashboard|✅|| @@ -524,35 +547,11 @@ To contribute a new metadata type, please see the [Contributing Metadata Types t -## Next Release (v57) -v57 introduces the following new types. Here's their current level of support +## Next Release (v58) +v58 introduces the following new types. Here's their current level of support |Metadata Type|Support|Notes| |:---|:---|:---| -|ActionLauncherItemDef|❌|Not supported, but support could be added| -|ActionableListDefinition|❌|Not supported, but support could be added| -|AppExplorationDataConsent|❌|Not supported, but support could be added| -|ClaimFinancialSettings|✅|| -|ClauseCatgConfiguration|✅|| -|DisclosureDefinition|✅|| -|DisclosureDefinitionVersion|✅|| -|DisclosureType|✅|| -|ExternalClientAppSettings|✅|| -|ExternalClientApplication|✅|| -|ExtlClntAppMobileConfigurablePolicies|✅|| -|ExtlClntAppMobileSettings|✅|| -|ExtlClntAppOauthConfigurablePolicies|✅|| -|ExtlClntAppOauthSettings|✅|| -|IdentityProviderSettings|✅|| -|IntegrationProviderDef|❌|Not supported, but support could be added| -|LocationUse|❌|Not supported, but support could be added| -|OmniSupervisorConfig|✅|| -|PaymentsIngestEnabledSettings|✅|| -|PersonAccountOwnerPowerUser|❌|Not supported, but support could be added| -|PipelineInspMetricConfig|❌|Not supported, but support could be added| -|ProductSpecificationTypeDefinition|❌|Not supported, but support could be added| -|ServiceProcess|❌|Not supported, but support could be added| -|WaveAnalyticAssetCollection|❌|Not supported, but support could be added| ## Additional Types diff --git a/package.json b/package.json index cf0dc3af04..bcde24d6b8 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "node": ">=14.0.0" }, "dependencies": { - "@salesforce/core": "^3.32.6", + "@salesforce/core": "^3.32.8", "@salesforce/kit": "^1.8.0", "@salesforce/ts-types": "^1.7.1", "archiver": "^5.3.1", @@ -117,4 +117,4 @@ "yarn": "1.22.4" }, "config": {} -} \ No newline at end of file +} diff --git a/src/client/metadataApiDeploy.ts b/src/client/metadataApiDeploy.ts index 888f0cd6ba..cac8816386 100644 --- a/src/client/metadataApiDeploy.ts +++ b/src/client/metadataApiDeploy.ts @@ -313,13 +313,20 @@ export class MetadataApiDeploy extends MetadataTransfer { const LifecycleInstance = Lifecycle.getInstance(); const connection = await this.getConnection(); + const apiVersion = connection.getApiVersion(); + // store for use in the scopedPostDeploy event this.orgId = connection.getAuthInfoFields().orgId; - if (this.components && !this.components.apiVersion && !this.components.sourceApiVersion) { - // if we have a ComponentSet, but got no version info, let's use the org's max version for calculating what goes into the package.xml - this.components.apiVersion = connection.getApiVersion(); - this.components.sourceApiVersion = connection.getApiVersion(); + + // If we have a ComponentSet but no version info, use the apiVersion from the Connection. + if (this.components) { + // this is the SOAP/REST API version of the connection + this.components.apiVersion ??= apiVersion; + + // this is used as the version in the manifest (package.xml). + this.components.sourceApiVersion ??= apiVersion; } + // only do event hooks if source, (NOT a metadata format) deploy if (this.options.components) { await LifecycleInstance.emit('scopedPreDeploy', { @@ -345,6 +352,16 @@ export class MetadataApiDeploy extends MetadataTransfer { const resolveIncludeSet = options.resolveSourcePaths ? new ComponentSet([], options.registry) : undefined; const result = new ComponentSet([], options.registry); - result.apiVersion = manifest.apiVersion; + + result.logger.debug(`Setting sourceApiVersion of ${manifest.apiVersion} on ComponentSet from manifest`); + result.sourceApiVersion = manifest.apiVersion; result.fullName = manifest.fullName; const addComponent = (component: MetadataComponent, deletionType?: DestructiveChangesType): void => { @@ -309,14 +311,22 @@ export class ComponentSet extends LazyCollection { throw new SfError(messages.getMessage('error_no_source_to_deploy'), 'ComponentSetError'); } + if ( + typeof options.usernameOrConnection !== 'string' && + this.apiVersion && + this.apiVersion !== options.usernameOrConnection.version + ) { + options.usernameOrConnection.setApiVersion(this.apiVersion); + this.logger.debug( + `Received conflicting apiVersion values for deploy. Using option=${this.apiVersion}, Ignoring apiVersion on connection=${options.usernameOrConnection.version}.` + ); + } + const operationOptions = Object.assign({}, options, { components: this, registry: this.registry, apiVersion: this.apiVersion, }); - // if (!options.apiVersion && !this.apiVersion && !this.sourceApiVersion) { - // operationOptions.apiVersion = `${await getCurrentApiVersion()}.0`; - // } const mdapiDeploy = new MetadataApiDeploy(operationOptions); await mdapiDeploy.start(); @@ -337,6 +347,17 @@ export class ComponentSet extends LazyCollection { apiVersion: this.apiVersion, }); + if ( + typeof options.usernameOrConnection !== 'string' && + this.apiVersion && + this.apiVersion !== options.usernameOrConnection.version + ) { + options.usernameOrConnection.setApiVersion(this.apiVersion); + this.logger.debug( + `Received conflicting apiVersion values for retrieve. Using option=${this.apiVersion}, Ignoring apiVersion on connection=${options.usernameOrConnection.version}.` + ); + } + const mdapiRetrieve = new MetadataApiRetrieve(operationOptions); await mdapiRetrieve.start(); return mdapiRetrieve; diff --git a/src/collections/componentSetBuilder.ts b/src/collections/componentSetBuilder.ts index aff6c89b0c..9c2e9ad386 100644 --- a/src/collections/componentSetBuilder.ts +++ b/src/collections/componentSetBuilder.ts @@ -168,13 +168,10 @@ export class ComponentSetBuilder { } } - if (apiversion) { - componentSet.apiVersion = apiversion; - } - - if (sourceapiversion) { - componentSet.sourceApiVersion = sourceapiversion; - } + componentSet.apiVersion ??= apiversion; + componentSet.sourceApiVersion ??= sourceapiversion; + logger.debug(`ComponentSet apiVersion = ${componentSet.apiVersion}`); + logger.debug(`ComponentSet sourceApiVersion = ${componentSet.sourceApiVersion}`); return componentSet; } diff --git a/test/client/metadataApiRetrieve.test.ts b/test/client/metadataApiRetrieve.test.ts index 32283af7e5..f75d913b47 100644 --- a/test/client/metadataApiRetrieve.test.ts +++ b/test/client/metadataApiRetrieve.test.ts @@ -373,7 +373,8 @@ describe('MetadataApiRetrieve', () => { await operation.start(); const result = await operation.pollStatus(); const expected = new RetrieveResult(response, toRetrieve, toRetrieve); - expect(result).to.deep.equalInAnyOrder(expected); + expect(result.response).to.deep.equalInAnyOrder(expected.response); + expect(result.components.toArray()).to.deep.equalInAnyOrder(expected.components.toArray()); }); it('should construct a result object with no components when components are forceIgnored', async () => { diff --git a/test/collections/componentSet.test.ts b/test/collections/componentSet.test.ts index 39784b6a5b..3addf7a6a8 100644 --- a/test/collections/componentSet.test.ts +++ b/test/collections/componentSet.test.ts @@ -9,9 +9,10 @@ import { join } from 'path'; import { MockTestOrgData, TestContext } from '@salesforce/core/lib/testSetup'; import { expect } from 'chai'; import { SinonStub } from 'sinon'; -import { AuthInfo, Connection, Messages } from '@salesforce/core'; +import { AuthInfo, ConfigAggregator, Connection, Lifecycle, Messages } from '@salesforce/core'; import { ComponentSet, + ComponentSetBuilder, ConnectionResolver, DestructiveChangesType, ManifestResolver, @@ -23,6 +24,7 @@ import { registry, RegistryAccess, SourceComponent, + ZipTreeContainer, } from '../../src'; import { decomposedtoplevel, matchingContentFile, mixedContentSingleFile } from '../mock'; import { MATCHING_RULES_COMPONENT } from '../mock/type-constants/customlabelsConstant'; @@ -37,6 +39,302 @@ describe('ComponentSet', () => { const $$ = new TestContext(); const testOrg = new MockTestOrgData(); + describe('apiVersion and sourceApiVersion', () => { + const maxVersion = '55.0'; + const configVersion = '54.0'; + const componentSetVersion = '53.0'; + const sourceApiVersion = '52.0'; + const manifestVersion = '51.0'; + const sourcepath = join(process.cwd(), 'test', 'nuts', 'local', 'replacements', 'testProj'); + + let connection: Connection; + let lifecycleEmitStub: sinon.SinonStub; + let connectionRetrieveStub: sinon.SinonStub; + let connectionDeployStub: sinon.SinonStub; + let retrieveMaxApiVersionStub: sinon.SinonStub; + let componentSet: ComponentSet; + + beforeEach(() => { + lifecycleEmitStub = $$.SANDBOX.stub(Lifecycle.prototype, 'emit'); + }); + + const stubConnection = async () => { + retrieveMaxApiVersionStub = $$.SANDBOX.stub(Connection.prototype, 'retrieveMaxApiVersion'); + retrieveMaxApiVersionStub.resolves(maxVersion); + connection = await testOrg.getConnection(); + connectionRetrieveStub = $$.SANDBOX.stub(connection.metadata, 'retrieve'); + connectionRetrieveStub.resolves({ id: '00DCompsetTest' }); + connectionDeployStub = $$.SANDBOX.stub(connection.metadata, 'deploy'); + connectionDeployStub.resolves({ id: '00DCompsetTest' }); + }; + + const stubConfig = () => { + $$.SANDBOX.stub(ConfigAggregator.prototype, 'getInfo') + .withArgs('org-api-version') + .returns({ + key: 'org-api-version', + location: ConfigAggregator.Location.LOCAL, + value: configVersion, + path: '', + isLocal: () => true, + isGlobal: () => false, + isEnvVar: () => false, + }); + }; + + const getManifestContent = (version) => ({ + types: [ + { members: ['replaceStuff'], name: 'ApexClass' }, + { members: ['TestObj__c.FieldA__c'], name: 'CustomField' }, + { members: ['TestObj__c'], name: 'CustomObject' }, + { members: ['Test'], name: 'StaticResource' }, + ], + version, + }); + + describe('retrieve', () => { + it('should default to max version supported by the target org', async () => { + componentSet = await ComponentSetBuilder.build({ sourcepath: [sourcepath] }); + await stubConnection(); + await componentSet.retrieve({ output: '', usernameOrConnection: connection }); + + const expectedPayload = { apiVersion: maxVersion, manifestVersion: maxVersion }; + expect(lifecycleEmitStub.args[1][0]).to.equal('apiVersionRetrieve'); + expect(lifecycleEmitStub.args[1][1]).to.deep.equal(expectedPayload); + expect(connectionRetrieveStub.called).to.be.true; + expect(connectionRetrieveStub.args[0][0]).to.deep.equal({ + apiVersion: maxVersion, + unpackaged: getManifestContent(maxVersion), + }); + }); + + it('should use the version in the sfdx config', async () => { + componentSet = await ComponentSetBuilder.build({ sourcepath: [sourcepath] }); + stubConfig(); + await stubConnection(); + await componentSet.retrieve({ output: '', usernameOrConnection: connection }); + + const expectedPayload = { apiVersion: configVersion, manifestVersion: configVersion }; + expect(lifecycleEmitStub.args[1][0]).to.equal('apiVersionRetrieve'); + expect(lifecycleEmitStub.args[1][1]).to.deep.equal(expectedPayload); + expect(connectionRetrieveStub.called).to.be.true; + expect(connectionRetrieveStub.args[0][0]).to.deep.equal({ + apiVersion: configVersion, + unpackaged: getManifestContent(configVersion), + }); + }); + + it('should use the version from a ComponentSet (usernameOrConnection = Connection)', async () => { + // This usecase is when you call ComponentSet.retrieve() passing in a + // connection object rather than an org username. The request is made using the apiVersion + // from the ComponentSet. + componentSet = await ComponentSetBuilder.build({ sourcepath: [sourcepath], apiversion: componentSetVersion }); + stubConfig(); + await stubConnection(); + await componentSet.retrieve({ output: '', usernameOrConnection: connection }); + + const expectedPayload = { apiVersion: componentSetVersion, manifestVersion: componentSetVersion }; + expect(lifecycleEmitStub.args[1][0]).to.equal('apiVersionRetrieve'); + expect(lifecycleEmitStub.args[1][1]).to.deep.equal(expectedPayload); + expect(connectionRetrieveStub.called).to.be.true; + expect(connectionRetrieveStub.args[0][0]).to.deep.equal({ + apiVersion: componentSetVersion, + unpackaged: getManifestContent(componentSetVersion), + }); + }); + + it('should use the version from a ComponentSet (usernameOrConnection = string)', async () => { + // This usecase is when you call ComponentSet.retrieve() passing in an org + // username rather than a connection. The request is made using the apiVersion + // from the ComponentSet. + componentSet = await ComponentSetBuilder.build({ sourcepath: [sourcepath], apiversion: componentSetVersion }); + stubConfig(); + await stubConnection(); + // Have to stub `Connection.create()` because passing an org username will result + // in calling MetadataTransfer.getConnection(), and we want to return our stub. + $$.SANDBOX.stub(Connection, 'create').resolves(connection); + await componentSet.retrieve({ output: '', usernameOrConnection: 'testorg' }); + + const expectedPayload = { apiVersion: componentSetVersion, manifestVersion: componentSetVersion }; + expect(lifecycleEmitStub.args[1][0]).to.equal('apiVersionRetrieve'); + expect(lifecycleEmitStub.args[1][1]).to.deep.equal(expectedPayload); + expect(connectionRetrieveStub.called).to.be.true; + expect(connectionRetrieveStub.args[0][0]).to.deep.equal({ + apiVersion: componentSetVersion, + unpackaged: getManifestContent(componentSetVersion), + }); + }); + + it('should use the sourceApiVersion from a ComponentSet', async () => { + componentSet = await ComponentSetBuilder.build({ + sourcepath: [sourcepath], + apiversion: componentSetVersion, + sourceapiversion: sourceApiVersion, + }); + stubConfig(); + await stubConnection(); + await componentSet.retrieve({ output: '', usernameOrConnection: connection }); + + const expectedPayload = { apiVersion: componentSetVersion, manifestVersion: sourceApiVersion }; + expect(lifecycleEmitStub.args[1][0]).to.equal('apiVersionRetrieve'); + expect(lifecycleEmitStub.args[1][1]).to.deep.equal(expectedPayload); + expect(connectionRetrieveStub.called).to.be.true; + expect(connectionRetrieveStub.args[0][0]).to.deep.equal({ + apiVersion: sourceApiVersion, + unpackaged: getManifestContent(sourceApiVersion), + }); + }); + + it('should use the version from the manifest', async () => { + $$.SANDBOX.stub(ManifestResolver.prototype, 'resolve').resolves({ + components: [{ fullName: 'Test', type: registry.types.apexclass }], + apiVersion: manifestVersion, + }); + componentSet = await ComponentSetBuilder.build({ + manifest: { directoryPaths: [sourcepath], manifestPath: '.' }, + apiversion: componentSetVersion, + sourceapiversion: sourceApiVersion, + }); + stubConfig(); + await stubConnection(); + await componentSet.retrieve({ output: '', usernameOrConnection: connection }); + + const expectedPayload = { apiVersion: componentSetVersion, manifestVersion }; + expect(lifecycleEmitStub.args[1][0]).to.equal('apiVersionRetrieve'); + expect(lifecycleEmitStub.args[1][1]).to.deep.equal(expectedPayload); + expect(connectionRetrieveStub.called).to.be.true; + expect(connectionRetrieveStub.args[0][0]).to.deep.equal({ + apiVersion: manifestVersion, + unpackaged: { types: [{ members: ['Test'], name: 'ApexClass' }], version: manifestVersion }, + }); + }); + }); + + describe('deploy', () => { + const verifyManifestVersionInZip = async (zipBuffer: Buffer, expectedVersion: string) => { + const tree = await ZipTreeContainer.create(zipBuffer); + const packageXmlBuffer = await tree.readFile('package.xml'); + expect(packageXmlBuffer.toString()).includes(`${expectedVersion}`); + }; + + it('should default to max version supported by the target org', async () => { + componentSet = await ComponentSetBuilder.build({ sourcepath: [sourcepath] }); + await stubConnection(); + await componentSet.deploy({ usernameOrConnection: connection }); + + const expectedPayload = { apiVersion: maxVersion, manifestVersion: maxVersion, webService: 'SOAP' }; + expect(lifecycleEmitStub.args[1][0]).to.equal('apiVersionDeploy'); + expect(lifecycleEmitStub.args[1][1]).to.deep.equal(expectedPayload); + expect(connectionDeployStub.called).to.be.true; + await verifyManifestVersionInZip(connectionDeployStub.args[0][0] as Buffer, maxVersion); + expect(connection.getApiVersion()).to.equal(maxVersion); + }); + + it('should use the version in the sfdx config', async () => { + componentSet = await ComponentSetBuilder.build({ sourcepath: [sourcepath] }); + stubConfig(); + await stubConnection(); + await componentSet.deploy({ usernameOrConnection: connection }); + + const expectedPayload = { apiVersion: configVersion, manifestVersion: configVersion, webService: 'SOAP' }; + expect(lifecycleEmitStub.args[1][0]).to.equal('apiVersionDeploy'); + expect(lifecycleEmitStub.args[1][1]).to.deep.equal(expectedPayload); + expect(connectionDeployStub.called).to.be.true; + await verifyManifestVersionInZip(connectionDeployStub.args[0][0] as Buffer, configVersion); + expect(connection.getApiVersion()).to.equal(configVersion); + }); + + it('should use the version from a ComponentSet (usernameOrConnection = connection)', async () => { + // This usecase is when you call ComponentSet.retrieve() passing in a + // connection object rather than an org username. The request is made using the apiVersion + // from the ComponentSet. + componentSet = await ComponentSetBuilder.build({ sourcepath: [sourcepath], apiversion: componentSetVersion }); + stubConfig(); + await stubConnection(); + await componentSet.deploy({ usernameOrConnection: connection }); + + const expectedPayload = { + apiVersion: componentSetVersion, + manifestVersion: componentSetVersion, + webService: 'SOAP', + }; + expect(lifecycleEmitStub.args[1][0]).to.equal('apiVersionDeploy'); + expect(lifecycleEmitStub.args[1][1]).to.deep.equal(expectedPayload); + expect(connectionDeployStub.called).to.be.true; + await verifyManifestVersionInZip(connectionDeployStub.args[0][0] as Buffer, componentSetVersion); + expect(connection.getApiVersion()).to.equal(componentSetVersion); + }); + + it('should use the version from a ComponentSet (usernameOrConnection = string)', async () => { + // This usecase is when you call ComponentSet.retrieve() passing in an org + // username rather than a connection. The request is made using the apiVersion + // from the ComponentSet. + componentSet = await ComponentSetBuilder.build({ sourcepath: [sourcepath], apiversion: componentSetVersion }); + stubConfig(); + await stubConnection(); + // Have to stub `Connection.create()` because passing an org username will result + // in calling MetadataTransfer.getConnection(), and we want to return our stub. + $$.SANDBOX.stub(Connection, 'create').resolves(connection); + await componentSet.deploy({ usernameOrConnection: 'testorg' }); + + const expectedPayload = { + apiVersion: componentSetVersion, + manifestVersion: componentSetVersion, + webService: 'SOAP', + }; + expect(lifecycleEmitStub.args[1][0]).to.equal('apiVersionDeploy'); + expect(lifecycleEmitStub.args[1][1]).to.deep.equal(expectedPayload); + expect(connectionDeployStub.called).to.be.true; + await verifyManifestVersionInZip(connectionDeployStub.args[0][0] as Buffer, componentSetVersion); + expect(connection.getApiVersion()).to.equal(componentSetVersion); + }); + + it('should use the sourceApiVersion from a ComponentSet', async () => { + componentSet = await ComponentSetBuilder.build({ + sourcepath: [sourcepath], + apiversion: componentSetVersion, + sourceapiversion: sourceApiVersion, + }); + stubConfig(); + await stubConnection(); + await componentSet.deploy({ usernameOrConnection: connection }); + + const expectedPayload = { + apiVersion: componentSetVersion, + manifestVersion: sourceApiVersion, + webService: 'SOAP', + }; + expect(lifecycleEmitStub.args[1][0]).to.equal('apiVersionDeploy'); + expect(lifecycleEmitStub.args[1][1]).to.deep.equal(expectedPayload); + expect(connectionDeployStub.called).to.be.true; + await verifyManifestVersionInZip(connectionDeployStub.args[0][0] as Buffer, sourceApiVersion); + expect(connection.getApiVersion()).to.equal(componentSetVersion); + }); + + it('should use the version from the manifest', async () => { + $$.SANDBOX.stub(ManifestResolver.prototype, 'resolve').resolves({ + components: [{ fullName: 'replaceStuff', type: registry.types.apexclass }], + apiVersion: manifestVersion, + }); + componentSet = await ComponentSetBuilder.build({ + manifest: { directoryPaths: [sourcepath], manifestPath: '.' }, + apiversion: componentSetVersion, + sourceapiversion: sourceApiVersion, + }); + stubConfig(); + await stubConnection(); + await componentSet.deploy({ usernameOrConnection: connection }); + + const expectedPayload = { apiVersion: componentSetVersion, manifestVersion, webService: 'SOAP' }; + expect(lifecycleEmitStub.args[1][0]).to.equal('apiVersionDeploy'); + expect(lifecycleEmitStub.args[1][1]).to.deep.equal(expectedPayload); + expect(connectionDeployStub.called).to.be.true; + await verifyManifestVersionInZip(connectionDeployStub.args[0][0] as Buffer, manifestVersion); + expect(connection.getApiVersion()).to.equal(componentSetVersion); + }); + }); + }); + describe('Initializers', () => { describe('fromSource', () => { const resolved = [matchingContentFile.COMPONENT]; diff --git a/test/nuts/perfResults/x64-linux-2xIntel-Xeon-CPU-E5-2673-v4-2-30GHz/eda.json b/test/nuts/perfResults/x64-linux-2xIntel-Xeon-CPU-E5-2673-v4-2-30GHz/eda.json index 8bf5d980a7..2771dad1ec 100644 --- a/test/nuts/perfResults/x64-linux-2xIntel-Xeon-CPU-E5-2673-v4-2-30GHz/eda.json +++ b/test/nuts/perfResults/x64-linux-2xIntel-Xeon-CPU-E5-2673-v4-2-30GHz/eda.json @@ -1,18 +1,18 @@ [ { "name": "componentSetCreate", - "duration": 324.71387500001583 + "duration": 319.0109829999856 }, { "name": "sourceToMdapi", - "duration": 7122.961764000007 + "duration": 6513.529799000011 }, { "name": "sourceToZip", - "duration": 6274.939941999997 + "duration": 4981.738588999986 }, { "name": "mdapiToSource", - "duration": 5758.478280999989 + "duration": 5392.311745999992 } ] \ No newline at end of file diff --git a/test/nuts/perfResults/x64-linux-2xIntel-Xeon-CPU-E5-2673-v4-2-30GHz/lotsOfClasses.json b/test/nuts/perfResults/x64-linux-2xIntel-Xeon-CPU-E5-2673-v4-2-30GHz/lotsOfClasses.json index 7e1827599e..f29bda6b42 100644 --- a/test/nuts/perfResults/x64-linux-2xIntel-Xeon-CPU-E5-2673-v4-2-30GHz/lotsOfClasses.json +++ b/test/nuts/perfResults/x64-linux-2xIntel-Xeon-CPU-E5-2673-v4-2-30GHz/lotsOfClasses.json @@ -1,18 +1,18 @@ [ { "name": "componentSetCreate", - "duration": 651.9775850000151 + "duration": 589.6669420000399 }, { "name": "sourceToMdapi", - "duration": 10889.311952999997 + "duration": 10443.746392000001 }, { "name": "sourceToZip", - "duration": 9392.743359000015 + "duration": 9044.706978000002 }, { "name": "mdapiToSource", - "duration": 7095.85508899999 + "duration": 6968.501856999996 } -] +] \ No newline at end of file diff --git a/test/nuts/perfResults/x64-linux-2xIntel-Xeon-CPU-E5-2673-v4-2-30GHz/lotsOfClassesOneDir.json b/test/nuts/perfResults/x64-linux-2xIntel-Xeon-CPU-E5-2673-v4-2-30GHz/lotsOfClassesOneDir.json index a41ec885e7..b613e55ba6 100644 --- a/test/nuts/perfResults/x64-linux-2xIntel-Xeon-CPU-E5-2673-v4-2-30GHz/lotsOfClassesOneDir.json +++ b/test/nuts/perfResults/x64-linux-2xIntel-Xeon-CPU-E5-2673-v4-2-30GHz/lotsOfClassesOneDir.json @@ -1,18 +1,18 @@ [ { "name": "componentSetCreate", - "duration": 1033.0352329999732 + "duration": 1047.5124270000379 }, { "name": "sourceToMdapi", - "duration": 16734.752318999992 + "duration": 15948.696778000041 }, { "name": "sourceToZip", - "duration": 15027.624977999978 + "duration": 13894.535199999984 }, { "name": "mdapiToSource", - "duration": 12297.485982999991 + "duration": 15508.926563000015 } ] \ No newline at end of file diff --git a/test/nuts/perfResults/x64-linux-2xIntel-Xeon-Platinum-8272CL-CPU-2-60GHz/eda.json b/test/nuts/perfResults/x64-linux-2xIntel-Xeon-Platinum-8272CL-CPU-2-60GHz/eda.json index 8e1db8e72b..7d4c8765a2 100644 --- a/test/nuts/perfResults/x64-linux-2xIntel-Xeon-Platinum-8272CL-CPU-2-60GHz/eda.json +++ b/test/nuts/perfResults/x64-linux-2xIntel-Xeon-Platinum-8272CL-CPU-2-60GHz/eda.json @@ -1,18 +1,18 @@ [ { "name": "componentSetCreate", - "duration": 206.68928799999412 + "duration": 215.97936900000786 }, { "name": "sourceToMdapi", - "duration": 6456.049191999991 + "duration": 6050.905942000012 }, { "name": "sourceToZip", - "duration": 5368.473570999995 + "duration": 4974.103418000013 }, { "name": "mdapiToSource", - "duration": 3560.2728149999894 + "duration": 3734.206409000006 } ] \ No newline at end of file diff --git a/test/nuts/perfResults/x64-linux-2xIntel-Xeon-Platinum-8272CL-CPU-2-60GHz/lotsOfClasses.json b/test/nuts/perfResults/x64-linux-2xIntel-Xeon-Platinum-8272CL-CPU-2-60GHz/lotsOfClasses.json index a945b5d3b0..0361178aea 100644 --- a/test/nuts/perfResults/x64-linux-2xIntel-Xeon-Platinum-8272CL-CPU-2-60GHz/lotsOfClasses.json +++ b/test/nuts/perfResults/x64-linux-2xIntel-Xeon-Platinum-8272CL-CPU-2-60GHz/lotsOfClasses.json @@ -1,18 +1,18 @@ [ { "name": "componentSetCreate", - "duration": 405.7750340000057 + "duration": 408.8743680000189 }, { "name": "sourceToMdapi", - "duration": 8594.81496599999 + "duration": 9369.04411799999 }, { "name": "sourceToZip", - "duration": 6676.589412000001 + "duration": 7376.75736399999 }, { "name": "mdapiToSource", - "duration": 4316.212134000001 + "duration": 4596.248850000004 } ] \ No newline at end of file diff --git a/test/nuts/perfResults/x64-linux-2xIntel-Xeon-Platinum-8272CL-CPU-2-60GHz/lotsOfClassesOneDir.json b/test/nuts/perfResults/x64-linux-2xIntel-Xeon-Platinum-8272CL-CPU-2-60GHz/lotsOfClassesOneDir.json index 342f5ad320..99aab3b62b 100644 --- a/test/nuts/perfResults/x64-linux-2xIntel-Xeon-Platinum-8272CL-CPU-2-60GHz/lotsOfClassesOneDir.json +++ b/test/nuts/perfResults/x64-linux-2xIntel-Xeon-Platinum-8272CL-CPU-2-60GHz/lotsOfClassesOneDir.json @@ -1,18 +1,18 @@ [ { "name": "componentSetCreate", - "duration": 687.1548699999985 + "duration": 712.6530869999551 }, { "name": "sourceToMdapi", - "duration": 11603.490995 + "duration": 11910.565894 }, { "name": "sourceToZip", - "duration": 9733.591315000027 + "duration": 13902.21678899997 }, { "name": "mdapiToSource", - "duration": 7484.829251999996 + "duration": 13933.210705999983 } ] \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 973284bf23..f7b6db9271 100644 --- a/yarn.lock +++ b/yarn.lock @@ -673,7 +673,7 @@ strip-ansi "6.0.1" ts-retry-promise "^0.7.0" -"@salesforce/core@^3.30.9", "@salesforce/core@^3.32.6": +"@salesforce/core@^3.30.9": version "3.32.6" resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-3.32.6.tgz#ee42663ffbf9db2478247fa7e6af3df652eebb5f" integrity sha512-+j9nXnVnK4H+YmRfhcmSW/RZu2XIM6zKlXImsy+XhWBV1zOvM1JZiQngj3pdrGck7hoMc68YfWP0UEVzmM/k+g== @@ -696,6 +696,29 @@ jsonwebtoken "8.5.1" ts-retry-promise "^0.7.0" +"@salesforce/core@^3.32.8": + version "3.32.8" + resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-3.32.8.tgz#21d97f75645f8928cacb04497e91d8b203753130" + integrity sha512-q6vvpaBmw/cCSff+MX+aWYLVP44AzEI8oMLk14g/sY2O+Bij0ZBtil5bjhjkQqp3dJnkwKgaqpnvYPsrXSUN1g== + dependencies: + "@salesforce/bunyan" "^2.0.0" + "@salesforce/kit" "^1.8.0" + "@salesforce/schemas" "^1.4.0" + "@salesforce/ts-types" "^1.5.21" + "@types/graceful-fs" "^4.1.5" + "@types/semver" "^7.3.13" + ajv "^8.11.2" + archiver "^5.3.0" + change-case "^4.1.2" + debug "^3.2.7" + faye "^1.4.0" + form-data "^4.0.0" + graceful-fs "^4.2.9" + js2xmlparser "^4.0.1" + jsforce "^2.0.0-beta.19" + jsonwebtoken "8.5.1" + ts-retry-promise "^0.7.0" + "@salesforce/dev-config@^3.0.0", "@salesforce/dev-config@^3.0.1": version "3.1.0" resolved "https://registry.yarnpkg.com/@salesforce/dev-config/-/dev-config-3.1.0.tgz#8eb5b35860ff60d1c1dc3fd9329b01a28475d5b9" @@ -1192,6 +1215,16 @@ ajv@^8.0.1, ajv@^8.11.0: require-from-string "^2.0.2" uri-js "^4.2.2" +ajv@^8.11.2: + version "8.11.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.2.tgz#aecb20b50607acf2569b6382167b65a96008bb78" + integrity sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + ansi-colors@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"