@salesforce/source-tracking 2.1.0 → 2.2.0

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.
@@ -9,17 +9,20 @@ 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");
17
16
  const remoteSourceTrackingService_1 = require("./shared/remoteSourceTrackingService");
18
17
  const localShadowRepo_1 = require("./shared/localShadowRepo");
18
+ const conflicts_1 = require("./shared/conflicts");
19
19
  const guards_1 = require("./shared/guards");
20
20
  const functions_1 = require("./shared/functions");
21
21
  const metadataKeys_1 = require("./shared/metadataKeys");
22
22
  const compatibility_1 = require("./compatibility");
23
+ const populateFilePaths_1 = require("./shared/populateFilePaths");
24
+ const populateTypesAndNames_1 = require("./shared/populateTypesAndNames");
25
+ const localComponentSetArray_1 = require("./shared/localComponentSetArray");
23
26
  /**
24
27
  * Manages source tracking files (remote and local)
25
28
  *
@@ -28,95 +31,48 @@ const compatibility_1 = require("./compatibility");
28
31
  */
29
32
  class SourceTracking extends kit_1.AsyncCreatable {
30
33
  constructor(options) {
34
+ var _a, _b, _c;
31
35
  super(options);
32
- this.registry = new source_deploy_retrieve_1.RegistryAccess();
33
36
  this.org = options.org;
37
+ this.orgId = this.org.getOrgId();
34
38
  this.projectPath = options.project.getPath();
35
39
  this.packagesDirs = options.project.getPackageDirectories();
36
40
  this.logger = core_1.Logger.childFromRoot('SourceTracking');
37
41
  this.project = options.project;
38
- this.hasSfdxTrackingFiles = (0, compatibility_1.hasSfdxTrackingFiles)(this.org.getOrgId(), this.projectPath);
42
+ this.ignoreConflicts = (_a = options.ignoreConflicts) !== null && _a !== void 0 ? _a : false;
43
+ this.ignoreLocalCache = (_b = options.ignoreLocalCache) !== null && _b !== void 0 ? _b : false;
44
+ this.subscribeSDREvents = (_c = options.subscribeSDREvents) !== null && _c !== void 0 ? _c : false;
45
+ this.hasSfdxTrackingFiles = (0, compatibility_1.hasSfdxTrackingFiles)(this.orgId, this.projectPath);
39
46
  }
40
47
  // eslint-disable-next-line class-methods-use-this
41
48
  async init() {
42
- // reserved for future use
49
+ await this.maybeSubscribeLifecycleEvents();
43
50
  }
44
51
  /**
45
52
  *
46
- * @param byPackageDir if true, returns one ComponentSet for each packageDir with changes
53
+ * @param byPackageDir if true, returns a ComponentSet for each packageDir that has any changes
54
+ * * if false, returns an array containing one ComponentSet with all changes
55
+ * * if not specified, this method will follow what sfdx-project.json says
47
56
  * @returns ComponentSet[]
48
57
  */
49
- async localChangesAsComponentSet(byPackageDir = false) {
50
- var _a;
51
- const [projectConfig] = await Promise.all([this.project.resolveProjectConfig(), this.ensureLocalTracking()]);
52
- (_a = this.forceIgnore) !== null && _a !== void 0 ? _a : (this.forceIgnore = source_deploy_retrieve_1.ForceIgnore.findAndCreate(this.project.getDefaultPackage().name));
53
- const sourceApiVersion = (0, ts_types_1.getString)(projectConfig, 'sourceApiVersion');
54
- // optimistic resolution...some files may not be possible to resolve
55
- const resolverForNonDeletes = new source_deploy_retrieve_1.MetadataResolver();
56
- // we need virtual components for the deletes.
57
- // TODO: could we use the same for the non-deletes?
58
- const [allNonDeletes, allDeletes] = (await Promise.all([this.localRepo.getNonDeleteFilenames(), this.localRepo.getDeleteFilenames()]))
59
- // remove the forceIgnored items early
60
- .map((group) => group.filter((item) => this.forceIgnore.accepts(item)));
58
+ async localChangesAsComponentSet(byPackageDir) {
59
+ const [projectConfig] = await Promise.all([
60
+ this.project.resolveProjectConfig(),
61
+ this.ensureLocalTracking(),
62
+ ]);
63
+ const sourceApiVersion = projectConfig.sourceApiVersion;
64
+ const [nonDeletes, deletes] = await Promise.all([
65
+ this.localRepo.getNonDeleteFilenames(),
66
+ this.localRepo.getDeleteFilenames(),
67
+ ]);
61
68
  // it'll be easier to filter filenames and work with smaller component sets than to filter SourceComponents
62
- const groupings = (byPackageDir
63
- ? this.packagesDirs.map((pkgDir) => ({
64
- path: pkgDir.name,
65
- nonDeletes: allNonDeletes.filter((f) => (0, functions_1.pathIsInFolder)(f, pkgDir.name)),
66
- deletes: allDeletes.filter((f) => (0, functions_1.pathIsInFolder)(f, pkgDir.name)),
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);
69
+ const groupings = (0, localComponentSetArray_1.getGroupedFiles)({
70
+ packageDirs: this.packagesDirs,
71
+ nonDeletes,
72
+ deletes,
73
+ }, byPackageDir !== null && byPackageDir !== void 0 ? byPackageDir : Boolean(projectConfig.pushPackageDirectoriesSequentially)); // if the users specified true or false for the param, that overrides the project config
75
74
  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);
75
+ return (0, localComponentSetArray_1.getComponentSets)(groupings, sourceApiVersion);
120
76
  }
121
77
  async remoteNonDeletesAsComponentSet() {
122
78
  const [changeResults, sourceBackedComponents] = await Promise.all([
@@ -176,12 +132,6 @@ class SourceTracking extends kit_1.AsyncCreatable {
176
132
  }
177
133
  return results;
178
134
  }
179
- /**
180
- * Get metadata changes made locally and in the org.
181
- *
182
- * @returns local and remote changed metadata
183
- *
184
- */
185
135
  async getChanges(options) {
186
136
  if ((options === null || options === void 0 ? void 0 : options.origin) === 'local') {
187
137
  await this.ensureLocalTracking();
@@ -218,13 +168,13 @@ class SourceTracking extends kit_1.AsyncCreatable {
218
168
  this.logger.debug('remoteChanges', remoteChanges);
219
169
  const filteredChanges = remoteChanges
220
170
  .filter(remoteFilterByState[options.state])
221
- // skip any remote types not in the registry. Will emit node warnings
222
- .filter((rce) => this.registrySupportsType(rce.type));
171
+ // skip any remote types not in the registry. Will emit warnings
172
+ .filter((rce) => (0, metadataKeys_1.registrySupportsType)(rce.type));
223
173
  if (options.format === 'ChangeResult') {
224
174
  return filteredChanges.map((change) => (0, remoteSourceTrackingService_1.remoteChangeElementToChangeResult)(change));
225
175
  }
226
176
  if (options.format === 'ChangeResultWithPaths') {
227
- return this.populateFilePaths(filteredChanges.map((change) => (0, remoteSourceTrackingService_1.remoteChangeElementToChangeResult)(change)));
177
+ return (0, populateFilePaths_1.populateFilePaths)(filteredChanges.map((change) => (0, remoteSourceTrackingService_1.remoteChangeElementToChangeResult)(change)), this.project.getPackageDirectories().map((pkgDir) => pkgDir.path));
228
178
  }
229
179
  // turn it into a componentSet to resolve filenames
230
180
  const remoteChangesAsComponentSet = new source_deploy_retrieve_1.ComponentSet(filteredChanges.map((element) => ({
@@ -247,6 +197,19 @@ class SourceTracking extends kit_1.AsyncCreatable {
247
197
  }
248
198
  throw new Error(`unsupported options: ${JSON.stringify(options)}`);
249
199
  }
200
+ /**
201
+ *
202
+ * Convenience method to reduce duplicated steps required to do a fka pull
203
+ * It's full of side effects: retrieving remote deletes, deleting those files locall, and then updating tracking files
204
+ * Most bizarrely, it then returns a ComponentSet of the remote nonDeletes.
205
+ *
206
+ * @returns the ComponentSet for what you would retrieve now that the deletes are done
207
+ */
208
+ async maybeApplyRemoteDeletesToLocal() {
209
+ const changesToDelete = await this.getChanges({ origin: 'remote', state: 'delete', format: 'SourceComponent' });
210
+ await this.deleteFilesAndUpdateTracking(changesToDelete);
211
+ return this.remoteNonDeletesAsComponentSet();
212
+ }
250
213
  /**
251
214
  *
252
215
  * returns immediately if there are no changesToDelete
@@ -294,8 +257,8 @@ class SourceTracking extends kit_1.AsyncCreatable {
294
257
  await this.ensureLocalTracking();
295
258
  // relative paths make smaller trees AND isogit wants them relative
296
259
  const relativeOptions = {
297
- files: ((_a = options.files) !== null && _a !== void 0 ? _a : []).map((file) => this.ensureRelative(file)),
298
- deletedFiles: ((_b = options.deletedFiles) !== null && _b !== void 0 ? _b : []).map((file) => this.ensureRelative(file)),
260
+ files: ((_a = options.files) !== null && _a !== void 0 ? _a : []).map((file) => (0, functions_1.ensureRelative)(file, this.projectPath)),
261
+ deletedFiles: ((_b = options.deletedFiles) !== null && _b !== void 0 ? _b : []).map((file) => (0, functions_1.ensureRelative)(file, this.projectPath)),
299
262
  };
300
263
  // 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
264
  // what got deleted? Any local changes NOT in the fileResponses but part of a successfully deployed bundle
@@ -329,17 +292,23 @@ class SourceTracking extends kit_1.AsyncCreatable {
329
292
  }
330
293
  await this.remoteSourceTrackingService.syncSpecifiedElements(fileResponses);
331
294
  }
295
+ async reReadLocalTrackingCache() {
296
+ await this.localRepo.getStatus(true);
297
+ }
332
298
  /**
333
299
  * If the local tracking shadowRepo doesn't exist, it will be created.
334
- * Does nothing if it already exists.
300
+ * 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
301
  * Useful before parallel operations
336
302
  */
337
303
  async ensureLocalTracking() {
338
304
  if (this.localRepo) {
305
+ if (this.ignoreLocalCache) {
306
+ await this.localRepo.getStatus(true);
307
+ }
339
308
  return;
340
309
  }
341
310
  this.localRepo = await localShadowRepo_1.ShadowRepo.getInstance({
342
- orgId: this.org.getOrgId(),
311
+ orgId: this.orgId,
343
312
  projectPath: (0, path_1.normalize)(this.projectPath),
344
313
  packageDirs: this.packagesDirs,
345
314
  hasSfdxTrackingFiles: this.hasSfdxTrackingFiles,
@@ -395,7 +364,7 @@ class SourceTracking extends kit_1.AsyncCreatable {
395
364
  * Deletes the remote tracking files
396
365
  */
397
366
  async clearRemoteTracking() {
398
- return remoteSourceTrackingService_1.RemoteSourceTrackingService.delete(this.org.getOrgId(), this.hasSfdxTrackingFiles);
367
+ return remoteSourceTrackingService_1.RemoteSourceTrackingService.delete(this.orgId, this.hasSfdxTrackingFiles);
399
368
  }
400
369
  /**
401
370
  * Sets the files to max revision so that no changes appear
@@ -409,6 +378,7 @@ class SourceTracking extends kit_1.AsyncCreatable {
409
378
  * Compares local and remote changes to detect conflicts
410
379
  */
411
380
  async getConflicts() {
381
+ var _a;
412
382
  // we're going to need have both initialized
413
383
  await Promise.all([this.ensureRemoteTracking(), this.ensureLocalTracking()]);
414
384
  // Strategy: check local changes first (since it'll be faster) to avoid callout
@@ -430,201 +400,128 @@ class SourceTracking extends kit_1.AsyncCreatable {
430
400
  if (remoteChanges.length === 0) {
431
401
  return [];
432
402
  }
433
- // index the remoteChanges by filename
434
- const fileNameIndex = new Map();
435
- const metadataKeyIndex = new Map();
436
- remoteChanges.map((change) => {
437
- var _a;
438
- if (change.name && change.type) {
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
- }
403
+ (_a = this.forceIgnore) !== null && _a !== void 0 ? _a : (this.forceIgnore = source_deploy_retrieve_1.ForceIgnore.findAndCreate(this.project.getDefaultPackage().path));
404
+ return (0, conflicts_1.getDedupedConflictsFromChanges)({
405
+ localChanges,
406
+ remoteChanges,
407
+ projectPath: this.projectPath,
408
+ forceIgnore: this.forceIgnore,
461
409
  });
462
- // deeply de-dupe
463
- return Array.from(conflicts);
464
410
  }
465
411
  /**
466
- * uses SDR to translate remote metadata records into local file paths (which only typically have the filename).
412
+ * handles both remote and local tracking
467
413
  *
468
- * @input elements: ChangeResult[]
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
414
+ * @param result FileResponse[]
472
415
  */
473
- populateTypesAndNames({ elements, excludeUnresolvable = false, resolveDeleted = false, useFsForceIgnore = true, }) {
474
- if (elements.length === 0) {
475
- return [];
416
+ async updateTrackingFromDeploy(deployResult) {
417
+ const successes = deployResult
418
+ .getFileResponses()
419
+ .filter((fileResponse) => fileResponse.state !== source_deploy_retrieve_1.ComponentStatus.Failed && fileResponse.filePath);
420
+ if (!successes.length) {
421
+ return;
476
422
  }
477
- this.logger.debug(`populateTypesAndNames for ${elements.length} change elements`);
478
- const filenames = elements.flatMap((element) => element.filenames).filter(ts_types_1.isString);
479
- // component set generated from the filenames on all local changes
480
- const resolver = new source_deploy_retrieve_1.MetadataResolver(undefined, resolveDeleted ? source_deploy_retrieve_1.VirtualTreeContainer.fromFilePaths(filenames) : undefined, useFsForceIgnore);
481
- const sourceComponents = filenames
482
- .flatMap((filename) => {
483
- try {
484
- return resolver.getComponentsFromPath(filename);
485
- }
486
- catch (e) {
487
- this.logger.warn(`unable to resolve ${filename}`);
488
- return undefined;
489
- }
490
- })
491
- .filter(guards_1.sourceComponentGuard);
492
- this.logger.debug(` matching SourceComponents have ${sourceComponents.length} items from local`);
493
- // make it simpler to find things later
494
- const elementMap = new Map();
495
- elements.map((element) => {
496
- var _a;
497
- (_a = element.filenames) === null || _a === void 0 ? void 0 : _a.map((filename) => {
498
- elementMap.set(this.ensureRelative(filename), element);
499
- });
500
- });
501
- // iterates the local components and sets their filenames
502
- sourceComponents.map((matchingComponent) => {
503
- var _a;
504
- if ((matchingComponent === null || matchingComponent === void 0 ? void 0 : matchingComponent.fullName) && (matchingComponent === null || matchingComponent === void 0 ? void 0 : matchingComponent.type.name)) {
505
- const filenamesFromMatchingComponent = [matchingComponent.xml, ...matchingComponent.walkContent()];
506
- // Set the ignored status at the component level so it can apply to all its files, some of which may not match the ignoreFile (ex: ApexClass)
507
- (_a = this.forceIgnore) !== null && _a !== void 0 ? _a : (this.forceIgnore = source_deploy_retrieve_1.ForceIgnore.findAndCreate(this.project.getDefaultPackage().path));
508
- const ignored = filenamesFromMatchingComponent
509
- .filter(ts_types_1.isString)
510
- .filter((filename) => !filename.includes('__tests__'))
511
- .some((filename) => this.forceIgnore.denies(filename));
512
- filenamesFromMatchingComponent.map((filename) => {
513
- if (filename && elementMap.has(filename)) {
514
- // add the type/name from the componentSet onto the element
515
- elementMap.set(filename, {
516
- origin: 'remote',
517
- ...elementMap.get(filename),
518
- type: matchingComponent.type.name,
519
- name: matchingComponent.fullName,
520
- ignored,
521
- });
423
+ await Promise.all([
424
+ this.updateLocalTracking({
425
+ // assertions allowed because filtered above
426
+ files: successes
427
+ .filter((fileResponse) => fileResponse.state !== source_deploy_retrieve_1.ComponentStatus.Deleted)
428
+ .map((fileResponse) => fileResponse.filePath),
429
+ deletedFiles: successes
430
+ .filter((fileResponse) => fileResponse.state === source_deploy_retrieve_1.ComponentStatus.Deleted)
431
+ .map((fileResponse) => fileResponse.filePath),
432
+ }),
433
+ this.updateRemoteTracking(successes.map(({ state, fullName, type, filePath }) => ({ state, fullName, type, filePath }))),
434
+ ]);
435
+ }
436
+ /**
437
+ * handles both remote and local tracking
438
+ *
439
+ * @param result FileResponse[]
440
+ */
441
+ async updateTrackingFromRetrieve(retrieveResult) {
442
+ const successes = retrieveResult
443
+ .getFileResponses()
444
+ .filter((fileResponse) => fileResponse.state !== source_deploy_retrieve_1.ComponentStatus.Failed);
445
+ if (!successes.length) {
446
+ return;
447
+ }
448
+ await Promise.all([
449
+ this.updateLocalTracking({
450
+ // assertion allowed because it's filtering out undefined
451
+ files: successes.map((fileResponse) => fileResponse.filePath).filter(Boolean),
452
+ }),
453
+ this.updateRemoteTracking(successes.map(({ state, fullName, type, filePath }) => ({ state, fullName, type, filePath })), true // retrieves don't need to poll for SourceMembers
454
+ ),
455
+ ]);
456
+ }
457
+ /**
458
+ * If you've already got an instance of STL, but need to change the conflicts setting
459
+ * normally you set this on instantiation
460
+ *
461
+ * @param value true/false
462
+ */
463
+ setIgnoreConflicts(value) {
464
+ this.ignoreConflicts = value;
465
+ }
466
+ async maybeSubscribeLifecycleEvents() {
467
+ if (this.subscribeSDREvents && (await this.org.tracksSource())) {
468
+ const lifecycle = core_1.Lifecycle.getInstance();
469
+ // the only thing STL uses pre events for is to check conflicts. So if you don't care about conflicts, don't listen!
470
+ if (!this.ignoreConflicts) {
471
+ this.logger.debug('subscribing to predeploy/retrieve events');
472
+ // subscribe to SDR `pre` events to handle conflicts before deploy/retrieve
473
+ lifecycle.on('scopedPreDeploy', async (e) => {
474
+ this.logger.debug('received scopedPreDeploy event');
475
+ if (e.orgId === this.orgId) {
476
+ (0, conflicts_1.throwIfConflicts)((0, conflicts_1.findConflictsInComponentSet)(e.componentSet, await this.getConflicts()));
477
+ }
478
+ });
479
+ lifecycle.on('scopedPreRetrieve', async (e) => {
480
+ this.logger.debug('received scopedPreRetrieve event');
481
+ if (e.orgId === this.orgId) {
482
+ (0, conflicts_1.throwIfConflicts)((0, conflicts_1.findConflictsInComponentSet)(e.componentSet, await this.getConflicts()));
522
483
  }
523
484
  });
524
485
  }
525
- });
526
- return excludeUnresolvable
527
- ? Array.from(new Set(elementMap.values())).filter((changeResult) => changeResult.name && changeResult.type)
528
- : Array.from(new Set(elementMap.values()));
486
+ // subscribe to SDR post-deploy event
487
+ this.logger.debug('subscribing to postdeploy/retrieve events');
488
+ // yes, the post hooks really have different payloads!
489
+ lifecycle.on('scopedPostDeploy', async (e) => {
490
+ this.logger.debug('received scopedPostDeploy event');
491
+ if (e.orgId === this.orgId) {
492
+ await this.updateTrackingFromDeploy(e.deployResult);
493
+ }
494
+ });
495
+ lifecycle.on('scopedPostRetrieve', async (e) => {
496
+ this.logger.debug('received scopedPostRetrieve event');
497
+ if (e.orgId === this.orgId) {
498
+ await this.updateTrackingFromRetrieve(e.retrieveResult);
499
+ }
500
+ });
501
+ }
529
502
  }
530
503
  async getLocalStatusRows() {
531
504
  await this.ensureLocalTracking();
532
505
  let results = [];
533
- const localDeletes = this.populateTypesAndNames({
506
+ const localDeletes = (0, populateTypesAndNames_1.populateTypesAndNames)({
534
507
  elements: await this.getChanges({ origin: 'local', state: 'delete', format: 'ChangeResult' }),
535
508
  excludeUnresolvable: true,
536
509
  resolveDeleted: true,
537
- useFsForceIgnore: false,
510
+ projectPath: this.projectPath,
538
511
  });
539
- const localAdds = this.populateTypesAndNames({
512
+ const localAdds = (0, populateTypesAndNames_1.populateTypesAndNames)({
540
513
  elements: await this.getChanges({ origin: 'local', state: 'add', format: 'ChangeResult' }),
541
514
  excludeUnresolvable: true,
542
- useFsForceIgnore: false,
515
+ projectPath: this.projectPath,
543
516
  });
544
- const localModifies = this.populateTypesAndNames({
517
+ const localModifies = (0, populateTypesAndNames_1.populateTypesAndNames)({
545
518
  elements: await this.getChanges({ origin: 'local', state: 'modify', format: 'ChangeResult' }),
546
519
  excludeUnresolvable: true,
547
- useFsForceIgnore: false,
520
+ projectPath: this.projectPath,
548
521
  });
549
522
  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
523
  return results;
551
524
  }
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
525
  async getLocalChangesAsFilenames(state) {
629
526
  if (state === 'modify') {
630
527
  return this.localRepo.getModifyFilenames();
@@ -641,22 +538,21 @@ class SourceTracking extends kit_1.AsyncCreatable {
641
538
  throw new Error(`unable to get local changes for state ${state}`);
642
539
  }
643
540
  localChangesToOutputRow(input, localType) {
644
- var _a, _b, _c;
541
+ var _a;
645
542
  this.logger.debug('converting ChangeResult to a row', input);
646
- const baseObject = {
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
- };
543
+ (_a = this.forceIgnore) !== null && _a !== void 0 ? _a : (this.forceIgnore = source_deploy_retrieve_1.ForceIgnore.findAndCreate(this.project.getDefaultPackage().path));
654
544
  if (input.filenames) {
655
- return input.filenames.map((filename) => ({
656
- ...baseObject,
657
- filePath: filename,
658
- origin: 'local',
659
- }));
545
+ return input.filenames.map((filename) => {
546
+ var _a, _b;
547
+ return ({
548
+ type: (_a = input.type) !== null && _a !== void 0 ? _a : '',
549
+ state: localType,
550
+ fullName: (_b = input.name) !== null && _b !== void 0 ? _b : '',
551
+ filePath: filename,
552
+ origin: 'local',
553
+ ignored: this.forceIgnore.denies(filename),
554
+ });
555
+ });
660
556
  }
661
557
  throw new Error('no filenames found for local ChangeResult');
662
558
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@salesforce/source-tracking",
3
3
  "description": "API for tracking local and remote Salesforce metadata changes",
4
- "version": "2.1.0",
4
+ "version": "2.2.0",
5
5
  "author": "Salesforce",
6
6
  "license": "BSD-3-Clause",
7
7
  "main": "lib/index.js",
@@ -43,9 +43,9 @@
43
43
  "/oclif.manifest.json"
44
44
  ],
45
45
  "dependencies": {
46
- "@salesforce/core": "^3.21.2",
47
- "@salesforce/kit": "^1.5.17",
48
- "@salesforce/source-deploy-retrieve": "^6.0.4",
46
+ "@salesforce/core": "^3.22.1",
47
+ "@salesforce/kit": "^1.5.42",
48
+ "@salesforce/source-deploy-retrieve": "^6.1.0",
49
49
  "graceful-fs": "^4.2.9",
50
50
  "isomorphic-git": "1.17.0",
51
51
  "ts-retry-promise": "^0.6.0"
@@ -63,9 +63,9 @@
63
63
  "cz-conventional-changelog": "^3.3.0",
64
64
  "eslint": "^7.32.0",
65
65
  "eslint-config-prettier": "^8.3.0",
66
- "eslint-config-salesforce": "^0.1.6",
66
+ "eslint-config-salesforce": "^1.0.1",
67
67
  "eslint-config-salesforce-license": "^0.1.6",
68
- "eslint-config-salesforce-typescript": "^0.2.7",
68
+ "eslint-config-salesforce-typescript": "^1.0.0",
69
69
  "eslint-plugin-header": "^3.1.1",
70
70
  "eslint-plugin-import": "2.25.4",
71
71
  "eslint-plugin-jsdoc": "^37.0.1",
@@ -80,7 +80,7 @@
80
80
  "sinon": "^10.0.0",
81
81
  "ts-node": "^10.1.0",
82
82
  "ts-prune": "^0.10.0",
83
- "typescript": "^4.4.4"
83
+ "typescript": "^4.7.4"
84
84
  },
85
85
  "config": {
86
86
  "commitizen": {