@salesforce/source-tracking 0.3.0 → 0.4.3

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.
@@ -1,2 +1,48 @@
1
- import { FileResponse } from '@salesforce/source-deploy-retrieve';
1
+ import { FileResponse, SourceComponent } from '@salesforce/source-deploy-retrieve';
2
+ export interface ChangeOptions {
3
+ origin: 'local' | 'remote';
4
+ state: 'add' | 'delete' | 'modify' | 'nondelete';
5
+ format: 'ChangeResult' | 'SourceComponent' | 'string' | 'ChangeResultWithPaths';
6
+ }
2
7
  export declare type RemoteSyncInput = Pick<FileResponse, 'fullName' | 'filePath' | 'type' | 'state'>;
8
+ export declare type StatusOutputRow = Pick<FileResponse, 'fullName' | 'filePath' | 'type'> & {
9
+ conflict?: boolean;
10
+ ignored?: boolean;
11
+ } & Pick<ChangeOptions, 'origin' | 'state'>;
12
+ export interface LocalUpdateOptions {
13
+ files?: string[];
14
+ deletedFiles?: string[];
15
+ }
16
+ export declare type RemoteChangeElement = {
17
+ name: string;
18
+ type: string;
19
+ deleted?: boolean;
20
+ modified?: boolean;
21
+ };
22
+ /**
23
+ * Summary type that supports both local and remote change types
24
+ */
25
+ export declare type ChangeResult = Partial<RemoteChangeElement> & {
26
+ origin: 'local' | 'remote';
27
+ filenames?: string[];
28
+ ignored?: boolean;
29
+ };
30
+ export declare type MemberRevision = {
31
+ serverRevisionCounter: number;
32
+ lastRetrievedFromServer: number | null;
33
+ memberType: string;
34
+ isNameObsolete: boolean;
35
+ };
36
+ export declare type SourceMember = {
37
+ MemberType: string;
38
+ MemberName: string;
39
+ IsNameObsolete: boolean;
40
+ RevisionCounter: number;
41
+ ignored?: boolean;
42
+ };
43
+ export interface ConflictError {
44
+ message: string;
45
+ name: 'conflict';
46
+ conflicts: ChangeResult[];
47
+ }
48
+ export declare type ChangeOptionType = ChangeResult | SourceComponent | string;
@@ -1,36 +1,11 @@
1
1
  import { Org, SfdxProject } from '@salesforce/core';
2
2
  import { AsyncCreatable } from '@salesforce/kit';
3
3
  import { ComponentSet, SourceComponent, FileResponse } from '@salesforce/source-deploy-retrieve';
4
- import { RemoteChangeElement } from './shared/remoteSourceTrackingService';
5
- import { RemoteSyncInput } from './shared/types';
6
- export declare const getKeyFromObject: (element: RemoteChangeElement | ChangeResult) => string;
7
- export declare const getKeyFromStrings: (metadataType: string, metadataName: string) => string;
8
- export declare type ChangeOptionType = ChangeResult | SourceComponent | string;
9
- export interface ChangeOptions {
10
- origin: 'local' | 'remote';
11
- state: 'add' | 'delete' | 'modify' | 'nondelete';
12
- format: 'ChangeResult' | 'SourceComponent' | 'string' | 'ChangeResultWithPaths';
13
- }
14
- export interface LocalUpdateOptions {
15
- files?: string[];
16
- deletedFiles?: string[];
17
- }
18
- /**
19
- * Summary type that supports both local and remote change types
20
- */
21
- export declare type ChangeResult = Partial<RemoteChangeElement> & {
22
- origin: 'local' | 'remote';
23
- filenames?: string[];
24
- };
25
- export interface ConflictError {
26
- message: string;
27
- name: 'conflict';
28
- conflicts: ChangeResult[];
29
- }
4
+ import { RemoteSyncInput, StatusOutputRow, ChangeOptions, ChangeResult, ChangeOptionType, LocalUpdateOptions } from './shared/types';
30
5
  export interface SourceTrackingOptions {
31
6
  org: Org;
32
7
  project: SfdxProject;
33
- /** defaults to sfdxProject sourceApiVersion unless provided */
8
+ /** @deprecated not used defaults to sfdxProject sourceApiVersion unless provided */
34
9
  apiVersion?: string;
35
10
  }
