@salesforce/source-tracking 0.2.2 → 0.4.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 +78 -0
- package/README.md +6 -6
- package/lib/compatibility.d.ts +4 -2
- package/lib/compatibility.js +7 -4
- package/lib/index.d.ts +2 -1
- package/lib/index.js +3 -0
- package/lib/shared/filenamesToVirtualTree.d.ts +1 -0
- package/lib/shared/filenamesToVirtualTree.js +1 -0
- package/lib/shared/functions.d.ts +3 -0
- package/lib/shared/functions.js +21 -0
- package/lib/shared/guards.d.ts +3 -0
- package/lib/shared/guards.js +19 -0
- package/lib/shared/localShadowRepo.d.ts +4 -4
- package/lib/shared/localShadowRepo.js +36 -25
- package/lib/shared/metadataKeys.js +6 -6
- package/lib/shared/remoteSourceTrackingService.d.ts +6 -20
- package/lib/shared/remoteSourceTrackingService.js +30 -13
- package/lib/shared/types.d.ts +47 -1
- package/lib/sourceTracking.d.ts +28 -34
- package/lib/sourceTracking.js +229 -92
- package/package.json +5 -8
package/lib/shared/types.d.ts
CHANGED
|
@@ -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;
|
package/lib/sourceTracking.d.ts
CHANGED
|
@@ -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 {
|
|
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';
|
|
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,34 @@ 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;
|
|
48
24
|
private localRepo;
|
|
49
25
|
private remoteSourceTrackingService;
|
|
26
|
+
private forceIgnore;
|
|
50
27
|
constructor(options: SourceTrackingOptions);
|
|
51
28
|
init(): Promise<void>;
|
|
52
29
|
localChangesAsComponentSet(): Promise<ComponentSet>;
|
|
30
|
+
/**
|
|
31
|
+
* Does most of the work for the force:source:status command.
|
|
32
|
+
* Outputs need a bit of massage since this aims to provide nice json.
|
|
33
|
+
*
|
|
34
|
+
* @param local you want local status
|
|
35
|
+
* @param remote you want remote status
|
|
36
|
+
* @returns StatusOutputRow[]
|
|
37
|
+
*/
|
|
38
|
+
getStatus({ local, remote }: {
|
|
39
|
+
local: boolean;
|
|
40
|
+
remote: boolean;
|
|
41
|
+
}): Promise<StatusOutputRow[]>;
|
|
53
42
|
/**
|
|
54
43
|
* Get metadata changes made locally and in the org.
|
|
55
44
|
*
|
|
56
45
|
* @returns local and remote changed metadata
|
|
46
|
+
*
|
|
57
47
|
*/
|
|
58
48
|
getChanges<T extends ChangeOptionType>(options?: ChangeOptions): Promise<T[]>;
|
|
59
49
|
/**
|
|
@@ -103,22 +93,26 @@ export declare class SourceTracking extends AsyncCreatable {
|
|
|
103
93
|
* Sets the files to max revision so that no changes appear
|
|
104
94
|
*/
|
|
105
95
|
resetRemoteTracking(serverRevision?: number): Promise<number>;
|
|
96
|
+
/**
|
|
97
|
+
* Compares local and remote changes to detect conflicts
|
|
98
|
+
*/
|
|
99
|
+
getConflicts(): Promise<ChangeResult[]>;
|
|
106
100
|
/**
|
|
107
101
|
* uses SDR to translate remote metadata records into local file paths (which only typically have the filename).
|
|
108
102
|
*
|
|
109
103
|
* @input elements: ChangeResult[]
|
|
110
104
|
* @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)
|
|
105
|
+
* @input resolveDeleted: constructs a virtualTree instead of the actual filesystem--useful when the files no longer exist
|
|
106
|
+
* @input useFsForceIgnore: (default behavior) use forceIgnore from the filesystem. If false, uses the base forceIgnore from SDR
|
|
111
107
|
*/
|
|
112
|
-
populateTypesAndNames
|
|
113
|
-
|
|
114
|
-
excludeUnresolvable?: boolean;
|
|
115
|
-
resolveDeleted?: boolean;
|
|
116
|
-
}): ChangeResult[];
|
|
117
|
-
getConflicts(): Promise<ChangeResult[]>;
|
|
108
|
+
private populateTypesAndNames;
|
|
109
|
+
private getLocalStatusRows;
|
|
118
110
|
/**
|
|
119
111
|
* uses SDR to translate remote metadata records into local file paths
|
|
120
112
|
*/
|
|
121
113
|
private populateFilePaths;
|
|
122
114
|
private ensureRelative;
|
|
115
|
+
private getLocalChangesAsFilenames;
|
|
116
|
+
private localChangesToOutputRow;
|
|
117
|
+
private remoteChangesToOutputRows;
|
|
123
118
|
}
|
|
124
|
-
export declare const stringGuard: (input: string | undefined) => input is string;
|
package/lib/sourceTracking.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
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
|
|
19
|
-
|
|
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
|
*
|
|
@@ -38,13 +33,18 @@ class SourceTracking extends kit_1.AsyncCreatable {
|
|
|
38
33
|
this.projectPath = options.project.getPath();
|
|
39
34
|
this.packagesDirs = options.project.getPackageDirectories();
|
|
40
35
|
this.logger = core_1.Logger.childFromRoot('SourceTracking');
|
|
36
|
+
this.project = options.project;
|
|
41
37
|
}
|
|
42
38
|
async init() {
|
|
43
|
-
// reserved for future use
|
|
39
|
+
// reserved for future use
|
|
44
40
|
}
|
|
45
41
|
async localChangesAsComponentSet() {
|
|
46
|
-
await this.ensureLocalTracking();
|
|
42
|
+
const [projectConfig] = await Promise.all([this.project.resolveProjectConfig(), this.ensureLocalTracking()]);
|
|
43
|
+
const sourceApiVersion = (0, ts_types_1.getString)(projectConfig, 'sourceApiVersion');
|
|
47
44
|
const componentSet = new source_deploy_retrieve_1.ComponentSet();
|
|
45
|
+
if (sourceApiVersion) {
|
|
46
|
+
componentSet.sourceApiVersion = sourceApiVersion;
|
|
47
|
+
}
|
|
48
48
|
const [nonDeletes, deletes] = await Promise.all([
|
|
49
49
|
this.localRepo.getNonDeleteFilenames(),
|
|
50
50
|
this.localRepo.getDeleteFilenames(),
|
|
@@ -68,39 +68,59 @@ class SourceTracking extends kit_1.AsyncCreatable {
|
|
|
68
68
|
return undefined;
|
|
69
69
|
}
|
|
70
70
|
})
|
|
71
|
-
.filter(sourceComponentGuard)
|
|
71
|
+
.filter(guards_1.sourceComponentGuard)
|
|
72
72
|
.map((component) => componentSet.add(component));
|
|
73
73
|
deletes
|
|
74
74
|
.flatMap((filename) => resolverForDeletes.getComponentsFromPath(filename))
|
|
75
|
-
.filter(sourceComponentGuard)
|
|
76
|
-
.map((component) => componentSet.add(component,
|
|
75
|
+
.filter(guards_1.sourceComponentGuard)
|
|
76
|
+
.map((component) => componentSet.add(component, source_deploy_retrieve_1.DestructiveChangesType.POST));
|
|
77
77
|
return componentSet;
|
|
78
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Does most of the work for the force:source:status command.
|
|
81
|
+
* Outputs need a bit of massage since this aims to provide nice json.
|
|
82
|
+
*
|
|
83
|
+
* @param local you want local status
|
|
84
|
+
* @param remote you want remote status
|
|
85
|
+
* @returns StatusOutputRow[]
|
|
86
|
+
*/
|
|
87
|
+
async getStatus({ local, remote }) {
|
|
88
|
+
let results = [];
|
|
89
|
+
if (local) {
|
|
90
|
+
results = results.concat(await this.getLocalStatusRows());
|
|
91
|
+
}
|
|
92
|
+
if (remote) {
|
|
93
|
+
await this.ensureRemoteTracking(true);
|
|
94
|
+
const [remoteDeletes, remoteModifies] = await Promise.all([
|
|
95
|
+
this.getChanges({ origin: 'remote', state: 'delete', format: 'ChangeResult' }),
|
|
96
|
+
this.getChanges({ origin: 'remote', state: 'nondelete', format: 'ChangeResultWithPaths' }),
|
|
97
|
+
]);
|
|
98
|
+
results = results.concat((await Promise.all(remoteDeletes.concat(remoteModifies).map((item) => this.remoteChangesToOutputRows(item)))).flat(1));
|
|
99
|
+
}
|
|
100
|
+
if (local && remote) {
|
|
101
|
+
// keys like ApexClass__MyClass.cls
|
|
102
|
+
const conflictFiles = (await this.getConflicts()).flatMap((conflict) => conflict.filenames).filter(guards_1.stringGuard);
|
|
103
|
+
results = results.map((row) => ({
|
|
104
|
+
...row,
|
|
105
|
+
conflict: !!row.filePath && conflictFiles.includes(row.filePath),
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
return results;
|
|
109
|
+
}
|
|
79
110
|
/**
|
|
80
111
|
* Get metadata changes made locally and in the org.
|
|
81
112
|
*
|
|
82
113
|
* @returns local and remote changed metadata
|
|
114
|
+
*
|
|
83
115
|
*/
|
|
84
116
|
async getChanges(options) {
|
|
85
117
|
if ((options === null || options === void 0 ? void 0 : options.origin) === 'local') {
|
|
86
|
-
let filenames = [];
|
|
87
118
|
await this.ensureLocalTracking();
|
|
88
|
-
|
|
89
|
-
filenames = await this.localRepo.getModifyFilenames();
|
|
90
|
-
}
|
|
91
|
-
if (options.state === 'nondelete') {
|
|
92
|
-
filenames = await this.localRepo.getNonDeleteFilenames();
|
|
93
|
-
}
|
|
94
|
-
if (options.state === 'delete') {
|
|
95
|
-
filenames = await this.localRepo.getDeleteFilenames();
|
|
96
|
-
}
|
|
97
|
-
if (options.state === 'add') {
|
|
98
|
-
filenames = await this.localRepo.getAddFilenames();
|
|
99
|
-
}
|
|
119
|
+
const filenames = await this.getLocalChangesAsFilenames(options.state);
|
|
100
120
|
if (options.format === 'string') {
|
|
101
121
|
return filenames;
|
|
102
122
|
}
|
|
103
|
-
if (options.format === 'ChangeResult') {
|
|
123
|
+
if (options.format === 'ChangeResult' || options.format === 'ChangeResultWithPaths') {
|
|
104
124
|
return filenames.map((filename) => ({
|
|
105
125
|
filenames: [filename],
|
|
106
126
|
origin: 'local',
|
|
@@ -120,35 +140,25 @@ class SourceTracking extends kit_1.AsyncCreatable {
|
|
|
120
140
|
return undefined;
|
|
121
141
|
}
|
|
122
142
|
})
|
|
123
|
-
.filter(sourceComponentGuard);
|
|
143
|
+
.filter(guards_1.sourceComponentGuard);
|
|
124
144
|
}
|
|
125
145
|
}
|
|
126
|
-
if (options
|
|
146
|
+
if ((options === null || options === void 0 ? void 0 : options.origin) === 'remote') {
|
|
127
147
|
await this.ensureRemoteTracking();
|
|
128
148
|
const remoteChanges = await this.remoteSourceTrackingService.retrieveUpdates();
|
|
129
149
|
this.logger.debug('remoteChanges', remoteChanges);
|
|
130
|
-
|
|
131
|
-
if (options.state === 'add') {
|
|
132
|
-
filteredChanges = remoteChanges.filter((change) => !change.deleted && !change.modified);
|
|
133
|
-
}
|
|
134
|
-
else if (options.state === 'modify') {
|
|
135
|
-
filteredChanges = remoteChanges.filter((change) => change.modified);
|
|
136
|
-
}
|
|
137
|
-
else if (options.state === 'delete') {
|
|
138
|
-
filteredChanges = remoteChanges.filter((change) => change.deleted);
|
|
139
|
-
}
|
|
140
|
-
else if (options.state === 'nondelete') {
|
|
141
|
-
filteredChanges = remoteChanges.filter((change) => !change.deleted);
|
|
142
|
-
}
|
|
150
|
+
const filteredChanges = remoteChanges.filter(remoteFilterByState[options.state]);
|
|
143
151
|
if (options.format === 'ChangeResult') {
|
|
144
|
-
return filteredChanges.map((change) => (
|
|
152
|
+
return filteredChanges.map((change) => (0, remoteSourceTrackingService_1.remoteChangeElementToChangeResult)(change));
|
|
153
|
+
}
|
|
154
|
+
if (options.format === 'ChangeResultWithPaths') {
|
|
155
|
+
return this.populateFilePaths(filteredChanges.map((change) => (0, remoteSourceTrackingService_1.remoteChangeElementToChangeResult)(change)));
|
|
145
156
|
}
|
|
146
157
|
// turn it into a componentSet to resolve filenames
|
|
147
|
-
const
|
|
158
|
+
const remoteChangesAsComponentSet = new source_deploy_retrieve_1.ComponentSet(filteredChanges.map((element) => ({
|
|
148
159
|
type: element === null || element === void 0 ? void 0 : element.type,
|
|
149
160
|
fullName: element === null || element === void 0 ? void 0 : element.name,
|
|
150
|
-
}));
|
|
151
|
-
const remoteChangesAsComponentSet = new source_deploy_retrieve_1.ComponentSet(remoteChangesAsComponentLike);
|
|
161
|
+
})));
|
|
152
162
|
const matchingLocalSourceComponentsSet = source_deploy_retrieve_1.ComponentSet.fromSource({
|
|
153
163
|
fsPaths: this.packagesDirs.map((dir) => dir.path),
|
|
154
164
|
include: remoteChangesAsComponentSet,
|
|
@@ -163,10 +173,7 @@ class SourceTracking extends kit_1.AsyncCreatable {
|
|
|
163
173
|
return matchingLocalSourceComponentsSet.getSourceComponents().toArray();
|
|
164
174
|
}
|
|
165
175
|
}
|
|
166
|
-
|
|
167
|
-
// eslint-disable-next-line no-console
|
|
168
|
-
this.logger.debug(options);
|
|
169
|
-
return [];
|
|
176
|
+
throw new Error(`unsupported options: ${JSON.stringify(options)}`);
|
|
170
177
|
}
|
|
171
178
|
/**
|
|
172
179
|
*
|
|
@@ -240,7 +247,7 @@ class SourceTracking extends kit_1.AsyncCreatable {
|
|
|
240
247
|
if (this.localRepo) {
|
|
241
248
|
return;
|
|
242
249
|
}
|
|
243
|
-
this.localRepo = await localShadowRepo_1.ShadowRepo.
|
|
250
|
+
this.localRepo = await localShadowRepo_1.ShadowRepo.getInstance({
|
|
244
251
|
orgId: this.orgId,
|
|
245
252
|
projectPath: this.projectPath,
|
|
246
253
|
packageDirs: this.packagesDirs,
|
|
@@ -305,20 +312,79 @@ class SourceTracking extends kit_1.AsyncCreatable {
|
|
|
305
312
|
const resetMembers = await this.remoteSourceTrackingService.reset(serverRevision);
|
|
306
313
|
return resetMembers.length;
|
|
307
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* Compares local and remote changes to detect conflicts
|
|
317
|
+
*/
|
|
318
|
+
async getConflicts() {
|
|
319
|
+
// we're going to need have both initialized
|
|
320
|
+
await Promise.all([this.ensureRemoteTracking(), this.ensureLocalTracking()]);
|
|
321
|
+
// Strategy: check local changes first (since it'll be faster) to avoid callout
|
|
322
|
+
// early return if either local or remote is empty
|
|
323
|
+
const localChanges = await this.getChanges({
|
|
324
|
+
state: 'nondelete',
|
|
325
|
+
origin: 'local',
|
|
326
|
+
format: 'ChangeResult',
|
|
327
|
+
});
|
|
328
|
+
if (localChanges.length === 0) {
|
|
329
|
+
return [];
|
|
330
|
+
}
|
|
331
|
+
const remoteChanges = await this.getChanges({
|
|
332
|
+
origin: 'remote',
|
|
333
|
+
state: 'nondelete',
|
|
334
|
+
// remote adds won't have a filename, so we ask for it to be resolved
|
|
335
|
+
format: 'ChangeResultWithPaths',
|
|
336
|
+
});
|
|
337
|
+
if (remoteChanges.length === 0) {
|
|
338
|
+
return [];
|
|
339
|
+
}
|
|
340
|
+
// index the remoteChanges by filename
|
|
341
|
+
const fileNameIndex = new Map();
|
|
342
|
+
const metadataKeyIndex = new Map();
|
|
343
|
+
remoteChanges.map((change) => {
|
|
344
|
+
var _a;
|
|
345
|
+
if (change.name && change.type) {
|
|
346
|
+
metadataKeyIndex.set((0, functions_1.getMetadataKey)(change.name, change.type), change);
|
|
347
|
+
}
|
|
348
|
+
(_a = change.filenames) === null || _a === void 0 ? void 0 : _a.map((filename) => {
|
|
349
|
+
fileNameIndex.set(filename, change);
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
const conflicts = new Set();
|
|
353
|
+
this.populateTypesAndNames({ elements: localChanges, excludeUnresolvable: true }).map((change) => {
|
|
354
|
+
var _a;
|
|
355
|
+
const metadataKey = (0, functions_1.getMetadataKey)(change.name, change.type);
|
|
356
|
+
// option 1: name and type match
|
|
357
|
+
if (metadataKeyIndex.has(metadataKey)) {
|
|
358
|
+
conflicts.add({ ...metadataKeyIndex.get(metadataKey) });
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
// option 2: some of the filenames match
|
|
362
|
+
(_a = change.filenames) === null || _a === void 0 ? void 0 : _a.map((filename) => {
|
|
363
|
+
if (fileNameIndex.has(filename)) {
|
|
364
|
+
conflicts.add({ ...fileNameIndex.get(filename) });
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
// deeply de-dupe
|
|
370
|
+
return Array.from(conflicts);
|
|
371
|
+
}
|
|
308
372
|
/**
|
|
309
373
|
* uses SDR to translate remote metadata records into local file paths (which only typically have the filename).
|
|
310
374
|
*
|
|
311
375
|
* @input elements: ChangeResult[]
|
|
312
376
|
* @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)
|
|
377
|
+
* @input resolveDeleted: constructs a virtualTree instead of the actual filesystem--useful when the files no longer exist
|
|
378
|
+
* @input useFsForceIgnore: (default behavior) use forceIgnore from the filesystem. If false, uses the base forceIgnore from SDR
|
|
313
379
|
*/
|
|
314
|
-
populateTypesAndNames({ elements, excludeUnresolvable = false, resolveDeleted = false, }) {
|
|
380
|
+
populateTypesAndNames({ elements, excludeUnresolvable = false, resolveDeleted = false, useFsForceIgnore = true, }) {
|
|
315
381
|
if (elements.length === 0) {
|
|
316
382
|
return [];
|
|
317
383
|
}
|
|
318
384
|
this.logger.debug(`populateTypesAndNames for ${elements.length} change elements`);
|
|
319
|
-
const filenames = elements.flatMap((element) => element.filenames).filter(
|
|
385
|
+
const filenames = elements.flatMap((element) => element.filenames).filter(guards_1.stringGuard);
|
|
320
386
|
// component set generated from the filenames on all local changes
|
|
321
|
-
const resolver = new source_deploy_retrieve_1.MetadataResolver(undefined, resolveDeleted ? (0, filenamesToVirtualTree_1.filenamesToVirtualTree)(filenames) : undefined);
|
|
387
|
+
const resolver = new source_deploy_retrieve_1.MetadataResolver(undefined, resolveDeleted ? (0, filenamesToVirtualTree_1.filenamesToVirtualTree)(filenames) : undefined, useFsForceIgnore);
|
|
322
388
|
const sourceComponents = filenames
|
|
323
389
|
.flatMap((filename) => {
|
|
324
390
|
try {
|
|
@@ -329,7 +395,7 @@ class SourceTracking extends kit_1.AsyncCreatable {
|
|
|
329
395
|
return undefined;
|
|
330
396
|
}
|
|
331
397
|
})
|
|
332
|
-
.filter(sourceComponentGuard);
|
|
398
|
+
.filter(guards_1.sourceComponentGuard);
|
|
333
399
|
this.logger.debug(` matching SourceComponents have ${sourceComponents.length} items from local`);
|
|
334
400
|
// make it simpler to find things later
|
|
335
401
|
const elementMap = new Map();
|
|
@@ -341,15 +407,24 @@ class SourceTracking extends kit_1.AsyncCreatable {
|
|
|
341
407
|
});
|
|
342
408
|
// iterates the local components and sets their filenames
|
|
343
409
|
sourceComponents.map((matchingComponent) => {
|
|
410
|
+
var _a;
|
|
344
411
|
if ((matchingComponent === null || matchingComponent === void 0 ? void 0 : matchingComponent.fullName) && (matchingComponent === null || matchingComponent === void 0 ? void 0 : matchingComponent.type.name)) {
|
|
345
412
|
const filenamesFromMatchingComponent = [matchingComponent.xml, ...matchingComponent.walkContent()];
|
|
413
|
+
// 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)
|
|
414
|
+
this.forceIgnore = (_a = this.forceIgnore) !== null && _a !== void 0 ? _a : source_deploy_retrieve_1.ForceIgnore.findAndCreate(this.project.getDefaultPackage().path);
|
|
415
|
+
const ignored = filenamesFromMatchingComponent
|
|
416
|
+
.filter(guards_1.stringGuard)
|
|
417
|
+
.filter((filename) => !filename.includes('__tests__'))
|
|
418
|
+
.some((filename) => this.forceIgnore.denies(filename));
|
|
346
419
|
filenamesFromMatchingComponent.map((filename) => {
|
|
347
420
|
if (filename && elementMap.has(filename)) {
|
|
348
421
|
// add the type/name from the componentSet onto the element
|
|
349
422
|
elementMap.set(filename, {
|
|
423
|
+
origin: 'remote',
|
|
350
424
|
...elementMap.get(filename),
|
|
351
425
|
type: matchingComponent.type.name,
|
|
352
426
|
name: matchingComponent.fullName,
|
|
427
|
+
ignored,
|
|
353
428
|
});
|
|
354
429
|
}
|
|
355
430
|
});
|
|
@@ -359,35 +434,27 @@ class SourceTracking extends kit_1.AsyncCreatable {
|
|
|
359
434
|
? Array.from(new Set(elementMap.values())).filter((changeResult) => changeResult.name && changeResult.type)
|
|
360
435
|
: Array.from(new Set(elementMap.values()));
|
|
361
436
|
}
|
|
362
|
-
async
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const
|
|
366
|
-
state: '
|
|
367
|
-
|
|
368
|
-
|
|
437
|
+
async getLocalStatusRows() {
|
|
438
|
+
await this.ensureLocalTracking();
|
|
439
|
+
let results = [];
|
|
440
|
+
const localDeletes = this.populateTypesAndNames({
|
|
441
|
+
elements: await this.getChanges({ origin: 'local', state: 'delete', format: 'ChangeResult' }),
|
|
442
|
+
excludeUnresolvable: true,
|
|
443
|
+
resolveDeleted: true,
|
|
444
|
+
useFsForceIgnore: false,
|
|
369
445
|
});
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
remoteChanges.map((change) => {
|
|
375
|
-
var _a;
|
|
376
|
-
(_a = change.filenames) === null || _a === void 0 ? void 0 : _a.map((filename) => {
|
|
377
|
-
fileNameIndex.set(filename, change);
|
|
378
|
-
});
|
|
446
|
+
const localAdds = this.populateTypesAndNames({
|
|
447
|
+
elements: await this.getChanges({ origin: 'local', state: 'add', format: 'ChangeResult' }),
|
|
448
|
+
excludeUnresolvable: true,
|
|
449
|
+
useFsForceIgnore: false,
|
|
379
450
|
});
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
if (fileNameIndex.has(filename)) {
|
|
385
|
-
conflicts.add({ ...fileNameIndex.get(filename) });
|
|
386
|
-
}
|
|
387
|
-
});
|
|
451
|
+
const localModifies = this.populateTypesAndNames({
|
|
452
|
+
elements: await this.getChanges({ origin: 'local', state: 'modify', format: 'ChangeResult' }),
|
|
453
|
+
excludeUnresolvable: true,
|
|
454
|
+
useFsForceIgnore: false,
|
|
388
455
|
});
|
|
389
|
-
|
|
390
|
-
return
|
|
456
|
+
results = results.concat(localAdds.flatMap((item) => this.localChangesToOutputRow(item, 'add')), localModifies.flatMap((item) => this.localChangesToOutputRow(item, 'modify')), localDeletes.flatMap((item) => this.localChangesToOutputRow(item, 'delete')));
|
|
457
|
+
return results;
|
|
391
458
|
}
|
|
392
459
|
/**
|
|
393
460
|
* uses SDR to translate remote metadata records into local file paths
|
|
@@ -405,9 +472,11 @@ class SourceTracking extends kit_1.AsyncCreatable {
|
|
|
405
472
|
const remoteChangesAsComponentSet = new source_deploy_retrieve_1.ComponentSet(remoteChangesAsComponentLike);
|
|
406
473
|
this.logger.debug(` the generated component set has ${remoteChangesAsComponentSet.size.toString()} items`);
|
|
407
474
|
if (remoteChangesAsComponentSet.size < elements.length) {
|
|
475
|
+
// iterate the elements to see which ones didn't make it into the component set
|
|
408
476
|
throw new Error(`unable to generate complete component set for ${elements
|
|
409
|
-
.
|
|
410
|
-
.
|
|
477
|
+
.filter((element) => !remoteChangesAsComponentSet.has({ type: element === null || element === void 0 ? void 0 : element.type, fullName: element === null || element === void 0 ? void 0 : element.name }))
|
|
478
|
+
.map((element) => `${element.name} (${element.type})`)
|
|
479
|
+
.join(os_1.EOL)}`);
|
|
411
480
|
}
|
|
412
481
|
const matchingLocalSourceComponentsSet = source_deploy_retrieve_1.ComponentSet.fromSource({
|
|
413
482
|
fsPaths: this.packagesDirs.map((dir) => dir.path),
|
|
@@ -417,16 +486,17 @@ class SourceTracking extends kit_1.AsyncCreatable {
|
|
|
417
486
|
// make it simpler to find things later
|
|
418
487
|
const elementMap = new Map();
|
|
419
488
|
elements.map((element) => {
|
|
420
|
-
elementMap.set((0,
|
|
489
|
+
elementMap.set((0, functions_1.getKeyFromObject)(element), element);
|
|
421
490
|
});
|
|
422
491
|
// iterates the local components and sets their filenames
|
|
423
492
|
for (const matchingComponent of matchingLocalSourceComponentsSet.getSourceComponents().toArray()) {
|
|
424
493
|
if (matchingComponent.fullName && matchingComponent.type.name) {
|
|
425
494
|
this.logger.debug(`${matchingComponent.fullName}|${matchingComponent.type.name} matches ${matchingComponent.xml} and maybe ${matchingComponent.walkContent().toString()}`);
|
|
426
|
-
const key = (0,
|
|
495
|
+
const key = (0, functions_1.getMetadataKey)(matchingComponent.type.name, matchingComponent.fullName);
|
|
427
496
|
elementMap.set(key, {
|
|
428
497
|
...elementMap.get(key),
|
|
429
498
|
modified: true,
|
|
499
|
+
origin: 'remote',
|
|
430
500
|
filenames: [matchingComponent.xml, ...matchingComponent.walkContent()].filter((filename) => filename),
|
|
431
501
|
});
|
|
432
502
|
}
|
|
@@ -436,13 +506,80 @@ class SourceTracking extends kit_1.AsyncCreatable {
|
|
|
436
506
|
ensureRelative(filePath) {
|
|
437
507
|
return path.isAbsolute(filePath) ? path.relative(this.projectPath, filePath) : filePath;
|
|
438
508
|
}
|
|
509
|
+
async getLocalChangesAsFilenames(state) {
|
|
510
|
+
if (state === 'modify') {
|
|
511
|
+
return this.localRepo.getModifyFilenames();
|
|
512
|
+
}
|
|
513
|
+
if (state === 'nondelete') {
|
|
514
|
+
return this.localRepo.getNonDeleteFilenames();
|
|
515
|
+
}
|
|
516
|
+
if (state === 'delete') {
|
|
517
|
+
return this.localRepo.getDeleteFilenames();
|
|
518
|
+
}
|
|
519
|
+
if (state === 'add') {
|
|
520
|
+
return this.localRepo.getAddFilenames();
|
|
521
|
+
}
|
|
522
|
+
throw new Error(`unable to get local changes for state ${state}`);
|
|
523
|
+
}
|
|
524
|
+
localChangesToOutputRow(input, localType) {
|
|
525
|
+
var _a, _b, _c;
|
|
526
|
+
this.logger.debug('converting ChangeResult to a row', input);
|
|
527
|
+
const baseObject = {
|
|
528
|
+
type: (_a = input.type) !== null && _a !== void 0 ? _a : '',
|
|
529
|
+
origin: 'local',
|
|
530
|
+
state: localType,
|
|
531
|
+
fullName: (_b = input.name) !== null && _b !== void 0 ? _b : '',
|
|
532
|
+
// ignored property will be set in populateTypesAndNames
|
|
533
|
+
ignored: (_c = input.ignored) !== null && _c !== void 0 ? _c : false,
|
|
534
|
+
};
|
|
535
|
+
if (input.filenames) {
|
|
536
|
+
return input.filenames.map((filename) => ({
|
|
537
|
+
...baseObject,
|
|
538
|
+
filePath: filename,
|
|
539
|
+
origin: 'local',
|
|
540
|
+
}));
|
|
541
|
+
}
|
|
542
|
+
throw new Error('no filenames found for local ChangeResult');
|
|
543
|
+
}
|
|
544
|
+
// this will eventually have async call to figure out the target file locations for remote changes
|
|
545
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
546
|
+
async remoteChangesToOutputRows(input) {
|
|
547
|
+
var _a, _b, _c, _d;
|
|
548
|
+
this.logger.debug('converting ChangeResult to a row', input);
|
|
549
|
+
this.forceIgnore = (_a = this.forceIgnore) !== null && _a !== void 0 ? _a : source_deploy_retrieve_1.ForceIgnore.findAndCreate(this.project.getDefaultPackage().path);
|
|
550
|
+
const baseObject = {
|
|
551
|
+
type: (_b = input.type) !== null && _b !== void 0 ? _b : '',
|
|
552
|
+
origin: input.origin,
|
|
553
|
+
state: stateFromChangeResult(input),
|
|
554
|
+
fullName: (_c = input.name) !== null && _c !== void 0 ? _c : '',
|
|
555
|
+
};
|
|
556
|
+
// it's easy to check ignores if the filePaths exist locally
|
|
557
|
+
if ((_d = input.filenames) === null || _d === void 0 ? void 0 : _d.length) {
|
|
558
|
+
return input.filenames.map((filename) => ({
|
|
559
|
+
...baseObject,
|
|
560
|
+
filePath: filename,
|
|
561
|
+
ignored: this.forceIgnore.denies(filename),
|
|
562
|
+
}));
|
|
563
|
+
}
|
|
564
|
+
// when the file doesn't exist locally, there are no filePaths
|
|
565
|
+
// So we can't say whether it's ignored or not
|
|
566
|
+
return [baseObject];
|
|
567
|
+
}
|
|
439
568
|
}
|
|
440
569
|
exports.SourceTracking = SourceTracking;
|
|
441
|
-
const
|
|
442
|
-
|
|
570
|
+
const remoteFilterByState = {
|
|
571
|
+
add: (change) => !change.deleted && !change.modified,
|
|
572
|
+
modify: (change) => change.modified === true,
|
|
573
|
+
delete: (change) => change.deleted === true,
|
|
574
|
+
nondelete: (change) => !change.deleted,
|
|
443
575
|
};
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
576
|
+
const stateFromChangeResult = (input) => {
|
|
577
|
+
if (input.deleted) {
|
|
578
|
+
return 'delete';
|
|
579
|
+
}
|
|
580
|
+
if (input.modified) {
|
|
581
|
+
return 'modify';
|
|
582
|
+
}
|
|
583
|
+
return 'add';
|
|
447
584
|
};
|
|
448
585
|
//# sourceMappingURL=sourceTracking.js.map
|