@salesforce/core
Version:
Core libraries to interact with SFDX projects, orgs, and APIs.
324 lines • 14.9 kB
JavaScript
/*
* Copyright (c) 2021, salesforce.com, inc.
* All rights reserved.
* 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
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createRecordTypeAndBusinessProcessFileContent = exports.createObjectFileContent = exports.RequestStatus = void 0;
const path = __importStar(require("node:path"));
const kit_1 = require("@salesforce/kit");
const ts_types_1 = require("@salesforce/ts-types");
const js2xmlparser = __importStar(require("js2xmlparser"));
const logger_1 = require("../logger/logger");
const sfError_1 = require("../sfError");
const pollingClient_1 = require("../status/pollingClient");
const zipWriter_1 = require("../util/zipWriter");
const directoryWriter_1 = require("../util/directoryWriter");
const lifecycleEvents_1 = require("../lifecycleEvents");
const messages_1 = require("../messages");
;
var RequestStatus;
(function (RequestStatus) {
RequestStatus["Pending"] = "Pending";
RequestStatus["InProgress"] = "InProgress";
RequestStatus["Succeeded"] = "Succeeded";
RequestStatus["SucceededPartial"] = "SucceededPartial";
RequestStatus["Failed"] = "Failed";
RequestStatus["Canceling"] = "Canceling";
RequestStatus["Canceled"] = "Canceled";
})(RequestStatus || (exports.RequestStatus = RequestStatus = {}));
const breakPolling = ['Succeeded', 'SucceededPartial', 'Failed', 'Canceled'];
const createObjectFileContent = ({ allRecordTypes = [], allBusinessProcesses = [], apiVersion, settingData, objectSettingsData, }) => {
const output = {
'@': {
xmlns: 'http://soap.sforce.com/2006/04/metadata',
},
types: [],
};
if (settingData) {
const strings = Object.keys(settingData).map((item) => (0, kit_1.upperFirst)(item).replace('Settings', ''));
output.types.push({ members: strings, name: 'Settings' });
}
if (objectSettingsData) {
const strings = Object.keys(objectSettingsData).map((item) => (0, kit_1.upperFirst)(item));
output.types.push({ members: strings, name: 'CustomObject' });
if (allRecordTypes.length > 0) {
output.types.push({ members: allRecordTypes, name: 'RecordType' });
}
if (allBusinessProcesses.length > 0) {
output.types.push({ members: allBusinessProcesses, name: 'BusinessProcess' });
}
}
return { ...output, ...{ version: apiVersion } };
};
exports.createObjectFileContent = createObjectFileContent;
const calculateBusinessProcess = (objectName, defaultRecordType, capitalizeBusinessProcess) => {
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 = capitalizeBusinessProcess
? `${(0, kit_1.upperFirst)(defaultRecordType)}Process`
: `${defaultRecordType}Process`;
switch (objectName) {
case 'Case':
businessProcessPicklistVal = 'New';
break;
case 'Lead':
businessProcessPicklistVal = 'New - Not Contacted';
break;
case 'Opportunity':
businessProcessPicklistVal = 'Prospecting';
break;
case 'Solution':
businessProcessPicklistVal = 'Draft';
}
}
return [businessProcessName, businessProcessPicklistVal];
};
const createRecordTypeAndBusinessProcessFileContent = (objectName, json, allRecordTypes, allBusinessProcesses, capitalizeRecordTypes) => {
let output = {
'@': {
xmlns: 'http://soap.sforce.com/2006/04/metadata',
},
};
const name = (0, kit_1.upperFirst)(objectName);
const sharingModel = json.sharingModel;
if (sharingModel) {
output = {
...output,
sharingModel: (0, kit_1.upperFirst)(sharingModel),
};
}
const defaultRecordType = capitalizeRecordTypes
? (0, kit_1.upperFirst)(json.defaultRecordType)
: json.defaultRecordType;
if (typeof defaultRecordType === 'string') {
// We need to keep track of these globally for when we generate the package XML.
allRecordTypes.push(`${name}.${defaultRecordType}`);
const [businessProcessName, businessProcessPicklistVal] = calculateBusinessProcess(name, defaultRecordType, capitalizeRecordTypes);
// Create the record type
const recordTypes = {
fullName: defaultRecordType,
label: defaultRecordType,
active: true,
};
output = {
...output,
recordTypes: {
...recordTypes,
},
};
if (businessProcessName) {
// We need to keep track of these globally for the package.xml
const values = {
fullName: businessProcessPicklistVal,
};
if (name !== 'Opportunity') {
values.default = true;
}
allBusinessProcesses.push(`${name}.${businessProcessName}`);
output = {
...output,
recordTypes: {
...recordTypes,
businessProcess: businessProcessName,
},
businessProcesses: {
fullName: businessProcessName,
isActive: true,
values,
},
};
}
}
return output;
};
exports.createRecordTypeAndBusinessProcessFileContent = createRecordTypeAndBusinessProcessFileContent;
/**
* Helper class for dealing with the settings that are defined in a scratch definition file. This class knows how to extract the
* settings from the definition, how to expand them into a MD directory and how to generate a package.xml.
*/
class SettingsGenerator {
settingData;
objectSettingsData;
logger;
writer;
allRecordTypes = [];
allBusinessProcesses = [];
shapeDirName;
packageFilePath;
capitalizeRecordTypes;
constructor(options) {
this.logger = logger_1.Logger.childFromRoot('SettingsGenerator');
if (options?.capitalizeRecordTypes === undefined) {
const messages = new messages_1.Messages('@salesforce/core', 'scratchOrgSettingsGenerator', new Map([["noCapitalizeRecordTypeConfigVar", "Record types defined in the scratch org definition file will stop being capitalized by default in a future release.\nSet the `org-capitalize-record-types` config var to `true` to enforce capitalization."]]));
void lifecycleEvents_1.Lifecycle.getInstance().emitWarning(messages.getMessage('noCapitalizeRecordTypeConfigVar'));
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 ?? kit_1.env.getString('SFDX_MDAPI_TEMP_DIR');
this.shapeDirName = options?.shapeDirName ?? `shape_${Date.now()}`;
this.packageFilePath = path.join(this.shapeDirName, 'package.xml');
let storePath;
if (!options?.asDirectory) {
storePath = mdApiTmpDir ? path.join(mdApiTmpDir, `${this.shapeDirName}.zip`) : undefined;
this.writer = new zipWriter_1.ZipWriter(storePath);
}
else {
storePath = mdApiTmpDir ? path.join(mdApiTmpDir, this.shapeDirName) : undefined;
this.writer = new directoryWriter_1.DirectoryWriter(storePath);
}
}
/** extract the settings from the scratch def file, if they are present. */
// eslint-disable-next-line @typescript-eslint/require-await
async extract(scratchDef) {
this.logger.debug('extracting settings from scratch definition file');
this.settingData = scratchDef.settings;
this.objectSettingsData = scratchDef.objectSettings;
this.logger.debug('settings are', this.settingData);
return { settings: this.settingData, objectSettings: this.objectSettingsData };
}
/** True if we are currently tracking setting or object setting data. */
hasSettings() {
return !(0, kit_1.isEmpty)(this.settingData) || !(0, kit_1.isEmpty)(this.objectSettingsData);
}
/** Create temporary deploy directory used to upload the scratch org shape.
* This will create the dir, all of the .setting files and minimal object files needed for objectSettings
*/
async createDeploy() {
const settingsDir = path.join(this.shapeDirName, 'settings');
const objectsDir = path.join(this.shapeDirName, 'objects');
await Promise.all([
this.writeSettingsIfNeeded(settingsDir),
this.writeObjectSettingsIfNeeded(objectsDir, this.allRecordTypes, this.allBusinessProcesses),
]);
}
/**
* Deploys the settings to the org.
*/
async deploySettingsViaFolder(scratchOrg, apiVersion, timeout = kit_1.Duration.minutes(10)) {
const username = scratchOrg.getUsername();
const logger = await logger_1.Logger.child('deploySettingsViaFolder');
await this.createDeployPackageContents(apiVersion);
const connection = scratchOrg.getConnection();
logger.debug(`deploying to apiVersion: ${apiVersion}`);
connection.setApiVersion(apiVersion);
const { id } = await connection.deploy(this.writer.buffer, {});
logger.debug(`deploying settings id ${id}`);
let result = await connection.metadata.checkDeployStatus(id);
const pollingOptions = {
async poll() {
try {
result = await connection.metadata.checkDeployStatus(id, true);
logger.debug(`Deploy id: ${id} status: ${result.status}`);
if (breakPolling.includes(result.status)) {
return {
completed: true,
payload: result.status,
};
}
return {
completed: false,
};
}
catch (error) {
logger.debug(`An error occurred trying to check deploy id: ${id}`);
logger.debug(`Error: ${error.message}`);
logger.debug('Re-trying deploy check again....');
return {
completed: false,
};
}
},
timeout,
frequency: kit_1.Duration.seconds(1),
timeoutErrorName: 'DeployingSettingsTimeoutError',
};
const client = await pollingClient_1.PollingClient.create(pollingOptions);
const status = await client.subscribe();
if (status !== RequestStatus.Succeeded.toString()) {
const componentFailures = (0, ts_types_1.ensureObject)(result.details).componentFailures;
const failures = (Array.isArray(componentFailures) ? componentFailures : [componentFailures])
.map((failure) => `[${failure.problemType}] ${failure.fullName} : ${failure.problem} `)
.join('\n');
throw sfError_1.SfError.create({
message: `A scratch org was created with username ${username}, but the settings failed to deploy due to: \n${failures}`,
name: 'ProblemDeployingSettings',
data: { ...result, username },
});
}
}
async createDeployPackageContents(apiVersion) {
const packageObjectProps = (0, exports.createObjectFileContent)({
allRecordTypes: this.allRecordTypes,
allBusinessProcesses: this.allBusinessProcesses,
apiVersion,
settingData: this.settingData,
objectSettingsData: this.objectSettingsData,
});
const xml = js2xmlparser.parse('Package', packageObjectProps);
await this.writer.addToStore(xml, this.packageFilePath);
await this.writer.finalize();
}
getShapeDirName() {
return this.shapeDirName;
}
/**
* Returns the destination where the writer placed the settings content.
*
*/
getDestinationPath() {
return this.writer.getDestinationPath();
}
async writeObjectSettingsIfNeeded(objectsDir, allRecordTypes, allbusinessProcesses) {
if (this.objectSettingsData) {
await Promise.all(Object.entries(this.objectSettingsData).map(([item, value]) => {
const fileContent = (0, exports.createRecordTypeAndBusinessProcessFileContent)(item, value, allRecordTypes, allbusinessProcesses, this.capitalizeRecordTypes);
const xml = js2xmlparser.parse('CustomObject', fileContent);
return this.writer.addToStore(xml, path.join(objectsDir, (0, kit_1.upperFirst)(item) + '.object'));
}));
}
}
async writeSettingsIfNeeded(settingsDir) {
if (this.settingData) {
await Promise.all(Object.entries(this.settingData).map(([item, value]) => {
const typeName = (0, kit_1.upperFirst)(item);
const fname = typeName.replace('Settings', '');
const fileContent = js2xmlparser.parse(typeName, value);
return this.writer.addToStore(fileContent, path.join(settingsDir, fname + '.settings'));
}));
}
}
}
exports.default = SettingsGenerator;
//# sourceMappingURL=scratchOrgSettingsGenerator.js.map
;