36
11
  /**
@@ -41,19 +16,35 @@ export interface SourceTrackingOptions {
41
16
  */
42
17
  export declare class SourceTracking extends AsyncCreatable {
43
18
  private orgId;
19
+ private project;
44
20
  private projectPath;
45
21
  private packagesDirs;
46
22
  private username;
47
23
  private logger;
24
+ private registry;
48
25
  private localRepo;
49
26
  private remoteSourceTrackingService;
27
+ private forceIgnore;
50
28
  constructor(options: SourceTrackingOptions);
51
29
  init(): Promise<void>;
52
30
  localChangesAsComponentSet(): Promise<ComponentSet>;
31
+ /**
32
+ * Does most of the work for the force:source:status command.
33
+ * Outputs need a bit of massage since this aims to provide nice json.
34
+ *
35
+ * @param local you want local status
36
+ * @param remote you want remote status
37
+ * @returns StatusOutputRow[]
38
+ */
39
+ getStatus({ local, remote }: {
40
+ local: boolean;
41
+ remote: boolean;
42
+ }): Promise<StatusOutputRow[]>;
53
43
  /**
54
44
  * Get metadata changes made locally and in the org.
55
45
  *
56
46
  * @returns local and remote changed metadata
47
+ *
57
48
  */
58
49
  getChanges<T extends ChangeOptionType>(options?: ChangeOptions): Promise<T[]>;
59
50
  /**
@@ -103,23 +94,27 @@ export declare class SourceTracking extends AsyncCreatable {
103
94
  * Sets the files to max revision so that no changes appear
104
95
  */
105
96
  resetRemoteTracking(serverRevision?: number): Promise<number>;
97
+ /**
98
+ * Compares local and remote changes to detect conflicts
99
+ */
100
+ getConflicts(): Promise<ChangeResult[]>;
106
101
  /**
107
102
  * uses SDR to translate remote metadata records into local file paths (which only typically have the filename).
108
103
  *
109
104
  * @input elements: ChangeResult[]
110
105
  * @input excludeUnresolvables: boolean Filter out components where you can't get the name and type (that is, it's probably not a valid source component)
106
+ * @input resolveDeleted: constructs a virtualTree instead of the actual filesystem--useful when the files no longer exist
107
+ * @input useFsForceIgnore: (default behavior) use forceIgnore from the filesystem. If false, uses the base forceIgnore from SDR
111
108
  */
112
- populateTypesAndNames({ elements, excludeUnresolvable, resolveDeleted, }: {
113
- elements: ChangeResult[];
114
- excludeUnresolvable?: boolean;
115
- resolveDeleted?: boolean;
116
- }): ChangeResult[];
117
- getConflicts(): Promise<ChangeResult[]>;
109
+ private populateTypesAndNames;
110
+ private getLocalStatusRows;
111
+ private registrySupportsType;
118
112
  /**
119
113
  * uses SDR to translate remote metadata records into local file paths
120
114
  */
121
115
  private populateFilePaths;
122
116
  private ensureRelative;
123
117
  private getLocalChangesAsFilenames;
118
+ private localChangesToOutputRow;
119
+ private remoteChangesToOutputRows;
124
120
  }
125
- export declare const stringGuard: (input: string | undefined) => input is string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.stringGuard = exports.SourceTracking = exports.getKeyFromStrings = exports.getKeyFromObject = void 0;
3
+ exports.SourceTracking = void 0;
4
4
  /*
5
5
  * Copyright (c) 2021, salesforce.com, inc.
6
6
  * All rights reserved.
@@ -9,21 +9,16 @@ exports.stringGuard = exports.SourceTracking = exports.getKeyFromStrings = expor
9
9
  */
10
10
  const fs = require("fs");
11
11
  const path = require("path");
12
+ const os_1 = require("os");
12
13
  const core_1 = require("@salesforce/core");
13
14
  const kit_1 = require("@salesforce/kit");
15
+ const ts_types_1 = require("@salesforce/ts-types");
14
16
  const source_deploy_retrieve_1 = require("@salesforce/source-deploy-retrieve");
