Skip to content

Commit b5aa59e

Browse files
committed
fix: provide better errors when deleting sandboxes
1 parent d25c08b commit b5aa59e

File tree

10 files changed

+1424
-969
lines changed

10 files changed

+1424
-969
lines changed

messages/delete_sandbox.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Delete a sandbox.
66

77
Salesforce CLI marks the org for deletion in the production org that contains the sandbox licenses and then deletes all local references to the org from your computer.
88
Specify a sandbox with either the username you used when you logged into it, or the alias you gave the sandbox when you created it. Run "<%= config.bin %> org list" to view all your orgs, including sandboxes, and their aliases.
9+
Both the sandbox and the associated production org must already be authenticated with the CLI to successfully delete the sandbox.
910

1011
# examples
1112

@@ -17,7 +18,7 @@ Specify a sandbox with either the username you used when you logged into it, or
1718

1819
<%= config.bin %> <%= command.id %> --target-org [email protected]
1920

20-
- Delete the sandbox without prompting to confirm :
21+
- Delete the sandbox without prompting to confirm:
2122

2223
<%= config.bin %> <%= command.id %> --target-org my-sandbox --no-prompt
2324

@@ -41,6 +42,11 @@ Successfully marked sandbox %s for deletion.
4142

4243
There is no sandbox with the username %s.
4344

44-
# error.isNotSandbox
45+
# error.unknownSandbox
4546

46-
The target org, %s, is not a sandbox.
47+
The org with username: %s is not known by the CLI to be a sandbox
48+
49+
# error.unknownSandbox.actions
50+
51+
Re-authenticate the sandbox with the CLI and try again.
52+
Ensure the CLI has authenticated with the sandbox's production org.

src/commands/org/delete/sandbox.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* Licensed under the BSD 3-Clause license.
55
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
*/
7-
import { AuthInfo, AuthRemover, Messages, Org, StateAggregator } from '@salesforce/core';
7+
import { AuthInfo, AuthRemover, Messages, Org, SfError, StateAggregator } from '@salesforce/core';
88
import { Flags, SfCommand } from '@salesforce/sf-plugins-core';
99
import { orgThatMightBeDeleted } from '../../../shared/flags';
1010

@@ -16,7 +16,7 @@ export interface SandboxDeleteResponse {
1616
username: string;
1717
}
1818

