UNPKG

@salesforce/core

Version:

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

324 lines 14.9 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 */ 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