@salesforce/plugin-org
Version:
Commands to interact with Salesforce orgs
209 lines • 9.62 kB
JavaScript
/*
* Copyright (c) 2020, 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
*/
import os from 'node:os';
import { SfCommand } from '@salesforce/sf-plugins-core';
import { AuthInfo, Lifecycle, Messages, SandboxEvents, SandboxRequestCache, } from '@salesforce/core';
import { SandboxStages } from './sandboxStages.js';
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-org', 'sandboxbase');
export class SandboxCommandBase extends SfCommand {
stages;
latestSandboxProgressObj;
sandboxAuth;
prodOrg;
pollingTimeOut = false;
// initialized at top of run method
sandboxRequestConfig;
sandboxRequestData;
action;
sandboxUsername;
constructor(argv, config) {
super(argv, config);
this.action =
this.constructor.name === 'RefreshSandbox'
? 'Refresh'
: ['CreateSandbox', 'ResumeSandbox'].includes(this.constructor.name)
? 'Create'
: 'Create/Refresh';
}
async getSandboxRequestConfig() {
if (!this.sandboxRequestConfig) {
this.sandboxRequestConfig = await SandboxRequestCache.create();
}
return this.sandboxRequestConfig;
}
async calculateTrackingSetting(tracking = true) {
// sandbox types that don't support tracking
if (this.sandboxRequestData?.sandboxRequest.LicenseType &&
['Partial', 'Full'].includes(this.sandboxRequestData.sandboxRequest.LicenseType)) {
return false;
}
// returns false for a sandbox type that supports it but user has opted out
if (tracking === false) {
return false;
}
// on a resume, we might not have a prod org...it's optional?
if (this.prodOrg) {
// if user hasn't opted out of tracking, and sandbox type supports it, verify that prod org supports tracking-enabled sandboxes
const sourceTrackingSettings = await this.prodOrg
.getConnection()
.singleRecordQuery('SELECT IsSourceTrackingSandboxesEnabled FROM SourceTrackingSettings', { tooling: true });
if (sourceTrackingSettings?.IsSourceTrackingSandboxesEnabled !== true) {
return false;
}
}
// default for Dev/DevPro when prod org has feature enabled for sandboxes
return true;
}
registerLifecycleListenersAndMSO(lifecycle, options) {
this.stages = new SandboxStages({
refresh: options.mso.refresh ?? false,
jsonEnabled: this.jsonEnabled(),
title: options.isAsync ? `${options.mso.title} (async)` : options.mso.title,
});
this.stages.start();
lifecycle.on('POLLING_TIME_OUT', async () => {
this.pollingTimeOut = true;
this.stages.stop();
return Promise.resolve(this.updateSandboxRequestData());
});
lifecycle.on(SandboxEvents.EVENT_RESUME, async (results) => {
this.stages.start();
this.latestSandboxProgressObj = results;
this.stages.update(this.latestSandboxProgressObj);
return Promise.resolve(this.updateSandboxRequestData());
});
lifecycle.on(SandboxEvents.EVENT_ASYNC_RESULT, async (results) => {
// this event is fired by commands on poll timeout without any payload,
// we want to make sure to only update state if there's payload (event from sfdx-core).
if (results) {
this.latestSandboxProgressObj = results;
this.stages.update(this.latestSandboxProgressObj);
this.updateSandboxRequestData();
}
this.stages.stop('async');
if (this.pollingTimeOut) {
this.warn(messages.getMessage('warning.ClientTimeoutWaitingForSandboxProcess', [this.action.toLowerCase()]));
}
return Promise.resolve(this.info(messages.getMessage('checkSandboxStatus', this.getCheckSandboxStatusParams())));
});
lifecycle.on(SandboxEvents.EVENT_STATUS, async (results) => {
// this starts MSO for:
// * org create/create sandbox
this.stages.start();
this.latestSandboxProgressObj = results.sandboxProcessObj;
this.updateSandboxRequestData();
this.stages.update(this.latestSandboxProgressObj);
return Promise.resolve();
});
lifecycle.on(SandboxEvents.EVENT_AUTH, async (results) => {
this.sandboxUsername = results.authUserName;
this.stages.auth();
this.sandboxAuth = results;
return Promise.resolve();
});
lifecycle.on(SandboxEvents.EVENT_RESULT, async (results) => {
this.latestSandboxProgressObj = results.sandboxProcessObj;
this.sandboxUsername = results.sandboxRes.authUserName;
this.updateSandboxRequestData();
this.stages.update(results.sandboxProcessObj);
if (results.sandboxRes?.authUserName) {
const authInfo = await AuthInfo.create({ username: results.sandboxRes?.authUserName });
await authInfo.handleAliasAndDefaultSettings({
alias: options.alias,
setDefault: options.setDefault ?? false,
setDefaultDevHub: false,
setTracksSource: await this.calculateTrackingSetting(options.tracksSource),
});
}
this.stages.stop();
this.removeSandboxProgressConfig();
this.reportResults(results);
});
lifecycle.on(SandboxEvents.EVENT_MULTIPLE_SBX_PROCESSES, async (results) => {
const [resumingProcess, ...otherSbxProcesses] = results;
const sbxProcessIds = otherSbxProcesses.map((sbxProcess) => sbxProcess.Id);
const sbxProcessStatuses = otherSbxProcesses.map((sbxProcess) => sbxProcess.Status);
this.warn(messages.getMessage('warning.MultipleMatchingSandboxProcesses', [
otherSbxProcesses[0].SandboxName,
sbxProcessIds.toString(),
sbxProcessStatuses.toString(),
resumingProcess.Id,
sbxProcessIds[0],
this.prodOrg?.getUsername(),
]));
return Promise.resolve();
});
}
reportResults(results) {
this.logSuccess([
messages.getMessage('sandboxSuccess'),
messages.getMessages('sandboxSuccess.actions', [this.config.bin, results.sandboxRes?.authUserName]),
].join(os.EOL));
}
updateSandboxRequestData() {
if (this.sandboxRequestData && this.latestSandboxProgressObj) {
this.sandboxRequestData.sandboxProcessObject = this.latestSandboxProgressObj;
}
this.saveSandboxProgressConfig();
}
saveSandboxProgressConfig() {
if (this.sandboxRequestData?.sandboxProcessObject.SandboxName && this.sandboxRequestData) {
this.sandboxRequestConfig.set(this.sandboxRequestData.sandboxProcessObject.SandboxName, this.sandboxRequestData);
this.sandboxRequestConfig.writeSync();
}
}
// Gets the SandboxName either from the request data or the SandboxProcessObject
getSandboxName() {
return this.sandboxRequestData?.sandboxProcessObject.SandboxName ?? this.latestSandboxProgressObj?.SandboxName;
}
// Not sure why the lint rule is complaining since it's definitely called.
// eslint-disable-next-line class-methods-use-this
getSandboxUsername(prodOrgUsername, sandboxName) {
return `${prodOrgUsername}.${sandboxName}`;
}
// Adds the sandbox username to the command JSON if we know it.
getSandboxCommandResponse() {
let sbxUsername;
if (this.sandboxUsername) {
sbxUsername = this.sandboxUsername;
}
else {
const prodOrgUsername = this.prodOrg?.getUsername();
const sandboxName = this.getSandboxName();
if (prodOrgUsername && sandboxName) {
sbxUsername = this.getSandboxUsername(prodOrgUsername, sandboxName);
}
}
return { ...this.latestSandboxProgressObj, SandboxUsername: sbxUsername };
}
catch(error) {
if (this.stages) {
this.stages.stop('failed');
}
return super.catch(error);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async finally(_) {
const lifecycle = Lifecycle.getInstance();
lifecycle.removeAllListeners('POLLING_TIME_OUT');
lifecycle.removeAllListeners(SandboxEvents.EVENT_RESUME);
lifecycle.removeAllListeners(SandboxEvents.EVENT_ASYNC_RESULT);
lifecycle.removeAllListeners(SandboxEvents.EVENT_STATUS);
lifecycle.removeAllListeners(SandboxEvents.EVENT_AUTH);
lifecycle.removeAllListeners(SandboxEvents.EVENT_RESULT);
lifecycle.removeAllListeners(SandboxEvents.EVENT_MULTIPLE_SBX_PROCESSES);
return super.finally(_);
}
removeSandboxProgressConfig() {
if (this.latestSandboxProgressObj?.SandboxName) {
this.sandboxRequestConfig.unset(this.latestSandboxProgressObj.SandboxName);
this.sandboxRequestConfig.writeSync();
}
}
}
//# sourceMappingURL=sandboxCommandBase.js.map