15
17
  const remoteSourceTrackingService_1 = require("./shared/remoteSourceTrackingService");
16
18
  const localShadowRepo_1 = require("./shared/localShadowRepo");
17
19
  const filenamesToVirtualTree_1 = require("./shared/filenamesToVirtualTree");
18
- const getKeyFromObject = (element) => {
19
- if (element.type && element.name) {
20
- return (0, remoteSourceTrackingService_1.getMetadataKey)(element.type, element.name);
21
- }
22
- throw new Error(`unable to complete key from ${JSON.stringify(element)}`);
23
- };
24
- exports.getKeyFromObject = getKeyFromObject;
25
- // external users of SDR might need to convert a fileResponse to a key
26
- exports.getKeyFromStrings = remoteSourceTrackingService_1.getMetadataKey;
20
+ const guards_1 = require("./shared/guards");
21
+ const functions_1 = require("./shared/functions");
27
22
  /**
28
23
  * Manages source tracking files (remote and local)
29
24
  *
@@ -33,18 +28,24 @@ exports.getKeyFromStrings = remoteSourceTrackingService_1.getMetadataKey;
33
28
  class SourceTracking extends kit_1.AsyncCreatable {
34
29
  constructor(options) {
35
30
  super(options);
31
+ this.registry = new source_deploy_retrieve_1.RegistryAccess();
36
32
  this.orgId = options.org.getOrgId();
37
33
  this.username = options.org.getUsername();
38
34
  this.projectPath = options.project.getPath();
39
35
  this.packagesDirs = options.project.getPackageDirectories();
40
36
  this.logger = core_1.Logger.childFromRoot('SourceTracking');
37
+ this.project = options.project;
41
38
  }
42
39
  async init() {
43
- // reserved for future use. If not, can remove asyncCreatable
40
+ // reserved for future use
44
41
  }
45
42
  async localChangesAsComponentSet() {
46
- await this.ensureLocalTracking();
43
+ const [projectConfig] = await Promise.all([this.project.resolveProjectConfig(), this.ensureLocalTracking()]);
44
+ const sourceApiVersion = (0, ts_types_1.getString)(projectConfig, 'sourceApiVersion');
47
45
  const componentSet = new source_deploy_retrieve_1.ComponentSet();
46
+ if (sourceApiVersion) {
47
+ componentSet.sourceApiVersion = sourceApiVersion;
48
+ }
48
49
  const [nonDeletes, deletes] = await Promise.all([
49
50
  this.localRepo.getNonDeleteFilenames(),
50
51
  this.localRepo.getDeleteFilenames(),
@@ -68,18 +69,50 @@ class SourceTracking extends kit_1.AsyncCreatable {
68
69
  return undefined;
69
70
  }
70
71
  })
71
- .filter(sourceComponentGuard)
72
+ .filter(guards_1.sourceComponentGuard)
72
73
  .map((component) => componentSet.add(component));
73
74
  deletes
74
75
  .flatMap((filename) => resolverForDeletes.getComponentsFromPath(filename))
75
- .filter(sourceComponentGuard)
76
- .map((component) => componentSet.add(component, true));
76
+ .filter(guards_1.sourceComponentGuard)
77
+ .map((component) => componentSet.add(component, source_deploy_retrieve_1.DestructiveChangesType.POST));
77
78
  return componentSet;
78
79
  }
80
+ /**
81
+ * Does most of the work for the force:source:status command.
82
+ * Outputs need a bit of massage since this aims to provide nice json.
83
+ *
84
+ * @param local you want local status
85
+ * @param remote you want remote status
86
+ * @returns StatusOutputRow[]
87
+ */
88
+ async getStatus({ local, remote }) {
89
+ let results = [];
90
+ if (local) {
91
+ results = results.concat(await this.getLocalStatusRows());
92
+ }
93
+ if (remote) {
94
+ await this.ensureRemoteTracking(true);
95
+ const [remoteDeletes, remoteModifies] = await Promise.all([
96
+ this.getChanges({ origin: 'remote', state: 'delete', format: 'ChangeResult' }),
97
+ this.getChanges({ origin: 'remote', state: 'nondelete', format: 'ChangeResultWithPaths' }),
98
+ ]);
99
+ results = results.concat((await Promise.all(remoteDeletes.concat(remoteModifies).map((item) => this.remoteChangesToOutputRows(item)))).flat(1));
100
+ }
101
+ if (local && remote) {
102
+ // keys like ApexClass__MyClass.cls
103
+ const conflictFiles = (await this.getConflicts()).flatMap((conflict) => conflict.filenames).filter(guards_1.stringGuard);
104
+ results = results.map((row) => ({
105
+ ...row,
106
+ conflict: !!row.filePath && conflictFiles.includes(row.filePath),
107
+ }));
108
+ }
109
+ return results;
110
+ }
79
111
  /**
80
112
  * Get metadata changes made locally and in the org.
81
113
  *
82
114
  * @returns local and remote changed metadata
115
+ *
83
116
  */
