UNPKG

@salesforce/plugin-org

Version:

Commands to interact with Salesforce orgs

309 lines 13.3 kB
/* * Copyright 2026, Salesforce, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { Duration } from '@salesforce/kit'; import { Flags } from '@salesforce/sf-plugins-core'; import { Lifecycle, Messages, SandboxEvents, SfError } from '@salesforce/core'; import requestFunctions, { readSandboxDefFile } from '../../../shared/sandboxRequest.js'; import { SandboxCommandBase } from '../../../shared/sandboxCommandBase.js'; import { SandboxLicenseType } from '../../../shared/orgTypes.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-org', 'create.sandbox'); const getLicenseTypes = () => Object.values(SandboxLicenseType); export default class CreateSandbox extends SandboxCommandBase { static summary = messages.getMessage('summary'); static description = messages.getMessage('description'); static examples = messages.getMessages('examples'); static aliases = ['env:create:sandbox']; static deprecateAliases = true; // eslint-disable-next-line sf-plugin/spread-base-flags static flags = { // needs to change when new flags are available 'definition-file': Flags.file({ exists: true, char: 'f', summary: messages.getMessage('flags.definitionFile.summary'), description: messages.getMessage('flags.definitionFile.description'), }), 'set-default': Flags.boolean({ char: 's', summary: messages.getMessage('flags.setDefault.summary'), }), alias: Flags.string({ char: 'a', summary: messages.getMessage('flags.alias.summary'), description: messages.getMessage('flags.alias.description'), }), wait: Flags.duration({ char: 'w', summary: messages.getMessage('flags.wait.summary'), description: messages.getMessage('flags.wait.description'), min: 1, unit: 'minutes', default: Duration.minutes(30), helpValue: '<minutes>', exclusive: ['async'], }), 'poll-interval': Flags.duration({ char: 'i', summary: messages.getMessage('flags.poll-interval.summary'), min: 15, unit: 'seconds', default: Duration.seconds(30), helpValue: '<seconds>', exclusive: ['async'], }), async: Flags.boolean({ summary: messages.getMessage('flags.async.summary'), description: messages.getMessage('flags.async.description'), exclusive: ['wait', 'poll-interval'], }), name: Flags.string({ char: 'n', summary: messages.getMessage('flags.name.summary'), description: messages.getMessage('flags.name.description'), parse: (name) => { if (name.length > 10) { throw messages.createError('error.SandboxNameLength', [name]); } return Promise.resolve(name); }, }), 'source-sandbox-name': Flags.string({ summary: messages.getMessage('flags.source-sandbox-name.summary'), description: messages.getMessage('flags.source-sandbox-name.description'), exclusive: ['license-type', 'source-id'], deprecateAliases: true, aliases: ['clone', 'c'], }), 'source-id': Flags.salesforceId({ summary: messages.getMessage('flags.source-id.summary'), description: messages.getMessage('flags.source-id.description'), exclusive: ['license-type', 'source-sandbox-name'], length: 'both', startsWith: '0GQ', char: undefined, }), 'license-type': Flags.custom({ options: getLicenseTypes(), })({ char: 'l', summary: messages.getMessage('flags.licenseType.summary'), exclusive: ['source-sandbox-name', 'source-id'], }), 'target-org': Flags.requiredOrg({ char: 'o', summary: messages.getMessage('flags.targetOrg.summary'), description: messages.getMessage('flags.targetOrg.description'), required: true, }), 'no-prompt': Flags.boolean({ summary: messages.getMessage('flags.noPrompt.summary'), }), 'no-track-source': Flags.boolean({ summary: messages.getMessage('flags.no-track-source.summary'), description: messages.getMessage('flags.no-track-source.description'), allowNo: false, }), }; flags; async run() { this.sandboxRequestConfig = await this.getSandboxRequestConfig(); this.flags = (await this.parse(CreateSandbox)).flags; this.debug('Create started with args %s ', this.flags); this.validateFlags(); return this.createSandbox(); } getCheckSandboxStatusParams() { return [ this.config.bin, ...(this.latestSandboxProgressObj ? [this.latestSandboxProgressObj.Id] : []), this.flags['target-org'].getUsername(), ]; } async createSandboxRequest() { const requestOptions = { ...(this.flags.name ? { SandboxName: this.flags.name } : {}), ...(this.flags['source-sandbox-name'] ? { SourceSandboxName: this.flags['source-sandbox-name'] } : this.flags['source-id'] ? { SourceId: this.flags['source-id'] } : {}), ...(!this.flags['source-sandbox-name'] && !this.flags['source-id'] && this.flags['license-type'] ? { LicenseType: this.flags['license-type'] } : {}), }; const { sandboxReq, srcSandboxName, srcId } = await requestFunctions.createSandboxRequest(this.flags['definition-file'], undefined, requestOptions); let apexId; let groupId; if (sandboxReq.ApexClassName) { apexId = await requestFunctions.getApexClassIdByName(this.flags['target-org'].getConnection(), sandboxReq.ApexClassName); // convert name to ID delete sandboxReq.ApexClassName; } if (sandboxReq.ActivationUserGroupName) { groupId = await requestFunctions.getUserGroupIdByName(this.flags['target-org'].getConnection(), sandboxReq.ActivationUserGroupName); delete sandboxReq.ActivationUserGroupName; } const nameOrId = srcSandboxName ?? srcId; // Get source sandbox features if cloning if (nameOrId) { const sourceFeatures = await this.getSandboxFeatures(nameOrId); if (sourceFeatures) { sandboxReq.Features = sourceFeatures; } } return { ...sandboxReq, ...(srcSandboxName ? { SourceId: await requestFunctions.getSrcIdByName(this.flags['target-org'].getConnection(), srcSandboxName) } : {}), ...(srcId ? { SourceId: srcId } : {}), ...(apexId ? { ApexClassId: apexId } : {}), ...(groupId ? { ActivationUserGroupId: groupId } : {}), }; } async getSandboxFeatures(sandboxNameOrId) { const prodOrg = this.flags['target-org']; try { if (sandboxNameOrId?.startsWith('0GQ')) { const sbxInfo = await prodOrg.querySandboxInfo({ id: sandboxNameOrId }); if (sbxInfo?.Features) { return sbxInfo.Features; } return (await prodOrg.querySandboxProcessBySandboxInfoId(sandboxNameOrId)).Features; } const sbxInfo = await prodOrg.querySandboxInfo({ name: sandboxNameOrId }); if (sbxInfo?.Features) { return sbxInfo.Features; } return (await prodOrg.querySandboxProcessBySandboxName(sandboxNameOrId)).Features; } catch (err) { if (err instanceof SfError && (err.name.includes('AUTH') ?? err.name.includes('CONNECTION'))) { this.warn(`Warning: Could not retrieve sandbox features due to ${err.name}.`); } throw err; } } async createSandbox() { const lifecycle = Lifecycle.getInstance(); this.prodOrg = this.flags['target-org']; const sandboxReq = await this.createSandboxRequest(); await this.confirmSandboxReq({ ...sandboxReq, }); this.initSandboxProcessData(sandboxReq); this.registerLifecycleListenersAndMSO(lifecycle, { mso: { title: 'Sandbox Create', }, isAsync: this.flags.async, setDefault: this.flags['set-default'], alias: this.flags.alias, prodOrg: this.prodOrg, tracksSource: this.flags['no-track-source'] === true ? false : undefined, }); this.debug('Calling create with SandboxRequest: %s ', sandboxReq); try { const sandboxProcessObject = await this.prodOrg.createSandbox(sandboxReq, { wait: this.flags.wait, interval: this.flags['poll-interval'], async: this.flags.async, }); this.latestSandboxProgressObj = sandboxProcessObject; this.saveSandboxProgressConfig(); if (this.flags.async) { process.exitCode = 68; } return this.getSandboxCommandResponse(); } catch (err) { if (this.pollingTimeOut && this.latestSandboxProgressObj) { void lifecycle.emit(SandboxEvents.EVENT_ASYNC_RESULT, undefined); process.exitCode = 68; return this.getSandboxCommandResponse(); } else if (err instanceof SfError && err.name === 'SandboxCreateNotCompleteError' && this.latestSandboxProgressObj) { void lifecycle.emit(SandboxEvents.EVENT_ASYNC_RESULT, undefined); process.exitCode = 68; return this.latestSandboxProgressObj; } throw err; } } initSandboxProcessData(sandboxReq) { this.sandboxRequestData = { ...this.sandboxRequestData, alias: this.flags.alias, setDefault: this.flags['set-default'], prodOrgUsername: this.flags['target-org'].getUsername(), action: 'Create', sandboxProcessObject: { SandboxName: sandboxReq.SandboxName, }, sandboxRequest: sandboxReq, tracksSource: this.flags['no-track-source'] === true ? false : undefined, }; this.saveSandboxProgressConfig(); } async confirmSandboxReq(sandboxReq) { if (this.flags['no-prompt'] || this.jsonEnabled()) return; const data = Object.entries(sandboxReq).map(([key, value]) => ({ key, value })); this.table({ data, columns: [ { key: 'key', name: 'Field' }, { key: 'value', name: 'Value' }, ], title: 'Config Sandbox Request', }); if (!(await this.confirm({ message: messages.getMessage('isConfigurationOk'), }))) { throw messages.createError('error.UserNotSatisfiedWithSandboxConfig'); } } validateFlags() { if (!this.flags['poll-interval'] || !this.flags.wait) { return; } if (this.flags['poll-interval'].seconds > this.flags.wait.seconds) { throw messages.createError('error.pollIntervalGreaterThanWait', [ this.flags['poll-interval'].seconds, this.flags.wait.seconds, ]); } if (!this.flags['definition-file']) { return undefined; } const parsedDef = readSandboxDefFile(this.flags['definition-file']); if (this.flags['source-id'] && parsedDef.SourceId) { throw messages.createError('error.bothIdFlagAndDefFilePropertyAreProvided'); } if (this.flags['source-sandbox-name'] && parsedDef.SourceSandboxName) { throw messages.createError('error.bothNameFlagAndDefFilePropertyAreProvided'); } if (this.flags['source-id'] && parsedDef.SourceSandboxName) { throw messages.createError('error.bothIdFlagAndNameDefFileAreNotAllowed'); } if (this.flags['source-sandbox-name'] && parsedDef.SourceId) { throw messages.createError('error.bothIdFlagAndNameDefFileAreNotAllowed'); } } } //# sourceMappingURL=sandbox.js.map