diff --git a/messages/envVars.md b/messages/envVars.md index bba841d59..4b1e39f02 100644 --- a/messages/envVars.md +++ b/messages/envVars.md @@ -311,3 +311,7 @@ Deprecated environment variable: %s. Please use %s instead. Deprecated environment variable: %s. Please use %s instead. Your environment has both variables populated, and with different values. The value from %s will be used. + +# sfCapitalizeRecordTypes + +Boolean indicating whether or not to capitalize object settings. diff --git a/src/config/envVars.ts b/src/config/envVars.ts index 911675872..a6a2a5bde 100644 --- a/src/config/envVars.ts +++ b/src/config/envVars.ts @@ -91,6 +91,7 @@ export enum EnvironmentVariable { 'SF_UPDATE_INSTRUCTIONS' = 'SF_UPDATE_INSTRUCTIONS', 'SF_INSTALLER' = 'SF_INSTALLER', 'SF_ENV' = 'SF_ENV', + 'SF_CAPITALIZE_RECORD_TYPES' = 'SF_CAPITALIZE_RECORD_TYPES', } type EnvMetaData = { description: string; @@ -417,6 +418,10 @@ export const SUPPORTED_ENV_VARS: EnvType = { description: getMessage(EnvironmentVariable.SF_ENV), synonymOf: null, }, + [EnvironmentVariable.SF_CAPITALIZE_RECORD_TYPES]: { + description: getMessage(EnvironmentVariable.SF_CAPITALIZE_RECORD_TYPES), + synonymOf: null, + }, }; export class EnvVars extends Env { diff --git a/src/org/orgConfigProperties.ts b/src/org/orgConfigProperties.ts index 7a6825609..2b3c9c94f 100644 --- a/src/org/orgConfigProperties.ts +++ b/src/org/orgConfigProperties.ts @@ -48,9 +48,17 @@ export enum OrgConfigProperties { * The url for the debugger configuration. */ ORG_ISV_DEBUGGER_URL = 'org-isv-debugger-url', + /** + * Capitalize record types when deploying scratch org settings + */ + ORG_CAPITALIZE_RECORD_TYPES = 'org-capitalize-record-types', } export const ORG_CONFIG_ALLOWED_PROPERTIES = [ + { + key: OrgConfigProperties.ORG_CAPITALIZE_RECORD_TYPES, + description: messages.getMessage(OrgConfigProperties.ORG_CUSTOM_METADATA_TEMPLATES), + }, { key: OrgConfigProperties.ORG_CUSTOM_METADATA_TEMPLATES, description: messages.getMessage(OrgConfigProperties.ORG_CUSTOM_METADATA_TEMPLATES), diff --git a/src/org/scratchOrgCreate.ts b/src/org/scratchOrgCreate.ts index ce2cc3e64..7e048d303 100644 --- a/src/org/scratchOrgCreate.ts +++ b/src/org/scratchOrgCreate.ts @@ -4,7 +4,7 @@ * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import { Duration } from '@salesforce/kit'; +import { Duration, toBoolean } from '@salesforce/kit'; import { ensureString } from '@salesforce/ts-types'; import { Messages } from '../messages'; import { Logger } from '../logger/logger'; @@ -229,7 +229,12 @@ export const scratchOrgCreate = async (options: ScratchOrgCreateOptions): Promis }); // gets the scratch org settings (will use in both signup paths AND to deploy the settings) - const settingsGenerator = new SettingsGenerator(); + const settingsGenerator = new SettingsGenerator({ + capitalizeRecordTypes: toBoolean( + (await ConfigAggregator.create()).getInfo('org-capitalize-record-types').value ?? true + ), + }); + const settings = await settingsGenerator.extract(scratchOrgInfo); logger.debug(`the scratch org def file has settings: ${settingsGenerator.hasSettings()}`); diff --git a/src/org/scratchOrgSettingsGenerator.ts b/src/org/scratchOrgSettingsGenerator.ts index 575bf0c42..960c1fc04 100644 --- a/src/org/scratchOrgSettingsGenerator.ts +++ b/src/org/scratchOrgSettingsGenerator.ts @@ -82,13 +82,19 @@ export const createObjectFileContent = ({ return { ...output, ...{ version: apiVersion } }; }; -const calculateBusinessProcess = (objectName: string, defaultRecordType: string): Array => { +const calculateBusinessProcess = ( + objectName: string, + defaultRecordType: string, + capitalizeBusinessProcess: boolean +): Array => { let businessProcessName = null; let businessProcessPicklistVal = null; // These four objects require any record type to specify a "business process"-- // a restricted set of items from a standard picklist on the object. if (['Case', 'Lead', 'Opportunity', 'Solution'].includes(objectName)) { - businessProcessName = upperFirst(defaultRecordType) + 'Process'; + businessProcessName = capitalizeBusinessProcess + ? `${upperFirst(defaultRecordType)}Process` + : `${defaultRecordType}Process`; switch (objectName) { case 'Case': businessProcessPicklistVal = 'New'; @@ -110,7 +116,8 @@ export const createRecordTypeAndBusinessProcessFileContent = ( objectName: string, json: Record, allRecordTypes: string[], - allBusinessProcesses: string[] + allBusinessProcesses: string[], + capitalizeRecordTypes: boolean ): JsonMap => { let output = { '@': { @@ -126,15 +133,23 @@ export const createRecordTypeAndBusinessProcessFileContent = ( }; } - const defaultRecordType = json.defaultRecordType; + const defaultRecordType = capitalizeRecordTypes + ? upperFirst(json.defaultRecordType as string) + : json.defaultRecordType; + if (typeof defaultRecordType === 'string') { // We need to keep track of these globally for when we generate the package XML. - allRecordTypes.push(`${name}.${upperFirst(defaultRecordType)}`); - const [businessProcessName, businessProcessPicklistVal] = calculateBusinessProcess(name, defaultRecordType); + allRecordTypes.push(`${name}.${defaultRecordType}`); + const [businessProcessName, businessProcessPicklistVal] = calculateBusinessProcess( + name, + defaultRecordType, + capitalizeRecordTypes + ); + // Create the record type const recordTypes = { - fullName: upperFirst(defaultRecordType), - label: upperFirst(defaultRecordType), + fullName: defaultRecordType, + label: defaultRecordType, active: true, }; @@ -186,9 +201,16 @@ export default class SettingsGenerator { private allBusinessProcesses: string[] = []; private readonly shapeDirName: string; private readonly packageFilePath: string; - - public constructor(options?: { mdApiTmpDir?: string; shapeDirName?: string; asDirectory?: boolean }) { + private readonly capitalizeRecordTypes: boolean; + + public constructor(options?: { + mdApiTmpDir?: string; + shapeDirName?: string; + asDirectory?: boolean; + capitalizeRecordTypes?: boolean; + }) { this.logger = Logger.childFromRoot('SettingsGenerator'); + this.capitalizeRecordTypes = options?.capitalizeRecordTypes ?? false; // If SFDX_MDAPI_TEMP_DIR is set, copy settings to that dir for people to inspect. const mdApiTmpDir = options?.mdApiTmpDir ?? env.getString('SFDX_MDAPI_TEMP_DIR'); this.shapeDirName = options?.shapeDirName ?? `shape_${Date.now()}`; @@ -344,7 +366,8 @@ export default class SettingsGenerator { item, value, allRecordTypes, - allbusinessProcesses + allbusinessProcesses, + this.capitalizeRecordTypes ); const xml = js2xmlparser.parse('CustomObject', fileContent); return this.writer.addToStore(xml, path.join(objectsDir, upperFirst(item) + '.object')); diff --git a/test/unit/org/scratchOrgSettingsGeneratorTest.ts b/test/unit/org/scratchOrgSettingsGeneratorTest.ts index 9d137f508..b7b1088d1 100644 --- a/test/unit/org/scratchOrgSettingsGeneratorTest.ts +++ b/test/unit/org/scratchOrgSettingsGeneratorTest.ts @@ -639,7 +639,8 @@ describe('scratchOrgSettingsGenerator', () => { 'account', objectSettingsData.account, allRecordTypes, - allbusinessProcesses + allbusinessProcesses, + true ); expect(recordTypeAndBusinessProcessFileContent).to.deep.equal({ '@': { xmlns: 'http://soap.sforce.com/2006/04/metadata' }, @@ -649,6 +650,30 @@ describe('scratchOrgSettingsGenerator', () => { expect(allbusinessProcesses).to.deep.equal([]); }); + it('createRecordTypeAndBusinessProcessFileContent with account type, not capitalized', () => { + const objectSettingsDataLowercaseRecordType = { + account: { + defaultRecordType: 'personAccount', + }, + }; + + const allRecordTypes: string[] = []; + const allbusinessProcesses: string[] = []; + const recordTypeAndBusinessProcessFileContent = createRecordTypeAndBusinessProcessFileContent( + 'account', + objectSettingsDataLowercaseRecordType.account, + allRecordTypes, + allbusinessProcesses, + false + ); + expect(recordTypeAndBusinessProcessFileContent).to.deep.equal({ + '@': { xmlns: 'http://soap.sforce.com/2006/04/metadata' }, + recordTypes: { fullName: 'personAccount', label: 'personAccount', active: true }, + }); + expect(allRecordTypes).to.deep.equal(['Account.personAccount']); + expect(allbusinessProcesses).to.deep.equal([]); + }); + it('createRecordTypeAndBusinessProcessFileContent with opportunity values', () => { const allRecordTypes: string[] = []; const allbusinessProcesses: string[] = []; @@ -656,7 +681,8 @@ describe('scratchOrgSettingsGenerator', () => { 'opportunity', objectSettingsData.opportunity, allRecordTypes, - allbusinessProcesses + allbusinessProcesses, + true ); expect(recordTypeAndBusinessProcessFileContent).to.deep.equal({ '@': { xmlns: 'http://soap.sforce.com/2006/04/metadata' }, @@ -686,7 +712,8 @@ describe('scratchOrgSettingsGenerator', () => { 'case', objectSettingsData.case, allRecordTypes, - allbusinessProcesses + allbusinessProcesses, + true ); expect(recordTypeAndBusinessProcessFileContent).to.deep.equal({ '@': { xmlns: 'http://soap.sforce.com/2006/04/metadata' }, @@ -709,6 +736,44 @@ describe('scratchOrgSettingsGenerator', () => { expect(allRecordTypes).to.deep.equal(['Case.Default']); expect(allbusinessProcesses).to.deep.equal(['Case.DefaultProcess']); }); + + it('createRecordTypeAndBusinessProcessFileContent with case values, not capitalized', () => { + const objectSettingsDataLowercaseRecordType = { + case: { + defaultRecordType: 'default', + sharingModel: 'private', + }, + }; + const allRecordTypes: string[] = []; + const allbusinessProcesses: string[] = []; + const recordTypeAndBusinessProcessFileContent = createRecordTypeAndBusinessProcessFileContent( + 'case', + objectSettingsDataLowercaseRecordType.case, + allRecordTypes, + allbusinessProcesses, + false + ); + expect(recordTypeAndBusinessProcessFileContent).to.deep.equal({ + '@': { xmlns: 'http://soap.sforce.com/2006/04/metadata' }, + sharingModel: 'Private', + recordTypes: { + fullName: 'default', + label: 'default', + active: true, + businessProcess: 'defaultProcess', + }, + businessProcesses: { + fullName: 'defaultProcess', + isActive: true, + values: { + fullName: 'New', + default: true, + }, + }, + }); + expect(allRecordTypes).to.deep.equal(['Case.default']); + expect(allbusinessProcesses).to.deep.equal(['Case.defaultProcess']); + }); }); describe('createObjectFileContent', () => {