@@ -29,8 +29,18 @@ export type ManifestOption = {
2929} ;
3030
3131type MetadataOption = {
32+ /**
33+ * Array of metadata type:name pairs to include in the ComponentSet.
34+ */
3235 metadataEntries : string [ ] ;
36+ /**
37+ * Array of filesystem directory paths to search for local metadata to include in the ComponentSet.
38+ */
3339 directoryPaths : string [ ] ;
40+ /**
41+ * Array of metadata type:name pairs to exclude from the ComponentSet.
42+ */
43+ excludedEntries ?: string [ ] ;
3444 /**
3545 * Array of metadata type:name pairs to delete before the deploy. Use of wildcards is not allowed.
3646 */
@@ -60,6 +70,14 @@ export type ComponentSetOptions = {
6070
6171type MetadataMap = Map < string , string [ ] > ;
6272
73+ let logger : Logger ;
74+ const getLogger = ( ) : Logger => {
75+ if ( ! logger ) {
76+ logger = Logger . childFromRoot ( 'ComponentSetBuilder' ) ;
77+ }
78+ return logger ;
79+ } ;
80+
6381export class ComponentSetBuilder {
6482 /**
6583 * Builds a ComponentSet that can be used for source conversion,
@@ -71,14 +89,13 @@ export class ComponentSetBuilder {
7189 */
7290
7391 public static async build ( options : ComponentSetOptions ) : Promise < ComponentSet > {
74- const logger = Logger . childFromRoot ( 'componentSetBuilder' ) ;
7592 let componentSet : ComponentSet | undefined ;
7693
7794 const { sourcepath, manifest, metadata, packagenames, org } = options ;
7895 const registry = new RegistryAccess ( undefined , options . projectDir ) ;
7996
8097 if ( sourcepath ) {
81- logger . debug ( `Building ComponentSet from sourcepath: ${ sourcepath . join ( ', ' ) } ` ) ;
98+ getLogger ( ) . debug ( `Building ComponentSet from sourcepath: ${ sourcepath . join ( ', ' ) } ` ) ;
8299 const fsPaths = sourcepath . map ( validateAndResolvePath ) ;
83100 componentSet = ComponentSet . fromSource ( {
84101 fsPaths,
@@ -88,16 +105,16 @@ export class ComponentSetBuilder {
88105
89106 // Return empty ComponentSet and use packageNames in the connection via `.retrieve` options
90107 if ( packagenames ) {
91- logger . debug ( `Building ComponentSet for packagenames: ${ packagenames . toString ( ) } ` ) ;
108+ getLogger ( ) . debug ( `Building ComponentSet for packagenames: ${ packagenames . toString ( ) } ` ) ;
92109 componentSet ??= new ComponentSet ( undefined , registry ) ;
93110 }
94111
95112 // Resolve manifest with source in package directories.
96113 if ( manifest ) {
97- logger . debug ( `Building ComponentSet from manifest: ${ manifest . manifestPath } ` ) ;
114+ getLogger ( ) . debug ( `Building ComponentSet from manifest: ${ manifest . manifestPath } ` ) ;
98115 assertFileExists ( manifest . manifestPath ) ;
99116
100- logger . debug ( `Searching in packageDir: ${ manifest . directoryPaths . join ( ', ' ) } for matching metadata` ) ;
117+ getLogger ( ) . debug ( `Searching in packageDir: ${ manifest . directoryPaths . join ( ', ' ) } for matching metadata` ) ;
101118 componentSet = await ComponentSet . fromManifest ( {
102119 manifestPath : manifest . manifestPath ,
103120 resolveSourcePaths : manifest . directoryPaths ,
@@ -108,9 +125,10 @@ export class ComponentSetBuilder {
108125 } ) ;
109126 }
110127
111- // Resolve metadata entries with source in package directories.
112- if ( metadata ) {
113- logger . debug ( `Building ComponentSet from metadata: ${ metadata . metadataEntries . toString ( ) } ` ) ;
128+ // Resolve metadata entries with source in package directories, unless we are building a ComponentSet
129+ // from metadata in an org.
130+ if ( metadata && ! org ) {
131+ getLogger ( ) . debug ( `Building ComponentSet from metadata: ${ metadata . metadataEntries . toString ( ) } ` ) ;
114132 const directoryPaths = metadata . directoryPaths ;
115133 componentSet ??= new ComponentSet ( undefined , registry ) ;
116134 const componentSetFilter = new ComponentSet ( undefined , registry ) ;
@@ -122,7 +140,7 @@ export class ComponentSetBuilder {
122140 . map ( addToComponentSet ( componentSet ) )
123141 . map ( addToComponentSet ( componentSetFilter ) ) ;
124142
125- logger . debug ( `Searching for matching metadata in directories: ${ directoryPaths . join ( ', ' ) } ` ) ;
143+ getLogger ( ) . debug ( `Searching for matching metadata in directories: ${ directoryPaths . join ( ', ' ) } ` ) ;
126144
127145 // add destructive changes if defined. Because these are deletes, all entries
128146 // are resolved to SourceComponents
@@ -170,25 +188,8 @@ export class ComponentSetBuilder {
170188 // Resolve metadata entries with an org connection
171189 if ( org ) {
172190 componentSet ??= new ComponentSet ( undefined , registry ) ;
173-
174- logger . debug (
175- `Building ComponentSet from targetUsername: ${ org . username } ${
176- metadata ? `filtered by metadata: ${ metadata . metadataEntries . toString ( ) } ` : ''
177- } `
178- ) ;
179-
180- const mdMap = metadata
181- ? buildMapFromComponents ( metadata . metadataEntries . map ( entryToTypeAndName ( registry ) ) )
182- : ( new Map ( ) as MetadataMap ) ;
183-
184- const fromConnection = await ComponentSet . fromConnection ( {
185- usernameOrConnection : ( await StateAggregator . getInstance ( ) ) . aliases . getUsername ( org . username ) ?? org . username ,
186- componentFilter : getOrgComponentFilter ( org , mdMap , metadata ) ,
187- metadataTypes : mdMap . size ? Array . from ( mdMap . keys ( ) ) : undefined ,
188- registry,
189- } ) ;
190-
191- fromConnection . toArray ( ) . map ( addToComponentSet ( componentSet ) ) ;
191+ const orgComponentSet = await this . resolveOrgComponents ( registry , org , metadata ) ;
192+ orgComponentSet . toArray ( ) . map ( addToComponentSet ( componentSet ) ) ;
192193 }
193194
194195 // there should have been a componentSet created by this point.
@@ -197,9 +198,35 @@ export class ComponentSetBuilder {
197198 componentSet . sourceApiVersion ??= options . sourceapiversion ;
198199 componentSet . projectDirectory = options . projectDir ;
199200
200- logComponents ( logger , componentSet ) ;
201+ logComponents ( componentSet ) ;
201202 return componentSet ;
202203 }
204+
205+ private static async resolveOrgComponents (
206+ registry : RegistryAccess ,
207+ org : OrgOption ,
208+ metadata ?: MetadataOption
209+ ) : Promise < ComponentSet > {
210+ let mdMap = new Map ( ) as MetadataMap ;
211+ let debugMsg = `Building ComponentSet from metadata in an org using targetUsername: ${ org . username } ` ;
212+ if ( metadata ) {
213+ if ( metadata . metadataEntries ?. length ) {
214+ debugMsg += ` filtering on metadata: ${ metadata . metadataEntries . toString ( ) } ` ;
215+ }
216+ if ( metadata . excludedEntries ?. length ) {
217+ debugMsg += ` excluding metadata: ${ metadata . excludedEntries . toString ( ) } ` ;
218+ }
219+ mdMap = buildMapFromMetadata ( metadata , registry ) ;
220+ }
221+ getLogger ( ) . debug ( debugMsg ) ;
222+
223+ return ComponentSet . fromConnection ( {
224+ usernameOrConnection : ( await StateAggregator . getInstance ( ) ) . aliases . getUsername ( org . username ) ?? org . username ,
225+ componentFilter : getOrgComponentFilter ( org , mdMap , metadata ) ,
226+ metadataTypes : mdMap . size ? Array . from ( mdMap . keys ( ) ) : undefined ,
227+ registry,
228+ } ) ;
229+ }
203230}
204231
205232const addToComponentSet =
@@ -234,27 +261,27 @@ const assertNoWildcardInDestructiveEntries = (mdEntry: MetadataTypeAndMetadataNa
234261
235262/** This is only for debug output of matched files based on the command flags.
236263 * It will log up to 20 file matches. */
237- const logComponents = ( logger : Logger , componentSet : ComponentSet ) : void => {
238- logger . debug ( `Matching metadata files (${ componentSet . size } ):` ) ;
264+ const logComponents = ( componentSet : ComponentSet ) : void => {
265+ getLogger ( ) . debug ( `Matching metadata files (${ componentSet . size } ):` ) ;
239266
240267 const components = componentSet . getSourceComponents ( ) . toArray ( ) ;
241268
242269 components
243270 . slice ( 0 , 20 )
244271 . map ( ( cmp ) => cmp . content ?? cmp . xml ?? cmp . fullName )
245- . map ( ( m ) => logger . debug ( m ) ) ;
246- if ( components . length > 20 ) logger . debug ( `(showing 20 of ${ componentSet . size } matches)` ) ;
272+ . map ( ( m ) => getLogger ( ) . debug ( m ) ) ;
273+ if ( components . length > 20 ) getLogger ( ) . debug ( `(showing 20 of ${ componentSet . size } matches)` ) ;
247274
248- logger . debug ( `ComponentSet apiVersion = ${ componentSet . apiVersion ?? '<not set>' } ` ) ;
249- logger . debug ( `ComponentSet sourceApiVersion = ${ componentSet . sourceApiVersion ?? '<not set>' } ` ) ;
275+ getLogger ( ) . debug ( `ComponentSet apiVersion = ${ componentSet . apiVersion ?? '<not set>' } ` ) ;
276+ getLogger ( ) . debug ( `ComponentSet sourceApiVersion = ${ componentSet . sourceApiVersion ?? '<not set>' } ` ) ;
250277} ;
251278
252279const getOrgComponentFilter = (
253280 org : OrgOption ,
254281 mdMap : MetadataMap ,
255282 metadata ?: MetadataOption
256283) : FromConnectionOptions [ 'componentFilter' ] =>
257- metadata
284+ metadata ?. metadataEntries ?. length
258285 ? ( component : Partial < FileProperties > ) : boolean => {
259286 if ( component . type && component . fullName ) {
260287 const mdMapEntry = mdMap . get ( component . type ) ;
@@ -312,11 +339,33 @@ const typeAndNameToMetadataComponents =
312339 . filter ( ( cs ) => minimatch ( cs . fullName , metadataName ) )
313340 : [ { type, fullName : metadataName } ] ;
314341
315- // TODO: use Map.groupBy when it's available
316- const buildMapFromComponents = ( components : MetadataTypeAndMetadataName [ ] ) : MetadataMap => {
342+ const buildMapFromMetadata = ( mdOption : MetadataOption , registry : RegistryAccess ) : MetadataMap => {
317343 const mdMap : MetadataMap = new Map < string , string [ ] > ( ) ;
318- components . map ( ( cmp ) => {
319- mdMap . set ( cmp . type . name , [ ...( mdMap . get ( cmp . type . name ) ?? [ ] ) , cmp . metadataName ] ) ;
320- } ) ;
344+
345+ // Add metadata type entries we were told to include
346+ if ( mdOption . metadataEntries ?. length ) {
347+ mdOption . metadataEntries . map ( entryToTypeAndName ( registry ) ) . map ( ( cmp ) => {
348+ mdMap . set ( cmp . type . name , [ ...( mdMap . get ( cmp . type . name ) ?? [ ] ) , cmp . metadataName ] ) ;
349+ } ) ;
350+ }
351+
352+ // Build an array of excluded types from the options
353+ if ( mdOption . excludedEntries ?. length ) {
354+ const excludedTypes : string [ ] = [ ] ;
355+ mdOption . excludedEntries . map ( entryToTypeAndName ( registry ) ) . map ( ( cmp ) => {
356+ if ( cmp . metadataName === '*' ) {
357+ excludedTypes . push ( cmp . type . name ) ;
358+ }
359+ } ) ;
360+ if ( mdMap . size === 0 ) {
361+ // we are excluding specific metadata types from all supported types
362+ Object . values ( registry . getRegistry ( ) . types ) . map ( ( t ) => {
363+ if ( ! excludedTypes . includes ( t . name ) ) {
364+ mdMap . set ( t . name , [ ] ) ;
365+ }
366+ } ) ;
367+ }
368+ }
369+
321370 return mdMap ;
322371} ;
0 commit comments