UNPKG

@salesforce/core

Version:

Core libraries to interact with SFDX projects, orgs, and APIs.

277 lines 12.7 kB
"use strict"; /* * 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 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.createRecordTypeAndBusinessProcessFileContent = exports.createObjectFileContent = exports.RequestStatus = void 0; const path = require("path"); const kit_1 = require("@salesforce/kit"); const ts_types_1 = require("@salesforce/ts-types"); const js2xmlparser = 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"); 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 || (exports.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) => { 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 = (0, kit_1.upperFirst)(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) => { 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 = json.defaultRecordType; if (typeof defaultRecordType === 'string') { // We need to keep track of these globally for when we generate the package XML. allRecordTypes.push(`${name}.${(0, kit_1.upperFirst)(defaultRecordType)}`); const [businessProcessName, businessProcessPicklistVal] = calculateBusinessProcess(name, defaultRecordType); // Create the record type const recordTypes = { fullName: (0, kit_1.upperFirst)(defaultRecordType), label: (0, kit_1.upperFirst)(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 { constructor(options) { this.allRecordTypes = []; this.allBusinessProcesses = []; this.logger = logger_1.Logger.childFromRoot('SettingsGenerator'); // 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) { 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'); const error = new sfError_1.SfError(`A scratch org was created with username ${username}, but the settings failed to deploy due to: \n${failures}`, 'ProblemDeployingSettings'); error.setData(result); throw error; } } 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); 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