6
6
*/
7
7
8
8
import * as path from 'node:path' ;
9
- import { Logger , Messages , SfError , StateAggregator } from '@salesforce/core' ;
9
+ import { AuthInfo , Connection , Logger , Messages , SfError , StateAggregator , trimTo15 } from '@salesforce/core' ;
10
10
import fs from 'graceful-fs' ;
11
11
import { minimatch } from 'minimatch' ;
12
12
import { MetadataComponent } from '../resolve/types' ;
@@ -78,6 +78,8 @@ const getLogger = (): Logger => {
78
78
return logger ;
79
79
} ;
80
80
81
+ const PSEUDO_TYPES = { AGENT : 'Agent' } ;
82
+
81
83
export class ComponentSetBuilder {
82
84
/**
83
85
* Builds a ComponentSet that can be used for source conversion,
@@ -188,7 +190,7 @@ export class ComponentSetBuilder {
188
190
// Resolve metadata entries with an org connection
189
191
if ( org ) {
190
192
componentSet ??= new ComponentSet ( undefined , registry ) ;
191
- const orgComponentSet = await this . resolveOrgComponents ( registry , org , metadata ) ;
193
+ const orgComponentSet = await this . resolveOrgComponents ( registry , options ) ;
192
194
orgComponentSet . toArray ( ) . map ( addToComponentSet ( componentSet ) ) ;
193
195
}
194
196
@@ -204,14 +206,26 @@ export class ComponentSetBuilder {
204
206
205
207
private static async resolveOrgComponents (
206
208
registry : RegistryAccess ,
207
- org : OrgOption ,
208
- metadata ?: MetadataOption
209
+ options : ComponentSetOptions
209
210
) : Promise < ComponentSet > {
211
+ // Get a connection from the OrgOption
212
+ const { apiversion, org, metadata } = options ;
213
+ if ( ! org ) {
214
+ throw SfError . create ( { message : 'ComponentSetBuilder.resolveOrgComponents() requires an OrgOption' } ) ;
215
+ }
216
+ const username = ( await StateAggregator . getInstance ( ) ) . aliases . getUsername ( org . username ) ?? org . username ;
217
+ const connection = await Connection . create ( { authInfo : await AuthInfo . create ( { username } ) } ) ;
218
+ if ( apiversion ) {
219
+ connection . setApiVersion ( apiversion ) ;
220
+ }
221
+
210
222
let mdMap = new Map ( ) as MetadataMap ;
211
- let debugMsg = `Building ComponentSet from metadata in an org using targetUsername: ${ org . username } ` ;
223
+ let debugMsg = `Building ComponentSet from metadata in an org using targetUsername: ${ username } ` ;
212
224
if ( metadata ) {
213
225
if ( metadata . metadataEntries ?. length ) {
214
226
debugMsg += ` filtering on metadata: ${ metadata . metadataEntries . toString ( ) } ` ;
227
+ // Replace pseudo-types from the metadataEntries
228
+ metadata . metadataEntries = await replacePseudoTypes ( metadata . metadataEntries , connection ) ;
215
229
}
216
230
if ( metadata . excludedEntries ?. length ) {
217
231
debugMsg += ` excluding metadata: ${ metadata . excludedEntries . toString ( ) } ` ;
@@ -221,7 +235,7 @@ export class ComponentSetBuilder {
221
235
getLogger ( ) . debug ( debugMsg ) ;
222
236
223
237
return ComponentSet . fromConnection ( {
224
- usernameOrConnection : ( await StateAggregator . getInstance ( ) ) . aliases . getUsername ( org . username ) ?? org . username ,
238
+ usernameOrConnection : connection ,
225
239
componentFilter : getOrgComponentFilter ( org , mdMap , metadata ) ,
226
240
metadataTypes : mdMap . size ? Array . from ( mdMap . keys ( ) ) : undefined ,
227
241
registry,
@@ -369,3 +383,76 @@ const buildMapFromMetadata = (mdOption: MetadataOption, registry: RegistryAccess
369
383
370
384
return mdMap ;
371
385
} ;
386
+
387
+ // Replace pseudo types with actual types.
388
+ const replacePseudoTypes = async ( mdEntries : string [ ] , connection : Connection ) : Promise < string [ ] > => {
389
+ const pseudoEntries : string [ ] [ ] = [ ] ;
390
+ let replacedEntries : string [ ] = [ ] ;
391
+
392
+ mdEntries . map ( ( rawEntry ) => {
393
+ const [ typeName , ...name ] = rawEntry . split ( ':' ) ;
394
+ if ( Object . values ( PSEUDO_TYPES ) . includes ( typeName ) ) {
395
+ pseudoEntries . push ( [ typeName , name . join ( ':' ) . trim ( ) ] ) ;
396
+ } else {
397
+ replacedEntries . push ( rawEntry ) ;
398
+ }
399
+ } ) ;
400
+
401
+ if ( pseudoEntries . length ) {
402
+ await Promise . all (
403
+ pseudoEntries . map ( async ( pseudoEntry ) => {
404
+ const pseudoType = pseudoEntry [ 0 ] ;
405
+ const pseudoName = pseudoEntry [ 1 ] || '*' ;
406
+ getLogger ( ) . debug ( `Converting pseudo-type ${ pseudoType } :${ pseudoName } ` ) ;
407
+ if ( pseudoType === PSEUDO_TYPES . AGENT ) {
408
+ const agentMdEntries = await buildAgentMdEntries ( pseudoName , connection ) ;
409
+ replacedEntries = [ ...replacedEntries , ...agentMdEntries ] ;
410
+ }
411
+ } )
412
+ ) ;
413
+ }
414
+
415
+ return replacedEntries ;
416
+ } ;
417
+
418
+ // From a Bot developer name, get all related BotVersion, GenAiPlanner, and GenAiPlugin metadata.
419
+ const buildAgentMdEntries = async ( botName : string , connection : Connection ) : Promise < string [ ] > => {
420
+ if ( botName === '*' ) {
421
+ // Get all Agent top level metadata
422
+ return Promise . resolve ( [ 'Bot' , 'BotVersion' , 'GenAiPlanner' , 'GenAiPlugin' ] ) ;
423
+ }
424
+
425
+ const mdEntries = [ `Bot:${ botName } ` , `BotVersion:${ botName } .v1` , `GenAiPlanner:${ botName } ` ] ;
426
+
427
+ try {
428
+ // Query for the GenAiPlannerId
429
+ const genAiPlannerIdQuery = `SELECT Id FROM GenAiPlannerDefinition WHERE DeveloperName = '${ botName } '` ;
430
+ const plannerId = ( await connection . singleRecordQuery < { Id : string } > ( genAiPlannerIdQuery , { tooling : true } ) ) . Id ;
431
+
432
+ if ( plannerId ) {
433
+ const plannerId15 = trimTo15 ( plannerId ) ;
434
+ // Query for the GenAiPlugins associated with the 15 char GenAiPlannerId
435
+ const genAiPluginNames = (
436
+ await connection . tooling . query < { DeveloperName : string } > (
437
+ `SELECT DeveloperName FROM GenAiPluginDefinition WHERE DeveloperName LIKE 'p_${ plannerId15 } %'`
438
+ )
439
+ ) . records ;
440
+ if ( genAiPluginNames . length ) {
441
+ genAiPluginNames . map ( ( r ) => mdEntries . push ( `GenAiPlugin:${ r . DeveloperName } ` ) ) ;
442
+ } else {
443
+ getLogger ( ) . debug ( `No GenAiPlugin metadata matches for plannerId: ${ plannerId15 } ` ) ;
444
+ }
445
+ } else {
446
+ getLogger ( ) . debug ( `No GenAiPlanner metadata matches for Bot: ${ botName } ` ) ;
447
+ }
448
+ } catch ( err ) {
449
+ const wrappedErr = SfError . wrap ( err ) ;
450
+ getLogger ( ) . debug ( `Error when querying for GenAiPlugin by Bot name: ${ botName } \n${ wrappedErr . message } ` ) ;
451
+ if ( wrappedErr . stack ) {
452
+ getLogger ( ) . debug ( wrappedErr . stack ) ;
453
+ }
454
+ }
455
+
456
+ // Get specific Agent top level metadata.
457
+ return Promise . resolve ( mdEntries ) ;
458
+ } ;
0 commit comments