From 95200843bdd88497856703d5941fb5ad71053d2f Mon Sep 17 00:00:00 2001 From: Steve Hetzel Date: Fri, 30 Aug 2024 11:54:04 -0600 Subject: [PATCH] fix: update manifest resolver to identify InFolder parents with trailing slashes --- src/resolve/manifestResolver.ts | 36 +++++++++++++---- test/resolve/manifestResolver.test.ts | 58 +++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 7 deletions(-) diff --git a/src/resolve/manifestResolver.ts b/src/resolve/manifestResolver.ts index 00726f8cd2..5967d7ca81 100644 --- a/src/resolve/manifestResolver.ts +++ b/src/resolve/manifestResolver.ts @@ -71,8 +71,8 @@ export class ManifestResolver { const type = this.registry.getTypeByName(typeMembers.name); const parentType = type.folderType ? this.registry.getTypeByName(type.folderType) : undefined; return ensureArray(typeMembers.members).map((fullName, _index, members) => ({ - fullName, - type: parentType && isMemberNestedInFolder(fullName, type, parentType, members) ? parentType : type, + fullName: resolveFullName(fullName, parentType), + type: !parentType ? type : resolveType(fullName, type, members, parentType), })); }); @@ -113,6 +113,33 @@ const getValidatedType = return typeMembers; }; +// Mostly for parents of InFolder types to strip off trailing "/" characters +// in fullNames. Otherwise just returns the fullName. +const resolveFullName = (fullName: string, parentType?: MetadataType): string => + parentType?.folderContentType && fullName.endsWith('/') ? fullName.substring(0, fullName.length - 1) : fullName; + +// Resolve the correct metadata type from metadata entries in a manifest. +// Parents of InFolder types can be detected by looking for a trailing "/" +// character. +const resolveType = ( + fullName: string, + type: MetadataType, + members: string[], + parentType?: MetadataType +): MetadataType => { + // Quick short-circuit for non-parent types and non-folderTypes + if (!parentType || !type.folderType) { + return type; + } + + // Detect parents of InFolder types by looking for a trailing slash on InFolder types + if (parentType?.folderContentType && fullName.endsWith('/')) { + return parentType; + } + + return isMemberNestedInFolder(fullName, type, parentType, members) ? parentType : type; +}; + // Use the folderType instead of the type from the manifest when: // 1. InFolder types: (report, dashboard, emailTemplate, document) // 1a. type.inFolder === true (from metadataRegistry.json) AND @@ -129,11 +156,6 @@ const isMemberNestedInFolder = ( parentType: MetadataType, members: string[] ): boolean => { - // Quick short-circuit for non-folderTypes - if (!type.folderType) { - return false; - } - const isInFolderType = type.inFolder; const isNestedInFolder = !fullName.includes('/') || members.some((m) => m.includes(`${fullName}/`)); const isNonMatchingFolder = parentType && parentType.folderType !== parentType.id; diff --git a/test/resolve/manifestResolver.test.ts b/test/resolve/manifestResolver.test.ts index 6fce8bf9bb..cb24f02344 100644 --- a/test/resolve/manifestResolver.test.ts +++ b/test/resolve/manifestResolver.test.ts @@ -195,6 +195,64 @@ describe('ManifestResolver', () => { expect(result.components).to.deep.equal(expected); }); + it('should resolve nested InFolder types with trailing slashes', async () => { + const registry = new RegistryAccess(); + const reportType = registry.getTypeByName('report'); + const reportFolderType = registry.getTypeByName('reportFolder'); + const folderManifest: VirtualFile = { + name: 'reports-package.xml', + data: Buffer.from(` + + + foo/ + foo/subfoo/ + foo/subfoo/MySubFooReport1 + foo/subfoo/MySubFooReport2 + bar/MyBarReport1 + bar/MyBarReport2 + Report + + 52.0 + \n`), + }; + const tree = new VirtualTreeContainer([ + { + dirPath: '.', + children: [folderManifest], + }, + ]); + const resolver = new ManifestResolver(tree); + const result = await resolver.resolve(folderManifest.name); + const expected: MetadataComponent[] = [ + { + fullName: 'foo', + type: reportFolderType, + }, + { + fullName: 'foo/subfoo', + type: reportFolderType, + }, + { + fullName: 'foo/subfoo/MySubFooReport1', + type: reportType, + }, + { + fullName: 'foo/subfoo/MySubFooReport2', + type: reportType, + }, + { + fullName: 'bar/MyBarReport1', + type: reportType, + }, + { + fullName: 'bar/MyBarReport2', + type: reportType, + }, + ]; + + expect(result.components).to.deep.equal(expected); + }); + it('should resolve folderType types (Territory2*)', async () => { const registry = new RegistryAccess(); const t2ModelType = registry.getTypeByName('Territory2Model');