generated from salesforcecli/plugin-template
/
pull.ts
149 lines (130 loc) · 5.17 KB
/
pull.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/*
* Copyright (c) 2020, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import { flags, FlagsConfig } from '@salesforce/command';
import { Duration } from '@salesforce/kit';
import { Messages } from '@salesforce/core';
import { FileResponse, RequestStatus, RetrieveResult, SourceComponent } from '@salesforce/source-deploy-retrieve';
import { SourceTracking } from '@salesforce/source-tracking';
import { SourceCommand } from '../../../sourceCommand';
import { PullResponse, PullResultFormatter } from '../../../formatters/source/pullFormatter';
import { trackingSetup, updateTracking } from '../../../trackingFunctions';
Messages.importMessagesDirectory(__dirname);
const messages = Messages.load('@salesforce/plugin-source', 'pull', [
'flags.forceoverwrite',
'description',
'help',
'flags.waitLong',
]);
export default class Pull extends SourceCommand {
public static aliases = ['force:source:beta:pull'];
public static description = messages.getMessage('description');
public static help = messages.getMessage('help');
protected static readonly flagsConfig: FlagsConfig = {
forceoverwrite: flags.boolean({
char: 'f',
description: messages.getMessage('flags.forceoverwrite'),
}),
// TODO: use shared flags from plugin-source
wait: flags.minutes({
char: 'w',
default: Duration.minutes(33),
min: Duration.minutes(0), // wait=0 means deploy is asynchronous
description: messages.getMessage('flags.waitLong'),
}),
};
protected static requiresUsername = true;
protected static requiresProject = true;
protected readonly lifecycleEventNames = ['preretrieve', 'postretrieve'];
protected tracking: SourceTracking;
protected retrieveResult: RetrieveResult;
protected deleteFileResponses: FileResponse[];
public async run(): Promise<PullResponse> {
await this.preChecks();
await this.retrieve();
// do not parallelize delete and retrieve...we only get to delete IF retrieve was successful
await this.doDeletes(); // deletes includes its tracking file operations
await updateTracking({
result: this.retrieveResult,
ux: this.ux,
tracking: this.tracking,
});
this.ux.stopSpinner();
return this.formatResult();
}
protected async preChecks(): Promise<void> {
this.ux.startSpinner('Loading source tracking information');
this.tracking = await trackingSetup({
commandName: 'force:source:pull',
ignoreConflicts: this.getFlag<boolean>('forceoverwrite', false),
org: this.org,
project: this.project,
ux: this.ux,
});
}
protected async doDeletes(): Promise<void> {
this.ux.setSpinnerStatus('Checking for deletes from the org and updating source tracking files');
const changesToDelete = await this.tracking.getChanges<SourceComponent>({
origin: 'remote',
state: 'delete',
format: 'SourceComponent',
});
this.deleteFileResponses = await this.tracking.deleteFilesAndUpdateTracking(changesToDelete);
}
protected async retrieve(): Promise<void> {
const componentSet = await this.tracking.remoteNonDeletesAsComponentSet();
// if it is't local, add it as a
if (componentSet.size === 0) {
return;
}
componentSet.sourceApiVersion = await this.getSourceApiVersion();
if (this.getFlag<string>('apiversion')) {
componentSet.apiVersion = this.getFlag<string>('apiversion');
}
const mdapiRetrieve = await componentSet.retrieve({
usernameOrConnection: this.org.getUsername(),
merge: true,
output: this.project.getDefaultPackage().fullPath,
});
this.ux.setSpinnerStatus('Retrieving metadata from the org');
// assume: remote deletes that get deleted locally don't fire hooks?
await this.lifecycle.emit('preretrieve', componentSet.toArray());
this.retrieveResult = await mdapiRetrieve.pollStatus({ timeout: this.getFlag<Duration>('wait') });
// Assume: remote deletes that get deleted locally don't fire hooks.
await this.lifecycle.emit('postretrieve', this.retrieveResult.getFileResponses());
}
protected resolveSuccess(): void {
const StatusCodeMap = new Map<RequestStatus, number>([
[RequestStatus.Succeeded, 0],
[RequestStatus.Canceled, 1],
[RequestStatus.Failed, 1],
[RequestStatus.InProgress, 69],
[RequestStatus.Pending, 69],
[RequestStatus.Canceling, 69],
]);
// there might not be a retrieveResult if we don't have anything to retrieve
if (this.retrieveResult && this.retrieveResult.response.status) {
this.setExitCode(StatusCodeMap.get(this.retrieveResult.response.status) ?? 1);
}
}
protected formatResult(): PullResponse {
const formatterOptions = {
verbose: this.getFlag<boolean>('verbose', false),
};
const formatter = new PullResultFormatter(
this.logger,
this.ux,
formatterOptions,
this.retrieveResult,
this.deleteFileResponses
);
// Only display results to console when JSON flag is unset.
if (!this.isJsonOutput()) {
formatter.display();
}
return formatter.getJson();
}
}