UNPKG

12.6 kBJavaScriptView Raw
1"use strict";
2/*
3 * Copyright (c) 2021, salesforce.com, inc.
4 * All rights reserved.
5 * Licensed under the BSD 3-Clause license.
6 * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
7 */
8Object.defineProperty(exports, "__esModule", { value: true });
9exports.createRecordTypeAndBusinessProcessFileContent = exports.createObjectFileContent = exports.RequestStatus = void 0;
10const path = require("path");
11const kit_1 = require("@salesforce/kit");
12const ts_types_1 = require("@salesforce/ts-types");
13const js2xmlparser = require("js2xmlparser");
14const logger_1 = require("../logger");
15const sfError_1 = require("../sfError");
16const pollingClient_1 = require("../status/pollingClient");
17const zipWriter_1 = require("../util/zipWriter");
18const directoryWriter_1 = require("../util/directoryWriter");
19var RequestStatus;
20(function (RequestStatus) {
21 RequestStatus["Pending"] = "Pending";
22 RequestStatus["InProgress"] = "InProgress";
23 RequestStatus["Succeeded"] = "Succeeded";
24 RequestStatus["SucceededPartial"] = "SucceededPartial";
25 RequestStatus["Failed"] = "Failed";
26 RequestStatus["Canceling"] = "Canceling";
27 RequestStatus["Canceled"] = "Canceled";
28})(RequestStatus = exports.RequestStatus || (exports.RequestStatus = {}));
29const breakPolling = ['Succeeded', 'SucceededPartial', 'Failed', 'Canceled'];
30const createObjectFileContent = ({ allRecordTypes = [], allBusinessProcesses = [], apiVersion, settingData, objectSettingsData, }) => {
31 const output = {
32 '@': {
33 xmlns: 'http://soap.sforce.com/2006/04/metadata',
34 },
35 types: [],
36 };
37 if (settingData) {
38 const strings = Object.keys(settingData).map((item) => (0, kit_1.upperFirst)(item).replace('Settings', ''));
39 output.types.push({ members: strings, name: 'Settings' });
40 }
41 if (objectSettingsData) {
42 const strings = Object.keys(objectSettingsData).map((item) => (0, kit_1.upperFirst)(item));
43 output.types.push({ members: strings, name: 'CustomObject' });
44 if (allRecordTypes.length > 0) {
45 output.types.push({ members: allRecordTypes, name: 'RecordType' });
46 }
47 if (allBusinessProcesses.length > 0) {
48 output.types.push({ members: allBusinessProcesses, name: 'BusinessProcess' });
49 }
50 }
51 return { ...output, ...{ version: apiVersion } };
52};
53exports.createObjectFileContent = createObjectFileContent;
54const calculateBusinessProcess = (objectName, defaultRecordType) => {
55 let businessProcessName = null;
56 let businessProcessPicklistVal = null;
57 // These four objects require any record type to specify a "business process"--
58 // a restricted set of items from a standard picklist on the object.
59 if (['Case', 'Lead', 'Opportunity', 'Solution'].includes(objectName)) {
60 businessProcessName = (0, kit_1.upperFirst)(defaultRecordType) + 'Process';
61 switch (objectName) {
62 case 'Case':
63 businessProcessPicklistVal = 'New';
64 break;
65 case 'Lead':
66 businessProcessPicklistVal = 'New - Not Contacted';
67 break;
68 case 'Opportunity':
69 businessProcessPicklistVal = 'Prospecting';
70 break;
71 case 'Solution':
72 businessProcessPicklistVal = 'Draft';
73 }
74 }
75 return [businessProcessName, businessProcessPicklistVal];
76};
77const createRecordTypeAndBusinessProcessFileContent = (objectName, json, allRecordTypes, allBusinessProcesses) => {
78 let output = {
79 '@': {
80 xmlns: 'http://soap.sforce.com/2006/04/metadata',
81 },
82 };
83 const name = (0, kit_1.upperFirst)(objectName);
84 const sharingModel = json.sharingModel;
85 if (sharingModel) {
86 output = {
87 ...output,
88 sharingModel: (0, kit_1.upperFirst)(sharingModel),
89 };
90 }
91 const defaultRecordType = json.defaultRecordType;
92 if (typeof defaultRecordType === 'string') {
93 // We need to keep track of these globally for when we generate the package XML.
94 allRecordTypes.push(`${name}.${(0, kit_1.upperFirst)(defaultRecordType)}`);
95 const [businessProcessName, businessProcessPicklistVal] = calculateBusinessProcess(name, defaultRecordType);
96 // Create the record type
97 const recordTypes = {
98 fullName: (0, kit_1.upperFirst)(defaultRecordType),
99 label: (0, kit_1.upperFirst)(defaultRecordType),
100 active: true,
101 };
102 output = {
103 ...output,
104 recordTypes: {
105 ...recordTypes,
106 },
107 };
108 if (businessProcessName) {
109 // We need to keep track of these globally for the package.xml
110 const values = {
111 fullName: businessProcessPicklistVal,
112 };
113 if (name !== 'Opportunity') {
114 values.default = true;
115 }
116 allBusinessProcesses.push(`${name}.${businessProcessName}`);
117 output = {
118 ...output,
119 recordTypes: {
120 ...recordTypes,
121 businessProcess: businessProcessName,
122 },
123 businessProcesses: {
124 fullName: businessProcessName,
125 isActive: true,
126 values,
127 },
128 };
129 }
130 }
131 return output;
132};
133exports.createRecordTypeAndBusinessProcessFileContent = createRecordTypeAndBusinessProcessFileContent;
134/**
135 * Helper class for dealing with the settings that are defined in a scratch definition file. This class knows how to extract the
136 * settings from the definition, how to expand them into a MD directory and how to generate a package.xml.
137 */
138class SettingsGenerator {
139 constructor(options) {
140 this.allRecordTypes = [];
141 this.allBusinessProcesses = [];
142 this.logger = logger_1.Logger.childFromRoot('SettingsGenerator');
143 // If SFDX_MDAPI_TEMP_DIR is set, copy settings to that dir for people to inspect.
144 const mdApiTmpDir = options?.mdApiTmpDir ?? kit_1.env.getString('SFDX_MDAPI_TEMP_DIR');
145 this.shapeDirName = options?.shapeDirName ?? `shape_${Date.now()}`;
146 this.packageFilePath = path.join(this.shapeDirName, 'package.xml');
147 let storePath;
148 if (!options?.asDirectory) {
149 storePath = mdApiTmpDir ? path.join(mdApiTmpDir, `${this.shapeDirName}.zip`) : undefined;
150 this.writer = new zipWriter_1.ZipWriter(storePath);
151 }
152 else {
153 storePath = mdApiTmpDir ? path.join(mdApiTmpDir, this.shapeDirName) : undefined;
154 this.writer = new directoryWriter_1.DirectoryWriter(storePath);
155 }
156 }
157 /** extract the settings from the scratch def file, if they are present. */
158 // eslint-disable-next-line @typescript-eslint/require-await
159 async extract(scratchDef) {
160 this.logger.debug('extracting settings from scratch definition file');
161 this.settingData = scratchDef.settings;
162 this.objectSettingsData = scratchDef.objectSettings;
163 this.logger.debug('settings are', this.settingData);
164 return { settings: this.settingData, objectSettings: this.objectSettingsData };
165 }
166 /** True if we are currently tracking setting or object setting data. */
167 hasSettings() {
168 return !(0, kit_1.isEmpty)(this.settingData) || !(0, kit_1.isEmpty)(this.objectSettingsData);
169 }
170 /** Create temporary deploy directory used to upload the scratch org shape.
171 * This will create the dir, all of the .setting files and minimal object files needed for objectSettings
172 */
173 async createDeploy() {
174 const settingsDir = path.join(this.shapeDirName, 'settings');
175 const objectsDir = path.join(this.shapeDirName, 'objects');
176 await Promise.all([
177 this.writeSettingsIfNeeded(settingsDir),
178 this.writeObjectSettingsIfNeeded(objectsDir, this.allRecordTypes, this.allBusinessProcesses),
179 ]);
180 }
181 /**
182 * Deploys the settings to the org.
183 */
184 async deploySettingsViaFolder(scratchOrg, apiVersion) {
185 const username = scratchOrg.getUsername();
186 const logger = await logger_1.Logger.child('deploySettingsViaFolder');
187 await this.createDeployPackageContents(apiVersion);
188 const connection = scratchOrg.getConnection();
189 logger.debug(`deploying to apiVersion: ${apiVersion}`);
190 connection.setApiVersion(apiVersion);
191 const { id } = await connection.deploy(this.writer.buffer, {});
192 logger.debug(`deploying settings id ${id}`);
193 let result = await connection.metadata.checkDeployStatus(id);
194 const pollingOptions = {
195 async poll() {
196 try {
197 result = await connection.metadata.checkDeployStatus(id, true);
198 logger.debug(`Deploy id: ${id} status: ${result.status}`);
199 if (breakPolling.includes(result.status)) {
200 return {
201 completed: true,
202 payload: result.status,
203 };
204 }
205 return {
206 completed: false,
207 };
208 }
209 catch (error) {
210 logger.debug(`An error occurred trying to check deploy id: ${id}`);
211 logger.debug(`Error: ${error.message}`);
212 logger.debug('Re-trying deploy check again....');
213 return {
214 completed: false,
215 };
216 }
217 },
218 timeout: kit_1.Duration.minutes(10),
219 frequency: kit_1.Duration.seconds(1),
220 timeoutErrorName: 'DeployingSettingsTimeoutError',
221 };
222 const client = await pollingClient_1.PollingClient.create(pollingOptions);
223 const status = await client.subscribe();
224 if (status !== RequestStatus.Succeeded) {
225 const componentFailures = (0, ts_types_1.ensureObject)(result.details).componentFailures;
226 const failures = (Array.isArray(componentFailures) ? componentFailures : [componentFailures])
227 .map((failure) => failure.problem)
228 .join('\n');
229 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');
230 error.setData(result);
231 throw error;
232 }
233 }
234 async createDeployPackageContents(apiVersion) {
235 const packageObjectProps = (0, exports.createObjectFileContent)({
236 allRecordTypes: this.allRecordTypes,
237 allBusinessProcesses: this.allBusinessProcesses,
238 apiVersion,
239 settingData: this.settingData,
240 objectSettingsData: this.objectSettingsData,
241 });
242 const xml = js2xmlparser.parse('Package', packageObjectProps);
243 await this.writer.addToStore(xml, this.packageFilePath);
244 await this.writer.finalize();
245 }
246 getShapeDirName() {
247 return this.shapeDirName;
248 }
249 /**
250 * Returns the destination where the writer placed the settings content.
251 *
252 */
253 getDestinationPath() {
254 return this.writer.getDestinationPath();
255 }
256 async writeObjectSettingsIfNeeded(objectsDir, allRecordTypes, allbusinessProcesses) {
257 if (this.objectSettingsData) {
258 await Promise.all(Object.entries(this.objectSettingsData).map(([item, value]) => {
259 const fileContent = (0, exports.createRecordTypeAndBusinessProcessFileContent)(item, value, allRecordTypes, allbusinessProcesses);
260 const xml = js2xmlparser.parse('CustomObject', fileContent);
261 return this.writer.addToStore(xml, path.join(objectsDir, (0, kit_1.upperFirst)(item) + '.object'));
262 }));
263 }
264 }
265 async writeSettingsIfNeeded(settingsDir) {
266 if (this.settingData) {
267 await Promise.all(Object.entries(this.settingData).map(([item, value]) => {
268 const typeName = (0, kit_1.upperFirst)(item);
269 const fname = typeName.replace('Settings', '');
270 const fileContent = js2xmlparser.parse(typeName, value);
271 return this.writer.addToStore(fileContent, path.join(settingsDir, fname + '.settings'));
272 }));
273 }
274 }
275}
276exports.default = SettingsGenerator;
277//# sourceMappingURL=scratchOrgSettingsGenerator.js.map
\No newline at end of file