From 6ba254e731e48d4b8787fad7d231c075401c4eaf Mon Sep 17 00:00:00 2001 From: Steve Hetzel Date: Mon, 17 Oct 2022 15:29:52 -0600 Subject: [PATCH] fix: display error message from response (#608) * fix: display error message from response * fix: display error message from response for push --- messages/deploy.json | 2 +- messages/md.deploy.json | 2 +- messages/push.json | 2 +- src/commands/force/source/push.ts | 3 ++- src/formatters/deployResultFormatter.ts | 22 ++++++++++------- .../mdapi/mdDeployResultFormatter.ts | 4 +++- src/formatters/source/pushResultFormatter.ts | 11 +++++++-- test/formatters/deployResultFormatter.test.ts | 21 ++++++++++++++++ .../mdDeployResultFormatter.test.ts | 21 ++++++++++++++++ test/formatters/pushResultFormatter.test.ts | 24 +++++++++++++++++-- 10 files changed, 94 insertions(+), 18 deletions(-) diff --git a/messages/deploy.json b/messages/deploy.json index a32f7c8b0..d539dc275 100644 --- a/messages/deploy.json +++ b/messages/deploy.json @@ -84,7 +84,7 @@ "checkOnlySuccessVerbose": "Successfully validated the deployment.", "deploySuccess": "Deploy Succeeded.", "deployCanceled": "The deployment has been canceled by %s.", - "deployFailed": "Deploy failed.", + "deployFailed": "Deploy failed. %s", "asyncDeployQueued": "Deploy has been queued.", "asyncDeployCancel": "Run sfdx force:source:deploy:cancel -i %s -u %s to cancel the deploy.", "asyncDeployReport": "Run sfdx force:source:deploy:report -i %s -u %s to get the latest status.", diff --git a/messages/md.deploy.json b/messages/md.deploy.json index a6cddedde..a34591c93 100644 --- a/messages/md.deploy.json +++ b/messages/md.deploy.json @@ -48,7 +48,7 @@ "soapDeploy": "Deploy metadata with SOAP API instead of the default REST API. Because SOAP API has a lower .ZIP file size limit (400 MB uncompressed, 39 MB compressed), Salesforce recommends REST API deployment. This flag provides backwards compatibility with API version 50.0 and earlier when deploy used SOAP API by default." }, "noRestDeploy": "REST deploy is not available for this org. This feature is currently for internal Salesforce use only.", - "deployFailed": "The metadata deploy operation failed.", + "deployFailed": "The metadata deploy operation failed. %s", "asyncDeployQueued": "Deploy has been queued.", "asyncDeployCancel": "Run sfdx force:mdapi:deploy:cancel -i %s -u %s to cancel the deploy.", "asyncDeployReport": "Run sfdx force:mdapi:deploy:report -i %s -u %s to get the latest status.", diff --git a/messages/push.json b/messages/push.json index 4667cf291..990db2b13 100644 --- a/messages/push.json +++ b/messages/push.json @@ -12,6 +12,6 @@ "ignorewarningsLong": "Completes the deployment even if warnings are generated.", "quiet": "minimize json and sdtout output on success" }, - "sourcepushFailed": "Push failed.", + "sourcepushFailed": "Push failed. %s", "sequentialFail": "Check the order of your dependencies and ensure all metadata is included." } diff --git a/src/commands/force/source/push.ts b/src/commands/force/source/push.ts index dac3df2b2..dc8ac613a 100644 --- a/src/commands/force/source/push.ts +++ b/src/commands/force/source/push.ts @@ -179,7 +179,8 @@ export default class Push extends DeployCommand { result.response.status === RequestStatus.Succeeded || // successful-ish (only warnings about deleted things that are already deleted) (result.response.status === RequestStatus.Failed && - result.getFileResponses().every((fr) => fr.state !== 'Failed')) + result.getFileResponses().every((fr) => fr.state !== 'Failed') && + !result.response.errorMessage) ); // all successes if (this.deployResults.every((result) => isSuccessLike(result))) { diff --git a/src/formatters/deployResultFormatter.ts b/src/formatters/deployResultFormatter.ts index b31407c31..a3536a64c 100644 --- a/src/formatters/deployResultFormatter.ts +++ b/src/formatters/deployResultFormatter.ts @@ -89,7 +89,9 @@ export class DeployResultFormatter extends ResultFormatter { // Throw a DeployFailed error unless the deployment was successful. if (!this.isSuccess()) { - throw new SfError(messages.getMessage('deployFailed'), 'DeployFailed'); + // Add error message directly on the DeployResult (e.g., a GACK) + const errMsg = this.getResponse()?.errorMessage ?? ''; + throw new SfError(messages.getMessage('deployFailed', [errMsg]), 'DeployFailed'); } this.ux.log(messages.getMessage(this.isCheckOnly() ? 'checkOnlySuccessVerbose' : 'deploySuccess')); @@ -180,14 +182,16 @@ export class DeployResultFormatter extends ResultFormatter { }); } - this.ux.log(''); - this.ux.styledHeader(chalk.red(`Component Failures [${failures.length}]`)); - this.ux.table(failures, { - problemType: { header: 'Type' }, - fullName: { header: 'Name' }, - error: { header: 'Problem' }, - }); - this.ux.log(''); + if (failures.length) { + this.ux.log(''); + this.ux.styledHeader(chalk.red(`Component Failures [${failures.length}]`)); + this.ux.table(failures, { + problemType: { header: 'Type' }, + fullName: { header: 'Name' }, + error: { header: 'Problem' }, + }); + this.ux.log(''); + } } } diff --git a/src/formatters/mdapi/mdDeployResultFormatter.ts b/src/formatters/mdapi/mdDeployResultFormatter.ts index d57db790f..e1f6270ef 100644 --- a/src/formatters/mdapi/mdDeployResultFormatter.ts +++ b/src/formatters/mdapi/mdDeployResultFormatter.ts @@ -101,7 +101,9 @@ export class MdDeployResultFormatter extends ResultFormatter { } // TODO: the toolbelt version of this is returning an SfError shape. This returns a status=1 and the result (mdapi response) but not the error name, etc if (!this.isSuccess()) { - const error = new SfError(messages.getMessage('deployFailed'), 'mdapiDeployFailed'); + // Add error message directly on the DeployResult (e.g., a GACK) + const errMsg = this.getResponse()?.errorMessage ?? ''; + const error = new SfError(messages.getMessage('deployFailed', [errMsg]), 'mdapiDeployFailed'); error.setData(this.result); throw error; } diff --git a/src/formatters/source/pushResultFormatter.ts b/src/formatters/source/pushResultFormatter.ts index 8f8a9edb1..d8060ffc2 100644 --- a/src/formatters/source/pushResultFormatter.ts +++ b/src/formatters/source/pushResultFormatter.ts @@ -49,7 +49,7 @@ export class PushResultFormatter extends ResultFormatter { public getJson(): PushResponse { // throws a particular json structure. commandName property will be appended by sfdxCommand when this throws if (process.exitCode !== 0) { - const error = new SfError(messages.getMessage('sourcepushFailed'), 'DeployFailed', [], process.exitCode); + const error = new SfError(messages.getMessage('sourcepushFailed', ['']), 'DeployFailed', [], process.exitCode); const errorData = this.fileResponses.filter((fileResponse) => fileResponse.state === ComponentStatus.Failed); error.setData(errorData); error['result'] = errorData; @@ -85,7 +85,14 @@ export class PushResultFormatter extends ResultFormatter { // Throw a DeployFailed error unless the deployment was successful. if (!this.isSuccess()) { - throw new SfError(messages.getMessage('sourcepushFailed'), 'PushFailed'); + // Add error message directly on the DeployResult (e.g., a GACK) + let errMsg = ''; + this.results?.forEach((res) => { + if (res.response?.errorMessage) { + errMsg += `${res.response?.errorMessage}\n`; + } + }); + throw new SfError(messages.getMessage('sourcepushFailed', [errMsg]), 'PushFailed'); } } diff --git a/test/formatters/deployResultFormatter.test.ts b/test/formatters/deployResultFormatter.test.ts index 366668380..8f3969d7d 100644 --- a/test/formatters/deployResultFormatter.test.ts +++ b/test/formatters/deployResultFormatter.test.ts @@ -114,6 +114,27 @@ describe('DeployResultFormatter', () => { expect(tableStub.firstCall.args[0]).to.deep.equal(fileResponses); }); + it('should output as expected for a deploy failure (GACK)', async () => { + const errorMessage = + 'UNKNOWN_EXCEPTION: An unexpected error occurred. Please include this ErrorId if you contact support: 1730955361-49792 (-1117026034)'; + const deployFailure = getDeployResult('failed', { errorMessage }); + deployFailure.response.details.componentFailures = []; + deployFailure.response.details.componentSuccesses = []; + delete deployFailure.response.details.runTestResult; + const formatter = new DeployResultFormatter(logger, ux as UX, {}, deployFailure); + sandbox.stub(formatter, 'isSuccess').returns(false); + + try { + formatter.display(); + expect(false, 'should have thrown a DeployFailed error').to.be.true; + } catch (err) { + const error = err as Error; + expect(error.message).to.equal(`Deploy failed. ${errorMessage}`); + expect(styledHeaderStub.called).to.equal(false); + expect(tableStub.called).to.equal(false); + } + }); + it('should output as expected for a test failure with verbose', async () => { const formatter = new DeployResultFormatter(logger, ux as UX, { verbose: true }, deployResultTestFailure); formatter.display(); diff --git a/test/formatters/mdDeployResultFormatter.test.ts b/test/formatters/mdDeployResultFormatter.test.ts index fde2ce5bf..5113c87de 100644 --- a/test/formatters/mdDeployResultFormatter.test.ts +++ b/test/formatters/mdDeployResultFormatter.test.ts @@ -123,6 +123,27 @@ describe('mdDeployResultFormatter', () => { } }); + it('should output as expected for a deploy failure (GACK)', async () => { + const errorMessage = + 'UNKNOWN_EXCEPTION: An unexpected error occurred. Please include this ErrorId if you contact support: 1730955361-49792 (-1117026034)'; + const deployFailure = getDeployResult('failed', { errorMessage }); + deployFailure.response.details.componentFailures = []; + deployFailure.response.details.componentSuccesses = []; + delete deployFailure.response.details.runTestResult; + const formatter = new MdDeployResultFormatter(logger, ux as UX, {}, deployFailure); + sandbox.stub(formatter, 'isSuccess').returns(false); + + try { + formatter.display(); + expect(false, 'should have thrown a DeployFailed error').to.be.true; + } catch (err) { + const error = err as Error; + expect(error.message).to.equal(`The metadata deploy operation failed. ${errorMessage}`); + expect(styledHeaderStub.called).to.equal(false); + expect(tableStub.called).to.equal(false); + } + }); + it('should output as expected for a test failure with verbose', async () => { process.exitCode = 1; const formatter = new MdDeployResultFormatter(logger, ux as UX, { verbose: true }, deployResultTestFailure); diff --git a/test/formatters/pushResultFormatter.test.ts b/test/formatters/pushResultFormatter.test.ts index 07cd34a47..6be482ec6 100644 --- a/test/formatters/pushResultFormatter.test.ts +++ b/test/formatters/pushResultFormatter.test.ts @@ -67,7 +67,7 @@ describe('PushResultFormatter', () => { formatter.getJson(); throw new Error('should have thrown'); } catch (error) { - expect(error).to.have.property('message', 'Push failed.'); + expect(error).to.have.property('message', 'Push failed. '); expect(error).to.have.property('name', 'DeployFailed'); expect(error).to.have.property('stack').includes('DeployFailed:'); expect(error).to.have.property('actions').deep.equal([]); @@ -87,7 +87,7 @@ describe('PushResultFormatter', () => { formatter.getJson(); throw new Error('should have thrown'); } catch (error) { - expect(error).to.have.property('message', 'Push failed.'); + expect(error).to.have.property('message', 'Push failed. '); expect(error).to.have.property('result').deep.equal([expectedFail]); } }); @@ -102,6 +102,26 @@ describe('PushResultFormatter', () => { expect(headerStub.callCount, JSON.stringify(headerStub.args)).to.equal(1); expect(tableStub.callCount, JSON.stringify(tableStub.args)).to.equal(1); }); + it('should output as expected for a deploy failure (GACK)', async () => { + const errorMessage = + 'UNKNOWN_EXCEPTION: An unexpected error occurred. Please include this ErrorId if you contact support: 1730955361-49792 (-1117026034)'; + const deployFailure = getDeployResult('failed', { errorMessage }); + deployFailure.response.details.componentFailures = []; + deployFailure.response.details.componentSuccesses = []; + delete deployFailure.response.details.runTestResult; + const formatter = new PushResultFormatter(logger, uxMock as UX, {}, [deployFailure]); + sandbox.stub(formatter, 'isSuccess').returns(false); + + try { + formatter.display(); + expect(false, 'should have thrown a PushFailed error').to.be.true; + } catch (err) { + const error = err as Error; + expect(error.message).to.equal(`Push failed. ${errorMessage}\n`); + expect(headerStub.called).to.equal(false); + expect(tableStub.called).to.equal(false); + } + }); describe('quiet', () => { it('does not display successes for quiet', () => { process.exitCode = 0;