Skip to content

Commit c6665ab

Browse files
fix: deploy first pass
1 parent 4596da6 commit c6665ab

File tree

7 files changed

+279
-85
lines changed

7 files changed

+279
-85
lines changed

command-snapshot.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,25 @@
11
[
2+
{
3+
"command": "force:source:deploy",
4+
"plugin": "@salesforce/plugin-source",
5+
"flags": [
6+
"apiversion",
7+
"checkonly",
8+
"ignoreerrors",
9+
"ignorewarnings",
10+
"json",
11+
"loglevel",
12+
"manifest",
13+
"metadata",
14+
"runtests",
15+
"sourcepath",
16+
"targetusername",
17+
"testlevel",
18+
"validateddeployrequestid",
19+
"verbose",
20+
"wait"
21+
]
22+
},
223
{
324
"command": "force:source:retrieve",
425
"plugin": "@salesforce/plugin-source",

messages/deploy.json

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,18 @@
1414
],
1515
"flags": {
1616
"sourcePath": "comma-separated list of source file paths to retrieve",
17-
"manifestParamDescription": "file path for manifest (package.xml) of components to retrieve",
18-
"metadataParamDescription": "comma-separated list of metadata component names",
19-
"wait": "wait time for command to finish in minutes",
2017
"manifest": "file path for manifest (package.xml) of components to retrieve",
2118
"metadata": "comma-separated list of metadata component names",
19+
"wait": "wait time for command to finish in minutes",
2220
"packagename": "a comma-separated list of packages to retrieve",
23-
"verbose": "verbose output of retrieve result"
21+
"verbose": "verbose output of retrieve result",
22+
"checkonly": "validate deploy but don’t save to the org",
23+
"testLevel": "deployment testing level",
24+
"runTests": "tests to run if --testlevel RunSpecifiedTests",
25+
"ignoreErrors": "ignore any errors and do not roll back deployment",
26+
"ignoreWarnings": "whether a warning will allow a deployment to complete successfully",
27+
"validateDeployRequestId": "request ID of the validated deployment to run a Quick Deploy"
2428
},
25-
"SourceRetrieveError": "Could not retrieve files in the sourcepath%s"
29+
"SourceRetrieveError": "Could not retrieve files in the sourcepath%s",
30+
"checkOnlySuccess": "\nSuccessfully validated the deployment"
2631
}

messages/retrieve.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,14 @@
2323
"verbose": "verbose output of retrieve result"
2424
},
2525
"SourceRetrieveError": "Could not retrieve files in the sourcepath%s",
26-
"retrieveTimeout": "Your retrieve request did not complete within the specified wait time [%s minutes]. Try again with a longer wait time."
26+
"retrieveTimeout": "Your retrieve request did not complete within the specified wait time [%s minutes]. Try again with a longer wait time.",
27+
"retrievedSourceHeader": "Retrieved Source",
28+
"fullNameTableColumn": "FULL NAME",
29+
"typeTableColumn": "TYPE",
30+
"workspacePathTableColumn": "PROJECT PATH",
31+
"NoResultsFound": "No results found",
32+
"metadataNotFoundWarning": "WARNING: The following metadata isn’t in your org. If it’s not new, someone deleted it from the org.",
33+
"columnNumberColumn": "COLUMN NUMBER",
34+
"lineNumberColumn": "LINE NUMBER",
35+
"errorColumn": "PROBLEM"
2736
}

messages/sourceCommand.json

