diff --git a/METADATA_SUPPORT.md b/METADATA_SUPPORT.md index c11e5dbcdc..00089c1738 100644 --- a/METADATA_SUPPORT.md +++ b/METADATA_SUPPORT.md @@ -4,7 +4,7 @@ This list compares metadata types found in Salesforce v54 with the [metadata reg This repository is used by both the Salesforce CLIs and Salesforce's VSCode Extensions. -Currently, there are 420/465 supported metadata types. +Currently, there are 421/465 supported metadata types. For status on any existing gaps, please search or file an issue in the [Salesforce CLI issues only repo](https://github.com/forcedotcom/cli/issues). To contribute a new metadata type, please see the [Contributing Metadata Types to the Registry](./contributing/metadata.md) @@ -178,7 +178,7 @@ To contribute a new metadata type, please see the [Contributing Metadata Types t |EmailIntegrationSettings|✅|| |EmailServicesFunction|✅|| |EmailTemplate|✅|| -|EmailTemplateFolder|❌|Not supported, but support could be added| +|EmailTemplateFolder|✅|| |EmailTemplateSettings|✅|| |EmbeddedServiceBranding|✅|| |EmbeddedServiceConfig|✅|| @@ -489,6 +489,7 @@ v55 introduces the following new types. Here's their current level of support |ExternalDataTranObject|❌|Not supported, but support could be added| |RegisteredExternalService|❌|Not supported, but support could be added| |StreamingAppDataConnector|❌|Not supported, but support could be added| +|VoiceSettings|✅|| ## Additional Types diff --git a/src/client/metadataApiDeploy.ts b/src/client/metadataApiDeploy.ts index 9e0603dad6..d0b2d7140d 100644 --- a/src/client/metadataApiDeploy.ts +++ b/src/client/metadataApiDeploy.ts @@ -197,6 +197,10 @@ export class DeployResult implements MetadataTransferResult { // strip document extension from fullName message.fullName = join(dirname(message.fullName), basename(message.fullName, extname(message.fullName))); break; + // Treat emailTemplateFolder as EmailFolder + case registry.types.emailtemplatefolder.name: + message.componentType = registry.types.emailfolder.name; + break; default: } return message; diff --git a/src/registry/metadataRegistry.json b/src/registry/metadataRegistry.json index 60e36fb962..b39b56ea8f 100644 --- a/src/registry/metadataRegistry.json +++ b/src/registry/metadataRegistry.json @@ -1921,6 +1921,16 @@ "strictDirectoryName": false, "folderContentType": "emailtemplate" }, + "emailtemplatefolder": { + "id": "emailtemplatefolder", + "name": "EmailTemplateFolder", + "suffix": "emailTemplateFolder", + "directoryName": "email", + "inFolder": false, + "strictDirectoryName": false, + "folderContentType": "emailtemplate", + "aliasFor": "emailfolder" + }, "inboundnetworkconnection": { "id": "inboundnetworkconnection", "name": "InboundNetworkConnection", diff --git a/src/registry/nonSupportedTypes.ts b/src/registry/nonSupportedTypes.ts index 9f7806f3bc..efcb7e962d 100644 --- a/src/registry/nonSupportedTypes.ts +++ b/src/registry/nonSupportedTypes.ts @@ -31,8 +31,6 @@ export const settings = [ 'botSettings', // have not successfully deployed this because of licensing errors when deploying settings ]; export const metadataTypes = [ - 'EmailTemplateFolder', // not a real addressable type (parent of email template) - // things that don't show up in describe so far 'PicklistValue', // only existed in v37, so it's hard to describe! 'AppointmentAssignmentPolicy', // not in describe? diff --git a/src/registry/registryAccess.ts b/src/registry/registryAccess.ts index e298baf6db..44eaa34310 100644 --- a/src/registry/registryAccess.ts +++ b/src/registry/registryAccess.ts @@ -16,6 +16,7 @@ export class RegistryAccess { private strictFolderTypes: MetadataType[]; private folderContentTypes: MetadataType[]; + private aliasTypes: MetadataType[]; public constructor(registry: MetadataRegistry = defaultRegistry) { this.registry = registry; @@ -40,7 +41,10 @@ export class RegistryAccess { if (!this.registry.types[lower]) { throw new RegistryError('error_missing_type_definition', lower); } - return this.registry.types[lower]; + // redirect via alias + return this.registry.types[lower].aliasFor + ? this.registry.types[this.registry.types[lower].aliasFor] + : this.registry.types[lower]; } /** @@ -64,7 +68,8 @@ export class RegistryAccess { * @returns The first metadata type object that fulfills the predicate */ public findType(predicate: (type: MetadataType) => boolean): MetadataType { - return Object.values(this.registry.types).find(predicate); + const firstMatch = Object.values(this.registry.types).find(predicate); + return firstMatch.aliasFor ? this.registry.types[firstMatch.aliasFor] : firstMatch; } /** @@ -93,16 +98,28 @@ export class RegistryAccess { */ public getFolderContentTypes(): MetadataType[] { if (!this.folderContentTypes) { - this.folderContentTypes = []; - for (const type of Object.values(this.registry.types)) { - if (type.folderContentType) { - this.folderContentTypes.push(type); - } - } + this.folderContentTypes = Object.values(this.registry.types).filter( + (type) => type.folderContentType && !type.aliasFor + ); } return this.folderContentTypes; } + /** + * Query for the types that have the aliasFor property defined. + * E.g., EmailTemplateFolder + * + * @see {@link MetadataType.aliasFor} + * + * @returns An array of metadata type objects that have aliasFor + */ + public getAliasTypes(): MetadataType[] { + if (!this.aliasTypes) { + this.aliasTypes = Object.values(this.registry.types).filter((type) => type.aliasFor); + } + return this.aliasTypes; + } + public get apiVersion(): string { return this.registry.apiVersion; } diff --git a/src/registry/types.ts b/src/registry/types.ts index 44b8297bd4..4be2eba892 100644 --- a/src/registry/types.ts +++ b/src/registry/types.ts @@ -106,6 +106,11 @@ export interface MetadataType { ``` */ supportsWildcardAndName?: boolean; + + /** + * Whenever this type is requested, return the aliasFor type instead + */ + aliasFor?: string; /** * Type definitions for child types, if the type has any. * diff --git a/test/registry/registryAccess.test.ts b/test/registry/registryAccess.test.ts index 167779f657..41ffee2f79 100644 --- a/test/registry/registryAccess.test.ts +++ b/test/registry/registryAccess.test.ts @@ -16,6 +16,10 @@ describe('RegistryAccess', () => { }); describe('getTypeByName', () => { + it('should return alias of a type when one exists', () => { + expect(registryAccess.getTypeByName('EmailTemplateFolder')).to.deep.equal(registry.types.emailfolder); + }); + it('should fetch type regardless of casing', () => { expect(registryAccess.getTypeByName('apexclass')).to.deep.equal(registry.types.apexclass); }); @@ -55,6 +59,10 @@ describe('RegistryAccess', () => { const foundType = registryAccess.findType((type: MetadataType) => type.suffix === 'objectTranslation'); expect(foundType).to.deep.equal(registry.types.customobjecttranslation); }); + it('should resolve aliases', () => { + const foundType = registryAccess.findType((type: MetadataType) => type.suffix === 'emailTemplateFolder'); + expect(foundType).to.deep.equal(registry.types.emailfolder); + }); }); describe('getStrictFolderTypes', () => { @@ -66,6 +74,14 @@ describe('RegistryAccess', () => { }); }); + describe('aliasTypes', () => { + it('should return 1 aliases type', () => { + const aliasTypes = registryAccess.getAliasTypes(); + expect(aliasTypes.length).to.equal(1); + expect(aliasTypes[0].name).to.equal('EmailTemplateFolder'); + }); + }); + describe('getFolderContentTypes', () => { it('should return all the types with a folderContentType property defined', () => { const type = registry.types.reportfolder; @@ -74,5 +90,8 @@ describe('RegistryAccess', () => { const type4 = registry.types.emailfolder; expect(registryAccess.getFolderContentTypes()).to.deep.equal([type, type2, type3, type4]); }); + it('should not include EmailTemplateFolder', () => { + expect(registryAccess.getFolderContentTypes()).to.not.deep.include(registry.types.emailtemplatefolder); + }); }); }); diff --git a/test/registry/registryValidation.test.ts b/test/registry/registryValidation.test.ts index e465eef650..a0073edd71 100644 --- a/test/registry/registryValidation.test.ts +++ b/test/registry/registryValidation.test.ts @@ -71,6 +71,18 @@ describe('Registry Validation', () => { }); }); + describe('aliases', () => { + describe('all aliases point to real types', () => { + Object.values(registry.types) + .filter((type) => type.aliasFor) + .forEach((aliasType) => { + it(`${aliasType.name} is aliased to ${aliasType.aliasFor} and that exists`, () => { + expect(registry.types[aliasType.aliasFor]).to.exist; + }); + }); + }); + }); + describe('suffixes', () => { describe('all properties of suffixes match a real parent or child type', () => { Object.entries(registry.suffixes).forEach(([suffix, typeId]) => { @@ -102,7 +114,9 @@ describe('Registry Validation', () => { const suffixMap = new Map(); Object.values(registry.types) - .filter((type) => type.suffix && !type.strictDirectoryName && !knownExceptions.includes(type.name)) + .filter( + (type) => type.suffix && !type.aliasFor && !type.strictDirectoryName && !knownExceptions.includes(type.name) + ) .map((type) => { // mapping for the type's suffix suffixMap.set(type.suffix, type.id);