5
5
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6
6
*/
7
7
import { dirname , join } from 'node:path' ;
8
- import { JsonMap } from '@salesforce/ts-types' ;
8
+ import { AnyJson , JsonMap , getString , isJsonMap } from '@salesforce/ts-types' ;
9
9
import { ensureArray } from '@salesforce/kit' ;
10
10
import { Messages } from '@salesforce/core' ;
11
11
import { MetadataComponent , SourceComponent } from '../../resolve' ;
@@ -70,9 +70,10 @@ export class DecomposedMetadataTransformer extends BaseMetadataTransformer {
70
70
if ( ! childType ) {
71
71
throw messages . createError ( 'error_missing_child_type_definition' , [ type . name , childTypeId ] ) ;
72
72
}
73
- const tagValues = ensureArray ( tagValue ) ;
74
- for ( const value of tagValues as [ { fullName : string ; name : string } ] ) {
75
- const entryName = value . fullName || value . name ;
73
+ const tagValues = ensureArray ( tagValue ) . filter ( isJsonMap ) ;
74
+ // iterate each array member if it's Object-like (ex: customField of a CustomObject)
75
+ for ( const value of tagValues ) {
76
+ const entryName = extractUniqueElementValue ( value , childType . uniqueIdElement ) ;
76
77
const childComponent : MetadataComponent = {
77
78
fullName : `${ parentFullName } .${ entryName } ` ,
78
79
type : childType ,
@@ -196,7 +197,7 @@ export class DecomposedMetadataTransformer extends BaseMetadataTransformer {
196
197
forComponent : MetadataComponent ,
197
198
props : Partial < Omit < DecompositionStateValue , 'origin' > > = { }
198
199
) : void {
199
- const key = ` ${ forComponent . type . name } # ${ forComponent . fullName } ` ;
200
+ const key = getKey ( forComponent ) ;
200
201
const withOrigin = Object . assign ( { origin : forComponent . parent ?? forComponent } , props ) ;
201
202
this . context . decomposition . transactionState . set ( key , {
202
203
...( this . context . decomposition . transactionState . get ( key ) ?? { } ) ,
@@ -205,26 +206,37 @@ export class DecomposedMetadataTransformer extends BaseMetadataTransformer {
205
206
}
206
207
207
208
private getDecomposedState ( forComponent : MetadataComponent ) : DecompositionStateValue | undefined {
208
- const key = `${ forComponent . type . name } #${ forComponent . fullName } ` ;
209
- return this . context . decomposition . transactionState . get ( key ) ;
209
+ return this . context . decomposition . transactionState . get ( getKey ( forComponent ) ) ;
210
210
}
211
211
}
212
212
213
- const getComposedMetadataEntries = async (
214
- component : SourceComponent
215
- ) : Promise < Array < [ string , { fullname ?: string ; name ?: string } ] > > => {
213
+ const getKey = ( component : MetadataComponent ) : string => ` ${ component . type . name } # ${ component . fullName } ` ;
214
+
215
+ const getComposedMetadataEntries = async ( component : SourceComponent ) : Promise < Array < [ string , AnyJson ] > > => {
216
216
const composedMetadata = ( await component . parseXml ( ) ) [ component . type . name ] ;
217
217
// composedMetadata might be undefined if you call toSourceFormat() from a non-source-backed Component
218
218
return composedMetadata ? Object . entries ( composedMetadata ) : [ ] ;
219
219
} ;
220
220
221
+ /** where the file goes if there's nothing to merge with */
221
222
const getDefaultOutput = ( component : MetadataComponent ) : SourcePath => {
222
223
const { parent, fullName, type } = component ;
223
- const [ baseName , childName ] = fullName . split ( '.' ) ;
224
+ const [ baseName , ...tail ] = fullName . split ( '.' ) ;
225
+ // there could be a '.' inside the child name (ex: PermissionSet.FieldPermissions.field uses Obj__c.Field__c)
226
+ // we put folders for each object in (ex) FieldPermissions because of the dot
227
+ const childName = tail . length ? join ( ...tail ) : undefined ;
224
228
const baseComponent = ( parent ?? component ) as SourceComponent ;
225
- let output = ` ${ childName ?? baseName } . ${ component . type . suffix } ${ META_XML_SUFFIX } ` ;
226
- if ( parent ?. type . strategies ?. decomposition === DecompositionStrategy . FolderPerType ) {
227
- output = join ( type . directoryName , output ) ;
228
- }
229
+ const output = join (
230
+ parent ?. type . strategies ?. decomposition === DecompositionStrategy . FolderPerType ? type . directoryName : '' ,
231
+ ` ${ childName ?? baseName } . ${ component . type . suffix } ${ META_XML_SUFFIX } `
232
+ ) ;
229
233
return join ( baseComponent . getPackageRelativePath ( baseName , 'source' ) , output ) ;
230
234
} ;
235
+
236
+ /** handle wide-open reading of values from elements inside any metadata xml file.
237
+ * Return the value of the matching element if supplied, or defaults `fullName` then `name` */
238
+ const extractUniqueElementValue = ( xml : JsonMap , elementName ?: string ) : string | undefined =>
239
+ elementName ? getString ( xml , elementName ) ?? getStandardElements ( xml ) : getStandardElements ( xml ) ;
240
+
241
+ const getStandardElements = ( xml : JsonMap ) : string | undefined =>
242
+ getString ( xml , 'fullName' ) ?? getString ( xml , 'name' ) ?? undefined ;
0 commit comments