@jayree/sfdx-plugin-org
Version:
A Salesforce CLI plugin containing commands to configure State and Country/Territory Picklists and other org settings.
241 lines • 12.1 kB
JavaScript
/*
* Copyright 2026, jayree
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import os from 'node:os';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { Flags, SfCommand, orgApiVersionFlagWithDeprecations, requiredOrgFlagWithDeprecations, } from '@salesforce/sf-plugins-core';
import { Messages, SfProject } from '@salesforce/core';
import createDebug from 'debug';
import fs from 'fs-extra';
import { ComponentSet, MetadataResolver, NodeFSTreeContainer, RegistryAccess, } from '@salesforce/source-deploy-retrieve';
// eslint-disable-next-line no-underscore-dangle
const __filename = fileURLToPath(import.meta.url);
// eslint-disable-next-line no-underscore-dangle
const __dirname = dirname(__filename);
Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@jayree/sfdx-plugin-org', 'scratchorgsettings');
function camelize(str) {
return str.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, (match, index) => {
if (+match === 0)
return ''; // or if (/\s+/.test(match)) for white spaces
return index === 0 ? match.toLowerCase() : match.toUpperCase();
});
}
async function resolve(fpath) {
const resolver = new MetadataResolver(new RegistryAccess(), new NodeFSTreeContainer());
const [c] = resolver.getComponentsFromPath(fpath);
return c.parseXml();
}
export default class ScratchOrgSettings extends SfCommand {
static summary = messages.getMessage('commandDescription');
static examples = [
`$ sfdx jayree:org:settings
$ sfdx jayree:org:settings -u me@my.org
$ sfdx jayree:org:settings -u MyTestOrg1 -w`,
];
static flags = {
'target-org': requiredOrgFlagWithDeprecations,
'api-version': orgApiVersionFlagWithDeprecations,
writetoprojectscratchdeffile: Flags.boolean({
char: 'w',
summary: messages.getMessage('flags.writetoprojectscratchdeffile.summary'),
}),
file: Flags.string({
char: 'f',
summary: messages.getMessage('flags.file.summary'),
}),
};
static requiresProject = true;
static deprecateAliases = true;
static aliases = ['jayree:org:settings'];
// eslint-disable-next-line complexity
async run() {
const { flags } = await this.parse(ScratchOrgSettings);
const debug = createDebug('jayree:scratchorg:settings');
const removeEmpty = (obj) => {
Object.entries(obj).forEach(([key, val]) => {
if (val && typeof val === 'object') {
val = removeEmpty(val);
}
if (val === null || val === '' || (typeof val === 'object' && !Object.keys(val).length)) {
delete obj[key];
}
});
return obj;
};
const sortKeys = (obj) => {
const ordered = {};
Object.keys(obj)
.sort()
.forEach((key) => {
ordered[key] = obj[key];
});
return ordered;
};
let settings = {};
const targetDir = process.env.SFDX_MDAPI_TEMP_DIR ?? os.tmpdir();
const destRoot = join(targetDir, 'RetrieveSettings');
process.once('exit', () => {
fs.removeSync(destRoot);
});
const sfdxProject = await SfProject.resolve();
const sfdxProjectJson = await sfdxProject.retrieveSfProjectJson();
const sfdxProjectVersion = sfdxProjectJson.getContents().sourceApiVersion;
const apiVersion = flags['api-version'] ?? sfdxProjectVersion ?? (await flags['target-org'].retrieveMaxApiVersion());
this.log(`Using ${destRoot} and apiVersion=${apiVersion}`);
const componentSet = new ComponentSet([{ fullName: '*', type: 'Settings' }]);
const mdapiRetrieve = await componentSet.retrieve({
usernameOrConnection: flags['target-org'].getUsername(),
output: destRoot,
apiVersion,
});
const retrieveResult = await mdapiRetrieve.pollStatus(1000);
for await (const setting of retrieveResult
.getFileResponses()
.filter((component) => component.type === 'Settings')) {
const settingsXml = await resolve(setting.filePath);
Object.keys(settingsXml).forEach((key) => {
Object.entries(settingsXml[key]).forEach(([property, value]) => {
if (!settings[camelize(key)]) {
settings[camelize(key)] = {};
}
if (property !== '@_xmlns') {
settings[camelize(key)][property] = value;
}
});
});
}
if (typeof settings['addressSettings'] !== 'undefined') {
delete settings['addressSettings'];
debug('delete ' + 'addressSettings');
}
if (typeof settings['searchSettings'] !== 'undefined') {
delete settings['searchSettings'];
debug('delete ' + 'searchSettings');
}
if (typeof settings['analyticsSettings'] !== 'undefined') {
delete settings['analyticsSettings'];
debug('delete ' + 'analyticsSettings');
}
if (typeof settings['activitiesSettings'] !== 'undefined') {
if (typeof settings['activitiesSettings']['allowUsersToRelateMultipleContactsToTasksAndEvents'] !== 'undefined') {
delete settings['activitiesSettings']['allowUsersToRelateMultipleContactsToTasksAndEvents'];
debug('delete ' + 'activitiesSettings:allowUsersToRelateMultipleContactsToTasksAndEvents');
this.warn("You can't use the Tooling API or Metadata API to enable or disable Shared Activities.To enable this feature, visit the Activity Settings page in Setup.To disable this feature, contact Salesforce.");
}
}
if (typeof settings['territory2Settings'] !== 'undefined') {
if (typeof settings['territory2Settings']['enableTerritoryManagement2'] !== 'undefined') {
settings['territory2Settings'] = {
enableTerritoryManagement2: settings['territory2Settings']['enableTerritoryManagement2'],
};
debug('set ' + 'enableTerritoryManagement2');
}
}
if (typeof settings['forecastingSettings'] !== 'undefined') {
if (typeof settings['forecastingSettings']['forecastingCategoryMappings'] !== 'undefined') {
delete settings['forecastingSettings']['forecastingCategoryMappings'];
debug('delete ' + 'forecastingSettings:forecastingCategoryMappings');
}
if (typeof settings['forecastingSettings']['forecastingTypeSettings'] !== 'undefined') {
delete settings['forecastingSettings']['forecastingTypeSettings'];
debug('delete ' + 'forecastingSettings:forecastingTypeSettings');
}
}
if (typeof settings['caseSettings'] !== 'undefined') {
if (typeof settings['caseSettings']['caseFeedItemSettings'] !== 'undefined') {
if (typeof settings['caseSettings']['caseFeedItemSettings'][0] !== 'undefined') {
if (typeof settings['caseSettings']['caseFeedItemSettings'][0]['feedItemType'] !==
'undefined') {
if (settings['caseSettings']['caseFeedItemSettings'][0]['feedItemType'] ===
'EMAIL_MESSAGE_EVENT') {
settings['caseSettings']['caseFeedItemSettings'][0]['feedItemType'] =
'EmailMessageEvent';
debug('set ' + 'caseSettings:caseFeedItemSettings:feedItemType');
}
}
}
}
}
settings = removeEmpty(settings);
settings = sortKeys(settings);
if (flags.writetoprojectscratchdeffile) {
const deffilepath =
// eslint-disable-next-line @typescript-eslint/await-thenable
flags.file ?? join(this.project?.getPath(), 'config', 'project-scratch-def.json');
let deffile = {};
await fs
.readFile(deffilepath, 'utf8')
.then((data) => {
deffile = JSON.parse(data);
if (deffile['edition'] === 'Enterprise') {
if (!deffile['features'].includes('LiveAgent')) {
if (typeof settings['liveAgentSettings'] !== 'undefined') {
if (typeof settings['liveAgentSettings']['enableLiveAgent'] !== 'undefined') {
if (settings['liveAgentSettings']['enableLiveAgent'] === 'false') {
delete settings['liveAgentSettings'];
debug('delete ' + 'liveAgentSettings');
this.warn('liveAgentSettings: Not available for deploy for this organization');
}
}
}
}
if (typeof settings['knowledgeSettings'] !== 'undefined') {
if (typeof settings['knowledgeSettings']['enableKnowledge'] !== 'undefined') {
if (settings['knowledgeSettings']['enableKnowledge'] === 'false') {
delete settings['knowledgeSettings'];
debug('delete ' + 'knowledgeSettings');
this.warn("knowledgeSettings: Once enabled, Salesforce Knowledge can't be disabled.");
}
}
}
if (typeof settings['caseSettings'] !== 'undefined') {
if (typeof settings['caseSettings']['emailToCase'] !== 'undefined') {
if (typeof settings['caseSettings']['emailToCase']['enableEmailToCase'] !== 'undefined') {
if (settings['caseSettings']['emailToCase']['enableEmailToCase'] === 'false') {
delete settings['caseSettings']['emailToCase'];
debug('delete ' + 'caseSettings:emailToCase');
this.warn('EmailToCaseSettings: Email to case cannot be disabled once it has been enabled.');
}
}
}
}
}
deffile['settings'] = settings;
})
.catch((err) => {
const error = err;
if (error.code === 'ENOENT' && !flags.file) {
throw Error("default file 'project-scratch-def.json' not found, please use --file flag");
}
else {
throw error;
}
});
await fs.writeFile(deffilepath, JSON.stringify(deffile, null, 2));
}
else {
this.styledHeader(`received settings from Org: ${flags['target-org'].getUsername()} (${flags['target-org'].getOrgId()})`);
this.styledJSON(settings);
}
return {
settings,
orgId: flags['target-org'].getOrgId(),
username: flags['target-org'].getUsername(),
};
}
}
//# sourceMappingURL=settings.js.map