From 725bf67bf0c7b4ba274644581005ed921862da09 Mon Sep 17 00:00:00 2001 From: Cristian Dominguez Date: Tue, 16 Jan 2024 19:10:53 -0300 Subject: [PATCH 1/6] feat(scratch): add `org-capitalize-record-types` --- messages/envVars.md | 4 ++ src/config/envVars.ts | 5 ++ src/org/orgConfigProperties.ts | 8 +++ src/org/scratchOrgCreate.ts | 9 ++- src/org/scratchOrgSettingsGenerator.ts | 45 +++++++++--- .../org/scratchOrgSettingsGeneratorTest.ts | 71 ++++++++++++++++++- 6 files changed, 126 insertions(+), 16 deletions(-) diff --git a/messages/envVars.md b/messages/envVars.md index bba841d596..4b1e39f020 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 9116758727..a6a2a5bdec 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 7a68256098..2b3c9c94fa 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 ce2cc3e647..7e048d3037 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 575bf0c421..960c1fc045 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 9d137f5080..b7b1088d15 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', () => { From 4a3a5118610d0978fba6e6e8d67e73195936bfb5 Mon Sep 17 00:00:00 2001 From: svc-cli-bot Date: Wed, 17 Jan 2024 14:40:11 +0000 Subject: [PATCH 2/6] chore(release): 6.4.8-dev.0 [skip ci] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3a89bbeb47..fa594e3ccc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@salesforce/core", - "version": "6.4.7", + "version": "6.4.8-dev.0", "description": "Core libraries to interact with SFDX projects, orgs, and APIs.", "main": "lib/exported", "types": "lib/exported.d.ts", From fc3221886ed47d83999de102e7d76e82a29c0c0c Mon Sep 17 00:00:00 2001 From: Cristian Dominguez Date: Wed, 17 Jan 2024 19:27:04 -0300 Subject: [PATCH 3/6] fix: emit warning if config var is unset --- messages/config.md | 4 ++++ src/org/orgConfigProperties.ts | 2 +- src/org/scratchOrgCreate.ts | 20 +++++++++++++++----- src/org/scratchOrgSettingsGenerator.ts | 10 +++++++++- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/messages/config.md b/messages/config.md index 1fe9b6a29f..76dceab95f 100644 --- a/messages/config.md +++ b/messages/config.md @@ -151,6 +151,10 @@ A valid repository URL or directory for the custom org metadata templates. A valid repository URL or directory for the custom org metadata templates. +# org-capitalize-record-types + +Whether record types are capitalized on scrach org creation. + # invalidId The given id %s is not a valid 15 or 18 character Salesforce ID. diff --git a/src/org/orgConfigProperties.ts b/src/org/orgConfigProperties.ts index 2b3c9c94fa..2c2a50f672 100644 --- a/src/org/orgConfigProperties.ts +++ b/src/org/orgConfigProperties.ts @@ -57,7 +57,7 @@ export enum OrgConfigProperties { export const ORG_CONFIG_ALLOWED_PROPERTIES = [ { key: OrgConfigProperties.ORG_CAPITALIZE_RECORD_TYPES, - description: messages.getMessage(OrgConfigProperties.ORG_CUSTOM_METADATA_TEMPLATES), + description: messages.getMessage(OrgConfigProperties.ORG_CAPITALIZE_RECORD_TYPES), }, { key: OrgConfigProperties.ORG_CUSTOM_METADATA_TEMPLATES, diff --git a/src/org/scratchOrgCreate.ts b/src/org/scratchOrgCreate.ts index 7e048d3037..05dd52a1ec 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, toBoolean } from '@salesforce/kit'; +import { Duration } from '@salesforce/kit'; import { ensureString } from '@salesforce/ts-types'; import { Messages } from '../messages'; import { Logger } from '../logger/logger'; @@ -150,7 +150,12 @@ export const scratchOrgResume = async (jobId: string): Promise => { // a project isn't required for org:create } }; + +async function getCapitalizeRecordTypesConfig(): Promise { + const configAgg = await ConfigAggregator.create(); + return configAgg.getInfo('org-capitalize-record-types').value as boolean | undefined; +} diff --git a/src/org/scratchOrgSettingsGenerator.ts b/src/org/scratchOrgSettingsGenerator.ts index 960c1fc045..6b6c012ec4 100644 --- a/src/org/scratchOrgSettingsGenerator.ts +++ b/src/org/scratchOrgSettingsGenerator.ts @@ -16,6 +16,7 @@ import { StatusResult } from '../status/types'; import { PollingClient } from '../status/pollingClient'; import { ZipWriter } from '../util/zipWriter'; import { DirectoryWriter } from '../util/directoryWriter'; +import { Lifecycle } from '../lifecycleEvents'; import { ScratchOrgInfo, ObjectSetting } from './scratchOrgTypes'; import { Org } from './org'; @@ -210,7 +211,14 @@ export default class SettingsGenerator { capitalizeRecordTypes?: boolean; }) { this.logger = Logger.childFromRoot('SettingsGenerator'); - this.capitalizeRecordTypes = options?.capitalizeRecordTypes ?? false; + if (options?.capitalizeRecordTypes === undefined) { + void Lifecycle.getInstance().emitWarning( + 'record types will stop being capitalized by default in a future release.' + ); + this.capitalizeRecordTypes = true; + } else { + this.capitalizeRecordTypes = options.capitalizeRecordTypes; + } // 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()}`; From 5e00c9a0030a1d7f6a39913529b3c230949da637 Mon Sep 17 00:00:00 2001 From: Cristian Dominguez Date: Thu, 18 Jan 2024 11:22:09 -0300 Subject: [PATCH 4/6] chore: improve message --- messages/envVars.md | 2 +- messages/scratchOrgSettingsGenerator.md | 4 ++++ src/org/scratchOrgSettingsGenerator.ts | 8 +++++--- 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 messages/scratchOrgSettingsGenerator.md diff --git a/messages/envVars.md b/messages/envVars.md index 4b1e39f020..64f1b15810 100644 --- a/messages/envVars.md +++ b/messages/envVars.md @@ -314,4 +314,4 @@ Your environment has both variables populated, and with different values. The va # sfCapitalizeRecordTypes -Boolean indicating whether or not to capitalize object settings. +Whether record types are capitalized on scrach org creation. diff --git a/messages/scratchOrgSettingsGenerator.md b/messages/scratchOrgSettingsGenerator.md new file mode 100644 index 0000000000..3b2eb5da79 --- /dev/null +++ b/messages/scratchOrgSettingsGenerator.md @@ -0,0 +1,4 @@ +# noCapitalizeRecordTypeConfigVar + +Record types defined in the scratch org definition file will stop being capitalized by default in a future release. +Set the `org-capitalize-record-types` config var to `true` to enforce capitalization. diff --git a/src/org/scratchOrgSettingsGenerator.ts b/src/org/scratchOrgSettingsGenerator.ts index 6b6c012ec4..1d9d62d192 100644 --- a/src/org/scratchOrgSettingsGenerator.ts +++ b/src/org/scratchOrgSettingsGenerator.ts @@ -17,9 +17,12 @@ import { PollingClient } from '../status/pollingClient'; import { ZipWriter } from '../util/zipWriter'; import { DirectoryWriter } from '../util/directoryWriter'; import { Lifecycle } from '../lifecycleEvents'; +import { Messages } from '../messages'; import { ScratchOrgInfo, ObjectSetting } from './scratchOrgTypes'; import { Org } from './org'; +Messages.importMessagesDirectory(__dirname); + export enum RequestStatus { Pending = 'Pending', InProgress = 'InProgress', @@ -212,9 +215,8 @@ export default class SettingsGenerator { }) { this.logger = Logger.childFromRoot('SettingsGenerator'); if (options?.capitalizeRecordTypes === undefined) { - void Lifecycle.getInstance().emitWarning( - 'record types will stop being capitalized by default in a future release.' - ); + const messages = Messages.loadMessages('@salesforce/core', 'scratchOrgSettingsGenerator'); + void Lifecycle.getInstance().emitWarning(messages.getMessage('noCapitalizeRecordTypeConfigVar')); this.capitalizeRecordTypes = true; } else { this.capitalizeRecordTypes = options.capitalizeRecordTypes; From 94fe825c8e4772bce6ed97f3ab6842e5011f7dd2 Mon Sep 17 00:00:00 2001 From: Cristian Dominguez Date: Thu, 18 Jan 2024 18:53:27 -0300 Subject: [PATCH 5/6] fix: properly handle undefined --- src/org/scratchOrgCreate.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/org/scratchOrgCreate.ts b/src/org/scratchOrgCreate.ts index 05dd52a1ec..414a8fcc9c 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'; @@ -341,5 +341,10 @@ const getSignupTargetLoginUrl = async (): Promise => { async function getCapitalizeRecordTypesConfig(): Promise { const configAgg = await ConfigAggregator.create(); - return configAgg.getInfo('org-capitalize-record-types').value as boolean | undefined; + const value = configAgg.getInfo('org-capitalize-record-types').value as string | undefined; + + if (value !== undefined) return toBoolean(value); + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + return value as undefined; } From b0ae322d7ad67619131835d6b5c85b8cc7c59026 Mon Sep 17 00:00:00 2001 From: Cristian Dominguez Date: Mon, 22 Jan 2024 10:26:11 -0300 Subject: [PATCH 6/6] chore: typo --- messages/config.md | 2 +- messages/envVars.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/messages/config.md b/messages/config.md index 76dceab95f..7039fea6f8 100644 --- a/messages/config.md +++ b/messages/config.md @@ -153,7 +153,7 @@ A valid repository URL or directory for the custom org metadata templates. # org-capitalize-record-types -Whether record types are capitalized on scrach org creation. +Whether record types are capitalized on scratch org creation. # invalidId diff --git a/messages/envVars.md b/messages/envVars.md index 64f1b15810..f46ea1c39a 100644 --- a/messages/envVars.md +++ b/messages/envVars.md @@ -314,4 +314,4 @@ Your environment has both variables populated, and with different values. The va # sfCapitalizeRecordTypes -Whether record types are capitalized on scrach org creation. +Whether record types are capitalized on scratch org creation.