Skip to content

Commit d1bb8be

Browse files
authored
fix: deploy output fixes (#74)
1 parent 056f38a commit d1bb8be

File tree

3 files changed

+38
-22
lines changed

3 files changed

+38
-22
lines changed

messages/deploy.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,7 @@
2828
"soapDeploy": "deploy metadata with SOAP API instead of REST API"
2929
},
3030
"checkOnlySuccess": "Successfully validated the deployment. %s components deployed and %s tests run.\nUse the --verbose parameter to see detailed output.",
31-
"MissingDeployId": "No deploy ID was provided or found in deploy history"
31+
"MissingDeployId": "No deploy ID was provided or found in deploy history",
32+
"deployCanceled": "The deployment has been canceled by %s",
33+
"deployFailed": "Deploy failed."
3234
}

src/commands/force/source/deploy.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ export class Deploy extends DeployCommand {
101101
protected readonly lifecycleEventNames = ['predeploy', 'postdeploy'];
102102

103103
private isAsync = false;
104+
private isRest = false;
104105

105106
public async run(): Promise<DeployCommandResult | DeployCommandAsyncResult> {
106107
await this.deploy();
@@ -114,6 +115,8 @@ export class Deploy extends DeployCommand {
114115
// 3. recent validation - deploy metadata that's already been validated by the org
115116
protected async deploy(): Promise<void> {
116117
this.isAsync = this.getFlag<Duration>('wait').quantity === 0;
118+
this.isRest = await this.isRestDeploy();
119+
this.log(`*** Deploying with ${this.isRest ? 'REST' : 'SOAP'} API ***`);
117120

118121
if (this.flags.validateddeployrequestid) {
119122
this.deployResult = await this.deployRecentValidation();
@@ -193,11 +196,10 @@ export class Deploy extends DeployCommand {
193196
private async deployRecentValidation(): Promise<DeployResult> {
194197
const conn = this.org.getConnection();
195198
const id = this.getFlag<string>('validateddeployrequestid');
196-
const rest = await this.isRestDeploy();
197199

198200
// TODO: This is an async call so we need to poll unless `--wait 0`
199201
// See mdapiCheckStatusApi.ts for the toolbelt polling impl.
200-
const response = await conn.deployRecentValidation({ id, rest });
202+
const response = await conn.deployRecentValidation({ id, rest: this.isRest });
201203

202204
if (!this.isAsync) {
203205
// Remove this and add polling if we need to poll in the plugin.
@@ -247,8 +249,9 @@ export class Deploy extends DeployCommand {
247249
this.progressBar.stop();
248250
});
249251

250-
deploy.onError(() => {
252+
deploy.onError((error: Error) => {
251253
this.progressBar.stop();
254+
throw error;
252255
});
253256
}
254257
}

src/formatters/deployResultFormatter.ts

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
import * as path from 'path';
99
import * as chalk from 'chalk';
1010
import { UX } from '@salesforce/command';
11-
import { Logger, Messages } from '@salesforce/core';
12-
import { getBoolean, getString, getNumber } from '@salesforce/ts-types';
11+
import { Logger, Messages, SfdxError } from '@salesforce/core';
12+
import { get, getBoolean, getString, getNumber } from '@salesforce/ts-types';
1313
import { DeployResult } from '@salesforce/source-deploy-retrieve';
1414
import {
1515
FileResponse,
@@ -61,13 +61,13 @@ export class DeployResultFormatter extends ResultFormatter {
6161
* @returns a JSON formatted result matching the provided type.
6262
*/
6363
public getJson(): DeployCommandResult | DeployCommandAsyncResult {
64-
const json = this.result.response as DeployCommandResult | DeployCommandAsyncResult;
64+
const json = this.getResponse() as DeployCommandResult | DeployCommandAsyncResult;
6565
json.deployedSource = this.fileResponses;
6666
json.outboundFiles = []; // to match toolbelt version
67-
json.deploys = [Object.assign({}, this.result.response)]; // to match toolbelt version
67+
json.deploys = [Object.assign({}, this.getResponse())]; // to match toolbelt version
6868

6969
if (this.isAsync()) {
70-
// json = this.result.response; // <-- TODO: ensure the response matches toolbelt
70+
// json = this.getResponse(); // <-- TODO: ensure the response matches toolbelt
7171
return json as DeployCommandAsyncResult;
7272
}
7373

@@ -93,20 +93,24 @@ export class DeployResultFormatter extends ResultFormatter {
9393
}
9494
if (this.hasStatus(RequestStatus.Canceled)) {
9595
const canceledByName = getString(this.result, 'response.canceledByName', 'unknown');
96-
this.ux.log(`The deployment has been canceled by ${canceledByName}`);
97-
return;
96+
throw new SfdxError(messages.getMessage('deployCanceled', [canceledByName]), 'DeployFailed');
9897
}
9998
this.displaySuccesses();
10099
this.displayFailures();
101100
this.displayTestResults();
101+
102+
// Throw a DeployFailed error unless the deployment was successful.
103+
if (!this.isSuccess()) {
104+
throw new SfdxError(messages.getMessage('deployFailed'), 'DeployFailed');
105+
}
102106
}
103107

104108
protected hasStatus(status: RequestStatus): boolean {
105109
return getString(this.result, 'response.status') === status;
106110
}
107111

108112
protected hasComponents(): boolean {
109-
return getNumber(this.result, 'components.size', 0) === 0;
113+
return getNumber(this.result, 'components.size', 0) > 0;
110114
}
111115

112116
protected isRunTestsEnabled(): boolean {
@@ -121,6 +125,10 @@ export class DeployResultFormatter extends ResultFormatter {
121125
return getNumber(this.result, `response.${field}`, 0);
122126
}
123127

128+
protected getResponse(): MetadataApiDeployStatus {
129+
return get(this.result, 'response', {}) as MetadataApiDeployStatus;
130+
}
131+
124132
protected displaySuccesses(): void {
125133
if (this.isSuccess() && this.hasComponents()) {
126134
// sort by type then filename then fullname
@@ -156,21 +164,24 @@ export class DeployResultFormatter extends ResultFormatter {
156164
protected displayFailures(): void {
157165
if (this.hasStatus(RequestStatus.Failed) && this.hasComponents()) {
158166
// sort by filename then fullname
159-
const failures = this.fileResponses.sort((i, j) => {
160-
if (i.filePath === j.filePath) {
161-
// if they have the same directoryName then sort by fullName
162-
return i.fullName < j.fullName ? 1 : -1;
163-
}
164-
return i.filePath < j.filePath ? 1 : -1;
165-
});
167+
const failures = this.fileResponses
168+
.filter((fileResponse) => fileResponse.state === 'Failed')
169+
.sort((i, j) => {
170+
if (i.filePath === j.filePath) {
171+
// if they have the same directoryName then sort by fullName
172+
return i.fullName < j.fullName ? 1 : -1;
173+
}
174+
return i.filePath < j.filePath ? 1 : -1;
175+
});
166176
this.ux.log('');
167177
this.ux.styledHeader(chalk.red(`Component Failures [${failures.length}]`));
178+
// TODO: do we really need the project path or file path in the table?
179+
// Seems like we can just provide the full name and devs will know.
168180
this.ux.table(failures, {
169181
columns: [
170-
{ key: 'componentType', label: 'Type' },
171-
{ key: 'fileName', label: 'File' },
182+
{ key: 'problemType', label: 'Type' },
172183
{ key: 'fullName', label: 'Name' },
173-
{ key: 'problem', label: 'Problem' },
184+
{ key: 'error', label: 'Problem' },
174185
],
175186
});
176187
this.ux.log('');

0 commit comments

Comments
 (0)