Skip to content

Commit

Permalink
feat: add deploy size and count warnings (#1435)
Browse files Browse the repository at this point in the history
  • Loading branch information
shetzel authored Oct 8, 2024
1 parent bd3d017 commit 3ebdc07
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

0 comments on commit 3ebdc07

Please sign in to comment.