@@ -17,6 +17,7 @@ import {
17
17
SfProject ,
18
18
} from '@salesforce/core' ;
19
19
import { isString } from '@salesforce/ts-types' ;
20
+ import { objectHasSomeRealValues } from '../utils/decomposed' ;
20
21
import { MetadataApiDeploy , MetadataApiDeployOptions } from '../client/metadataApiDeploy' ;
21
22
import { MetadataApiRetrieve } from '../client/metadataApiRetrieve' ;
22
23
import type { MetadataApiRetrieveOptions } from '../client/types' ;
@@ -406,74 +407,60 @@ export class ComponentSet extends LazyCollection<MetadataComponent> {
406
407
* @returns Object representation of a package manifest
407
408
*/
408
409
public async getObject ( destructiveType ?: DestructiveChangesType ) : Promise < PackageManifestObject > {
409
- const version = await this . getApiVersion ( ) ;
410
-
411
410
// If this ComponentSet has components marked for delete, we need to
412
411
// only include those components in a destructiveChanges.xml and
413
412
// all other components in the regular manifest.
414
- let components = this . components ;
415
- if ( this . getTypesOfDestructiveChanges ( ) . length ) {
416
- components = destructiveType ? this . destructiveComponents [ destructiveType ] : this . manifestComponents ;
417
- }
413
+ const components = this . getTypesOfDestructiveChanges ( ) . length
414
+ ? destructiveType
415
+ ? this . destructiveComponents [ destructiveType ]
416
+ : this . manifestComponents
417
+ : this . components ;
418
418
419
- const typeMap = new Map < string , string [ ] > ( ) ;
419
+ const typeMap = new Map < string , Set < string > > ( ) ;
420
420
421
- const addToTypeMap = ( type : MetadataType , fullName : string ) : void => {
422
- if ( type . isAddressable !== false ) {
423
- const typeName = type . name ;
424
- if ( ! typeMap . has ( typeName ) ) {
425
- typeMap . set ( typeName , [ ] ) ;
426
- }
427
- const typeEntry = typeMap . get ( typeName ) ;
428
- if ( fullName === ComponentSet . WILDCARD && ! type . supportsWildcardAndName && ! destructiveType ) {
429
- // if the type doesn't support mixed wildcards and specific names, overwrite the names to be a wildcard
430
- typeMap . set ( typeName , [ fullName ] ) ;
431
- } else if (
432
- typeEntry &&
433
- ! typeEntry . includes ( fullName ) &&
434
- ( ! typeEntry . includes ( ComponentSet . WILDCARD ) || type . supportsWildcardAndName )
435
- ) {
436
- // if the type supports both wildcards and names, add them regardless
437
- typeMap . get ( typeName ) ?. push ( fullName ) ;
438
- }
439
- }
440
- } ;
441
-
442
- for ( const key of components . keys ( ) ) {
421
+ [ ...components . entries ( ) ] . map ( ( [ key , cmpMap ] ) => {
443
422
const [ typeId , fullName ] = splitOnFirstDelimiter ( key ) ;
444
- let type = this . registry . getTypeByName ( typeId ) ;
445
-
446
- if ( type . folderContentType ) {
447
- type = this . registry . getTypeByName ( type . folderContentType ) ;
448
- }
449
- addToTypeMap (
450
- type ,
451
- // they're reassembled like CustomLabels.MyLabel
452
- this . registry . getParentType ( type . name ) ?. strategies ?. recomposition === 'startEmpty' && fullName . includes ( '.' )
453
- ? fullName . split ( '.' ) [ 1 ]
454
- : fullName
455
- ) ;
423
+ const type = this . registry . getTypeByName ( typeId ) ;
456
424
457
425
// Add children
458
- const componentMap = components . get ( key ) ;
459
- if ( componentMap ) {
460
- for ( const comp of componentMap . values ( ) ) {
461
- for ( const child of comp . getChildren ( ) ) {
462
- addToTypeMap ( child . type , child . fullName ) ;
463
- }
426
+ [ ...( cmpMap ?. values ( ) ?? [ ] ) ]
427
+ . flatMap ( ( c ) => c . getChildren ( ) )
428
+ . map ( ( child ) => addToTypeMap ( { typeMap, type : child . type , fullName : child . fullName , destructiveType } ) ) ;
429
+
430
+ // logic: if this is a decomposed type, skip its inclusion in the manifest if the parent is "empty"
431
+ if (
432
+ type . strategies ?. transformer === 'decomposed' &&
433
+ // exclude (ex: CustomObjectTranslation) where there are no addressable children
434
+ Object . values ( type . children ?. types ?? { } ) . some ( ( t ) => t . unaddressableWithoutParent !== true ) &&
435
+ Object . values ( type . children ?. types ?? { } ) . some ( ( t ) => t . isAddressable !== false )
436
+ ) {
437
+ const parentComp = [ ...( cmpMap ?. values ( ) ?? [ ] ) ] . find ( ( c ) => c . fullName === fullName ) ;
438
+ if ( parentComp ?. xml && ! objectHasSomeRealValues ( type ) ( parentComp . parseXmlSync ( ) ) ) {
439
+ return ;
464
440
}
465
441
}
466
- }
442
+
443
+ addToTypeMap ( {
444
+ typeMap,
445
+ type : type . folderContentType ? this . registry . getTypeByName ( type . folderContentType ) : type ,
446
+ fullName :
447
+ this . registry . getParentType ( type . name ) ?. strategies ?. recomposition === 'startEmpty' && fullName . includes ( '.' )
448
+ ? // they're reassembled like CustomLabels.MyLabel
449
+ fullName . split ( '.' ) [ 1 ]
450
+ : fullName ,
451
+ destructiveType,
452
+ } ) ;
453
+ } ) ;
467
454
468
455
const typeMembers = Array . from ( typeMap . entries ( ) )
469
- . map ( ( [ typeName , members ] ) => ( { members : members . sort ( ) , name : typeName } ) )
456
+ . map ( ( [ typeName , members ] ) => ( { members : [ ... members ] . sort ( ) , name : typeName } ) )
470
457
. sort ( ( a , b ) => ( a . name > b . name ? 1 : - 1 ) ) ;
471
458
472
459
return {
473
460
Package : {
474
461
...{
475
462
types : typeMembers ,
476
- version,
463
+ version : await this . getApiVersion ( ) ,
477
464
} ,
478
465
...( this . fullName ? { fullName : this . fullName } : { } ) ,
479
466
} ,
@@ -750,3 +737,28 @@ const splitOnFirstDelimiter = (input: string): [string, string] => {
750
737
const indexOfSplitChar = input . indexOf ( KEY_DELIMITER ) ;
751
738
return [ input . substring ( 0 , indexOfSplitChar ) , input . substring ( indexOfSplitChar + 1 ) ] ;
752
739
} ;
740
+
741
+ /** side effect: mutates the typeMap property */
742
+ const addToTypeMap = ( {
743
+ typeMap,
744
+ type,
745
+ fullName,
746
+ destructiveType,
747
+ } : {
748
+ typeMap : Map < string , Set < string > > ;
749
+ type : MetadataType ;
750
+ fullName : string ;
751
+ destructiveType ?: DestructiveChangesType ;
752
+ } ) : void => {
753
+ if ( type . isAddressable === false ) return ;
754
+ if ( fullName === ComponentSet . WILDCARD && ! type . supportsWildcardAndName && ! destructiveType ) {
755
+ // if the type doesn't support mixed wildcards and specific names, overwrite the names to be a wildcard
756
+ typeMap . set ( type . name , new Set ( [ fullName ] ) ) ;
757
+ return ;
758
+ }
759
+ const existing = typeMap . get ( type . name ) ?? new Set < string > ( ) ;
760
+ if ( ! existing . has ( ComponentSet . WILDCARD ) || type . supportsWildcardAndName ) {
761
+ // if the type supports both wildcards and names, add them regardless
762
+ typeMap . set ( type . name , existing . add ( fullName ) ) ;
763
+ }
764
+ } ;
0 commit comments