@salesforce/source-tracking 2.1.2 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/README.md +0 -26
- package/lib/index.d.ts +1 -1
- package/lib/index.js +3 -1
- package/lib/shared/conflicts.d.ts +16 -0
- package/lib/shared/conflicts.js +81 -0
- package/lib/shared/expectedSourceMembers.d.ts +2 -0
- package/lib/shared/expectedSourceMembers.js +74 -0
- package/lib/shared/functions.d.ts +2 -0
- package/lib/shared/functions.js +6 -2
- package/lib/shared/localComponentSetArray.d.ts +15 -0
- package/lib/shared/localComponentSetArray.js +87 -0
- package/lib/shared/localShadowRepo.js +1 -1
- package/lib/shared/metadataKeys.d.ts +1 -0
- package/lib/shared/metadataKeys.js +18 -2
- package/lib/shared/populateFilePaths.d.ts +9 -0
- package/lib/shared/populateFilePaths.js +77 -0
- package/lib/shared/populateTypesAndNames.d.ts +18 -0
- package/lib/shared/populateTypesAndNames.js +79 -0
- package/lib/shared/remoteSourceTrackingService.d.ts +0 -5
- package/lib/shared/remoteSourceTrackingService.js +6 -78
- package/lib/shared/types.d.ts +12 -4
- package/lib/shared/types.js +5 -0
- package/lib/sourceTracking.d.ts +87 -17
- package/lib/sourceTracking.js +181 -288
- package/package.json +12 -12
package/lib/sourceTracking.js
CHANGED
|
@@ -9,17 +9,22 @@ exports.SourceTracking = void 0;
|
|
|
9
9
|
*/
|
|
10
10
|
const fs = require("fs");
|
|
11
11
|
const path_1 = require("path");
|
|
12
|
-
const os_1 = require("os");
|
|
13
12
|
const core_1 = require("@salesforce/core");
|
|
14
13
|
const kit_1 = require("@salesforce/kit");
|
|
15
14
|
const ts_types_1 = require("@salesforce/ts-types");
|
|
16
15
|
const source_deploy_retrieve_1 = require("@salesforce/source-deploy-retrieve");
|
|
16
|
+
// this is not exported by SDR (see the comments in SDR regarding its limitations)
|
|
17
|
+
const filePathGenerator_1 = require("@salesforce/source-deploy-retrieve/lib/src/utils/filePathGenerator");
|
|
17
18
|
const remoteSourceTrackingService_1 = require("./shared/remoteSourceTrackingService");
|
|
18
19
|
const localShadowRepo_1 = require("./shared/localShadowRepo");
|
|
20
|
+
const conflicts_1 = require("./shared/conflicts");
|
|
19
21
|
const guards_1 = require("./shared/guards");
|
|
20
22
|
const functions_1 = require("./shared/functions");
|
|
21
23
|
const metadataKeys_1 = require("./shared/metadataKeys");
|
|
22
24
|
const compatibility_1 = require("./compatibility");
|
|
25
|
+
const populateFilePaths_1 = require("./shared/populateFilePaths");
|
|
26
|
+
const populateTypesAndNames_1 = require("./shared/populateTypesAndNames");
|
|
27
|
+
const localComponentSetArray_1 = require("./shared/localComponentSetArray");
|
|
23
28
|
/**
|
|
24
29
|
* Manages source tracking files (remote and local)
|
|
25
30
|
*
|
|
@@ -29,94 +34,46 @@ const compatibility_1 = require("./compatibility");
|
|
|
29
34
|
class SourceTracking extends kit_1.AsyncCreatable {
|
|
30
35
|
constructor(options) {
|
|
31
36
|
super(options);
|
|
32
|
-
this.registry = new source_deploy_retrieve_1.RegistryAccess();
|
|
33
37
|
this.org = options.org;
|
|
38
|
+
this.orgId = this.org.getOrgId();
|
|
34
39
|
this.projectPath = options.project.getPath();
|
|
35
40
|
this.packagesDirs = options.project.getPackageDirectories();
|
|
36
41
|
this.logger = core_1.Logger.childFromRoot('SourceTracking');
|
|
37
42
|
this.project = options.project;
|
|
38
|
-
this.
|
|
43
|
+
this.ignoreConflicts = options.ignoreConflicts ?? false;
|
|
44
|
+
this.ignoreLocalCache = options.ignoreLocalCache ?? false;
|
|
45
|
+
this.subscribeSDREvents = options.subscribeSDREvents ?? false;
|
|
46
|
+
this.hasSfdxTrackingFiles = (0, compatibility_1.hasSfdxTrackingFiles)(this.orgId, this.projectPath);
|
|
39
47
|
}
|
|
40
48
|
// eslint-disable-next-line class-methods-use-this
|
|
41
49
|
async init() {
|
|
42
|
-
|
|
50
|
+
await this.maybeSubscribeLifecycleEvents();
|
|
43
51
|
}
|
|
44
52
|
/**
|
|
45
53
|
*
|
|
46
|
-
* @param byPackageDir if true, returns
|
|
54
|
+
* @param byPackageDir if true, returns a ComponentSet for each packageDir that has any changes
|
|
55
|
+
* * if false, returns an array containing one ComponentSet with all changes
|
|
56
|
+
* * if not specified, this method will follow what sfdx-project.json says
|
|
47
57
|
* @returns ComponentSet[]
|
|
48
58
|
*/
|
|
49
|
-
async localChangesAsComponentSet(byPackageDir
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// remove the forceIgnored items early
|
|
60
|
-
.map((group) => group.filter((item) => this.forceIgnore.accepts(item)));
|
|
59
|
+
async localChangesAsComponentSet(byPackageDir) {
|
|
60
|
+
const [projectConfig] = await Promise.all([
|
|
61
|
+
this.project.resolveProjectConfig(),
|
|
62
|
+
this.ensureLocalTracking(),
|
|
63
|
+
]);
|
|
64
|
+
const sourceApiVersion = projectConfig.sourceApiVersion;
|
|
65
|
+
const [nonDeletes, deletes] = await Promise.all([
|
|
66
|
+
this.localRepo.getNonDeleteFilenames(),
|
|
67
|
+
this.localRepo.getDeleteFilenames(),
|
|
68
|
+
]);
|
|
61
69
|
// it'll be easier to filter filenames and work with smaller component sets than to filter SourceComponents
|
|
62
|
-
const groupings = (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}))
|
|
68
|
-
: [
|
|
69
|
-
{
|
|
70
|
-
nonDeletes: allNonDeletes,
|
|
71
|
-
deletes: allDeletes,
|
|
72
|
-
path: this.packagesDirs.map((dir) => dir.name).join(';'),
|
|
73
|
-
},
|
|
74
|
-
]).filter((group) => group.deletes.length || group.nonDeletes.length);
|
|
70
|
+
const groupings = (0, localComponentSetArray_1.getGroupedFiles)({
|
|
71
|
+
packageDirs: this.packagesDirs,
|
|
72
|
+
nonDeletes,
|
|
73
|
+
deletes,
|
|
74
|
+
}, byPackageDir ?? Boolean(projectConfig.pushPackageDirectoriesSequentially)); // if the users specified true or false for the param, that overrides the project config
|
|
75
75
|
this.logger.debug(`will build array of ${groupings.length} componentSet(s)`);
|
|
76
|
-
return groupings
|
|
77
|
-
.map((grouping) => {
|
|
78
|
-
this.logger.debug(`building componentSet for ${grouping.path} (deletes: ${grouping.deletes.length} nonDeletes: ${grouping.nonDeletes.length})`);
|
|
79
|
-
const componentSet = new source_deploy_retrieve_1.ComponentSet();
|
|
80
|
-
if (sourceApiVersion) {
|
|
81
|
-
componentSet.sourceApiVersion = sourceApiVersion;
|
|
82
|
-
}
|
|
83
|
-
const resolverForDeletes = new source_deploy_retrieve_1.MetadataResolver(undefined, source_deploy_retrieve_1.VirtualTreeContainer.fromFilePaths(grouping.deletes));
|
|
84
|
-
grouping.deletes
|
|
85
|
-
.flatMap((filename) => resolverForDeletes.getComponentsFromPath(filename))
|
|
86
|
-
.filter(guards_1.sourceComponentGuard)
|
|
87
|
-
.map((component) => {
|
|
88
|
-
// if the component is a file in a bundle type AND there are files from the bundle that are not deleted, set the bundle for deploy, not for delete
|
|
89
|
-
if ((0, functions_1.isBundle)(component) && component.content && fs.existsSync(component.content)) {
|
|
90
|
-
// all bundle types have a directory name
|
|
91
|
-
try {
|
|
92
|
-
resolverForNonDeletes
|
|
93
|
-
.getComponentsFromPath((0, path_1.resolve)(component.content))
|
|
94
|
-
.filter(guards_1.sourceComponentGuard)
|
|
95
|
-
.map((nonDeletedComponent) => componentSet.add(nonDeletedComponent));
|
|
96
|
-
}
|
|
97
|
-
catch (e) {
|
|
98
|
-
this.logger.warn(`unable to find component at ${component.content}. That's ok if it was supposed to be deleted`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
componentSet.add(component, source_deploy_retrieve_1.DestructiveChangesType.POST);
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
grouping.nonDeletes
|
|
106
|
-
.flatMap((filename) => {
|
|
107
|
-
try {
|
|
108
|
-
return resolverForNonDeletes.getComponentsFromPath((0, path_1.resolve)(filename));
|
|
109
|
-
}
|
|
110
|
-
catch (e) {
|
|
111
|
-
this.logger.warn(`unable to resolve ${filename}`);
|
|
112
|
-
return undefined;
|
|
113
|
-
}
|
|
114
|
-
})
|
|
115
|
-
.filter(guards_1.sourceComponentGuard)
|
|
116
|
-
.map((component) => componentSet.add(component));
|
|
117
|
-
return componentSet;
|
|
118
|
-
})
|
|
119
|
-
.filter((componentSet) => componentSet.size > 0);
|
|
76
|
+
return (0, localComponentSetArray_1.getComponentSets)(groupings, sourceApiVersion);
|
|
120
77
|
}
|
|
121
78
|
async remoteNonDeletesAsComponentSet() {
|
|
122
79
|
const [changeResults, sourceBackedComponents] = await Promise.all([
|
|
@@ -176,14 +133,8 @@ class SourceTracking extends kit_1.AsyncCreatable {
|
|
|
176
133
|
}
|
|
177
134
|
return results;
|
|
178
135
|
}
|
|
179
|
-
/**
|
|
180
|
-
* Get metadata changes made locally and in the org.
|
|
181
|
-
*
|
|
182
|
-
* @returns local and remote changed metadata
|
|
183
|
-
*
|
|
184
|
-
*/
|
|
185
136
|
async getChanges(options) {
|
|
186
|
-
if (
|
|
137
|
+
if (options?.origin === 'local') {
|
|
187
138
|
await this.ensureLocalTracking();
|
|
188
139
|
const filenames = await this.getLocalChangesAsFilenames(options.state);
|
|
189
140
|
if (options.format === 'string') {
|
|
@@ -212,24 +163,24 @@ class SourceTracking extends kit_1.AsyncCreatable {
|
|
|
212
163
|
.filter(guards_1.sourceComponentGuard);
|
|
213
164
|
}
|
|
214
165
|
}
|
|
215
|
-
if (
|
|
166
|
+
if (options?.origin === 'remote') {
|
|
216
167
|
await this.ensureRemoteTracking();
|
|
217
168
|
const remoteChanges = await this.remoteSourceTrackingService.retrieveUpdates();
|
|
218
169
|
this.logger.debug('remoteChanges', remoteChanges);
|
|
219
170
|
const filteredChanges = remoteChanges
|
|
220
171
|
.filter(remoteFilterByState[options.state])
|
|
221
|
-
// skip any remote types not in the registry. Will emit
|
|
222
|
-
.filter((rce) =>
|
|
172
|
+
// skip any remote types not in the registry. Will emit warnings
|
|
173
|
+
.filter((rce) => (0, metadataKeys_1.registrySupportsType)(rce.type));
|
|
223
174
|
if (options.format === 'ChangeResult') {
|
|
224
175
|
return filteredChanges.map((change) => (0, remoteSourceTrackingService_1.remoteChangeElementToChangeResult)(change));
|
|
225
176
|
}
|
|
226
177
|
if (options.format === 'ChangeResultWithPaths') {
|
|
227
|
-
return
|
|
178
|
+
return (0, populateFilePaths_1.populateFilePaths)(filteredChanges.map((change) => (0, remoteSourceTrackingService_1.remoteChangeElementToChangeResult)(change)), this.project.getPackageDirectories().map((pkgDir) => pkgDir.path));
|
|
228
179
|
}
|
|
229
180
|
// turn it into a componentSet to resolve filenames
|
|
230
181
|
const remoteChangesAsComponentSet = new source_deploy_retrieve_1.ComponentSet(filteredChanges.map((element) => ({
|
|
231
|
-
type: element
|
|
232
|
-
fullName: element
|
|
182
|
+
type: element?.type,
|
|
183
|
+
fullName: element?.name,
|
|
233
184
|
})));
|
|
234
185
|
const matchingLocalSourceComponentsSet = source_deploy_retrieve_1.ComponentSet.fromSource({
|
|
235
186
|
fsPaths: this.packagesDirs.map((dir) => (0, path_1.resolve)(dir.fullPath)),
|
|
@@ -247,6 +198,13 @@ class SourceTracking extends kit_1.AsyncCreatable {
|
|
|
247
198
|
}
|
|
248
199
|
throw new Error(`unsupported options: ${JSON.stringify(options)}`);
|
|
249
200
|
}
|
|
201
|
+
async maybeApplyRemoteDeletesToLocal(returnDeleteFileResponses) {
|
|
202
|
+
const changesToDelete = await this.getChanges({ origin: 'remote', state: 'delete', format: 'SourceComponent' });
|
|
203
|
+
const fileResponsesFromDelete = await this.deleteFilesAndUpdateTracking(changesToDelete);
|
|
204
|
+
return returnDeleteFileResponses
|
|
205
|
+
? { componentSetFromNonDeletes: await this.remoteNonDeletesAsComponentSet(), fileResponsesFromDelete }
|
|
206
|
+
: this.remoteNonDeletesAsComponentSet();
|
|
207
|
+
}
|
|
250
208
|
/**
|
|
251
209
|
*
|
|
252
210
|
* returns immediately if there are no changesToDelete
|
|
@@ -274,15 +232,12 @@ class SourceTracking extends kit_1.AsyncCreatable {
|
|
|
274
232
|
})), true // skip polling because it's a pull
|
|
275
233
|
),
|
|
276
234
|
]);
|
|
277
|
-
return filenames.map((filename) => {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
fullName: (_b = sourceComponentByFileName.get(filename)) === null || _b === void 0 ? void 0 : _b.fullName,
|
|
284
|
-
});
|
|
285
|
-
});
|
|
235
|
+
return filenames.map((filename) => ({
|
|
236
|
+
state: 'Deleted',
|
|
237
|
+
filename,
|
|
238
|
+
type: sourceComponentByFileName.get(filename)?.type.name,
|
|
239
|
+
fullName: sourceComponentByFileName.get(filename)?.fullName,
|
|
240
|
+
}));
|
|
286
241
|
}
|
|
287
242
|
/**
|
|
288
243
|
* Update tracking for the options passed.
|
|
@@ -290,12 +245,11 @@ class SourceTracking extends kit_1.AsyncCreatable {
|
|
|
290
245
|
* @param options the files to update
|
|
291
246
|
*/
|
|
292
247
|
async updateLocalTracking(options) {
|
|
293
|
-
var _a, _b;
|
|
294
248
|
await this.ensureLocalTracking();
|
|
295
249
|
// relative paths make smaller trees AND isogit wants them relative
|
|
296
250
|
const relativeOptions = {
|
|
297
|
-
files: (
|
|
298
|
-
deletedFiles: (
|
|
251
|
+
files: (options.files ?? []).map((file) => (0, functions_1.ensureRelative)(file, this.projectPath)),
|
|
252
|
+
deletedFiles: (options.deletedFiles ?? []).map((file) => (0, functions_1.ensureRelative)(file, this.projectPath)),
|
|
299
253
|
};
|
|
300
254
|
// plot twist: if you delete a member of a bundle (ex: lwc/foo/foo.css) and push, it'll not be in the fileResponses (deployedFiles) or deletedFiles
|
|
301
255
|
// what got deleted? Any local changes NOT in the fileResponses but part of a successfully deployed bundle
|
|
@@ -329,17 +283,23 @@ class SourceTracking extends kit_1.AsyncCreatable {
|
|
|
329
283
|
}
|
|
330
284
|
await this.remoteSourceTrackingService.syncSpecifiedElements(fileResponses);
|
|
331
285
|
}
|
|
286
|
+
async reReadLocalTrackingCache() {
|
|
287
|
+
await this.localRepo.getStatus(true);
|
|
288
|
+
}
|
|
332
289
|
/**
|
|
333
290
|
* If the local tracking shadowRepo doesn't exist, it will be created.
|
|
334
|
-
* Does nothing if it already exists
|
|
291
|
+
* Does nothing if it already exists, unless you've instantiate SourceTracking to not cache local status, in which case it'll re-read your files
|
|
335
292
|
* Useful before parallel operations
|
|
336
293
|
*/
|
|
337
294
|
async ensureLocalTracking() {
|
|
338
295
|
if (this.localRepo) {
|
|
296
|
+
if (this.ignoreLocalCache) {
|
|
297
|
+
await this.localRepo.getStatus(true);
|
|
298
|
+
}
|
|
339
299
|
return;
|
|
340
300
|
}
|
|
341
301
|
this.localRepo = await localShadowRepo_1.ShadowRepo.getInstance({
|
|
342
|
-
orgId: this.
|
|
302
|
+
orgId: this.orgId,
|
|
343
303
|
projectPath: (0, path_1.normalize)(this.projectPath),
|
|
344
304
|
packageDirs: this.packagesDirs,
|
|
345
305
|
hasSfdxTrackingFiles: this.hasSfdxTrackingFiles,
|
|
@@ -395,7 +355,7 @@ class SourceTracking extends kit_1.AsyncCreatable {
|
|
|
395
355
|
* Deletes the remote tracking files
|
|
396
356
|
*/
|
|
397
357
|
async clearRemoteTracking() {
|
|
398
|
-
return remoteSourceTrackingService_1.RemoteSourceTrackingService.delete(this.
|
|
358
|
+
return remoteSourceTrackingService_1.RemoteSourceTrackingService.delete(this.orgId, this.hasSfdxTrackingFiles);
|
|
399
359
|
}
|
|
400
360
|
/**
|
|
401
361
|
* Sets the files to max revision so that no changes appear
|
|
@@ -430,201 +390,128 @@ class SourceTracking extends kit_1.AsyncCreatable {
|
|
|
430
390
|
if (remoteChanges.length === 0) {
|
|
431
391
|
return [];
|
|
432
392
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
metadataKeyIndex.set((0, functions_1.getMetadataKey)(change.name, change.type), change);
|
|
440
|
-
}
|
|
441
|
-
(_a = change.filenames) === null || _a === void 0 ? void 0 : _a.map((filename) => {
|
|
442
|
-
fileNameIndex.set(filename, change);
|
|
443
|
-
});
|
|
444
|
-
});
|
|
445
|
-
const conflicts = new Set();
|
|
446
|
-
this.populateTypesAndNames({ elements: localChanges, excludeUnresolvable: true }).map((change) => {
|
|
447
|
-
var _a;
|
|
448
|
-
const metadataKey = (0, functions_1.getMetadataKey)(change.name, change.type);
|
|
449
|
-
// option 1: name and type match
|
|
450
|
-
if (metadataKeyIndex.has(metadataKey)) {
|
|
451
|
-
conflicts.add({ ...metadataKeyIndex.get(metadataKey) });
|
|
452
|
-
}
|
|
453
|
-
else {
|
|
454
|
-
// option 2: some of the filenames match
|
|
455
|
-
(_a = change.filenames) === null || _a === void 0 ? void 0 : _a.map((filename) => {
|
|
456
|
-
if (fileNameIndex.has(filename)) {
|
|
457
|
-
conflicts.add({ ...fileNameIndex.get(filename) });
|
|
458
|
-
}
|
|
459
|
-
});
|
|
460
|
-
}
|
|
393
|
+
this.forceIgnore ?? (this.forceIgnore = source_deploy_retrieve_1.ForceIgnore.findAndCreate(this.project.getDefaultPackage().path));
|
|
394
|
+
return (0, conflicts_1.getDedupedConflictsFromChanges)({
|
|
395
|
+
localChanges,
|
|
396
|
+
remoteChanges,
|
|
397
|
+
projectPath: this.projectPath,
|
|
398
|
+
forceIgnore: this.forceIgnore,
|
|
461
399
|
});
|
|
462
|
-
// deeply de-dupe
|
|
463
|
-
return Array.from(conflicts);
|
|
464
400
|
}
|
|
465
401
|
/**
|
|
466
|
-
*
|
|
402
|
+
* handles both remote and local tracking
|
|
467
403
|
*
|
|
468
|
-
* @
|
|
469
|
-
* @input excludeUnresolvable: boolean Filter out components where you can't get the name and type (that is, it's probably not a valid source component)
|
|
470
|
-
* @input resolveDeleted: constructs a virtualTree instead of the actual filesystem--useful when the files no longer exist
|
|
471
|
-
* @input useFsForceIgnore: (default behavior) use forceIgnore from the filesystem. If false, uses the base forceIgnore from SDR
|
|
404
|
+
* @param result FileResponse[]
|
|
472
405
|
*/
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
406
|
+
async updateTrackingFromDeploy(deployResult) {
|
|
407
|
+
const successes = deployResult
|
|
408
|
+
.getFileResponses()
|
|
409
|
+
.filter((fileResponse) => fileResponse.state !== source_deploy_retrieve_1.ComponentStatus.Failed && fileResponse.filePath);
|
|
410
|
+
if (!successes.length) {
|
|
411
|
+
return;
|
|
476
412
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
413
|
+
await Promise.all([
|
|
414
|
+
this.updateLocalTracking({
|
|
415
|
+
// assertions allowed because filtered above
|
|
416
|
+
files: successes
|
|
417
|
+
.filter((fileResponse) => fileResponse.state !== source_deploy_retrieve_1.ComponentStatus.Deleted)
|
|
418
|
+
.map((fileResponse) => fileResponse.filePath),
|
|
419
|
+
deletedFiles: successes
|
|
420
|
+
.filter((fileResponse) => fileResponse.state === source_deploy_retrieve_1.ComponentStatus.Deleted)
|
|
421
|
+
.map((fileResponse) => fileResponse.filePath),
|
|
422
|
+
}),
|
|
423
|
+
this.updateRemoteTracking(successes.map(({ state, fullName, type, filePath }) => ({ state, fullName, type, filePath }))),
|
|
424
|
+
]);
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* handles both remote and local tracking
|
|
428
|
+
*
|
|
429
|
+
* @param result FileResponse[]
|
|
430
|
+
*/
|
|
431
|
+
async updateTrackingFromRetrieve(retrieveResult) {
|
|
432
|
+
const successes = retrieveResult
|
|
433
|
+
.getFileResponses()
|
|
434
|
+
.filter((fileResponse) => fileResponse.state !== source_deploy_retrieve_1.ComponentStatus.Failed);
|
|
435
|
+
if (!successes.length) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
await Promise.all([
|
|
439
|
+
this.updateLocalTracking({
|
|
440
|
+
// assertion allowed because it's filtering out undefined
|
|
441
|
+
files: successes.map((fileResponse) => fileResponse.filePath).filter(Boolean),
|
|
442
|
+
}),
|
|
443
|
+
this.updateRemoteTracking(successes.map(({ state, fullName, type, filePath }) => ({ state, fullName, type, filePath })), true // retrieves don't need to poll for SourceMembers
|
|
444
|
+
),
|
|
445
|
+
]);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* If you've already got an instance of STL, but need to change the conflicts setting
|
|
449
|
+
* normally you set this on instantiation
|
|
450
|
+
*
|
|
451
|
+
* @param value true/false
|
|
452
|
+
*/
|
|
453
|
+
setIgnoreConflicts(value) {
|
|
454
|
+
this.ignoreConflicts = value;
|
|
455
|
+
}
|
|
456
|
+
async maybeSubscribeLifecycleEvents() {
|
|
457
|
+
if (this.subscribeSDREvents && (await this.org.tracksSource())) {
|
|
458
|
+
const lifecycle = core_1.Lifecycle.getInstance();
|
|
459
|
+
// the only thing STL uses pre events for is to check conflicts. So if you don't care about conflicts, don't listen!
|
|
460
|
+
if (!this.ignoreConflicts) {
|
|
461
|
+
this.logger.debug('subscribing to predeploy/retrieve events');
|
|
462
|
+
// subscribe to SDR `pre` events to handle conflicts before deploy/retrieve
|
|
463
|
+
lifecycle.on('scopedPreDeploy', async (e) => {
|
|
464
|
+
this.logger.debug('received scopedPreDeploy event');
|
|
465
|
+
if (e.orgId === this.orgId) {
|
|
466
|
+
(0, conflicts_1.throwIfConflicts)((0, conflicts_1.findConflictsInComponentSet)(e.componentSet, await this.getConflicts()));
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
lifecycle.on('scopedPreRetrieve', async (e) => {
|
|
470
|
+
this.logger.debug('received scopedPreRetrieve event');
|
|
471
|
+
if (e.orgId === this.orgId) {
|
|
472
|
+
(0, conflicts_1.throwIfConflicts)((0, conflicts_1.findConflictsInComponentSet)(e.componentSet, await this.getConflicts()));
|
|
522
473
|
}
|
|
523
474
|
});
|
|
524
475
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
476
|
+
// subscribe to SDR post-deploy event
|
|
477
|
+
this.logger.debug('subscribing to postdeploy/retrieve events');
|
|
478
|
+
// yes, the post hooks really have different payloads!
|
|
479
|
+
lifecycle.on('scopedPostDeploy', async (e) => {
|
|
480
|
+
this.logger.debug('received scopedPostDeploy event');
|
|
481
|
+
if (e.orgId === this.orgId) {
|
|
482
|
+
await this.updateTrackingFromDeploy(e.deployResult);
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
lifecycle.on('scopedPostRetrieve', async (e) => {
|
|
486
|
+
this.logger.debug('received scopedPostRetrieve event');
|
|
487
|
+
if (e.orgId === this.orgId) {
|
|
488
|
+
await this.updateTrackingFromRetrieve(e.retrieveResult);
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
}
|
|
529
492
|
}
|
|
530
493
|
async getLocalStatusRows() {
|
|
531
494
|
await this.ensureLocalTracking();
|
|
532
495
|
let results = [];
|
|
533
|
-
const localDeletes =
|
|
496
|
+
const localDeletes = (0, populateTypesAndNames_1.populateTypesAndNames)({
|
|
534
497
|
elements: await this.getChanges({ origin: 'local', state: 'delete', format: 'ChangeResult' }),
|
|
535
498
|
excludeUnresolvable: true,
|
|
536
499
|
resolveDeleted: true,
|
|
537
|
-
|
|
500
|
+
projectPath: this.projectPath,
|
|
538
501
|
});
|
|
539
|
-
const localAdds =
|
|
502
|
+
const localAdds = (0, populateTypesAndNames_1.populateTypesAndNames)({
|
|
540
503
|
elements: await this.getChanges({ origin: 'local', state: 'add', format: 'ChangeResult' }),
|
|
541
504
|
excludeUnresolvable: true,
|
|
542
|
-
|
|
505
|
+
projectPath: this.projectPath,
|
|
543
506
|
});
|
|
544
|
-
const localModifies =
|
|
507
|
+
const localModifies = (0, populateTypesAndNames_1.populateTypesAndNames)({
|
|
545
508
|
elements: await this.getChanges({ origin: 'local', state: 'modify', format: 'ChangeResult' }),
|
|
546
509
|
excludeUnresolvable: true,
|
|
547
|
-
|
|
510
|
+
projectPath: this.projectPath,
|
|
548
511
|
});
|
|
549
512
|
results = results.concat(localAdds.flatMap((item) => this.localChangesToOutputRow(item, 'add')), localModifies.flatMap((item) => this.localChangesToOutputRow(item, 'modify')), localDeletes.flatMap((item) => this.localChangesToOutputRow(item, 'delete')));
|
|
550
513
|
return results;
|
|
551
514
|
}
|
|
552
|
-
registrySupportsType(type) {
|
|
553
|
-
try {
|
|
554
|
-
if (metadataKeys_1.mappingsForSourceMemberTypesToMetadataType.has(type)) {
|
|
555
|
-
return true;
|
|
556
|
-
}
|
|
557
|
-
// this must use getTypeByName because findType doesn't support addressable child types (ex: customField!)
|
|
558
|
-
this.registry.getTypeByName(type);
|
|
559
|
-
return true;
|
|
560
|
-
}
|
|
561
|
-
catch (e) {
|
|
562
|
-
process.emitWarning(`Unable to find type ${type} in registry`);
|
|
563
|
-
return false;
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
/**
|
|
567
|
-
* uses SDR to translate remote metadata records into local file paths
|
|
568
|
-
*/
|
|
569
|
-
populateFilePaths(elements) {
|
|
570
|
-
if (elements.length === 0) {
|
|
571
|
-
return [];
|
|
572
|
-
}
|
|
573
|
-
this.logger.debug('populateFilePaths for change elements', elements);
|
|
574
|
-
// component set generated from an array of MetadataMember from all the remote changes
|
|
575
|
-
// but exclude the ones that aren't in the registry
|
|
576
|
-
const remoteChangesAsMetadataMember = elements
|
|
577
|
-
.map((element) => {
|
|
578
|
-
if (typeof element.type === 'string' && typeof element.name === 'string') {
|
|
579
|
-
return {
|
|
580
|
-
type: element.type,
|
|
581
|
-
fullName: element.name,
|
|
582
|
-
};
|
|
583
|
-
}
|
|
584
|
-
})
|
|
585
|
-
.filter(guards_1.metadataMemberGuard);
|
|
586
|
-
const remoteChangesAsComponentSet = new source_deploy_retrieve_1.ComponentSet(remoteChangesAsMetadataMember);
|
|
587
|
-
this.logger.debug(` the generated component set has ${remoteChangesAsComponentSet.size.toString()} items`);
|
|
588
|
-
if (remoteChangesAsComponentSet.size < elements.length) {
|
|
589
|
-
// there *could* be something missing
|
|
590
|
-
// some types (ex: LWC) show up as multiple files in the remote changes, but only one in the component set
|
|
591
|
-
// iterate the elements to see which ones didn't make it into the component set
|
|
592
|
-
const missingComponents = elements.filter((element) => !remoteChangesAsComponentSet.has({ type: element === null || element === void 0 ? void 0 : element.type, fullName: element === null || element === void 0 ? void 0 : element.name }));
|
|
593
|
-
// Throw if anything was actually missing
|
|
594
|
-
if (missingComponents.length > 0) {
|
|
595
|
-
throw new Error(`unable to generate complete component set for ${elements
|
|
596
|
-
.map((element) => `${element.name} (${element.type})`)
|
|
597
|
-
.join(os_1.EOL)}`);
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
const matchingLocalSourceComponentsSet = source_deploy_retrieve_1.ComponentSet.fromSource({
|
|
601
|
-
fsPaths: this.packagesDirs.map((dir) => dir.path),
|
|
602
|
-
include: remoteChangesAsComponentSet,
|
|
603
|
-
});
|
|
604
|
-
this.logger.debug(` local source-backed component set has ${matchingLocalSourceComponentsSet.size.toString()} items from remote`);
|
|
605
|
-
// make it simpler to find things later
|
|
606
|
-
const elementMap = new Map();
|
|
607
|
-
elements.map((element) => {
|
|
608
|
-
elementMap.set((0, functions_1.getKeyFromObject)(element), element);
|
|
609
|
-
});
|
|
610
|
-
// iterates the local components and sets their filenames
|
|
611
|
-
for (const matchingComponent of matchingLocalSourceComponentsSet.getSourceComponents().toArray()) {
|
|
612
|
-
if (matchingComponent.fullName && matchingComponent.type.name) {
|
|
613
|
-
this.logger.debug(`${matchingComponent.fullName}|${matchingComponent.type.name} matches ${matchingComponent.xml} and maybe ${matchingComponent.walkContent().toString()}`);
|
|
614
|
-
const key = (0, functions_1.getMetadataKey)(matchingComponent.type.name, matchingComponent.fullName);
|
|
615
|
-
elementMap.set(key, {
|
|
616
|
-
...elementMap.get(key),
|
|
617
|
-
modified: true,
|
|
618
|
-
origin: 'remote',
|
|
619
|
-
filenames: [matchingComponent.xml, ...matchingComponent.walkContent()].filter((filename) => filename),
|
|
620
|
-
});
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
return Array.from(elementMap.values());
|
|
624
|
-
}
|
|
625
|
-
ensureRelative(filePath) {
|
|
626
|
-
return (0, path_1.isAbsolute)(filePath) ? (0, path_1.relative)(this.projectPath, filePath) : filePath;
|
|
627
|
-
}
|
|
628
515
|
async getLocalChangesAsFilenames(state) {
|
|
629
516
|
if (state === 'modify') {
|
|
630
517
|
return this.localRepo.getModifyFilenames();
|
|
@@ -641,39 +528,34 @@ class SourceTracking extends kit_1.AsyncCreatable {
|
|
|
641
528
|
throw new Error(`unable to get local changes for state ${state}`);
|
|
642
529
|
}
|
|
643
530
|
localChangesToOutputRow(input, localType) {
|
|
644
|
-
var _a, _b, _c;
|
|
645
531
|
this.logger.debug('converting ChangeResult to a row', input);
|
|
646
|
-
|
|
647
|
-
type: (_a = input.type) !== null && _a !== void 0 ? _a : '',
|
|
648
|
-
origin: 'local',
|
|
649
|
-
state: localType,
|
|
650
|
-
fullName: (_b = input.name) !== null && _b !== void 0 ? _b : '',
|
|
651
|
-
// ignored property will be set in populateTypesAndNames
|
|
652
|
-
ignored: (_c = input.ignored) !== null && _c !== void 0 ? _c : false,
|
|
653
|
-
};
|
|
532
|
+
this.forceIgnore ?? (this.forceIgnore = source_deploy_retrieve_1.ForceIgnore.findAndCreate(this.project.getDefaultPackage().path));
|
|
654
533
|
if (input.filenames) {
|
|
655
534
|
return input.filenames.map((filename) => ({
|
|
656
|
-
|
|
535
|
+
type: input.type ?? '',
|
|
536
|
+
state: localType,
|
|
537
|
+
fullName: input.name ?? '',
|
|
657
538
|
filePath: filename,
|
|
658
539
|
origin: 'local',
|
|
540
|
+
ignored: this.forceIgnore.denies(filename),
|
|
659
541
|
}));
|
|
660
542
|
}
|
|
661
543
|
throw new Error('no filenames found for local ChangeResult');
|
|
662
544
|
}
|
|
663
|
-
//
|
|
545
|
+
// reserve the right to do something more sophisticated in the future
|
|
546
|
+
// via async for figuring out hypothetical filenames (ex: getting default packageDir)
|
|
664
547
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
665
548
|
async remoteChangesToOutputRows(input) {
|
|
666
|
-
var _a, _b, _c, _d;
|
|
667
549
|
this.logger.debug('converting ChangeResult to a row', input);
|
|
668
|
-
|
|
550
|
+
this.forceIgnore ?? (this.forceIgnore = source_deploy_retrieve_1.ForceIgnore.findAndCreate(this.project.getDefaultPackage().path));
|
|
669
551
|
const baseObject = {
|
|
670
|
-
type:
|
|
552
|
+
type: input.type ?? '',
|
|
671
553
|
origin: input.origin,
|
|
672
554
|
state: stateFromChangeResult(input),
|
|
673
|
-
fullName:
|
|
555
|
+
fullName: input.name ?? '',
|
|
674
556
|
};
|
|
675
557
|
// it's easy to check ignores if the filePaths exist locally
|
|
676
|
-
if (
|
|
558
|
+
if (input.filenames?.length) {
|
|
677
559
|
return input.filenames.map((filename) => ({
|
|
678
560
|
...baseObject,
|
|
679
561
|
filePath: filename,
|
|
@@ -681,7 +563,18 @@ class SourceTracking extends kit_1.AsyncCreatable {
|
|
|
681
563
|
}));
|
|
682
564
|
}
|
|
683
565
|
// when the file doesn't exist locally, there are no filePaths
|
|
684
|
-
//
|
|
566
|
+
// SDR can generate the hypothetical place it *would* go and check that
|
|
567
|
+
if (input.name && input.type) {
|
|
568
|
+
return [
|
|
569
|
+
{
|
|
570
|
+
...baseObject,
|
|
571
|
+
ignored: (0, filePathGenerator_1.filePathsFromMetadataComponent)({
|
|
572
|
+
fullName: input.name,
|
|
573
|
+
type: new source_deploy_retrieve_1.RegistryAccess().getTypeByName(input.type),
|
|
574
|
+
}).some((hypotheticalFilePath) => this.forceIgnore.denies(hypotheticalFilePath)),
|
|
575
|
+
},
|
|
576
|
+
];
|
|
577
|
+
}
|
|
685
578
|
return [baseObject];
|
|
686
579
|
}
|
|
687
580
|
}
|