Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sl/w 17246841 #1297

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
5 changes: 2 additions & 3 deletions messages/sandboxbase.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# sandboxSuccess

The sandbox org %s was successful.
Your sandbox is ready.

# sandboxSuccess.actions

The username for the sandbox is %s.
You can open the org by running "%s org open -o %s"
You can open it by running "%s org open -o %s"

# checkSandboxStatus

Expand Down
24 changes: 12 additions & 12 deletions src/commands/org/create/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export default class CreateSandbox extends SandboxCommandBase<SandboxCommandResp

this.debug('Create started with args %s ', this.flags);
this.validateFlags();

return this.createSandbox();
}

Expand Down Expand Up @@ -187,25 +188,24 @@ export default class CreateSandbox extends SandboxCommandBase<SandboxCommandResp

private async createSandbox(): Promise<SandboxCommandResponse> {
const lifecycle = Lifecycle.getInstance();

this.prodOrg = this.flags['target-org'];

this.registerLifecycleListeners(lifecycle, {
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,
});
const sandboxReq = await this.createSandboxRequest();
await this.confirmSandboxReq({
...sandboxReq,
});
this.initSandboxProcessData(sandboxReq);

if (!this.flags.async) {
this.spinner.start('Sandbox Create');
}
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);

Expand All @@ -217,12 +217,12 @@ export default class CreateSandbox extends SandboxCommandBase<SandboxCommandResp
});
this.latestSandboxProgressObj = sandboxProcessObject;
this.saveSandboxProgressConfig();

