@salesforce/core
Version:
Core libraries to interact with SFDX projects, orgs, and APIs.
210 lines • 9.92 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
*/
Object.defineProperty(exports, "__esModule", { value: true });
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");
const sfdxError_1 = require("./sfdxError");
const jsonXmlTools_1 = require("./util/jsonXmlTools");
const zipWriter_1 = require("./util/zipWriter");
const pollingClient_1 = require("./status/pollingClient");
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'];
/**
* 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() {
this.shapeDirName = `shape_${Date.now()}`;
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 = kit_1.env.getString('SFDX_MDAPI_TEMP_DIR');
this.writer = new zipWriter_1.ZipWriter(mdapiTmpDir);
}
/** extract the settings from the scratch def file, if they are present. */
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);
}
/** True if we are currently tracking setting or object setting data. */
hasSettings() {
return !kit_1.isEmpty(this.settingData) || !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)]);
}
/**
* Deploys the settings to the org.
*/
async deploySettingsViaFolder(scratchOrg, apiVersion) {
const username = scratchOrg.getUsername();
const logger = await logger_1.Logger.child('deploySettingsViaFolder');
this.createPackageXml(apiVersion);
await this.writer.finalize();
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: kit_1.Duration.minutes(10),
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 = ts_types_1.ensureObject(result.details).componentFailures;
const failures = (Array.isArray(componentFailures) ? componentFailures : [componentFailures])
.map((failure) => failure.problem)
.join('\n');
const error = new sfdxError_1.SfdxError(`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 writeObjectSettingsIfNeeded(objectsDir) {
if (!this.objectSettingsData || !Object.keys(this.objectSettingsData).length) {
return;
}
for (const objectName of Object.keys(this.objectSettingsData)) {
const value = this.objectSettingsData[objectName];
// writes the object file in source format
const objectDir = path.join(objectsDir, kit_1.upperFirst(objectName));
const customObjectDir = path.join(objectDir, `${kit_1.upperFirst(objectName)}.object`);
const customObjectXml = jsonXmlTools_1.JsonAsXml({
json: this.createObjectFileContent(value),
type: 'RecordType',
});
this.writer.addToZip(customObjectXml, customObjectDir);
if (value.defaultRecordType) {
const recordTypesDir = path.join(objectDir, 'recordTypes', `${kit_1.upperFirst(value.defaultRecordType)}.recordType`);
const recordTypesFileContent = this.createRecordTypeFileContent(objectName, value);
const recordTypesXml = jsonXmlTools_1.JsonAsXml({
json: recordTypesFileContent,
type: 'RecordType',
});
this.writer.addToZip(recordTypesXml, recordTypesDir);
// for things that required a businessProcess
if (recordTypesFileContent.businessProcess) {
const businessProcessesDir = path.join(objectDir, 'businessProcesses', `${recordTypesFileContent.businessProcess}.businessProcess`);
const businessProcessesXml = jsonXmlTools_1.JsonAsXml({
json: this.createBusinessProcessFileContent(objectName, recordTypesFileContent.businessProcess),
type: 'BusinessProcess',
});
this.writer.addToZip(businessProcessesXml, businessProcessesDir);
}
}
}
}
async writeSettingsIfNeeded(settingsDir) {
if (this.settingData) {
for (const item of Object.keys(this.settingData)) {
const value = ts_types_1.getObject(this.settingData, item);
const typeName = kit_1.upperFirst(item);
const fname = typeName.replace('Settings', '');
const fileContent = js2xmlparser.parse(typeName, value);
this.writer.addToZip(fileContent, path.join(settingsDir, fname + '.settings'));
}
}
}
createPackageXml(apiVersion) {
const pkg = `<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<types>
<members>*</members>
<name>Settings</name>
</types>
<types>
<members>*</members>
<name>CustomObject</name>
</types>
<version>${apiVersion}</version>
</Package>`;
this.writer.addToZip(pkg, path.join(this.shapeDirName, 'package.xml'));
}
createObjectFileContent(json) {
const output = {};
if (json.sharingModel) {
output.sharingModel = kit_1.upperFirst(json.sharingModel);
}
return output;
}
createRecordTypeFileContent(objectName, setting) {
const defaultRecordType = kit_1.upperFirst(setting.defaultRecordType);
const output = {
fullName: defaultRecordType,
label: defaultRecordType,
active: true,
};
// all the edge cases
if (['Case', 'Lead', 'Opportunity', 'Solution'].includes(kit_1.upperFirst(objectName))) {
return { ...output, businessProcess: `${defaultRecordType}Process` };
}
return output;
}
createBusinessProcessFileContent(objectName, businessProcessName) {
const objectToBusinessProcessPicklist = {
Opportunity: { fullName: 'Prospecting' },
Case: { fullName: 'New', default: true },
Lead: { fullName: 'New - Not Contacted', default: true },
Solution: { fullName: 'Draft', default: true },
};
return {
fullName: businessProcessName,
isActive: true,
values: [objectToBusinessProcessPicklist[kit_1.upperFirst(objectName)]],
};
}
}
exports.default = SettingsGenerator;
//# sourceMappingURL=scratchOrgSettingsGenerator.js.map