@salesforce/source-tracking 7.1.25 → 7.2.1
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/lib/shared/functions.d.ts +5 -0
- package/lib/shared/functions.js +14 -2
- package/lib/shared/metadataKeys.js +2 -2
- package/lib/shared/{expectedSourceMembers.d.ts → remote/expectedSourceMembers.d.ts} +1 -1
- package/lib/shared/{expectedSourceMembers.js → remote/expectedSourceMembers.js} +12 -12
- package/lib/shared/remote/fileOperations.d.ts +13 -0
- package/lib/shared/remote/fileOperations.js +94 -0
- package/lib/shared/remote/orgQueries.d.ts +18 -0
- package/lib/shared/remote/orgQueries.js +80 -0
- package/lib/shared/{remoteSourceTrackingService.d.ts → remote/remoteSourceTrackingService.d.ts} +24 -38
- package/lib/shared/{remoteSourceTrackingService.js → remote/remoteSourceTrackingService.js} +84 -148
- package/lib/shared/remote/types.d.ts +39 -0
- package/lib/shared/remote/types.js +19 -0
- package/lib/shared/types.d.ts +4 -13
- package/lib/sourceTracking.js +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
|
@@ -9,7 +9,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
9
9
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.
|
|
12
|
+
exports.remoteChangeElementToChangeResult = exports.RemoteSourceTrackingService = void 0;
|
|
13
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
14
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
15
15
|
const node_os_1 = require("node:os");
|
|
@@ -17,10 +17,11 @@ const ts_retry_promise_1 = require("ts-retry-promise");
|
|
|
17
17
|
const core_1 = require("@salesforce/core");
|
|
18
18
|
const kit_1 = require("@salesforce/kit");
|
|
19
19
|
const ts_types_1 = require("@salesforce/ts-types");
|
|
20
|
-
const metadataKeys_1 = require("
|
|
21
|
-
const functions_1 = require("
|
|
20
|
+
const metadataKeys_1 = require("../metadataKeys");
|
|
21
|
+
const functions_1 = require("../functions");
|
|
22
22
|
const expectedSourceMembers_1 = require("./expectedSourceMembers");
|
|
23
|
-
const
|
|
23
|
+
const fileOperations_1 = require("./fileOperations");
|
|
24
|
+
const orgQueries_1 = require("./orgQueries");
|
|
24
25
|
/*
|
|
25
26
|
* after some results have returned, how many times should we poll for missing sourcemembers
|
|
26
27
|
* even when there is a longer timeout remaining (because the deployment is very large)
|
|
@@ -31,37 +32,37 @@ const CONSECUTIVE_EMPTY_POLLING_RESULT_LIMIT = (core_1.envVars.getNumber('SF_SOU
|
|
|
31
32
|
* This service handles source tracking of metadata between a local project and an org.
|
|
32
33
|
* Source tracking state is persisted to .sfdx/orgs/<orgId>/maxRevision.json.
|
|
33
34
|
* This JSON file keeps track of `SourceMember` objects and the `serverMaxRevisionCounter`,
|
|
34
|
-
* which is the highest `
|
|
35
|
+
* which is the highest `RevisionCounter` value of all the tracked elements.
|
|
35
36
|
*
|
|
36
|
-
*
|
|
37
|
-
* * serverRevisionCounter: the current RevisionCounter on the server for this object
|
|
38
|
-
* * lastRetrievedFromServer: the RevisionCounter last retrieved from the server for this object
|
|
39
|
-
* * memberType: the metadata name of the SourceMember
|
|
40
|
-
* * isNameObsolete: `true` if this object has been deleted in the org
|
|
41
|
-
*
|
|
42
|
-
* ex.
|
|
37
|
+
* See @MemberRevision for the structure of the `MemberRevision` object. It's SourceMember (the tooling sobject) with the additional lastRetrievedFromServer field
|
|
43
38
|
```
|
|
44
39
|
{
|
|
40
|
+
fileVersion: 1,
|
|
45
41
|
serverMaxRevisionCounter: 3,
|
|
46
42
|
sourceMembers: {
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
ApexClass###MyClass: {
|
|
44
|
+
RevisionCounter: 3,
|
|
45
|
+
MemberType: ApexClass,
|
|
46
|
+
...,
|
|
49
47
|
lastRetrievedFromServer: 2,
|
|
50
|
-
memberType: ApexClass,
|
|
51
|
-
isNameObsolete: false
|
|
52
48
|
},
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
CustomObject###Student__c: {
|
|
50
|
+
RevisionCounter: 1,
|
|
51
|
+
MemberType: CustomObject,
|
|
52
|
+
...,
|
|
55
53
|
lastRetrievedFromServer: 1,
|
|
56
|
-
memberType: CustomObject,
|
|
57
|
-
isNameObsolete: false
|
|
58
54
|
}
|
|
59
55
|
}
|
|
60
56
|
}
|
|
61
57
|
```
|
|
62
|
-
* In this example, `
|
|
58
|
+
* In this example, `ApexClass###MyClass` has been changed in the org because the `serverRevisionCounter` is different
|
|
63
59
|
* from the `lastRetrievedFromServer`. When a pull is performed, all of the pulled members will have their counters set
|
|
64
60
|
* to the corresponding `RevisionCounter` from the `SourceMember` of the org.
|
|
61
|
+
*
|
|
62
|
+
* Tracking files are written to the older format described in `MemberRevisionLegacy`
|
|
63
|
+
* if the environment variable CURRENT_FILE_VERSION_ENV is not set to 1
|
|
64
|
+
*
|
|
65
|
+
* The "in memorgy" storage is in MemberRevision format.
|
|
65
66
|
*/
|
|
66
67
|
class RemoteSourceTrackingService {
|
|
67
68
|
/** map of constructed, init'ed instances; key is orgId. It's like a singleton at the org level */
|
|
@@ -74,13 +75,14 @@ class RemoteSourceTrackingService {
|
|
|
74
75
|
// A short term cache (within the same process) of query results based on a revision.
|
|
75
76
|
// Useful for source:pull, which makes 3 of the same queries; during status, building manifests, after pull success.
|
|
76
77
|
queryCache = new Map();
|
|
78
|
+
userQueryCache = new Map();
|
|
77
79
|
/**
|
|
78
80
|
* Initializes the service with existing remote source tracking data, or sets
|
|
79
81
|
* the state to begin source tracking of metadata changes in the org.
|
|
80
82
|
*/
|
|
81
83
|
constructor(options) {
|
|
82
84
|
this.org = options.org;
|
|
83
|
-
this.filePath = node_path_1.default.join(options.projectPath, '.sf', 'orgs', this.org.getOrgId(), FILENAME);
|
|
85
|
+
this.filePath = node_path_1.default.join(options.projectPath, '.sf', 'orgs', this.org.getOrgId(), fileOperations_1.FILENAME);
|
|
84
86
|
}
|
|
85
87
|
/**
|
|
86
88
|
* Get the singleton instance for a given user.
|
|
@@ -104,7 +106,7 @@ class RemoteSourceTrackingService {
|
|
|
104
106
|
* @returns the path of the deleted source tracking file
|
|
105
107
|
*/
|
|
106
108
|
static async delete(orgId) {
|
|
107
|
-
const fileToDelete = getFilePath(orgId);
|
|
109
|
+
const fileToDelete = (0, fileOperations_1.getFilePath)(orgId);
|
|
108
110
|
// the file might not exist, in which case we don't need to delete it
|
|
109
111
|
if (node_fs_1.default.existsSync(fileToDelete)) {
|
|
110
112
|
await node_fs_1.default.promises.unlink(fileToDelete);
|
|
@@ -112,7 +114,7 @@ class RemoteSourceTrackingService {
|
|
|
112
114
|
return node_path_1.default.isAbsolute(fileToDelete) ? fileToDelete : node_path_1.default.join(process.cwd(), fileToDelete);
|
|
113
115
|
}
|
|
114
116
|
/**
|
|
115
|
-
* pass in a
|
|
117
|
+
* pass in a series of SDR FilResponses .\
|
|
116
118
|
* it sets their last retrieved revision to the current revision counter from the server.
|
|
117
119
|
*/
|
|
118
120
|
async syncSpecifiedElements(elements) {
|
|
@@ -130,11 +132,18 @@ class RemoteSourceTrackingService {
|
|
|
130
132
|
this.logger.warn(`found no matching revision for ${metadataKey}`);
|
|
131
133
|
}
|
|
132
134
|
else if (doesNotMatchServer(revision)) {
|
|
133
|
-
quietLogger(`Syncing ${metadataKey} revision from ${revision.lastRetrievedFromServer ?? 'null'} to ${revision.
|
|
134
|
-
this.setMemberRevision(metadataKey, {
|
|
135
|
+
quietLogger(`Syncing ${metadataKey} revision from ${revision.lastRetrievedFromServer ?? 'null'} to ${revision.RevisionCounter}`);
|
|
136
|
+
this.setMemberRevision(metadataKey, {
|
|
137
|
+
...revision,
|
|
138
|
+
lastRetrievedFromServer: revision.RevisionCounter,
|
|
139
|
+
});
|
|
135
140
|
}
|
|
136
141
|
});
|
|
137
|
-
await
|
|
142
|
+
await (0, fileOperations_1.writeTrackingFile)({
|
|
143
|
+
filePath: this.filePath,
|
|
144
|
+
maxCounter: this.serverMaxRevisionCounter,
|
|
145
|
+
members: this.sourceMembers,
|
|
146
|
+
});
|
|
138
147
|
}
|
|
139
148
|
/**
|
|
140
149
|
* Resets source tracking state by first clearing all tracked data, then
|
|
@@ -152,8 +161,14 @@ class RemoteSourceTrackingService {
|
|
|
152
161
|
this.serverMaxRevisionCounter = 0;
|
|
153
162
|
this.sourceMembers = new Map();
|
|
154
163
|
const members = toRevision !== undefined && toRevision !== null
|
|
155
|
-
? await (0,
|
|
156
|
-
: await
|
|
164
|
+
? await (0, orgQueries_1.querySourceMembersTo)(this.org.getConnection(), toRevision)
|
|
165
|
+
: await (0, orgQueries_1.querySourceMembersFrom)({
|
|
166
|
+
fromRevision: 0,
|
|
167
|
+
logger: this.logger,
|
|
168
|
+
userQueryCache: this.userQueryCache,
|
|
169
|
+
queryCache: this.queryCache,
|
|
170
|
+
conn: this.org.getConnection(),
|
|
171
|
+
});
|
|
157
172
|
await this.trackSourceMembers(members, true);
|
|
158
173
|
return members.map((member) => (0, functions_1.getMetadataKey)(member.MemberType, member.MemberName));
|
|
159
174
|
}
|
|
@@ -166,17 +181,21 @@ class RemoteSourceTrackingService {
|
|
|
166
181
|
// to sync the retrieved SourceMembers; meaning it will update the lastRetrievedFromServer
|
|
167
182
|
// field to the SourceMember's RevisionCounter, and update the serverMaxRevisionCounter
|
|
168
183
|
// to the highest RevisionCounter.
|
|
169
|
-
async retrieveUpdates(
|
|
184
|
+
async retrieveUpdates() {
|
|
170
185
|
// Always track new SourceMember data, or update tracking when we sync.
|
|
171
|
-
const queriedSourceMembers = await
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
186
|
+
const queriedSourceMembers = await (0, orgQueries_1.querySourceMembersFrom)({
|
|
187
|
+
fromRevision: this.serverMaxRevisionCounter,
|
|
188
|
+
logger: this.logger,
|
|
189
|
+
userQueryCache: this.userQueryCache,
|
|
190
|
+
queryCache: this.queryCache,
|
|
191
|
+
conn: this.org.getConnection(),
|
|
192
|
+
});
|
|
193
|
+
await this.trackSourceMembers(queriedSourceMembers);
|
|
175
194
|
// Look for any changed that haven't been synced. I.e, the lastRetrievedFromServer
|
|
176
195
|
// does not match the serverRevisionCounter.
|
|
177
|
-
const returnElements = Array.from(this.sourceMembers.
|
|
178
|
-
.filter(
|
|
179
|
-
.map(revisionToRemoteChangeElement);
|
|
196
|
+
const returnElements = Array.from(this.sourceMembers.values())
|
|
197
|
+
.filter(doesNotMatchServer)
|
|
198
|
+
.map(fileOperations_1.revisionToRemoteChangeElement);
|
|
180
199
|
this.logger.debug(returnElements.length
|
|
181
200
|
? `Found ${returnElements.length} elements not synced with org`
|
|
182
201
|
: 'Remote source tracking is up to date');
|
|
@@ -192,8 +211,7 @@ class RemoteSourceTrackingService {
|
|
|
192
211
|
*/
|
|
193
212
|
async pollForSourceTracking(expectedMembers) {
|
|
194
213
|
if (core_1.envVars.getBoolean('SF_DISABLE_SOURCE_MEMBER_POLLING')) {
|
|
195
|
-
this.logger.warn('Not polling for SourceMembers since SF_DISABLE_SOURCE_MEMBER_POLLING = true.');
|
|
196
|
-
return;
|
|
214
|
+
return this.logger.warn('Not polling for SourceMembers since SF_DISABLE_SOURCE_MEMBER_POLLING = true.');
|
|
197
215
|
}
|
|
198
216
|
if (expectedMembers.length === 0) {
|
|
199
217
|
return;
|
|
@@ -202,7 +220,7 @@ class RemoteSourceTrackingService {
|
|
|
202
220
|
const originalOutstandingSize = outstandingSourceMembers.size;
|
|
203
221
|
// this will be the absolute timeout from the start of the poll. We can also exit early if it doesn't look like more results are coming in
|
|
204
222
|
let highestRevisionSoFar = this.serverMaxRevisionCounter;
|
|
205
|
-
const pollingTimeout = (0,
|
|
223
|
+
const pollingTimeout = (0, orgQueries_1.calculateTimeout)(this.logger)(outstandingSourceMembers.size);
|
|
206
224
|
let pollAttempts = 0;
|
|
207
225
|
let consecutiveEmptyResults = 0;
|
|
208
226
|
let someResultsReturned = false;
|
|
@@ -213,10 +231,10 @@ class RemoteSourceTrackingService {
|
|
|
213
231
|
pollAttempts += 1; // not used to stop polling, but for debug logging
|
|
214
232
|
// get sourceMembers added since our most recent max
|
|
215
233
|
// use the "new highest" revision from the last poll that returned results
|
|
216
|
-
const queriedMembers = await
|
|
234
|
+
const queriedMembers = await (0, orgQueries_1.querySourceMembersFrom)({
|
|
235
|
+
conn: this.org.getConnection(),
|
|
217
236
|
fromRevision: highestRevisionSoFar,
|
|
218
|
-
|
|
219
|
-
useCache: false,
|
|
237
|
+
logger: pollAttempts > 1 ? undefined : this.logger,
|
|
220
238
|
});
|
|
221
239
|
if (queriedMembers.length) {
|
|
222
240
|
queriedMembers.map((member) => {
|
|
@@ -299,7 +317,7 @@ ${formatSourceMemberWarnings(outstandingSourceMembers)}`),
|
|
|
299
317
|
}
|
|
300
318
|
}
|
|
301
319
|
/**
|
|
302
|
-
* Adds the given SourceMembers to the list of tracked MemberRevisions,
|
|
320
|
+
* Adds the given SourceMembers to the list of tracked MemberRevisions, optionally updating
|
|
303
321
|
* the lastRetrievedFromServer field (sync), and persists the changes to maxRevision.json.
|
|
304
322
|
*/
|
|
305
323
|
async trackSourceMembers(sourceMembers, sync = false) {
|
|
@@ -315,31 +333,20 @@ ${formatSourceMemberWarnings(outstandingSourceMembers)}`),
|
|
|
315
333
|
// try accessing the sourceMembers object at the index of the change's name
|
|
316
334
|
// if it exists, we'll update the fields - if it doesn't, we'll create and insert it
|
|
317
335
|
const key = (0, functions_1.getMetadataKey)(change.MemberType, change.MemberName);
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
// We are not yet tracking it so we'll insert a new record
|
|
332
|
-
quietLogger(`Inserting ${key} with RevisionCounter: ${change.RevisionCounter}${sync ? ' and syncing' : ''}`);
|
|
333
|
-
}
|
|
334
|
-
// If we are syncing changes then we need to update the lastRetrievedFromServer field to
|
|
335
|
-
// match the RevisionCounter from the SourceMember.
|
|
336
|
-
if (sync) {
|
|
337
|
-
sourceMember.lastRetrievedFromServer = change.RevisionCounter;
|
|
338
|
-
}
|
|
339
|
-
// Update the state with the latest SourceMember data
|
|
340
|
-
this.setMemberRevision(key, sourceMember);
|
|
336
|
+
const sourceMemberFromTracking = this.getSourceMember(key);
|
|
337
|
+
quietLogger(`${sourceMemberFromTracking ? `Updating ${key} to` : `Inserting ${key} with`} RevisionCounter: ${change.RevisionCounter}${sync ? ' and syncing' : ''}`);
|
|
338
|
+
this.setMemberRevision(key, {
|
|
339
|
+
...change,
|
|
340
|
+
// If we are syncing changes then we need to update the lastRetrievedFromServer field to
|
|
341
|
+
// match the RevisionCounter from the SourceMember.
|
|
342
|
+
lastRetrievedFromServer: sync ? change.RevisionCounter : sourceMemberFromTracking?.lastRetrievedFromServer,
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
await (0, fileOperations_1.writeTrackingFile)({
|
|
346
|
+
filePath: this.filePath,
|
|
347
|
+
maxCounter: this.serverMaxRevisionCounter,
|
|
348
|
+
members: this.sourceMembers,
|
|
341
349
|
});
|
|
342
|
-
await this.write();
|
|
343
350
|
}
|
|
344
351
|
/** reads the tracking file and inits the logger and contents */
|
|
345
352
|
async init() {
|
|
@@ -351,7 +358,7 @@ ${formatSourceMemberWarnings(outstandingSourceMembers)}`),
|
|
|
351
358
|
this.logger = core_1.Logger.getRawRootLogger().child({ name: this.constructor.name });
|
|
352
359
|
if (node_fs_1.default.existsSync(this.filePath)) {
|
|
353
360
|
// read the file contents and turn it into the map
|
|
354
|
-
const rawContents = await readFileContents(this.filePath);
|
|
361
|
+
const rawContents = await (0, fileOperations_1.readFileContents)(this.filePath);
|
|
355
362
|
if (rawContents.serverMaxRevisionCounter && rawContents.sourceMembers) {
|
|
356
363
|
this.serverMaxRevisionCounter = rawContents.serverMaxRevisionCounter;
|
|
357
364
|
this.sourceMembers = new Map(Object.entries(rawContents.sourceMembers ?? {}));
|
|
@@ -359,7 +366,11 @@ ${formatSourceMemberWarnings(outstandingSourceMembers)}`),
|
|
|
359
366
|
}
|
|
360
367
|
else {
|
|
361
368
|
// we need to init the file
|
|
362
|
-
await
|
|
369
|
+
await (0, fileOperations_1.writeTrackingFile)({
|
|
370
|
+
filePath: this.filePath,
|
|
371
|
+
maxCounter: this.serverMaxRevisionCounter,
|
|
372
|
+
members: this.sourceMembers,
|
|
373
|
+
});
|
|
363
374
|
}
|
|
364
375
|
return this;
|
|
365
376
|
}
|
|
@@ -373,31 +384,7 @@ ${formatSourceMemberWarnings(outstandingSourceMembers)}`),
|
|
|
373
384
|
const matchingKey = sourceMembers.get(key)
|
|
374
385
|
? key
|
|
375
386
|
: getDecodedKeyIfSourceMembersHas({ sourceMembers, key, logger: this.logger });
|
|
376
|
-
this.sourceMembers.set(matchingKey, sourceMember);
|
|
377
|
-
}
|
|
378
|
-
async querySourceMembersFrom({ fromRevision, quiet = false, useCache = true, } = {}) {
|
|
379
|
-
const rev = fromRevision ?? this.serverMaxRevisionCounter;
|
|
380
|
-
if (useCache) {
|
|
381
|
-
// Check cache first and return if found.
|
|
382
|
-
const cachedQueryResult = this.queryCache.get(rev);
|
|
383
|
-
if (cachedQueryResult) {
|
|
384
|
-
this.logger.debug(`Using cache for SourceMember query for revision ${rev}`);
|
|
385
|
-
return cachedQueryResult;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
// because `serverMaxRevisionCounter` is always updated, we need to select > to catch the most recent change
|
|
389
|
-
const query = `SELECT MemberType, MemberName, IsNameObsolete, RevisionCounter FROM SourceMember WHERE RevisionCounter > ${rev}`;
|
|
390
|
-
this.logger[quiet ? 'silent' : 'debug'](`Query: ${query}`);
|
|
391
|
-
const queryResult = await queryFn(this.org.getConnection(), query);
|
|
392
|
-
this.queryCache.set(rev, queryResult);
|
|
393
|
-
return queryResult;
|
|
394
|
-
}
|
|
395
|
-
async write() {
|
|
396
|
-
const lockResult = await (0, core_1.lockInit)(this.filePath);
|
|
397
|
-
await lockResult.writeAndUnlock(JSON.stringify({
|
|
398
|
-
serverMaxRevisionCounter: this.serverMaxRevisionCounter,
|
|
399
|
-
sourceMembers: Object.fromEntries(this.sourceMembers),
|
|
400
|
-
}, null, 4));
|
|
387
|
+
this.sourceMembers.set(matchingKey, { ...sourceMember, MemberName: decodeURIComponent(sourceMember.MemberName) });
|
|
401
388
|
}
|
|
402
389
|
}
|
|
403
390
|
exports.RemoteSourceTrackingService = RemoteSourceTrackingService;
|
|
@@ -418,11 +405,6 @@ const remoteChangeElementToChangeResult = (rce) => ({
|
|
|
418
405
|
origin: 'remote', // we know they're remote
|
|
419
406
|
});
|
|
420
407
|
exports.remoteChangeElementToChangeResult = remoteChangeElementToChangeResult;
|
|
421
|
-
const revisionToRemoteChangeElement = ([memberKey, memberRevision]) => ({
|
|
422
|
-
type: memberRevision.memberType,
|
|
423
|
-
name: memberKey.replace(`${memberRevision.memberType}__`, ''),
|
|
424
|
-
deleted: memberRevision.isNameObsolete,
|
|
425
|
-
});
|
|
426
408
|
/**
|
|
427
409
|
*
|
|
428
410
|
* iterate SourceMember keys and compare their decoded value with the decoded key.
|
|
@@ -444,47 +426,9 @@ const getDecodedKeyIfSourceMembersHas = ({ key, sourceMembers, logger, }) => {
|
|
|
444
426
|
}
|
|
445
427
|
return key;
|
|
446
428
|
};
|
|
447
|
-
const getFilePath = (orgId) => node_path_1.default.join('.sf', 'orgs', orgId, FILENAME);
|
|
448
|
-
const readFileContents = async (filePath) => {
|
|
449
|
-
try {
|
|
450
|
-
const contents = await node_fs_1.default.promises.readFile(filePath, 'utf8');
|
|
451
|
-
return (0, kit_1.parseJsonMap)(contents, filePath);
|
|
452
|
-
}
|
|
453
|
-
catch (e) {
|
|
454
|
-
core_1.Logger.childFromRoot('remoteSourceTrackingService:readFileContents').debug(`Error reading or parsing file file at ${filePath}. Will treat as an empty file.`, e);
|
|
455
|
-
return {};
|
|
456
|
-
}
|
|
457
|
-
};
|
|
458
|
-
const calculateTimeout = (logger) => (memberCount) => {
|
|
459
|
-
const overriddenTimeout = core_1.envVars.getNumber('SF_SOURCE_MEMBER_POLLING_TIMEOUT', 0);
|
|
460
|
-
if (overriddenTimeout > 0) {
|
|
461
|
-
logger.debug(`Overriding SourceMember polling timeout to ${overriddenTimeout}`);
|
|
462
|
-
return kit_1.Duration.seconds(overriddenTimeout);
|
|
463
|
-
}
|
|
464
|
-
// Calculate a polling timeout for SourceMembers based on the number of
|
|
465
|
-
// member names being polled plus a buffer of 5 seconds. This will
|
|
466
|
-
// wait 50s for each 1000 components, plus 5s.
|
|
467
|
-
const pollingTimeout = Math.ceil(memberCount * 0.05) + 5;
|
|
468
|
-
logger.debug(`Computed SourceMember polling timeout of ${pollingTimeout}s`);
|
|
469
|
-
return kit_1.Duration.seconds(pollingTimeout);
|
|
470
|
-
};
|
|
471
|
-
exports.calculateTimeout = calculateTimeout;
|
|
472
|
-
/** exported only for spy/mock */
|
|
473
|
-
const querySourceMembersTo = async (conn, toRevision) => {
|
|
474
|
-
const query = `SELECT MemberType, MemberName, IsNameObsolete, RevisionCounter FROM SourceMember WHERE RevisionCounter <= ${toRevision}`;
|
|
475
|
-
return queryFn(conn, query);
|
|
476
|
-
};
|
|
477
|
-
exports.querySourceMembersTo = querySourceMembersTo;
|
|
478
|
-
const queryFn = async (conn, query) => {
|
|
479
|
-
try {
|
|
480
|
-
return (await conn.tooling.query(query, { autoFetch: true, maxFetch: 50_000 })).records.map(sourceMemberCorrections);
|
|
481
|
-
}
|
|
482
|
-
catch (error) {
|
|
483
|
-
throw core_1.SfError.wrap(error);
|
|
484
|
-
}
|
|
485
|
-
};
|
|
486
429
|
/** organize by type and format for warning output */
|
|
487
430
|
const formatSourceMemberWarnings = (outstandingSourceMembers) => {
|
|
431
|
+
// TODO: use Map.groupBy when we node22 is minimum
|
|
488
432
|
// ex: CustomObject : [Foo__c, Bar__c]
|
|
489
433
|
const mapByType = Array.from(outstandingSourceMembers.values()).reduce((acc, value) => {
|
|
490
434
|
acc.set(value.type, [...(acc.get(value.type) ?? []), value.fullName]);
|
|
@@ -494,13 +438,5 @@ const formatSourceMemberWarnings = (outstandingSourceMembers) => {
|
|
|
494
438
|
.map(([type, names]) => ` - ${type}: ${names.join(', ')}`)
|
|
495
439
|
.join(node_os_1.EOL);
|
|
496
440
|
};
|
|
497
|
-
const
|
|
498
|
-
const doesNotMatchServer = (member) => member.serverRevisionCounter !== member.lastRetrievedFromServer;
|
|
499
|
-
/** A series of workarounds for server-side bugs. Each bug should be filed against a team, with a WI, so we know when these are fixed and can be removed */
|
|
500
|
-
const sourceMemberCorrections = (sourceMember) => {
|
|
501
|
-
if (sourceMember.MemberType === 'QuickActionDefinition') {
|
|
502
|
-
return { ...sourceMember, MemberType: 'QuickAction' }; // W-15837125
|
|
503
|
-
}
|
|
504
|
-
return sourceMember;
|
|
505
|
-
};
|
|
441
|
+
const doesNotMatchServer = (member) => member.RevisionCounter !== member.lastRetrievedFromServer;
|
|
506
442
|
//# sourceMappingURL=remoteSourceTrackingService.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** represents the contents of the config file stored in 'maxRevision.json' */
|
|
2
|
+
export type ContentsV1 = {
|
|
3
|
+
fileVersion: 1;
|
|
4
|
+
serverMaxRevisionCounter: number;
|
|
5
|
+
sourceMembers: Record<string, MemberRevision>;
|
|
6
|
+
};
|
|
7
|
+
export type ContentsV0 = {
|
|
8
|
+
fileVersion?: 0;
|
|
9
|
+
serverMaxRevisionCounter: number;
|
|
10
|
+
sourceMembers: Record<string, MemberRevisionLegacy>;
|
|
11
|
+
};
|
|
12
|
+
export type SourceMember = {
|
|
13
|
+
MemberType: string;
|
|
14
|
+
MemberName: string;
|
|
15
|
+
/** The change is a delete */
|
|
16
|
+
IsNameObsolete: boolean;
|
|
17
|
+
/** The change is an add (newly created metadata) */
|
|
18
|
+
IsNewMember: boolean;
|
|
19
|
+
RevisionCounter: number;
|
|
20
|
+
/** The recordId of the metadata */
|
|
21
|
+
MemberIdOrName: string;
|
|
22
|
+
/** userID of the person who made change */
|
|
23
|
+
ChangedBy: string;
|
|
24
|
+
};
|
|
25
|
+
export type MemberRevision = SourceMember & {
|
|
26
|
+
/** the last revision retrieved. Used for detecting changes*/
|
|
27
|
+
lastRetrievedFromServer?: number;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* @deprecated replaced by the new MemberRevision
|
|
31
|
+
* used for reading and writing the legacy tracking file format
|
|
32
|
+
*/
|
|
33
|
+
export type MemberRevisionLegacy = {
|
|
34
|
+
memberType: string;
|
|
35
|
+
serverRevisionCounter: number;
|
|
36
|
+
lastRetrievedFromServer: number | null;
|
|
37
|
+
isNameObsolete: boolean;
|
|
38
|
+
};
|
|
39
|
+
export declare const SOURCE_MEMBER_FIELDS: ("MemberType" | "MemberName" | "IsNameObsolete" | "IsNewMember" | "RevisionCounter" | "MemberIdOrName" | "ChangedBy")[];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2023, salesforce.com, inc.
|
|
4
|
+
* All rights reserved.
|
|
5
|
+
* Licensed under the BSD 3-Clause license.
|
|
6
|
+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.SOURCE_MEMBER_FIELDS = void 0;
|
|
10
|
+
exports.SOURCE_MEMBER_FIELDS = [
|
|
11
|
+
'MemberIdOrName',
|
|
12
|
+
'MemberType',
|
|
13
|
+
'MemberName',
|
|
14
|
+
'IsNameObsolete',
|
|
15
|
+
'RevisionCounter',
|
|
16
|
+
'IsNewMember',
|
|
17
|
+
'ChangedBy',
|
|
18
|
+
];
|
|
19
|
+
//# sourceMappingURL=types.js.map
|
package/lib/shared/types.d.ts
CHANGED
|
@@ -19,6 +19,10 @@ export type RemoteChangeElement = {
|
|
|
19
19
|
type: string;
|
|
20
20
|
deleted?: boolean;
|
|
21
21
|
modified?: boolean;
|
|
22
|
+
changedBy: string;
|
|
23
|
+
revisionCounter: number;
|
|
24
|
+
/** the ID of the metadata that was changed. Each metadata type has a different 3-char prefix */
|
|
25
|
+
memberIdOrName: string;
|
|
22
26
|
};
|
|
23
27
|
/**
|
|
24
28
|
* Summary type that supports both local and remote change types
|
|
@@ -28,19 +32,6 @@ export type ChangeResult = Partial<RemoteChangeElement> & {
|
|
|
28
32
|
filenames?: string[];
|
|
29
33
|
ignored?: boolean;
|
|
30
34
|
};
|
|
31
|
-
export type MemberRevision = {
|
|
32
|
-
serverRevisionCounter: number;
|
|
33
|
-
lastRetrievedFromServer: number | null;
|
|
34
|
-
memberType: string;
|
|
35
|
-
isNameObsolete: boolean;
|
|
36
|
-
};
|
|
37
|
-
export type SourceMember = {
|
|
38
|
-
MemberType: string;
|
|
39
|
-
MemberName: string;
|
|
40
|
-
IsNameObsolete: boolean;
|
|
41
|
-
RevisionCounter: number;
|
|
42
|
-
ignored?: boolean;
|
|
43
|
-
};
|
|
44
35
|
export type ConflictResponse = {
|
|
45
36
|
state: 'Conflict';
|
|
46
37
|
fullName: string;
|
package/lib/sourceTracking.js
CHANGED
|
@@ -19,7 +19,7 @@ const source_deploy_retrieve_1 = require("@salesforce/source-deploy-retrieve");
|
|
|
19
19
|
// this is not exported by SDR (see the comments in SDR regarding its limitations)
|
|
20
20
|
const filePathGenerator_1 = require("@salesforce/source-deploy-retrieve/lib/src/utils/filePathGenerator");
|
|
21
21
|
const performance_1 = require("@oclif/core/performance");
|
|
22
|
-
const remoteSourceTrackingService_1 = require("./shared/remoteSourceTrackingService");
|
|
22
|
+
const remoteSourceTrackingService_1 = require("./shared/remote/remoteSourceTrackingService");
|
|
23
23
|
const localShadowRepo_1 = require("./shared/local/localShadowRepo");
|
|
24
24
|
const conflicts_1 = require("./shared/conflicts");
|
|
25
25
|
const guards_1 = require("./shared/guards");
|