if (this.flags.async) {
process.exitCode = 68;
}
return this.getSandboxCommandResponse();
} catch (err) {
this.spinner.stop();
if (this.pollingTimeOut && this.latestSandboxProgressObj) {
void lifecycle.emit(SandboxEvents.EVENT_ASYNC_RESULT, undefined);
process.exitCode = 68;
Expand Down
14 changes: 8 additions & 6 deletions src/commands/org/refresh/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,18 +126,21 @@ export default class RefreshSandbox extends SandboxCommandBase<SandboxCommandRes
await this.confirmSandboxRefresh(this.sbxConfig);

const lifecycle = Lifecycle.getInstance();
this.registerLifecycleListeners(lifecycle, { isAsync: this.flags['async'], prodOrg: this.prodOrg });
this.registerLifecycleListenersAndMSO(lifecycle, {
mso: {
refresh: true,
title: 'Sandbox Refresh',
},
isAsync: this.flags['async'],
prodOrg: this.prodOrg,
});

// remove uneditable fields before refresh
const updateableSandboxInfo = omit(this.sbxConfig, uneditableFields);
this.debug('Calling refresh with SandboxInfo: %s ', updateableSandboxInfo);
this.initSandboxProcessData(this.sbxConfig);

try {
if (!this.flags.async) {
this.spinner.start('Sandbox Refresh');
}

const sandboxProcessObject = await this.prodOrg.refreshSandbox(updateableSandboxInfo, {
wait: this.flags['wait'],
interval: this.flags['poll-interval'],
Expand All @@ -158,7 +161,6 @@ export default class RefreshSandbox extends SandboxCommandBase<SandboxCommandRes
}
return this.getSandboxCommandResponse();
} catch (err) {
this.spinner.stop();
if (this.pollingTimeOut && this.latestSandboxProgressObj) {
void lifecycle.emit(SandboxEvents.EVENT_ASYNC_RESULT, undefined);
process.exitCode = 68;
Expand Down
18 changes: 11 additions & 7 deletions src/commands/org/resume/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import { EOL } from 'node:os';
import { Flags } from '@salesforce/sf-plugins-core';
import {
StateAggregator,
Expand Down Expand Up @@ -117,7 +118,11 @@ export default class ResumeSandbox extends SandboxCommandBase<SandboxCommandResp
this.flags['target-org'] = this.prodOrg;
const lifecycle = Lifecycle.getInstance();

this.registerLifecycleListeners(lifecycle, {
this.registerLifecycleListenersAndMSO(lifecycle, {
mso: {
title: 'Resume Sandbox',
refresh: this.sandboxRequestData.action === 'Refresh',
},
isAsync: false,
alias: this.sandboxRequestData.alias,
setDefault: this.sandboxRequestData.setDefault,
Expand All @@ -139,10 +144,6 @@ export default class ResumeSandbox extends SandboxCommandBase<SandboxCommandResp

const sandboxReq = this.createResumeSandboxRequest();

if (this.flags.wait?.seconds && this.flags.wait.seconds > 0) {
this.spinner.start(`Resume ${this.sandboxRequestData.action ?? 'Create/Refresh'}`);
}

this.debug('Calling resume with ResumeSandboxRequest: %s ', sandboxReq);

try {
Expand All @@ -152,7 +153,6 @@ export default class ResumeSandbox extends SandboxCommandBase<SandboxCommandResp
});
return this.getSandboxCommandResponse();
} catch (err) {
this.spinner.stop();
if (this.latestSandboxProgressObj && this.pollingTimeOut) {
void lifecycle.emit(SandboxEvents.EVENT_ASYNC_RESULT, undefined);
process.exitCode = 68;
Expand Down Expand Up @@ -185,12 +185,16 @@ export default class ResumeSandbox extends SandboxCommandBase<SandboxCommandResp
const entries = this.sandboxRequestConfig.entries() as Array<[string, SandboxRequestCacheEntry]>;
const sce = entries.find(([, e]) => e?.sandboxProcessObject?.Id === this.flags['job-id'])?.[1];
sandboxRequestCacheEntry = sce;
if (sandboxRequestCacheEntry === undefined) {
this.warn(
`Could not find a cache entry for ${this.flags['job-id']}.${EOL}If you are resuming a sandbox operation from a different machine note that we cannot set the alias/set-default flag values as those are saved locally.`
);
}
}

// If the action is in the cache entry, use it.
if (sandboxRequestCacheEntry?.action) {
this.action = sandboxRequestCacheEntry?.action;
this.sandboxProgress.action = sandboxRequestCacheEntry?.action;
}

return {
Expand Down
129 changes: 57 additions & 72 deletions src/shared/sandboxCommandBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* 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 { Config } from '@oclif/core';
import {
Expand All @@ -21,8 +20,7 @@ import {
SandboxUserAuthResponse,
StatusEvent,
} from '@salesforce/core';
import { SandboxProgress } from './sandboxProgress.js';
import { State } from './stagedProgress.js';
import { SandboxStages } from './sandboxStages.js';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-org', 'sandboxbase');
Expand All @@ -32,7 +30,7 @@ export type SandboxCommandResponse = SandboxProcessObject & {
};

export abstract class SandboxCommandBase<T> extends SfCommand<T> {
protected sandboxProgress: SandboxProgress;
protected stages!: SandboxStages;
protected latestSandboxProgressObj?: SandboxProcessObject;
protected sandboxAuth?: SandboxUserAuthResponse;
protected prodOrg?: Org;
Expand All @@ -47,10 +45,9 @@ export abstract class SandboxCommandBase<T> extends SfCommand<T> {
this.action =
this.constructor.name === 'RefreshSandbox'
? 'Refresh'
: this.constructor.name === 'CreateSandbox'
: ['CreateSandbox', 'ResumeSandbox'].includes(this.constructor.name)
? 'Create'
: 'Create/Refresh';
this.sandboxProgress = new SandboxProgress({ action: this.action });
}
protected async getSandboxRequestConfig(): Promise<SandboxRequestCache> {
if (!this.sandboxRequestConfig) {
Expand Down Expand Up @@ -85,72 +82,81 @@ export abstract class SandboxCommandBase<T> extends SfCommand<T> {
return true;
}

protected registerLifecycleListeners(
protected registerLifecycleListenersAndMSO(
lifecycle: Lifecycle,
options: { isAsync: boolean; alias?: string; setDefault?: boolean; prodOrg?: Org; tracksSource?: boolean }
options: {
mso: { title: string; refresh?: boolean };
isAsync: boolean;
alias?: string;
setDefault?: boolean;
prodOrg?: Org;
tracksSource?: boolean;
}
): void {
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: SandboxProcessObject) => {
this.stages.start();
this.latestSandboxProgressObj = results;
this.sandboxProgress.markPreviousStagesAsCompleted(
results.Status !== 'Completed' ? results.Status : 'Authenticating'
);
this.stages.update(this.latestSandboxProgressObj);

return Promise.resolve(this.updateSandboxRequestData());
});

lifecycle.on(SandboxEvents.EVENT_ASYNC_RESULT, async (results?: SandboxProcessObject) => {
this.latestSandboxProgressObj = results ?? this.latestSandboxProgressObj;
this.updateSandboxRequestData();
if (!options.isAsync) {
this.spinner.stop();
}
// things that require data on latestSandboxProgressObj
if (this.latestSandboxProgressObj) {
const progress = this.sandboxProgress.getSandboxProgress({
sandboxProcessObj: this.latestSandboxProgressObj,
sandboxRes: undefined,
});
const currentStage = progress.status;
this.sandboxProgress.markPreviousStagesAsCompleted(currentStage);
this.updateStage(currentStage, 'inProgress');
this.updateProgress(
{ sandboxProcessObj: this.latestSandboxProgressObj, sandboxRes: undefined },
options.isAsync
);
lifecycle.on(SandboxEvents.EVENT_ASYNC_RESULT, async (results: SandboxProcessObject | undefined) => {
// 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()]));
}
this.log(this.sandboxProgress.formatProgressStatus(false));
return Promise.resolve(this.info(messages.getMessage('checkSandboxStatus', this.getCheckSandboxStatusParams())));
});

lifecycle.on(SandboxEvents.EVENT_STATUS, async (results: StatusEvent) => {
// this starts MSO for:
// * org create/create sandbox
this.stages.start();
this.latestSandboxProgressObj = results.sandboxProcessObj;
this.updateSandboxRequestData();
const progress = this.sandboxProgress.getSandboxProgress(results);
const currentStage = progress.status;
this.updateStage(currentStage, 'inProgress');
return Promise.resolve(this.updateProgress(results, options.isAsync));

this.stages.update(this.latestSandboxProgressObj);

return Promise.resolve();
});

lifecycle.on(SandboxEvents.EVENT_AUTH, async (results: SandboxUserAuthResponse) => {
this.sandboxUsername = results.authUserName;
this.stages.auth();
this.sandboxAuth = results;
return Promise.resolve();
});

lifecycle.on(SandboxEvents.EVENT_RESULT, async (results: ResultEvent) => {
this.latestSandboxProgressObj = results.sandboxProcessObj;
this.sandboxUsername = results.sandboxRes.authUserName;
this.updateSandboxRequestData();
this.sandboxProgress.markPreviousStagesAsCompleted();
this.updateProgress(results, options.isAsync);
if (!options.isAsync) {
this.progress.stop();
}

this.stages.update(results.sandboxProcessObj);

if (results.sandboxRes?.authUserName) {
const authInfo = await AuthInfo.create({ username: results.sandboxRes?.authUserName });
await authInfo.handleAliasAndDefaultSettings({
Expand All @@ -160,8 +166,9 @@ export abstract class SandboxCommandBase<T> extends SfCommand<T> {
setTracksSource: await this.calculateTrackingSetting(options.tracksSource),
});
}
this.stages.stop();

this.removeSandboxProgressConfig();
this.updateProgress(results, options.isAsync);
this.reportResults(results);
});

Expand All @@ -185,43 +192,14 @@ export abstract class SandboxCommandBase<T> extends SfCommand<T> {
}

protected reportResults(results: ResultEvent): void {
this.log();
this.styledHeader(`Sandbox Org ${this.action} Status`);
this.log(this.sandboxProgress.formatProgressStatus(false));
this.logSuccess(
[
messages.getMessage('sandboxSuccess', [this.action.toLowerCase()]),
messages.getMessages('sandboxSuccess.actions', [
results.sandboxRes?.authUserName,
this.config.bin,
results.sandboxRes?.authUserName,
]),
messages.getMessage('sandboxSuccess'),
messages.getMessages('sandboxSuccess.actions', [this.config.bin, results.sandboxRes?.authUserName]),
].join(os.EOL)
);
}

protected updateProgress(
event: StatusEvent | (Omit<ResultEvent, 'sandboxRes'> & { sandboxRes?: ResultEvent['sandboxRes'] }),
isAsync: boolean
): void {
const sandboxProgress = this.sandboxProgress.getSandboxProgress(event);
this.sandboxUsername = (event as ResultEvent).sandboxRes?.authUserName;
this.sandboxProgress.statusData = {
sandboxUsername: this.sandboxUsername,
sandboxProgress,
sandboxProcessObj: event.sandboxProcessObj,
};
if (!isAsync) {
this.spinner.status = this.sandboxProgress.formatProgressStatus();
}
}

protected updateStage(stage: string | undefined, state: State): void {
if (stage) {
this.sandboxProgress.transitionStages(stage, state);
}
}

protected updateSandboxRequestData(): void {
if (this.sandboxRequestData && this.latestSandboxProgressObj) {
this.sandboxRequestData.sandboxProcessObject = this.latestSandboxProgressObj;
Expand Down Expand Up @@ -262,6 +240,13 @@ export abstract class SandboxCommandBase<T> extends SfCommand<T> {
return { ...(this.latestSandboxProgressObj as SandboxProcessObject), SandboxUsername: sbxUsername };
}

protected catch(error: Error): Promise<never> {
if (this.stages) {
this.stages.stop('failed');
}

return super.catch(error);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
protected async finally(_: Error | undefined): Promise<any> {
const lifecycle = Lifecycle.getInstance();
Expand Down
Loading
Loading