@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.
- package/CHANGELOG.md +91 -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 +4 -0
- package/lib/shared/guards.js +23 -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 +29 -34
- package/lib/sourceTracking.js +233 -85
- package/package.json +6 -9
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' | '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
|
|
113
|
-
|
|
114
|
-
|
|
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;
|
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
|
*
|
|
@@ -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
|
|
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,
|
|
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
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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) => (
|
|
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) => (
|
|
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
|
|
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.
|
|
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(
|
|
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
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
const
|
|
354
|
-
state: '
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
382
|
-
return
|
|
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
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
.
|
|
402
|
-
.
|
|
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,
|
|
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,
|
|
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
|
|
449
|
-
|
|
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
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|