Skip to content

Commit be97f98

Browse files
authored
Merge pull request #1443 from salesforcecli/sl/W-18539941
feat: add features when cloning sandbox - W-18539941
2 parents c5a956c + 5d1094e commit be97f98

File tree

10 files changed

+101
-12
lines changed

10 files changed

+101
-12
lines changed

messages/create.sandbox.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ You can also use this command to clone an existing sandbox. Use the --source-san
2222

2323
<%= config.bin %> <%= command.id %> --source-sandbox-name ExistingSandbox --name NewClonedSandbox --target-org prodOrg --alias MyDevSandbox --set-default --wait 30
2424

25+
- Clone the existing sandbox with ID "0GQB0000000TVobOAG" and do not wait.
26+
27+
<%= config.bin %> <%= command.id %> --source-id 0GQB0000000TVobOAG --name SbxClone --target-org prodOrg --async
28+
2529
# flags.setDefault.summary
2630

2731
Set the sandbox org as your default org.
@@ -74,7 +78,7 @@ ID of the sandbox org to clone.
7478

7579
# flags.source-id.description
7680

77-
The value of --source-id must be an existing sandbox. The existing sandbox, and the new sandbox specified with the --name flag, must both be associated with the production org (--target-org) that contains the sandbox licenses.
81+
The value of --source-id must be an existing sandbox (SandboxInfo.Id). The existing sandbox, and the new sandbox specified with the --name flag, must both be associated with the production org (--target-org) that contains the sandbox licenses.
7882

7983
You can specify either --source-sandbox-name or --source-id when cloning an existing sandbox, but not both.
8084

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"dependencies": {
88
"@oclif/core": "^4.4.0",
99
"@oclif/multi-stage-output": "^0.8.14",
10-
"@salesforce/core": "^8.13.0",
10+
"@salesforce/core": "^8.15.0",
1111
"@salesforce/kit": "^3.2.3",
1212
"@salesforce/sf-plugins-core": "^12.2.2",
1313
"@salesforce/source-deploy-retrieve": "^12.20.1",

schemas/org-create-sandbox.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@
4444
},
4545
"EndDate": {
4646
"type": "string"
47+
},
48+
"Features": {
49+
"type": "array",
50+
"items": {
51+
"type": "string"
52+
}
4753
}
4854
},
4955
"required": ["CreatedDate", "Id", "LicenseType", "SandboxInfoId", "SandboxName", "Status"]

schemas/org-refresh-sandbox.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@
4444
},
4545
"EndDate": {
4646
"type": "string"
47+
},
48+
"Features": {
49+
"type": "array",
50+
"items": {
51+
"type": "string"
52+
}
4753
}
4854
},
4955
"required": ["CreatedDate", "Id", "LicenseType", "SandboxInfoId", "SandboxName", "Status"]

schemas/org-resume-sandbox.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@
4444
},
4545
"EndDate": {
4646
"type": "string"
47+
},
48+
"Features": {
49+
"type": "array",
50+
"items": {
51+
"type": "string"
52+
}
4753
}
4854
},
4955
"required": ["CreatedDate", "Id", "LicenseType", "SandboxInfoId", "SandboxName", "Status"]

