diff --git a/src/resolve/adapters/baseSourceAdapter.ts b/src/resolve/adapters/baseSourceAdapter.ts index 6d75ad472..65ed3ceee 100644 --- a/src/resolve/adapters/baseSourceAdapter.ts +++ b/src/resolve/adapters/baseSourceAdapter.ts @@ -110,9 +110,12 @@ export abstract class BaseSourceAdapter implements SourceAdapter { const typeDirName = basename(this.type.inFolder ? dirname(parentPath) : parentPath); const nameMatchesParent = basename(parentPath) === metaXml.fullName; const inTypeDir = typeDirName === this.type.directoryName; - // if the parent folder name matches the fullName OR parent folder name is - // metadata type's directory name, it's a root metadata xml. - isRootMetadataXml = nameMatchesParent || inTypeDir; + const rootSuffixes = [this.type.suffix, this.type.legacySuffix].filter(Boolean); + const suffixMatchesRoot = rootSuffixes.includes(metaXml.suffix); + // Decomposed children can share the parent fullName (for example, + // MyPermissionSet.applicationVisibility-meta.xml). The suffix must + // identify the parent before the directory name can confirm it. + isRootMetadataXml = suffixMatchesRoot && (nameMatchesParent || inTypeDir); } else { isRootMetadataXml = true; } diff --git a/src/resolve/adapters/mixedContentSourceAdapter.ts b/src/resolve/adapters/mixedContentSourceAdapter.ts index c6fba2485..550015da6 100644 --- a/src/resolve/adapters/mixedContentSourceAdapter.ts +++ b/src/resolve/adapters/mixedContentSourceAdapter.ts @@ -16,7 +16,7 @@ import { dirname, basename, sep, join } from 'node:path'; import { Messages } from '@salesforce/core/messages'; import { SfError } from '@salesforce/core/sfError'; -import { baseName } from '../../utils/path'; +import { baseName, parseMetadataXml } from '../../utils/path'; import { SourcePath } from '../../common/types'; import { SourceComponent } from '../sourceComponent'; import { BaseSourceAdapter } from './baseSourceAdapter'; @@ -55,6 +55,19 @@ export class MixedContentSourceAdapter extends BaseSourceAdapter { protected getRootMetadataXmlPath(trigger: SourcePath): SourcePath | undefined { if (this.ownFolder) { const componentRoot = this.trimPathToContent(trigger); + + const rootSuffixes = [this.type.suffix, this.type.legacySuffix].filter( + (suffix): suffix is string => typeof suffix === 'string' + ); + const rootFile = this.tree.readDirectory(componentRoot).find((entry) => { + const metadata = parseMetadataXml(join(componentRoot, entry)); + return metadata?.suffix !== undefined && rootSuffixes.includes(metadata.suffix); + }); + + if (rootFile) { + return join(componentRoot, rootFile); + } + return this.tree.find('metadataXml', basename(componentRoot), componentRoot); } return this.findMetadataFromContent(trigger); diff --git a/test/resolve/metadataResolver.test.ts b/test/resolve/metadataResolver.test.ts index 8ab5ac973..a5ebb0785 100644 --- a/test/resolve/metadataResolver.test.ts +++ b/test/resolve/metadataResolver.test.ts @@ -183,6 +183,16 @@ describe('MetadataResolver', () => { expect(components2[0].type.name).to.equal('PermissionSet'); }); + it('Should resolve the Beta2 Permission Set parent when resolving its directory', () => { + const resolver = new MetadataResolver(regAccPermissionSet, SOURCE_FORMAT_PS.tree); + const permissionSetDirectory = join('main', 'default', 'permissionsets', 'myPS'); + const components = resolver.getComponentsFromPath(permissionSetDirectory); + + expect(components).to.have.lengthOf(1); + expect(components[0].type.name).to.equal('PermissionSet'); + expect(components[0].xml).to.equal(join(permissionSetDirectory, 'myPS.permissionset-meta.xml')); + }); + it('Should determine type for metadata file with known suffix and strictDirectoryName', () => { // CustomSite is an example. The conditions are: // 1. Type has "strictDirectoryName": true