Lines changed: 0 additions & 41 deletions
This file was deleted.
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/*
2+
* Copyright (c) 2020, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
import * as os from 'os';
8+
import * as path from 'path';
9+
import { flags, FlagsConfig } from '@salesforce/command';
10+
import { Lifecycle, Messages } from '@salesforce/core';
11+
import { SourceDeployResult } from '@salesforce/source-deploy-retrieve';
12+
import { Duration } from '@salesforce/kit';
13+
import { asString } from '@salesforce/ts-types';
14+
import * as chalk from 'chalk';
15+
import { DEFAULT_SRC_WAIT_MINUTES, MINIMUM_SRC_WAIT_MINUTES, SourceCommand } from '../../../sourceCommand';
16+
17+
Messages.importMessagesDirectory(__dirname);
18+
const messages = Messages.loadMessages('@salesforce/plugin-source', 'deploy');
19+
20+
export class deploy extends SourceCommand {
21+
public static readonly description = messages.getMessage('description');
22+
public static readonly examples = messages.getMessage('examples').split(os.EOL);
23+
public static readonly requiresProject = true;
24+
public static readonly requiresUsername = true;
25+
public static readonly flagsConfig: FlagsConfig = {
26+
checkonly: flags.boolean({
27+
char: 'c',
28+
description: messages.getMessage('flags.checkonly'),
29+
default: false,
30+
}),
31+
wait: flags.minutes({
32+
char: 'w',
33+
default: Duration.minutes(DEFAULT_SRC_WAIT_MINUTES),
34+
min: Duration.minutes(MINIMUM_SRC_WAIT_MINUTES),
35+
description: messages.getMessage('flags.wait'),
36+
}),
37+
testlevel: flags.enum({
38+
char: 'l',
39+
description: messages.getMessage('flags.testLevel'),
40+
options: ['NoTestRun', 'RunSpecifiedTests', 'RunLocalTests', 'RunAllTestsInOrg'],
41+
default: 'NoTestRun',
42+
}),
43+
runtests: flags.array({
44+
char: 'r',
45+
description: messages.getMessage('flags.runTests'),
46+
default: [],
47+
}),
48+
ignoreerrors: flags.boolean({
49+
char: 'o',
50+
description: messages.getMessage('flags.ignoreErrors'),
51+
default: false,
52+
}),
53+
ignorewarnings: flags.boolean({
54+
char: 'g',
55+
description: messages.getMessage('flags.ignoreWarnings'),
56+
default: false,
57+
}),
58+
validateddeployrequestid: flags.id({
59+
char: 'q',
60+
description: messages.getMessage('flags.validateDeployRequestId'),
61+
exclusive: [
62+
'manifest',
63+
'metadata',
64+
'sourcepath',
65+
'checkonly',
66+
'testlevel',
67+
'runtests',
68+
'ignoreerrors',
69+
'ignorewarnings',
70+
],
71+
}),
72+
verbose: flags.builtin({
73+
description: messages.getMessage('flags.verbose'),
74+
}),
75+
metadata: flags.array({
76+
char: 'm',
77+
description: messages.getMessage('flags.metadata'),
78+
exclusive: ['manifest', 'sourcepath'],
79+
}),
80+
sourcepath: flags.array({
81+
char: 'p',
82+
description: messages.getMessage('flags.sourcePath'),
83+
exclusive: ['manifest', 'metadata'],
84+
}),
85+
manifest: flags.filepath({
86+
char: 'x',
87+
description: messages.getMessage('flags.manifest'),
88+
exclusive: ['metadata', 'sourcepath'],
89+
}),
90+
};
91+
protected readonly lifecycleEventNames = ['predeploy', 'postdeploy'];
92+
93+
public async run(): Promise<SourceDeployResult> {
94+
if (this.flags.validatedeployrequestid) {
95+
// TODO: return this.doDeployRecentValidation();
96+
}
97+
const hookEmitter = Lifecycle.getInstance();
98+
99+
const cs = await this.createComponentSet({
100+
// safe to cast from the flags as an array of strings
101+
packagenames: this.flags.packagenames as string[],
102+
sourcepath: this.flags.sourcepath as string[],
103+
manifest: asString(this.flags.manifest),
104+
metadata: this.flags.metadata as string[],
105+
});
106+
107+
await hookEmitter.emit('predeploy', { packageXmlPath: cs.getPackageXml() });
108+
109+
const results = await cs.deploy(this.org.getUsername(), {
110+
wait: (this.flags.wait as Duration).milliseconds,
111+
apiOptions: {
112+
// TODO: build out more api options
113+
checkOnly: this.flags.checkonly as boolean,
114+
ignoreWarnings: this.flags.ignorewarnings as boolean,
115+
runTests: this.flags.runtests as string[],
116+
},
117+
});
118+
119+
await hookEmitter.emit('postdeploy', results);
120+
121+
this.print(results);
122+
123+
return results;
124+
}
125+
126+
private printComponentFailures(result: SourceDeployResult): void {
127+
if (result.status === 'Failed' && result.components) {
128+
// sort by filename then fullname
129+
const failures = result.components
130+
.sort((i, j) => {
131+
return i.component.type.directoryName < j.component.type.directoryName ? -1 : 1;
132+
})
133+
.sort((i, j) => {
134+
return i.component.fullName < j.component.fullName ? -1 : 1;
135+
});
136+
this.ux.log('');
137+
this.ux.styledHeader(chalk.red(`Component Failures [${failures.length}]`));
138+
this.ux.table(failures, {
139+
// TODO: these accessors are temporary until library JSON fixes
140+
columns: [
141+
{ key: 'component.type.name', label: 'Type' },
142+
{ key: 'diagnostics[0].filePath', label: 'File' },
143+
{ key: 'component.name', label: 'Name' },
144+
{ key: 'diagnostics[0].message', label: 'Problem' },
145+
],
146+
});
147+
this.ux.log('');
148+
}
149+
}
150+
151+
private printComponentSuccess(result: SourceDeployResult): void {
152+
if (result.success && result.components) {
153+
if (result.components.length > 0) {
154+
// sort by type then filename then fullname
155+
const files = result.components.sort((i, j) => {
156+
if (i.component.type.name === j.component.type.name) {
157+
// same metadata type, according to above comment sort on filename
158+
if (i.component.type.directoryName === j.component.type.directoryName) {
159+
// same filename's according to comment sort by fullName
160+
return i.component.fullName < j.component.fullName ? -1 : 1;
161+
}
162+
return i.component.type.directoryName < j.component.type.directoryName ? -1 : 1;
163+
}
164+
return i.component.type.name < j.component.type.name ? -1 : 1;
165+
});
166+
// get relative path for table output
167+
files.map((file) => {
168+
if (file.component.content) {
169+
return (file.component.content = path.relative(process.cwd(), file.component.content));
170+
}
171+
});
172+
this.ux.log('');
173+
this.ux.styledHeader(chalk.blue('Deployed Source'));
174+
this.ux.table(files, {
175+
// TODO: these accessors are temporary until library JSON fixes
176+
columns: [
177+
{ key: 'component.name', label: 'FULL NAME' },
178+
{ key: 'component.type.name', label: 'TYPE' },
179+
{ key: 'component.content', label: 'PROJECT PATH' },
180+
],
181+
});
182+
}
183+
}
184+
}
185+
186+
private print(result: SourceDeployResult): SourceDeployResult {
187+
this.printComponentSuccess(result);
188+
this.printComponentFailures(result);
189+
// TODO: this.printTestResults(result); <- this has WI @W-8903671@
190+
if (result.success && this.flags.checkonly) {
191+
this.log(messages.getMessage('checkOnlySuccess'));
192+
}
193+
194+
return result;
195+
}
196+
}

src/commands/force/source/retrieve.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Lifecycle, Messages, SfdxError } from '@salesforce/core';
1111
import { SourceRetrieveResult } from '@salesforce/source-deploy-retrieve';
1212
import { Duration } from '@salesforce/kit';
1313
import { asString } from '@salesforce/ts-types';
14+
import { blue, yellow } from 'chalk';
1415
import { DEFAULT_SRC_WAIT_MINUTES, MINIMUM_SRC_WAIT_MINUTES, SourceCommand } from '../../../sourceCommand';
1516

1617
Messages.importMessagesDirectory(__dirname);
@@ -82,4 +83,35 @@ export class retrieve extends SourceCommand {
8283

8384
return results;
8485
}
86+
87+
/**
88+
* to print the results table of successes, failures, partial failures
89+
*
90+
* @param results what the .deploy or .retrieve method returns
91+
* @param withoutState a boolean to add state, default to true
92+
*/
93+
public printTable(results: SourceRetrieveResult, withoutState?: boolean): void {
94+
const stateCol = withoutState ? [] : [{ key: 'state', label: messages.getMessage('stateTableColumn') }];
95+
96+
this.ux.styledHeader(blue(messages.getMessage('retrievedSourceHeader')));
97+
if (results.success && results.successes.length) {
98+
const columns = [
99+
{ key: 'properties.fullName', label: messages.getMessage('fullNameTableColumn') },
100+
{ key: 'properties.type', label: messages.getMessage('typeTableColumn') },
101+
{
102+
key: 'properties.fileName',
103+
label: messages.getMessage('workspacePathTableColumn'),
104+
},
105+
];
106+
this.ux.table(results.successes, { columns: [...stateCol, ...columns] });
107+
} else {
108+
this.ux.log(messages.getMessage('NoResultsFound'));
109+
}
110+
111+
if (results.status === 'PartialSuccess' && results.successes.length && results.failures.length) {
112+
this.ux.log('');
113+
this.ux.styledHeader(yellow(messages.getMessage('metadataNotFoundWarning')));
114+
results.failures.forEach((warning) => this.ux.log(warning.message));
115+
}
116+
}
85117
}

0 commit comments

Comments
 (0)