src/commands/org/create/sandbox.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,9 @@ export default class CreateSandbox extends SandboxCommandBase<SandboxCommandResp
9090
'source-id': Flags.salesforceId({
9191
summary: messages.getMessage('flags.source-id.summary'),
9292
description: messages.getMessage('flags.source-id.description'),
93-
exclusive: ['license-type'],
93+
exclusive: ['license-type', 'source-sandbox-name'],
9494
length: 'both',
95+
startsWith: '0GQ',
9596
char: undefined,
9697
}),
9798
'license-type': Flags.custom<SandboxLicenseType>({
@@ -137,7 +138,6 @@ export default class CreateSandbox extends SandboxCommandBase<SandboxCommandResp
137138
}
138139

139140
private async createSandboxRequest(): Promise<SandboxRequest> {
140-
// reuse the existing sandbox request generator, with this command's flags as the varargs
141141
const requestOptions = {
142142
...(this.flags.name ? { SandboxName: this.flags.name } : {}),
143143
...(this.flags['source-sandbox-name']
@@ -175,6 +175,16 @@ export default class CreateSandbox extends SandboxCommandBase<SandboxCommandResp
175175
delete sandboxReq.ActivationUserGroupName;
176176
}
177177

178+
const nameOrId = srcSandboxName ?? srcId;
179+
180+
// Get source sandbox features if cloning
181+
if (nameOrId) {
182+
const sourceFeatures = await this.getSandboxFeatures(nameOrId);
183+
if (sourceFeatures) {
184+
sandboxReq.Features = sourceFeatures;
185+
}
186+
}
187+
178188
return {
179189
...sandboxReq,
180190
...(srcSandboxName
@@ -186,6 +196,33 @@ export default class CreateSandbox extends SandboxCommandBase<SandboxCommandResp
186196
};
187197
}
188198

199+
private async getSandboxFeatures(sandboxNameOrId: string): Promise<string[] | undefined> {
200+
const prodOrg = this.flags['target-org'];
201+
202+
try {
203+
if (sandboxNameOrId?.startsWith('0GQ')) {
204+
const sbxInfo = await prodOrg.querySandboxInfo({ id: sandboxNameOrId });
205+
if (sbxInfo?.Features) {
206+
return sbxInfo.Features;
207+
}
208+
209+
return (await prodOrg.querySandboxProcessBySandboxInfoId(sandboxNameOrId)).Features;
210+
}
211+
212+
const sbxInfo = await prodOrg.querySandboxInfo({ name: sandboxNameOrId });
213+
if (sbxInfo?.Features) {
214+
return sbxInfo.Features;
215+
}
216+
217+
return (await prodOrg.querySandboxProcessBySandboxName(sandboxNameOrId)).Features;
218+
} catch (err) {
219+
if (err instanceof SfError && (err.name.includes('AUTH') ?? err.name.includes('CONNECTION'))) {
220+
this.warn(`Warning: Could not retrieve sandbox features due to ${err.name}.`);
221+
}
222+
throw err;
223+
}
224+
}
225+
189226
private async createSandbox(): Promise<SandboxCommandResponse> {
190227
const lifecycle = Lifecycle.getInstance();
191228
this.prodOrg = this.flags['target-org'];

src/shared/sandboxRequest.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export function readSandboxDefFile(
5555
export async function createSandboxRequest(
5656
definitionFile: string | undefined,
5757
logger?: Logger | undefined,
58-
properties?: Record<string, string | undefined>
58+
properties?: Record<string, string | undefined | string[]>
5959
): Promise<{
6060
sandboxReq: SandboxRequest & {
6161
ApexClassName: string | undefined;
@@ -67,7 +67,7 @@ export async function createSandboxRequest(
6767
export async function createSandboxRequest(
6868
definitionFile: string | undefined,
6969
logger?: Logger | undefined,
70-
properties?: Record<string, string | undefined>
70+
properties?: Record<string, string | undefined | string[]>
7171
): Promise<{
7272
sandboxReq: SandboxRequest & {
7373
ApexClassName: string | undefined;
@@ -77,7 +77,7 @@ export async function createSandboxRequest(
7777
export async function createSandboxRequest(
7878
definitionFile: string | undefined,
7979
logger?: Logger | undefined,
80-
properties?: Record<string, string | undefined>
80+
properties?: Record<string, string | undefined | string[]>
8181
): Promise<{ sandboxReq: SandboxRequest; srcSandboxName?: string; srcId?: string }> {
8282
if (!logger) {
8383
logger = await Logger.child('createSandboxRequest');
@@ -88,7 +88,9 @@ export async function createSandboxRequest(
8888

8989
const capitalizedVarArgs = properties ? lowerToUpper(properties) : {};
9090
// varargs override file input
91-
const sandboxReqWithName: SandboxRequest & { SourceSandboxName?: string; SourceId?: string } = {
91+
const sandboxReqWithName: SandboxRequest & {
92+
SourceSandboxName?: string;
93+
} = {
9294
...(sandboxDefFileContents as Record<string, unknown>),
9395
...capitalizedVarArgs,
9496
SandboxName:
@@ -116,6 +118,7 @@ export async function createSandboxRequest(
116118
return { sandboxReq };
117119
}
118120
}
121+
119122
export async function getApexClassIdByName(conn: Connection, className: string): Promise<string | undefined> {
120123
try {
121124
const result = (await conn.singleRecordQuery(`SELECT Id FROM ApexClass WHERE Name = '${className}'`)).Id;
@@ -124,6 +127,7 @@ export async function getApexClassIdByName(conn: Connection, className: string):
124127
throw cloneMessages.createError('error.apexClassQueryFailed', [className], [], err as Error);
125128
}
126129
}
130+
127131
export async function getUserGroupIdByName(conn: Connection, groupName: string): Promise<string | undefined> {
128132
try {
129133
const result = (await conn.singleRecordQuery(`SELECT id FROM Group WHERE NAME = '${groupName}'`)).Id;
@@ -132,6 +136,7 @@ export async function getUserGroupIdByName(conn: Connection, groupName: string):
132136
throw cloneMessages.createError('error.userGroupQueryFailed', [groupName], [], err as Error);
133137
}
134138
}
139+
135140
export async function getSrcIdByName(conn: Connection, sandboxName: string): Promise<string | undefined> {
136141
try {
137142
const result = (

test/shared/sandboxMockUtils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ const sandboxProcessFields = [
6666
'SourceId',
6767
'Description',
6868
'EndDate',
69+
'Features',
6970
];
7071

7172
export const updateSuccessResponse: SaveResult = {

test/shared/sandboxRequest.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,30 @@ describe('sandboxRequest builder', () => {
9696
assert(res);
9797
expect(res.sandboxReq.SandboxName).equals('realName');
9898
});
99+
100+
it('srcSandboxName with features', async () => {
101+
const res = await createSandboxRequest(undefined, $$.TEST_LOGGER, {
102+
SourceSandboxName: 'sbox',
103+
SandboxName: 'foo',
104+
Features: ['DataStorage'],
105+
});
106+
assert(res);
107+
expect(res.sandboxReq.SandboxName).equals('foo');
108+
expect(res.srcSandboxName).equals('sbox');
109+
expect(res.sandboxReq.Features).deep.equals(['DataStorage']);
110+
});
111+
112+
it('srcId with features', async () => {
113+
const res = await createSandboxRequest(undefined, $$.TEST_LOGGER, {
114+
SourceId: '0GQ000000000001',
115+
SandboxName: 'foo',
116+
Features: ['DataStorage'],
117+
});
118+
assert(res);
119+
expect(res.sandboxReq.SandboxName).equals('foo');
120+
expect(res.srcId).equals('0GQ000000000001');
121+
expect(res.sandboxReq.Features).deep.equals(['DataStorage']);
122+
});
99123
});
100124
describe('not clone', () => {
101125
it('throws without licenseType', async () => {

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1685,10 +1685,10 @@
16851685
strip-ansi "6.0.1"
16861686
ts-retry-promise "^0.8.1"
16871687

1688-
"@salesforce/core@^8.10.0", "@salesforce/core@^8.12.0", "@salesforce/core@^8.13.0", "@salesforce/core@^8.14.0", "@salesforce/core@^8.5.1", "@salesforce/core@^8.8.0":
1689-
version "8.14.0"
1690-
resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-8.14.0.tgz#fcdd8b641221fee668b95ed2ede56b251668077c"
1691-
integrity sha512-Ta1aY15TfgxLyFNNlkw60Mm3dDtiEb50TSp3/wzrbuMgkEGvFBEZQca/ChrjANXhpw8pURDUTzL4VV/1eGCHrQ==
1688+
"@salesforce/core@^8.10.0", "@salesforce/core@^8.12.0", "@salesforce/core@^8.14.0", "@salesforce/core@^8.15.0", "@salesforce/core@^8.5.1", "@salesforce/core@^8.8.0":
1689+
version "8.15.0"
1690+
resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-8.15.0.tgz#95d337a7a219c2d305117c9f5594617784a8642f"
1691+
integrity sha512-vTobdBQ7JRlApUDezUAVlC7O7VUk1e+9AM8+TZIVnj2Glub7E5vtwTJRDT48MJ/wWjdhH+9MFfUi2GPHymHmrQ==
16921692
dependencies:
16931693
"@jsforce/jsforce-node" "^3.8.2"
16941694
"@salesforce/kit" "^3.2.2"

0 commit comments

Comments
 (0)