Skip to content

Commit

Permalink
feat: add deploy size and count warnings
Browse files Browse the repository at this point in the history
  • Loading branch information
shetzel committed Oct 7, 2024
1 parent 287806d commit 1a6245c
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 3 deletions.
40 changes: 38 additions & 2 deletions src/client/metadataApiDeploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { format } from 'node:util';
import { isString } from '@salesforce/ts-types';
import JSZip from 'jszip';
import fs from 'graceful-fs';
import { Lifecycle, Messages, SfError } from '@salesforce/core';
import { Lifecycle, Messages, SfError, envVars } from '@salesforce/core';
import { ensureArray } from '@salesforce/kit';
import { RegistryAccess } from '../registry/registryAccess';
import { ReplacementEvent } from '../convert/types';
Expand Down Expand Up @@ -234,6 +234,7 @@ export class MetadataApiDeploy extends MetadataTransfer<
this.logger.debug(zipMessage);
await LifecycleInstance.emit('apiVersionDeploy', { webService, manifestVersion, apiVersion });
await LifecycleInstance.emit('deployZipData', { zipSize: this.zipSize, zipFileCount });
await this.warnIfDeployThresholdExceeded(this.zipSize, zipFileCount);

return this.isRestDeploy
? connection.metadata.deployRest(zipBuffer, optionsWithoutRest)
Expand Down Expand Up @@ -311,6 +312,41 @@ export class MetadataApiDeploy extends MetadataTransfer<
return deployResult;
}

// By default, an 80% deploy size threshold is used to warn users when their deploy size
// is approaching the limit enforced by the Metadata API. This includes the number of files
// being deployed as well as the byte size of the deployment. The threshold can be overridden
// to be a different percentage using the SF_DEPLOY_SIZE_THRESHOLD env var. An env var value
// of 100 would disable the client side warning. An env var value of 0 would always warn.
private async warnIfDeployThresholdExceeded(zipSize: number, zipFileCount: number | undefined): Promise<void> {
const thresholdPercentage = Math.abs(envVars.getNumber('SF_DEPLOY_SIZE_THRESHOLD', 80));
if (thresholdPercentage >= 100) {
this.logger.debug(
`Deploy size warning is disabled since SF_DEPLOY_SIZE_THRESHOLD is overridden to: ${thresholdPercentage}`
);
return;
}
if (thresholdPercentage !== 80) {
this.logger.debug(
`Deploy size warning threshold has been overridden by SF_DEPLOY_SIZE_THRESHOLD to: ${thresholdPercentage}`
);
}
// 39_000_000 is 39 MB in decimal format, which is the format used in buffer.byteLength
const fileSizeThreshold = Math.round(39_000_000 * (thresholdPercentage / 100));
const fileCountThreshold = Math.round(10_000 * (thresholdPercentage / 100));

if (zipSize > fileSizeThreshold) {
await Lifecycle.getInstance().emitWarning(
`Deployment zip file size is approaching the Metadata API limit (~39MB). Warning threshold is ${thresholdPercentage}% and size ${zipSize} > ${fileSizeThreshold}`
);
}

if (zipFileCount && zipFileCount > fileCountThreshold) {
await Lifecycle.getInstance().emitWarning(
`Deployment zip file count is approaching the Metadata API limit (10,000). Warning threshold is ${thresholdPercentage}% and count ${zipFileCount} > ${fileCountThreshold}`
);
}
}

private async getZipBuffer(): Promise<{ zipBuffer: Buffer; zipFileCount?: number }> {
const mdapiPath = this.options.mdapiPath;

Expand Down Expand Up @@ -339,7 +375,7 @@ export class MetadataApiDeploy extends MetadataTransfer<
}
}
};
this.logger.debug('Zipping directory for metadata deploy:', mdapiPath);
this.logger.debug(`Zipping directory for metadata deploy: ${mdapiPath}`);
zipDirRecursive(mdapiPath);