19-
export default class EnvDeleteSandbox extends SfCommand<SandboxDeleteResponse> {
19+
export default class DeleteSandbox extends SfCommand<SandboxDeleteResponse> {
2020
public static readonly summary = messages.getMessage('summary');
2121
public static readonly description = messages.getMessage('description');
2222
public static readonly examples = messages.getMessages('examples');
@@ -34,14 +34,30 @@ export default class EnvDeleteSandbox extends SfCommand<SandboxDeleteResponse> {
3434
};
3535

3636
public async run(): Promise<SandboxDeleteResponse> {
37-
const flags = (await this.parse(EnvDeleteSandbox)).flags;
37+
const flags = (await this.parse(DeleteSandbox)).flags;
3838
const username = flags['target-org'];
39+
let orgId: string;
3940

40-
const orgId = (await AuthInfo.create({ username })).getFields().orgId as string;
41-
const isSandbox = await (await StateAggregator.getInstance()).sandboxes.hasFile(orgId);
41+
try {
42+
const sbxAuthFields = (await AuthInfo.create({ username })).getFields();
43+
orgId = sbxAuthFields.orgId as string;
44+
} catch (error) {
45+
if (error instanceof SfError && error.name === 'NamedOrgNotFoundError') {
46+
error.actions = [
47+
`Ensure the alias or username for the ${username} org is correct.`,
48+
`Ensure the ${username} org has been authenticated with the CLI.`,
49+
];
50+
}
51+
throw error;
52+
}
53+
54+
// The StateAggregator identifies sandbox auth files with a pattern of
55+
// <sandbox_ID>.sandbox.json. E.g., 00DZ0000009T3VZMA0.sandbox.json
56+
const stateAggregator = await StateAggregator.getInstance();
57+
const cliCreatedSandbox = await stateAggregator.sandboxes.hasFile(orgId);
4258

43-
if (!isSandbox) {
44-
throw messages.createError('error.isNotSandbox', [username]);
59+
if (!cliCreatedSandbox) {
60+
throw messages.createError('error.unknownSandbox', [username]);
4561
}
4662

4763
if (flags['no-prompt'] || (await this.confirm(messages.getMessage('prompt.confirm', [username])))) {

src/commands/org/delete/scratch.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export interface ScratchDeleteResponse {
1717
username: string;
1818
}
1919

20-
export default class EnvDeleteScratch extends SfCommand<ScratchDeleteResponse> {
20+
export default class DeleteScratch extends SfCommand<ScratchDeleteResponse> {
2121
public static readonly summary = messages.getMessage('summary');
2222
public static readonly description = messages.getMessage('description');
2323
public static readonly examples = messages.getMessages('examples');
@@ -35,7 +35,7 @@ export default class EnvDeleteScratch extends SfCommand<ScratchDeleteResponse> {
3535
};
3636

3737
public async run(): Promise<ScratchDeleteResponse> {
38-
const flags = (await this.parse(EnvDeleteScratch)).flags;
38+
const flags = (await this.parse(DeleteScratch)).flags;
3939
const resolvedUsername = flags['target-org'];
4040
const orgId = (await AuthInfo.create({ username: resolvedUsername })).getFields().orgId as string;
4141

test/nut/scratchDelete.nut.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit';
1111
import { assert, expect } from 'chai';
1212
import { ScratchDeleteResponse } from '../../src/commands/org/delete/scratch';
1313

14-
describe('env:delete:scratch NUTs', () => {
14+
describe('org:delete:scratch NUTs', () => {
1515
const scratchOrgAlias = 'scratch-org';
1616
const scratchOrgAlias2 = 'scratch-org-2';
1717
const scratchOrgAlias3 = 'scratch-org-3';

test/unit/force/org/clone.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import * as requestFunctions from '../../../../src/shared/sandboxRequest';
1616

1717
config.truncateThreshold = 0;
1818

19-
describe('org:clone', () => {
19+
describe('[DEPRECATED] force:org:clone', () => {
2020
const $$ = new TestContext();
2121

2222
beforeEach(async () => {

test/unit/force/org/delete.test.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ import { MockTestOrgData, TestContext } from '@salesforce/core/lib/testSetup';
1010
import { config, expect } from 'chai';
1111
import { stubPrompter, stubSfCommandUx } from '@salesforce/sf-plugins-core';
1212
import { SandboxAccessor } from '@salesforce/core/lib/stateAggregator/accessors/sandboxAccessor';
13-
import { Delete } from '../../../../src/commands/force/org/delete';
13+
import { Delete as LegacyDelete } from '../../../../src/commands/force/org/delete';
1414

1515
config.truncateThreshold = 0;
1616

1717
Messages.importMessagesDirectory(__dirname);
1818
const messages = Messages.loadMessages('@salesforce/plugin-org', 'delete');
1919

20-
describe('org:delete', () => {
20+
describe('[DEPRECATED] force:org:delete', () => {
2121
const $$ = new TestContext();
2222
const testOrg = new MockTestOrgData();
2323
const testHub = new MockTestOrgData();
@@ -35,7 +35,7 @@ describe('org:delete', () => {
3535
it('will throw an error when no default set', async () => {
3636
await $$.stubConfig({});
3737
try {
38-
await Delete.run();
38+
await LegacyDelete.run();
3939
expect.fail('should have thrown an error');
4040
} catch (e) {
4141
const err = e as SfError;
@@ -46,7 +46,7 @@ describe('org:delete', () => {
4646

4747
it('will prompt before attempting to delete', async () => {
4848
await $$.stubConfig({ 'target-org': testOrg.username });
49-
const res = await Delete.run([]);
49+
const res = await LegacyDelete.run([]);
5050
expect(prompterStubs.confirm.calledOnce).to.equal(true);
5151
expect(prompterStubs.confirm.firstCall.args[0]).to.equal(
5252
messages.getMessage('confirmDelete', ['scratch', testOrg.username])
@@ -57,7 +57,7 @@ describe('org:delete', () => {
5757
it('will resolve a default alias', async () => {
5858
await $$.stubConfig({ 'target-org': 'myAlias' });
5959
$$.stubAliases({ myAlias: testOrg.username });
60-
const res = await Delete.run([]);
60+
const res = await LegacyDelete.run([]);
6161
expect(prompterStubs.confirm.calledOnce).to.equal(true);
6262
expect(prompterStubs.confirm.firstCall.args[0]).to.equal(
6363
messages.getMessage('confirmDelete', ['scratch', testOrg.username])
@@ -67,7 +67,7 @@ describe('org:delete', () => {
6767

6868
it('will determine sandbox vs scratch org and delete sandbox', async () => {
6969
$$.SANDBOX.stub(SandboxAccessor.prototype, 'hasFile').resolves(true);
70-
const res = await Delete.run(['--target-org', testOrg.username]);
70+
const res = await LegacyDelete.run(['--target-org', testOrg.username]);
7171
expect(prompterStubs.confirm.calledOnce).to.equal(true);
7272
expect(prompterStubs.confirm.firstCall.args[0]).to.equal(
7373
messages.getMessage('confirmDelete', ['sandbox', testOrg.username])
@@ -78,7 +78,7 @@ describe('org:delete', () => {
7878
it('will NOT prompt before deleting scratch org when flag is provided', async () => {
7979
$$.SANDBOX.stub(Org.prototype, 'isSandbox').resolves(false);
8080
$$.SANDBOX.stub(Org.prototype, 'delete').resolves();
81-
const res = await Delete.run(['--noprompt', '--target-org', testOrg.username]);
81+
const res = await LegacyDelete.run(['--noprompt', '--target-org', testOrg.username]);
8282
expect(prompterStubs.confirm.calledOnce).to.equal(false);
8383
expect(sfCommandUxStubs.log.callCount).to.equal(1);
8484
expect(sfCommandUxStubs.log.getCalls().flatMap((call) => call.args)).to.deep.include(
@@ -90,7 +90,7 @@ describe('org:delete', () => {
9090
it('will NOT prompt before deleting sandbox when flag is provided', async () => {
9191
$$.SANDBOX.stub(SandboxAccessor.prototype, 'hasFile').resolves(true);
9292
$$.SANDBOX.stub(Org.prototype, 'delete').resolves();
93-
const res = await Delete.run(['--noprompt', '--target-org', testOrg.username]);
93+
const res = await LegacyDelete.run(['--noprompt', '--target-org', testOrg.username]);
9494
expect(prompterStubs.confirm.calledOnce).to.equal(false);
9595
expect(sfCommandUxStubs.log.callCount).to.equal(1);
9696
expect(sfCommandUxStubs.log.getCalls().flatMap((call) => call.args)).to.deep.include(
@@ -102,7 +102,7 @@ describe('org:delete', () => {
102102
it('will catch the ScratchOrgNotFound and wrap correctly', async () => {
103103
$$.SANDBOX.stub(Org.prototype, 'isSandbox').resolves(false);
104104
$$.SANDBOX.stub(Org.prototype, 'delete').throws(new SfError('bah!', 'ScratchOrgNotFound'));
105-
const res = await Delete.run(['--noprompt', '--target-org', testOrg.username]);
105+
const res = await LegacyDelete.run(['--noprompt', '--target-org', testOrg.username]);
106106
expect(prompterStubs.confirm.calledOnce).to.equal(false);
107107
expect(res).to.deep.equal({ orgId: testOrg.orgId, username: testOrg.username });
108108
expect(sfCommandUxStubs.log.getCalls().flatMap((call) => call.args)).to.deep.include(
@@ -113,7 +113,7 @@ describe('org:delete', () => {
113113
it('will catch the SandboxNotFound and wrap correctly', async () => {
114114
$$.SANDBOX.stub(SandboxAccessor.prototype, 'hasFile').resolves(true);
115115
$$.SANDBOX.stub(Org.prototype, 'delete').throws(new SfError('bah!', 'SandboxNotFound'));
116-
const res = await Delete.run(['--noprompt', '--target-org', testOrg.username]);
116+
const res = await LegacyDelete.run(['--noprompt', '--target-org', testOrg.username]);
117117
expect(prompterStubs.confirm.called).to.equal(false);
118118
expect(res).to.deep.equal({ orgId: testOrg.orgId, username: testOrg.username });
119119
expect(

test/unit/force/org/sandboxCreate.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ config.truncateThreshold = 0;
2929
Messages.importMessagesDirectory(__dirname);
3030
const messages = Messages.loadMessages('@salesforce/plugin-org', 'create');
3131

32-
describe('org:create (sandbox paths)', () => {
32+
describe('[DEPRECATED] force:org:create (sandbox paths)', () => {
3333
const $$ = new TestContext();
3434
const testOrg = new MockTestOrgData();
3535
// stubs

test/unit/force/org/scratchOrgCreate.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const CREATE_RESULT = {
3434
warnings: [],
3535
};
3636

37-
describe('org:create', () => {
37+
describe('[DEPRECATED] force:org:create', () => {
3838
const $$ = new TestContext();
3939
const testHub = new MockTestOrgData();
4040
testHub.isDevHub = true;

0 commit comments

Comments
 (0)