9
9
10
10
import * as path from 'path' ;
11
11
import * as fs from 'fs' ;
12
+ import { retryDecorator } from 'ts-retry-promise' ;
12
13
import { ConfigFile , Logger , Org , SfdxError , Messages } from '@salesforce/core' ;
14
+ import { ComponentStatus } from '@salesforce/source-deploy-retrieve' ;
13
15
import { Dictionary , Optional } from '@salesforce/ts-types' ;
14
- import { Duration , env , toNumber } from '@salesforce/kit' ;
15
- import { retryDecorator } from 'ts-retry-promise' ;
16
+ import { env , toNumber } from '@salesforce/kit' ;
16
17
import { RemoteSyncInput } from '../shared/types' ;
17
18
import { getMetadataKeyFromFileResponse } from './metadataKeys' ;
18
19
@@ -312,27 +313,15 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
312
313
}
313
314
// Adds the given SourceMembers to the list of tracked MemberRevisions, optionally updating
314
315
// the lastRetrievedFromServer field (sync), and persists the changes to maxRevision.json.
315
- public async trackSourceMembers ( sourceMembers : SourceMember [ ] = [ ] , sync = false ) : Promise < void > {
316
+ public async trackSourceMembers ( sourceMembers : SourceMember [ ] , sync = false ) : Promise < void > {
317
+ if ( sourceMembers . length === 0 ) {
318
+ return ;
319
+ }
316
320
const quiet = sourceMembers . length > 100 ;
317
321
if ( quiet ) {
318
322
this . logger . debug ( `Upserting ${ sourceMembers . length } SourceMembers to maxRevision.json` ) ;
319
323
}
320
324
321
- // A sync with empty sourceMembers means "update all currently tracked elements".
322
- // This is what happens during a source:pull
323
- if ( ! sourceMembers . length && sync ) {
324
- const trackedRevisions = this . getSourceMembers ( ) ;
325
- for ( const key of Object . keys ( trackedRevisions ) ) {
326
- const member = trackedRevisions [ key ] ;
327
- if ( member ) {
328
- member . lastRetrievedFromServer = member . serverRevisionCounter ;
329
- trackedRevisions [ key ] = member ;
330
- }
331
- }
332
- await this . write ( ) ;
333
- return ;
334
- }
335
-
336
325
let serverMaxRevisionCounter = this . getServerMaxRevision ( ) ;
337
326
sourceMembers . forEach ( ( change ) => {
338
327
// try accessing the sourceMembers object at the index of the change's name
@@ -432,67 +421,77 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
432
421
* @param expectedMemberNames Array of metadata names to poll
433
422
* @param pollingTimeout maximum amount of time in seconds to poll for SourceMembers
434
423
*/
435
- public async pollForSourceTracking (
436
- expectedMemberNames : string [ ] ,
437
- pollingTimeout ?: Duration . Unit . SECONDS
438
- ) : Promise < void > {
424
+ public async pollForSourceTracking ( expectedMembers : RemoteSyncInput [ ] ) : Promise < void > {
439
425
if ( env . getBoolean ( 'SFDX_DISABLE_SOURCE_MEMBER_POLLING' , false ) ) {
440
426
this . logger . warn ( 'Not polling for SourceMembers since SFDX_DISABLE_SOURCE_MEMBER_POLLING = true.' ) ;
441
427
return ;
442
428
}
443
429
444
- if ( expectedMemberNames . length === 0 ) {
430
+ if ( expectedMembers . length === 0 ) {
445
431
// Don't bother polling if we're not matching SourceMembers
446
432
return ;
447
433
}
448
-
449
- const overriddenTimeout = toNumber ( env . getString ( 'SFDX_SOURCE_MEMBER_POLLING_TIMEOUT' , '0' ) ) ;
450
- if ( overriddenTimeout > 0 ) {
451
- this . logger . debug ( `Overriding SourceMember polling timeout to ${ overriddenTimeout } ` ) ;
452
- pollingTimeout = overriddenTimeout ;
453
- }
454
-
455
- // Calculate a polling timeout for SourceMembers based on the number of
456
- // member names being polled plus a buffer of 5 seconds. This will
457
- // wait 50s for each 1000 components, plus 5s.
458
- if ( ! pollingTimeout ) {
459
- pollingTimeout = Math . ceil ( expectedMemberNames . length * 0.05 ) + 5 ;
460
- this . logger . debug ( `Computed SourceMember polling timeout of ${ pollingTimeout } s` ) ;
461
- }
462
-
434
+ const adjustedExpectedMembers = expectedMembers . filter (
435
+ ( fileResponse ) =>
436
+ // unchanged files will never be in the sourceMembers. Not really sure why SDR returns them.
437
+ fileResponse . state !== ComponentStatus . Unchanged &&
438
+ // aura meta.xml aren't tracked as SourceMembers
439
+ ! (
440
+ fileResponse . filePath ?. startsWith ( 'AuraDefinition' ) &&
441
+ fileResponse . filePath ?. endsWith ( '.cmp-meta.xml' ) &&
442
+ // if a listView is the only change inside an object, the object won't have a sourceMember change. We won't wait for those to be found
443
+ fileResponse . type !== 'CustomObject'
444
+ )
445
+ ) ;
463
446
const fromRevision = this . getServerMaxRevision ( ) ;
447
+ const pollingTimeout = this . calculateTimeout ( adjustedExpectedMembers . length ) ;
464
448
this . logger . debug (
465
- `Polling for ${ expectedMemberNames . length } SourceMembers from revision ${ fromRevision } with timeout of ${ pollingTimeout } s`
449
+ `Polling for ${ adjustedExpectedMembers . length } SourceMembers from revision ${ fromRevision } with timeout of ${ pollingTimeout } s`
466
450
) ;
467
451
468
- let pollAttempts = 0 ;
452
+ const outstandingSourceMembers = new Map ( ) ;
469
453
470
- // we need to keep asking the server for sourceMembers until time runs out OR we find everything in matches
471
- const expectedSourceMembers = new Set ( expectedMemberNames ) ;
472
- let sourceMembers ;
473
- const poll = async ( ) : Promise < SourceMember [ ] > => {
454
+ // filter known Source tracking issues
455
+ adjustedExpectedMembers . map ( ( member ) => {
456
+ getMetadataKeyFromFileResponse ( member ) . map ( ( key ) => outstandingSourceMembers . set ( key , member ) ) ;
457
+ } ) ;
458
+
459
+ let pollAttempts = 0 ;
460
+ const poll = async ( ) : Promise < void > => {
474
461
pollAttempts += 1 ; // not used to stop polling, but for debug logging
475
- sourceMembers = await this . querySourceMembersFrom ( {
462
+
463
+ // get sourceMembers since maxRevision
464
+ const queriedMembers = await this . querySourceMembersFrom ( {
476
465
fromRevision,
477
466
quiet : pollAttempts !== 1 ,
478
467
useCache : false ,
479
468
} ) ;
480
469
481
- for ( const member of sourceMembers ) {
482
- expectedSourceMembers . delete ( member . MemberName ) ;
483
- }
470
+ // remove anything returned from the query list
471
+ queriedMembers . map ( ( member ) => {
472
+ outstandingSourceMembers . delete ( getMetadataKey ( member . MemberType , member . MemberName ) ) ;
473
+ } ) ;
484
474
485
475
this . logger . debug (
486
- `[${ pollAttempts } ] Found ${ expectedMemberNames . length - expectedSourceMembers . size } of ${
487
- expectedMemberNames . length
476
+ `[${ pollAttempts } ] Found ${ adjustedExpectedMembers . length - outstandingSourceMembers . size } of ${
477
+ adjustedExpectedMembers . length
488
478
} SourceMembers`
489
479
) ;
490
- if ( expectedSourceMembers . size === 0 ) {
491
- return sourceMembers ;
480
+
481
+ // update but don't sync
482
+ await this . trackSourceMembers ( queriedMembers , false ) ;
483
+
484
+ // exit if all have returned
485
+ if ( outstandingSourceMembers . size === 0 ) {
486
+ return ;
492
487
}
493
488
494
- if ( expectedSourceMembers . size < 20 ) {
495
- this . logger . debug ( `Still looking for SourceMembers: ${ Array . from ( expectedSourceMembers ) . join ( ',' ) } ` ) ;
489
+ if ( outstandingSourceMembers . size < 20 ) {
490
+ this . logger . debug (
491
+ outstandingSourceMembers . size < 20
492
+ ? `Still looking for SourceMembers: ${ Array . from ( outstandingSourceMembers . keys ( ) ) . join ( ',' ) } `
493
+ : `Still looking for ${ outstandingSourceMembers . size } Source Members`
494
+ ) ;
496
495
}
497
496
throw new Error ( ) ;
498
497
} ;
@@ -506,21 +505,31 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
506
505
this . logger . debug ( `Retrieved all SourceMember data after ${ pollAttempts } attempts` ) ;
507
506
} catch {
508
507
this . logger . warn ( `Polling for SourceMembers timed out after ${ pollAttempts } attempts` ) ;
509
- if ( expectedSourceMembers . size < 51 ) {
508
+ if ( outstandingSourceMembers . size < 51 ) {
510
509
this . logger . debug (
511
- `Could not find ${ expectedSourceMembers . size } SourceMembers: ${ Array . from ( expectedSourceMembers ) . join ( ',' ) } `
510
+ `Could not find ${ outstandingSourceMembers . size } SourceMembers: ${ Array . from ( outstandingSourceMembers ) . join (
511
+ ','
512
+ ) } `
512
513
) ;
513
514
} else {
514
- this . logger . debug ( `Could not find SourceMembers for ${ expectedSourceMembers . size } components` ) ;
515
+ this . logger . debug ( `Could not find SourceMembers for ${ outstandingSourceMembers . size } components` ) ;
515
516
}
516
517
}
518
+ }
519
+
520
+ private calculateTimeout ( memberCount : number ) : number {
521
+ const overriddenTimeout = toNumber ( env . getString ( 'SFDX_SOURCE_MEMBER_POLLING_TIMEOUT' , '0' ) ) ;
522
+ if ( overriddenTimeout > 0 ) {
523
+ this . logger . debug ( `Overriding SourceMember polling timeout to ${ overriddenTimeout } ` ) ;
524
+ return overriddenTimeout ;
525
+ }
517
526
518
- // NOTE: we are updating tracking for every SourceMember returned by the query once we match all memberNames
519
- // passed OR polling times out . This does not update SourceMembers of *only* the memberNames passed.
520
- // This means if we ever want to support tracking on source:deploy or source:retrieve we would need
521
- // to update tracking for only the matched SourceMembers. I.e., call trackSourceMembers() passing
522
- // only the SourceMembers that match the memberNames.
523
- await this . trackSourceMembers ( sourceMembers , true ) ;
527
+ // Calculate a polling timeout for SourceMembers based on the number of
528
+ // member names being polled plus a buffer of 5 seconds . This will
529
+ // wait 50s for each 1000 components, plus 5s.
530
+ const pollingTimeout = Math . ceil ( memberCount * 0.05 ) + 5 ;
531
+ this . logger . debug ( `Computed SourceMember polling timeout of ${ pollingTimeout } s` ) ;
532
+ return pollingTimeout ;
524
533
}
525
534
526
535
private async querySourceMembersFrom ( {
@@ -562,8 +571,11 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
562
571
this . logger . debug ( query ) ;
563
572
}
564
573
565
- const results = await this . org . getConnection ( ) . tooling . autoFetchQuery < T > ( query ) ;
566
-
567
- return results . records ;
574
+ try {
575
+ const results = await this . org . getConnection ( ) . tooling . autoFetchQuery < T > ( query ) ;
576
+ return results . records ;
577
+ } catch ( error ) {
578
+ throw SfdxError . wrap ( error as Error ) ;
579
+ }
568
580
}
569
581
}
0 commit comments