return {
Expand Down
95 changes: 94 additions & 1 deletion test/client/metadataApiDeploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { basename, join, sep } from 'node:path';
import { MockTestOrgData, TestContext } from '@salesforce/core/testSetup';
import chai, { assert, expect } from 'chai';
import { AnyJson, ensureString, getString } from '@salesforce/ts-types';
import { Lifecycle, Messages, PollingClient, StatusResult } from '@salesforce/core';
import { envVars, Lifecycle, Messages, PollingClient, StatusResult } from '@salesforce/core';
import { Duration } from '@salesforce/kit';
import deepEqualInAnyOrder = require('deep-equal-in-any-order');
import {
Expand Down Expand Up @@ -144,6 +144,99 @@ describe('MetadataApiDeploy', () => {

expect(operation.id).to.deep.equal(response.id);
});

it('should call warnIfDeployThresholdExceeded', async () => {
const component = matchingContentFile.COMPONENT;
const deployedComponents = new ComponentSet([component]);
const { operation, response } = await stubMetadataDeploy($$, testOrg, {
components: deployedComponents,
});
// @ts-expect-error stubbing private method
const warnStub = $$.SANDBOX.spy(operation, 'warnIfDeployThresholdExceeded');

await operation.start();

expect(operation.id).to.deep.equal(response.id);
expect(warnStub.callCount).to.equal(1, 'warnIfDeployThresholdExceeded() should have been called');
// 4 is the expected byte size (zipBuffer is set to '1234')
// undefined is expected since we're not computing the number of files in the zip
expect(warnStub.firstCall.args).to.deep.equal([4, undefined]);
});
});

describe('warnIfDeployThresholdExceeded', () => {
let emitWarningStub: sinon.SinonStub;

beforeEach(() => {
emitWarningStub = $$.SANDBOX.stub(Lifecycle.prototype, 'emitWarning').resolves();
});

it('should emit warning with default threshold when zipSize > 80%', async () => {
const loggerDebugSpy = $$.SANDBOX.spy($$.TEST_LOGGER, 'debug');
const mdapThis = { logger: $$.TEST_LOGGER };
// @ts-expect-error testing private method
await MetadataApiDeploy.prototype.warnIfDeployThresholdExceeded.call(mdapThis, 31_200_001, 8000);
expect(emitWarningStub.calledOnce, 'emitWarning for fileSize should have been called').to.be.true;
const warningMsg =
'Deployment zip file size is approaching the Metadata API limit (~39MB). Warning threshold is 80%';
expect(emitWarningStub.firstCall.args[0]).to.include(warningMsg);
expect(loggerDebugSpy.called).to.be.false;
});

it('should emit warning with default threshold when zipFileCount > 80%', async () => {
const loggerDebugSpy = $$.SANDBOX.spy($$.TEST_LOGGER, 'debug');
const mdapThis = { logger: $$.TEST_LOGGER };
// @ts-expect-error testing private method
await MetadataApiDeploy.prototype.warnIfDeployThresholdExceeded.call(mdapThis, 31_200_000, 8001);
expect(emitWarningStub.calledOnce, 'emitWarning for fileSize should have been called').to.be.true;
const warningMsg =
'Deployment zip file count is approaching the Metadata API limit (10,000). Warning threshold is 80%';
expect(emitWarningStub.firstCall.args[0]).to.include(warningMsg);
expect(loggerDebugSpy.called).to.be.false;
});

it('should not emit warning but log debug output when threshold >= 100%', async () => {
const loggerDebugSpy = $$.SANDBOX.spy($$.TEST_LOGGER, 'debug');
$$.SANDBOX.stub(envVars, 'getNumber').returns(100);
const mdapThis = { logger: $$.TEST_LOGGER };
// @ts-expect-error testing private method
await MetadataApiDeploy.prototype.warnIfDeployThresholdExceeded.call(mdapThis, 310_200_000, 12_000);
expect(emitWarningStub.called).to.be.false;
expect(loggerDebugSpy.calledOnce).to.be.true;
const expectedMsg = 'Deploy size warning is disabled since SF_DEPLOY_SIZE_THRESHOLD is overridden to: 100';
expect(loggerDebugSpy.firstCall.args[0]).to.equal(expectedMsg);
});

it('should emit warnings and log debug output with exceeded overridden threshold', async () => {
const loggerDebugSpy = $$.SANDBOX.spy($$.TEST_LOGGER, 'debug');
$$.SANDBOX.stub(envVars, 'getNumber').returns(75);
const mdapThis = { logger: $$.TEST_LOGGER };
// @ts-expect-error testing private method
await MetadataApiDeploy.prototype.warnIfDeployThresholdExceeded.call(mdapThis, 29_250_001, 7501);
expect(emitWarningStub.calledTwice, 'emitWarning for fileSize and fileCount should have been called').to.be
.true;
const fileSizeWarningMsg =
'Deployment zip file size is approaching the Metadata API limit (~39MB). Warning threshold is 75%';
const fileCountWarningMsg =
'Deployment zip file count is approaching the Metadata API limit (10,000). Warning threshold is 75%';
expect(emitWarningStub.firstCall.args[0]).to.include(fileSizeWarningMsg);
expect(emitWarningStub.secondCall.args[0]).to.include(fileCountWarningMsg);
expect(loggerDebugSpy.calledOnce).to.be.true;
const expectedMsg = 'Deploy size warning threshold has been overridden by SF_DEPLOY_SIZE_THRESHOLD to: 75';
expect(loggerDebugSpy.firstCall.args[0]).to.equal(expectedMsg);
});

it('should NOT emit warnings but log debug output with overridden threshold that is not exceeded', async () => {
const loggerDebugSpy = $$.SANDBOX.spy($$.TEST_LOGGER, 'debug');
$$.SANDBOX.stub(envVars, 'getNumber').returns(75);
const mdapThis = { logger: $$.TEST_LOGGER };
// @ts-expect-error testing private method
await MetadataApiDeploy.prototype.warnIfDeployThresholdExceeded.call(mdapThis, 29_250_000, 7500);
expect(emitWarningStub.called, 'emitWarning should not have been called').to.be.false;
expect(loggerDebugSpy.calledOnce).to.be.true;
const expectedMsg = 'Deploy size warning threshold has been overridden by SF_DEPLOY_SIZE_THRESHOLD to: 75';
expect(loggerDebugSpy.firstCall.args[0]).to.equal(expectedMsg);
});
});