84
117
  async getChanges(options) {
85
118
  if ((options === null || options === void 0 ? void 0 : options.origin) === 'local') {
@@ -108,38 +141,28 @@ class SourceTracking extends kit_1.AsyncCreatable {
108
141
  return undefined;
109
142
  }
110
143
  })
111
- .filter(sourceComponentGuard);
144
+ .filter(guards_1.sourceComponentGuard);
112
145
  }
113
146
  }
114
- if (options && options.origin === 'remote') {
147
+ if ((options === null || options === void 0 ? void 0 : options.origin) === 'remote') {
115
148
  await this.ensureRemoteTracking();
116
149
  const remoteChanges = await this.remoteSourceTrackingService.retrieveUpdates();
117
150
  this.logger.debug('remoteChanges', remoteChanges);
118
- let filteredChanges = [];
119
- if (options.state === 'add') {
120
- filteredChanges = remoteChanges.filter((change) => !change.deleted && !change.modified);
121
- }
122
- else if (options.state === 'modify') {
123
- filteredChanges = remoteChanges.filter((change) => change.modified);
124
- }
125
- else if (options.state === 'delete') {
126
- filteredChanges = remoteChanges.filter((change) => change.deleted);
127
- }
128
- else if (options.state === 'nondelete') {
129
- filteredChanges = remoteChanges.filter((change) => !change.deleted);
130
- }
151
+ const filteredChanges = remoteChanges
152
+ .filter(remoteFilterByState[options.state])
153
+ // skip any remote types not in the registry. Will emit node warnings
154
+ .filter((rce) => this.registrySupportsType(rce.type));
131
155
  if (options.format === 'ChangeResult') {
132
- return filteredChanges.map((change) => ({ ...change, origin: 'remote' }));
156
+ return filteredChanges.map((change) => (0, remoteSourceTrackingService_1.remoteChangeElementToChangeResult)(change));
133
157
  }
134
158
  if (options.format === 'ChangeResultWithPaths') {
135
- return this.populateFilePaths(filteredChanges.map((change) => ({ ...change, origin: 'remote' })));
159
+ return this.populateFilePaths(filteredChanges.map((change) => (0, remoteSourceTrackingService_1.remoteChangeElementToChangeResult)(change)));
136
160
  }
137
161
  // turn it into a componentSet to resolve filenames
138
- const remoteChangesAsComponentLike = filteredChanges.map((element) => ({
162
+ const remoteChangesAsComponentSet = new source_deploy_retrieve_1.ComponentSet(filteredChanges.map((element) => ({
139
163
  type: element === null || element === void 0 ? void 0 : element.type,
140
164
  fullName: element === null || element === void 0 ? void 0 : element.name,
141
- }));
142
- const remoteChangesAsComponentSet = new source_deploy_retrieve_1.ComponentSet(remoteChangesAsComponentLike);
165
+ })));
143
166
  const matchingLocalSourceComponentsSet = source_deploy_retrieve_1.ComponentSet.fromSource({
144
167
  fsPaths: this.packagesDirs.map((dir) => dir.path),
145
168
  include: remoteChangesAsComponentSet,
@@ -228,7 +251,7 @@ class SourceTracking extends kit_1.AsyncCreatable {
228
251
  if (this.localRepo) {
229
252
  return;
230
253
  }
231
- this.localRepo = await localShadowRepo_1.ShadowRepo.create({
254
+ this.localRepo = await localShadowRepo_1.ShadowRepo.getInstance({
232
255
  orgId: this.orgId,
233
256
  projectPath: this.projectPath,
234
257
  packageDirs: this.packagesDirs,
@@ -293,20 +316,79 @@ class SourceTracking extends kit_1.AsyncCreatable {
293
316
  const resetMembers = await this.remoteSourceTrackingService.reset(serverRevision);
294
317
  return resetMembers.length;
295
318
  }
319
+ /**
320
+ * Compares local and remote changes to detect conflicts
321
+ */
322
+ async getConflicts() {
323
+ // we're going to need have both initialized
324
+ await Promise.all([this.ensureRemoteTracking(), this.ensureLocalTracking()]);
325
+ // Strategy: check local changes first (since it'll be faster) to avoid callout
326
+ // early return if either local or remote is empty
327
+ const localChanges = await this.getChanges({
328
+ state: 'nondelete',
329
+ origin: 'local',
330
+ format: 'ChangeResult',
331
+ });
332
+ if (localChanges.length === 0) {
333
+ return [];
334
+ }
335
+ const remoteChanges = await this.getChanges({
336
+ origin: 'remote',
337
+ state: 'nondelete',
338
+ // remote adds won't have a filename, so we ask for it to be resolved
339
+ format: 'ChangeResultWithPaths',
340
+ });
341
+ if (remoteChanges.length === 0) {
342
+ return [];
343
+ }
344
+ // index the remoteChanges by filename
345
+ const fileNameIndex = new Map();
346
+ const metadataKeyIndex = new Map();
347
+ remoteChanges.map((change) => {
348
+ var _a;
349
+ if (change.name && change.type) {
350
+ metadataKeyIndex.set((0, functions_1.getMetadataKey)(change.name, change.type), change);
351
+ }
352
+ (_a = change.filenames) === null || _a === void 0 ? void 0 : _a.map((filename) => {
353
+ fileNameIndex.set(filename, change);
354
+ });
355
+ });
356
+ const conflicts = new Set();
357
+ this.populateTypesAndNames({ elements: localChanges, excludeUnresolvable: true }).map((change) => {
358
+ var _a;
359
+ const metadataKey = (0, functions_1.getMetadataKey)(change.name, change.type);
360
+ // option 1: name and type match
361
+ if (metadataKeyIndex.has(metadataKey)) {
362
+ conflicts.add({ ...metadataKeyIndex.get(metadataKey) });
363
+ }
364
+ else {
365
+ // option 2: some of the filenames match
366
+ (_a = change.filenames) === null || _a === void 0 ? void 0 : _a.map((filename) => {
367
+ if (fileNameIndex.has(filename)) {
368
+ conflicts.add({ ...fileNameIndex.get(filename) });
369
+ }
370
+ });
371
+ }
372
+ });
373
+ // deeply de-dupe
374
+ return Array.from(conflicts);
375
+ }
296
376
  /**
297
377
  * uses SDR to translate remote metadata records into local file paths (which only typically have the filename).
298
378
  *
299
379
  * @input elements: ChangeResult[]
300
380
  * @input excludeUnresolvables: boolean Filter out components where you can't get the name and type (that is, it's probably not a valid source component)
381
+ * @input resolveDeleted: constructs a virtualTree instead of the actual filesystem--useful when the files no longer exist
382
+ * @input useFsForceIgnore: (default behavior) use forceIgnore from the filesystem. If false, uses the base forceIgnore from SDR
301
383
  */
302
- populateTypesAndNames({ elements, excludeUnresolvable = false, resolveDeleted = false, }) {
384
+ populateTypesAndNames({ elements, excludeUnresolvable = false, resolveDeleted = false, useFsForceIgnore = true, }) {
303
385
  if (elements.length === 0) {
304
386
  return [];
305
387
  }
306
388
  this.logger.debug(`populateTypesAndNames for ${elements.length} change elements`);
307
- const filenames = elements.flatMap((element) => element.filenames).filter(exports.stringGuard);
389
+ const filenames = elements.flatMap((element) => element.filenames).filter(guards_1.stringGuard);
308
390
  // component set generated from the filenames on all local changes
309
- const resolver = new source_deploy_retrieve_1.MetadataResolver(undefined, resolveDeleted ? (0, filenamesToVirtualTree_1.filenamesToVirtualTree)(filenames) : undefined);
391
+ const resolver = new source_deploy_retrieve_1.MetadataResolver(undefined, resolveDeleted ? (0, filenamesToVirtualTree_1.filenamesToVirtualTree)(filenames) : undefined, useFsForceIgnore);
310
392
  const sourceComponents = filenames
311
393
  .flatMap((filename) => {
312
394
  try {
@@ -317,7 +399,7 @@ class SourceTracking extends kit_1.AsyncCreatable {
317
399
  return undefined;
318
400
  }
319
401
  })
320
- .filter(sourceComponentGuard);
402
+ .filter(guards_1.sourceComponentGuard);
321
403
  this.logger.debug(` matching SourceComponents have ${sourceComponents.length} items from local`);
322
404
  // make it simpler to find things later
323
405
  const elementMap = new Map();
@@ -329,15 +411,24 @@ class SourceTracking extends kit_1.AsyncCreatable {
329
411
  });
330
412
  // iterates the local components and sets their filenames
331
413
  sourceComponents.map((matchingComponent) => {
414
+ var _a;
332
415
  if ((matchingComponent === null || matchingComponent === void 0 ? void 0 : matchingComponent.fullName) && (matchingComponent === null || matchingComponent === void 0 ? void 0 : matchingComponent.type.name)) {
333
416
  const filenamesFromMatchingComponent = [matchingComponent.xml, ...matchingComponent.walkContent()];
417
+ // 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)
418
+ this.forceIgnore = (_a = this.forceIgnore) !== null && _a !== void 0 ? _a : source_deploy_retrieve_1.ForceIgnore.findAndCreate(this.project.getDefaultPackage().path);
419
+ const ignored = filenamesFromMatchingComponent
420
+ .filter(guards_1.stringGuard)
421
+ .filter((filename) => !filename.includes('__tests__'))
422
+ .some((filename) => this.forceIgnore.denies(filename));
334
423
  filenamesFromMatchingComponent.map((filename) => {
335
424
  if (filename && elementMap.has(filename)) {
336
425
  // add the type/name from the componentSet onto the element
337
426
  elementMap.set(filename, {
427
+ origin: 'remote',
338
428
  ...elementMap.get(filename),
339
429
  type: matchingComponent.type.name,
340
430
  name: matchingComponent.fullName,
431
+ ignored,
341
432
  });
342
433
  }
343
434
  });
@@ -347,39 +438,34 @@ class SourceTracking extends kit_1.AsyncCreatable {
347
438
  ? Array.from(new Set(elementMap.values())).filter((changeResult) => changeResult.name && changeResult.type)
348
439
  : Array.from(new Set(elementMap.values()));
349
440
  }
350
- async getConflicts() {
351
- // we're going to need have both initialized
352
- await Promise.all([this.ensureRemoteTracking(), this.ensureLocalTracking()]);
353
- const localChanges = await this.getChanges({
354
- state: 'nondelete',
355
- origin: 'local',
356
- format: 'ChangeResult',
357
- });
358
- const remoteChanges = await this.getChanges({
359
- origin: 'remote',
360
- state: 'nondelete',
361
- // remote adds won't have a filename, so we ask for it to be resolved
362
- format: 'ChangeResultWithPaths',
441
+ async getLocalStatusRows() {
442
+ await this.ensureLocalTracking();
443
+ let results = [];
444
+ const localDeletes = this.populateTypesAndNames({
445
+ elements: await this.getChanges({ origin: 'local', state: 'delete', format: 'ChangeResult' }),
446
+ excludeUnresolvable: true,
447
+ resolveDeleted: true,
448
+ useFsForceIgnore: false,
363
449
  });
364
- // index them by filename
365
- const fileNameIndex = new Map();
366
- remoteChanges.map((change) => {
367
- var _a;
368
- (_a = change.filenames) === null || _a === void 0 ? void 0 : _a.map((filename) => {
369
- fileNameIndex.set(filename, change);
370
- });
450
+ const localAdds = this.populateTypesAndNames({
451
+ elements: await this.getChanges({ origin: 'local', state: 'add', format: 'ChangeResult' }),
452
+ excludeUnresolvable: true,
453
+ useFsForceIgnore: false,
371
454
  });
372
- const conflicts = new Set();
373
- localChanges.map((change) => {
374
- var _a;
375
- (_a = change.filenames) === null || _a === void 0 ? void 0 : _a.map((filename) => {
376
- if (fileNameIndex.has(filename)) {
377
- conflicts.add({ ...fileNameIndex.get(filename) });
378
- }
379
- });
455
+ const localModifies = this.populateTypesAndNames({
456
+ elements: await this.getChanges({ origin: 'local', state: 'modify', format: 'ChangeResult' }),
457
+ excludeUnresolvable: true,
458
+ useFsForceIgnore: false,
380
459
  });
381
- // deeply de-dupe
382
- return Array.from(conflicts);
460
+ results = results.concat(localAdds.flatMap((item) => this.localChangesToOutputRow(item, 'add')), localModifies.flatMap((item) => this.localChangesToOutputRow(item, 'modify')), localDeletes.flatMap((item) => this.localChangesToOutputRow(item, 'delete')));
461
+ return results;
462
+ }
463
+ registrySupportsType(type) {
464
+ if (this.registry.findType((metadataType) => metadataType.name === type)) {
465
+ return true;
466
+ }
467
+ process.emitWarning(`Unable to find type ${type} in registry`);
468
+ return false;
383
469
  }
384
470
  /**
385
471
  * uses SDR to translate remote metadata records into local file paths
@@ -389,17 +475,26 @@ class SourceTracking extends kit_1.AsyncCreatable {
389
475
  return [];
390
476
  }
391
477
  this.logger.debug('populateFilePaths for change elements', elements);
392
- // component set generated from an array of ComponentLike from all the remote changes
393
- const remoteChangesAsComponentLike = elements.map((element) => ({
394
- type: element === null || element === void 0 ? void 0 : element.type,
395
- fullName: element === null || element === void 0 ? void 0 : element.name,
396
- }));
397
- const remoteChangesAsComponentSet = new source_deploy_retrieve_1.ComponentSet(remoteChangesAsComponentLike);
478
+ // component set generated from an array of MetadataMember from all the remote changes
479
+ // but exclude the ones that aren't in the registry
480
+ const remoteChangesAsMetadataMember = elements
481
+ .map((element) => {
482
+ if (typeof element.type === 'string' && typeof element.name === 'string') {
483
+ return {
484
+ type: element.type,
485
+ fullName: element.name,
486
+ };
487
+ }
488
+ })
489
+ .filter(guards_1.metadataMemberGuard);
490
+ const remoteChangesAsComponentSet = new source_deploy_retrieve_1.ComponentSet(remoteChangesAsMetadataMember);
398
491
  this.logger.debug(` the generated component set has ${remoteChangesAsComponentSet.size.toString()} items`);
399
492
  if (remoteChangesAsComponentSet.size < elements.length) {
493
+ // iterate the elements to see which ones didn't make it into the component set
400
494
  throw new Error(`unable to generate complete component set for ${elements
401
- .map((element) => `${element.name}(${element.type})`)
402
- .join(',')}`);
495
+ .filter((element) => !remoteChangesAsComponentSet.has({ type: element === null || element === void 0 ? void 0 : element.type, fullName: element === null || element === void 0 ? void 0 : element.name }))
496
+ .map((element) => `${element.name} (${element.type})`)
497
+ .join(os_1.EOL)}`);
403
498
  }
404
499
  const matchingLocalSourceComponentsSet = source_deploy_retrieve_1.ComponentSet.fromSource({
405
500
  fsPaths: this.packagesDirs.map((dir) => dir.path),
@@ -409,16 +504,17 @@ class SourceTracking extends kit_1.AsyncCreatable {
409
504
  // make it simpler to find things later
410
505
  const elementMap = new Map();
411
506
  elements.map((element) => {
412
- elementMap.set((0, exports.getKeyFromObject)(element), element);
507
+ elementMap.set((0, functions_1.getKeyFromObject)(element), element);
413
508
  });
414
509
  // iterates the local components and sets their filenames
415
510
  for (const matchingComponent of matchingLocalSourceComponentsSet.getSourceComponents().toArray()) {
416
511
  if (matchingComponent.fullName && matchingComponent.type.name) {
417
512
  this.logger.debug(`${matchingComponent.fullName}|${matchingComponent.type.name} matches ${matchingComponent.xml} and maybe ${matchingComponent.walkContent().toString()}`);
418
- const key = (0, exports.getKeyFromStrings)(matchingComponent.type.name, matchingComponent.fullName);
513
+ const key = (0, functions_1.getMetadataKey)(matchingComponent.type.name, matchingComponent.fullName);
419
514
  elementMap.set(key, {
420
515
  ...elementMap.get(key),
421
516
  modified: true,
517
+ origin: 'remote',
422
518
  filenames: [matchingComponent.xml, ...matchingComponent.walkContent()].filter((filename) => filename),
423
519
  });
424
520
  }
@@ -443,13 +539,65 @@ class SourceTracking extends kit_1.AsyncCreatable {
443
539
  }
444
540
  throw new Error(`unable to get local changes for state ${state}`);
445
541
  }
542
+ localChangesToOutputRow(input, localType) {
543
+ var _a, _b, _c;
544
+ this.logger.debug('converting ChangeResult to a row', input);
545
+ const baseObject = {
546
+ type: (_a = input.type) !== null && _a !== void 0 ? _a : '',
547
+ origin: 'local',
548
+ state: localType,
549
+ fullName: (_b = input.name) !== null && _b !== void 0 ? _b : '',
550
+ // ignored property will be set in populateTypesAndNames
551
+ ignored: (_c = input.ignored) !== null && _c !== void 0 ? _c : false,
552
+ };
553
+ if (input.filenames) {
554
+ return input.filenames.map((filename) => ({
555
+ ...baseObject,
556
+ filePath: filename,
557
+ origin: 'local',
558
+ }));
559
+ }
560
+ throw new Error('no filenames found for local ChangeResult');
561
+ }
562
+ // this will eventually have async call to figure out the target file locations for remote changes
563
+ // eslint-disable-next-line @typescript-eslint/require-await
564
+ async remoteChangesToOutputRows(input) {
565
+ var _a, _b, _c, _d;
566
+ this.logger.debug('converting ChangeResult to a row', input);
567
+ this.forceIgnore = (_a = this.forceIgnore) !== null && _a !== void 0 ? _a : source_deploy_retrieve_1.ForceIgnore.findAndCreate(this.project.getDefaultPackage().path);
568
+ const baseObject = {
569
+ type: (_b = input.type) !== null && _b !== void 0 ? _b : '',
570
+ origin: input.origin,
571
+ state: stateFromChangeResult(input),
572
+ fullName: (_c = input.name) !== null && _c !== void 0 ? _c : '',
573
+ };
574
+ // it's easy to check ignores if the filePaths exist locally
575
+ if ((_d = input.filenames) === null || _d === void 0 ? void 0 : _d.length) {
576
+ return input.filenames.map((filename) => ({
577
+ ...baseObject,
578
+ filePath: filename,
579
+ ignored: this.forceIgnore.denies(filename),
580
+ }));
581
+ }
582
+ // when the file doesn't exist locally, there are no filePaths
583
+ // So we can't say whether it's ignored or not
584
+ return [baseObject];
585
+ }
446
586
  }
447
587
  exports.SourceTracking = SourceTracking;
448
- const stringGuard = (input) => {
449
- return typeof input === 'string';
588
+ const remoteFilterByState = {
589
+ add: (change) => !change.deleted && !change.modified,
590
+ modify: (change) => change.modified === true,
591
+ delete: (change) => change.deleted === true,
592
+ nondelete: (change) => !change.deleted,
450
593
  };
451
- exports.stringGuard = stringGuard;
452
- const sourceComponentGuard = (input) => {
453
- return input instanceof source_deploy_retrieve_1.SourceComponent;
594
+ const stateFromChangeResult = (input) => {
595
+ if (input.deleted) {
596
+ return 'delete';
597
+ }
598
+ if (input.modified) {
599
+ return 'modify';
600
+ }
601
+ return 'add';
454
602
  };
455
603
  //# sourceMappingURL=sourceTracking.js.map