9
9
10
10
import * as path from 'path' ;
11
11
import { join as pathJoin } from 'path' ;
12
- import { ConfigFile , Connection , fs , Logger , Org , SfdxError } from '@salesforce/core' ;
12
+ import { ConfigFile , fs , Logger , Org , SfdxError , Messages } from '@salesforce/core' ;
13
13
import { Dictionary , Optional } from '@salesforce/ts-types' ;
14
- import { Duration , env , sleep , toNumber } from '@salesforce/kit' ;
14
+ import { Duration , env , toNumber } from '@salesforce/kit' ;
15
+ import { retryDecorator } from 'ts-retry-promise' ;
15
16
16
17
export type MemberRevision = {
17
18
serverRevisionCounter : number ;
@@ -33,6 +34,11 @@ export type ChangeElement = {
33
34
deleted ?: boolean ;
34
35
} ;
35
36
37
+ interface Contents {
38
+ serverMaxRevisionCounter : number ;
39
+ sourceMembers : Dictionary < MemberRevision > ;
40
+ }
41
+
36
42
export namespace RemoteSourceTrackingService {
37
43
// Constructor Options for RemoteSourceTrackingService.
38
44
export interface Options extends ConfigFile . Options {
@@ -84,14 +90,7 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
84
90
private static remoteSourceTrackingServiceDictionary : Dictionary < RemoteSourceTrackingService > = { } ;
85
91
protected logger ! : Logger ;
86
92
private org ! : Org ;
87
- private readonly FIRST_REVISION_COUNTER_API_VERSION : string = '47.0' ;
88
- private conn ! : Connection ;
89
- private currentApiVersion ! : string ;
90
93
private isSourceTrackedOrg = true ;
91
- protected contents ! : {
92
- serverMaxRevisionCounter : number ;
93
- sourceMembers : { [ key : string ] : MemberRevision } ;
94
- } ;
95
94
96
95
// A short term cache (within the same process) of query results based on a revision.
97
96
// Useful for source:pull, which makes 3 of the same queries; during status, building manifests, after pull success.
@@ -111,7 +110,7 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
111
110
if ( ! this . remoteSourceTrackingServiceDictionary [ options . username ] ) {
112
111
this . remoteSourceTrackingServiceDictionary [ options . username ] = await RemoteSourceTrackingService . create ( options ) ;
113
112
}
114
- return this . remoteSourceTrackingServiceDictionary [ options . username ] ;
113
+ return this . remoteSourceTrackingServiceDictionary [ options . username ] as RemoteSourceTrackingService ;
115
114
}
116
115
117
116
/**
@@ -132,8 +131,6 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
132
131
this . options . filename = RemoteSourceTrackingService . getFileName ( ) ;
133
132
this . org = await Org . create ( { aliasOrUsername : this . options . username } ) ;
134
133
this . logger = await Logger . child ( this . constructor . name ) ;
135
- this . conn = this . org . getConnection ( ) ;
136
- this . currentApiVersion = this . conn . getApiVersion ( ) ;
137
134
138
135
try {
139
136
await super . init ( ) ;
@@ -162,7 +159,7 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
162
159
// for SourceMembers. If a certain error is thrown during the query we won't try to do
163
160
// source tracking for this org. Calling querySourceMembersFrom() has the extra benefit
164
161
// of caching the query so we don't have to make an identical request in the same process.
165
- await this . querySourceMembersFrom ( 0 ) ;
162
+ await this . querySourceMembersFrom ( { fromRevision : 0 } ) ;
166
163
167
164
this . logger . debug ( 'Initializing source tracking state' ) ;
168
165
contents . serverMaxRevisionCounter = 0 ;
@@ -254,7 +251,7 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
254
251
if ( toRevision != null ) {
255
252
members = await this . querySourceMembersTo ( toRevision ) ;
256
253
} else {
257
- members = await this . querySourceMembersFrom ( 0 ) ;
254
+ members = await this . querySourceMembersFrom ( { fromRevision : 0 } ) ;
258
255
}
259
256
260
257
await this . trackSourceMembers ( members , true ) ;
@@ -273,19 +270,19 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
273
270
}
274
271
275
272
private getServerMaxRevision ( ) : number {
276
- return this [ 'contents' ] . serverMaxRevisionCounter ;
273
+ return ( this [ 'contents' ] as Contents ) . serverMaxRevisionCounter ;
277
274
}
278
275
279
276
private setServerMaxRevision ( revision = 0 ) : void {
280
- this [ 'contents' ] . serverMaxRevisionCounter = revision ;
277
+ ( this [ 'contents' ] as Contents ) . serverMaxRevisionCounter = revision ;
281
278
}
282
279
283
280
private getSourceMembers ( ) : Dictionary < MemberRevision > {
284
- return this [ 'contents' ] . sourceMembers ;
281
+ return ( this [ 'contents' ] as Contents ) . sourceMembers ;
285
282
}
286
283
287
284
private initSourceMembers ( ) : void {
288
- this [ 'contents' ] . sourceMembers = { } ;
285
+ ( this [ 'contents' ] as Contents ) . sourceMembers = { } ;
289
286
}
290
287
291
288
// Return a tracked element as MemberRevision data.
@@ -294,7 +291,7 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
294
291
}
295
292
296
293
private setMemberRevision ( key : string , sourceMember : MemberRevision ) : void {
297
- this . getContents ( ) . sourceMembers [ key ] = sourceMember ;
294
+ ( this . getContents ( ) as unknown as Contents ) . sourceMembers [ key ] = sourceMember ;
298
295
}
299
296
300
297
// Adds the given SourceMembers to the list of tracked MemberRevisions, optionally updating
@@ -439,51 +436,52 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
439
436
this . logger . debug ( `Computed SourceMember polling timeout of ${ pollingTimeout } s` ) ;
440
437
}
441
438
442
- const pollStartTime = Date . now ( ) ;
443
- let pollEndTime : number ;
444
- let totalPollTime = 0 ;
445
439
const fromRevision = this . getServerMaxRevision ( ) ;
446
440
this . logger . debug (
447
441
`Polling for ${ memberNames . length } SourceMembers from revision ${ fromRevision } with timeout of ${ pollingTimeout } s`
448
442
) ;
449
443
450
- let pollAttempts = 1 ;
444
+ let pollAttempts = 0 ;
451
445
446
+ // we need to keep asking the server for sourceMembers until time runs out OR we find everything in matches
452
447
const matches = new Set ( memberNames ) ;
453
-
454
- retry ;
448
+ let sourceMembers ;
455
449
const poll = async ( ) : Promise < SourceMember [ ] > => {
456
- const allMembers = await this . querySourceMembersFrom ( fromRevision , pollAttempts !== 1 , false ) ;
450
+ pollAttempts += 1 ;
451
+ sourceMembers = await this . querySourceMembersFrom ( {
452
+ fromRevision,
453
+ quiet : pollAttempts !== 1 ,
454
+ useCache : false ,
455
+ } ) ;
457
456
458
- for ( const member of allMembers ) {
457
+ for ( const member of sourceMembers ) {
459
458
matches . delete ( member . MemberName ) ;
460
459
}
461
460
462
461
this . logger . debug (
463
462
`[${ pollAttempts } ] Found ${ memberNames . length - matches . size } of ${ memberNames . length } SourceMembers`
464
463
) ;
465
- pollEndTime = Date . now ( ) ;
466
- totalPollTime = Math . round ( ( pollEndTime - pollStartTime ) / 1000 ) || 1 ;
467
- if ( matches . size === 0 || totalPollTime >= pollingTimeout ) {
468
- return allMembers ;
464
+ if ( matches . size === 0 ) {
465
+ return sourceMembers ;
469
466
}
470
467
471
468
if ( matches . size < 20 ) {
472
- this . logger . debug ( `Still looking for SourceMembers: ${ [ ... matches ] . join ( ',' ) } ` ) ;
469
+ this . logger . debug ( `Still looking for SourceMembers: ${ Array . from ( matches ) . join ( ',' ) } ` ) ;
473
470
}
474
-
475
- await sleep ( Duration . seconds ( 1 ) ) ;
476
- pollAttempts += 1 ;
477
- return poll ( ) ;
471
+ throw new Error ( ) ;
478
472
} ;
479
- const sourceMembers = await poll ( ) ;
480
-
481
- if ( matches . size === 0 ) {
482
- this . logger . debug ( `Retrieved all SourceMember data after ${ totalPollTime } s and ${ pollAttempts } attempts` ) ;
483
- } else {
484
- this . logger . warn ( `Polling for SourceMembers timed out after ${ totalPollTime } s and ${ pollAttempts } attempts` ) ;
473
+ const pollingFunction = retryDecorator ( poll , {
474
+ timeout : pollingTimeout * 1000 ,
475
+ delay : 1000 ,
476
+ retries : 'INFINITELY' ,
477
+ } ) ;
478
+ try {
479
+ await pollingFunction ( ) ;
480
+ this . logger . debug ( `Retrieved all SourceMember data after ${ pollAttempts } attempts` ) ;
481
+ } catch {
482
+ this . logger . warn ( `Polling for SourceMembers timed out after ${ pollAttempts } attempts` ) ;
485
483
if ( matches . size < 51 ) {
486
- this . logger . debug ( `Could not find ${ matches . size } SourceMembers: ${ [ ... matches ] . join ( ',' ) } ` ) ;
484
+ this . logger . debug ( `Could not find ${ matches . size } SourceMembers: ${ Array . from ( matches ) . join ( ',' ) } ` ) ;
487
485
} else {
488
486
this . logger . debug ( `Could not find SourceMembers for ${ matches . size } components` ) ;
489
487
}
@@ -497,7 +495,11 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
497
495
await this . trackSourceMembers ( sourceMembers , true ) ;
498
496
}
499
497
500
- private async querySourceMembersFrom ( fromRevision ?: number , quiet = false , useCache = true ) : Promise < SourceMember [ ] > {
498
+ private async querySourceMembersFrom ( {
499
+ fromRevision,
500
+ quiet = false ,
501
+ useCache = true ,
502
+ } : { fromRevision ?: number ; quiet ?: boolean ; useCache ?: boolean } = { } ) : Promise < SourceMember [ ] > {
501
503
const rev = fromRevision != null ? fromRevision : this . getServerMaxRevision ( ) ;
502
504
503
505
if ( useCache ) {
@@ -519,27 +521,21 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
519
521
520
522
private async querySourceMembersTo ( toRevision : number , quiet = false ) : Promise < SourceMember [ ] > {
521
523
const query = `SELECT MemberType, MemberName, IsNameObsolete, RevisionCounter FROM SourceMember WHERE RevisionCounter <= ${ toRevision } ` ;
522
- return this . query ( query , quiet ) ;
524
+ return this . query < SourceMember > ( query , quiet ) ;
523
525
}
524
526
525
527
private async query < T > ( query : string , quiet = false ) : Promise < T [ ] > {
526
- // to switch to using RevisionCounter - apiVersion > 46.0
527
- // set the api version of the connection to 47.0, query, revert api version
528
528
if ( ! this . isSourceTrackedOrg ) {
529
- throw SfdxError . create ( 'salesforce-alm' , 'source' , 'NonSourceTrackedOrgError' ) ;
529
+ Messages . importMessagesDirectory ( __dirname ) ;
530
+ this . messages = Messages . loadMessages ( '@salesforce/source-tracking' , 'source' ) ;
531
+ throw SfdxError . create ( '@salesforce/source-tracking' , 'source' , 'NonSourceTrackedOrgError' ) ;
530
532
}
531
533
if ( ! quiet ) {
532
534
this . logger . debug ( query ) ;
533
535
}
534
536
535
- let results ;
536
- if ( parseFloat ( this . currentApiVersion ) < parseFloat ( this . FIRST_REVISION_COUNTER_API_VERSION ) ) {
537
- this . conn . setApiVersion ( this . FIRST_REVISION_COUNTER_API_VERSION ) ;
538
- results = await this . conn . tooling . autoFetchQuery < T > ( query ) ;
539
- this . conn . setApiVersion ( this . currentApiVersion ) ;
540
- } else {
541
- results = await this . conn . tooling . autoFetchQuery < T > ( query ) ;
542
- }
537
+ const results = await this . org . getConnection ( ) . tooling . autoFetchQuery < T > ( query ) ;
538
+
543
539
return results . records ;
544
540
}
545
541
}
0 commit comments