describe('pollStatus', () => {
Expand Down

2 comments on commit 1a6245c

@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: 1a6245c Previous: 444561b Ratio
eda-componentSetCreate-linux 239 ms 243 ms 0.98
eda-sourceToMdapi-linux 2418 ms 2439 ms 0.99
eda-sourceToZip-linux 1905 ms 1962 ms 0.97
eda-mdapiToSource-linux 2990 ms 2968 ms 1.01
lotsOfClasses-componentSetCreate-linux 431 ms 435 ms 0.99
lotsOfClasses-sourceToMdapi-linux 3757 ms 3736 ms 1.01
lotsOfClasses-sourceToZip-linux 3174 ms 3258 ms 0.97
lotsOfClasses-mdapiToSource-linux 3588 ms 3706 ms 0.97
lotsOfClassesOneDir-componentSetCreate-linux 749 ms 757 ms 0.99
lotsOfClassesOneDir-sourceToMdapi-linux 6599 ms 6710 ms 0.98
lotsOfClassesOneDir-sourceToZip-linux 5637 ms 5881 ms 0.96
lotsOfClassesOneDir-mdapiToSource-linux 6703 ms 6637 ms 1.01

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.

Benchmark

Benchmark suite Current: 1a6245c Previous: 444561b Ratio
eda-componentSetCreate-win32 619 ms 603 ms 1.03
eda-sourceToMdapi-win32 4321 ms 4254 ms 1.02
eda-sourceToZip-win32 2897 ms 2934 ms 0.99
eda-mdapiToSource-win32 5724 ms 5672 ms 1.01
lotsOfClasses-componentSetCreate-win32 1226 ms 1219 ms 1.01
lotsOfClasses-sourceToMdapi-win32 7587 ms 7621 ms 1.00
lotsOfClasses-sourceToZip-win32 4964 ms 5009 ms 0.99
lotsOfClasses-mdapiToSource-win32 7719 ms 7907 ms 0.98
lotsOfClassesOneDir-componentSetCreate-win32 2055 ms 2124 ms 0.97
lotsOfClassesOneDir-sourceToMdapi-win32 13657 ms 13748 ms 0.99
lotsOfClassesOneDir-sourceToZip-win32 9174 ms 9101 ms 1.01
lotsOfClassesOneDir-mdapiToSource-win32 13954 ms 14124 ms 0.99

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

Please sign in to comment.