Skip to content

Commit e5b53d6

Browse files
Merge pull request #681 from salesforcecli/sm/email-and-source-org
Sm/email-and-source-org
2 parents 5c295e8 + c232e86 commit e5b53d6

File tree

7 files changed

+171
-32
lines changed

7 files changed

+171
-32
lines changed

command-snapshot.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"command": "org:create:scratch",
7474
"plugin": "@salesforce/plugin-org",
7575
"flags": [
76+
"admin-email",
7677
"alias",
7778
"api-version",
7879
"async",
@@ -87,6 +88,7 @@
8788
"no-namespace",
8889
"release",
8990
"set-default",
91+
"source-org",
9092
"target-dev-hub",
9193
"track-source",
9294
"username",

messages/create_scratch.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ For either method, you can also use these flags; if you use them with --definiti
1313
* --username
1414
* --release
1515
* --edition
16+
* --admin-email (equivalent to the "adminEmail" option)
17+
* --source-org (equivalent to the "sourceOrg" option)
1618

1719
If you want to set options other than the preceding ones, such as org features or settings, you must use a definition file.
1820

@@ -84,6 +86,14 @@ The scratch org definition file is a blueprint for the scratch org. It mimics th
8486

8587
Consumer key of the Dev Hub connected app.
8688

89+
# flags.adminEmail.summary
90+
91+
Email address that will be applied to the org's admin user. Overrides the value of the "adminEmail" option in the definition file, if set.
92+
93+
# flags.sourceOrg.summary
94+
95+
15-character ID of the org whose shape the new scratch org will be based on. Overrides the value of the "sourceOrg" option in the definition file, if set.
96+
8797
# flags.wait.summary
8898

8999
Number of minutes to wait for the scratch org to be ready.

src/commands/org/create/scratch.ts

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@
55
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
*/
77

8-
import * as fs from 'fs';
9-
import { Duration } from '@salesforce/kit';
108
import {
119
Messages,
12-
ScratchOrgCreateOptions,
1310
Lifecycle,
1411
ScratchOrgLifecycleEvent,
1512
scratchOrgLifecycleEventName,
@@ -18,6 +15,8 @@ import {
1815
SfError,
1916
} from '@salesforce/core';
2017
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
18+
import { Duration } from '@salesforce/kit';
19+
import { buildScratchOrgRequest } from '../../../shared/scratchOrgRequest';
2120
import { buildStatus } from '../../../shared/scratchOrgOutput';
2221
import { ScratchCreateResponse } from '../../../shared/orgTypes';
2322
Messages.importMessagesDirectory(__dirname);
@@ -85,7 +84,7 @@ export default class EnvCreateScratch extends SfCommand<ScratchCreateResponse> {
8584
}),
8685
'duration-days': Flags.duration({
8786
unit: 'days',
88-
defaultValue: 7,
87+
default: Duration.days(7),
8988
min: 1,
9089
max: 30,
9190
char: 'y',
@@ -94,7 +93,7 @@ export default class EnvCreateScratch extends SfCommand<ScratchCreateResponse> {
9493
}),
9594
wait: Flags.duration({
9695
unit: 'minutes',
97-
defaultValue: 5,
96+
default: Duration.minutes(5),
9897
min: 2,
9998
char: 'w',
10099
helpValue: '<minutes>',
@@ -128,40 +127,28 @@ export default class EnvCreateScratch extends SfCommand<ScratchCreateResponse> {
128127
description: messages.getMessage('flags.release.description'),
129128
options: ['preview', 'previous'],
130129
}),
130+
'admin-email': Flags.string({
131+
summary: messages.getMessage('flags.adminEmail.summary'),
132+
}),
133+
'source-org': Flags.salesforceId({
134+
summary: messages.getMessage('flags.sourceOrg.summary'),
135+
startsWith: '00D',
136+
length: 15,
137+
}),
131138
};
139+
132140
public async run(): Promise<ScratchCreateResponse> {
133141
const lifecycle = Lifecycle.getInstance();
134142
const { flags } = await this.parse(EnvCreateScratch);
135143
const baseUrl = flags['target-dev-hub'].getField(Org.Fields.INSTANCE_URL)?.toString();
136144
if (!baseUrl) {
137145
throw new SfError('No instance URL found for the dev hub');
138146
}
139-
const orgConfig = {
140-
...(flags['definition-file']
141-
? (JSON.parse(await fs.promises.readFile(flags['definition-file'], 'utf-8')) as Record<string, unknown>)
142-
: {}),
143-
...(flags.edition ? { edition: flags.edition } : {}),
144-
...(flags.username ? { username: flags.username } : {}),
145-
...(flags.description ? { description: flags.description } : {}),
146-
...(flags.name ? { orgName: flags.name } : {}),
147-
...(flags.release ? { release: flags.release } : {}),
148-
};
149-
150-
const createCommandOptions: ScratchOrgCreateOptions = {
151-
hubOrg: flags['target-dev-hub'],
152-
clientSecret: flags['client-id'] ? await this.clientSecretPrompt() : undefined,
153-
connectedAppConsumerKey: flags['client-id'],
154-
durationDays: (flags['duration-days'] as Duration).days,
155-
nonamespace: flags['no-namespace'],
156-
noancestors: flags['no-ancestors'],
157-
wait: flags.async ? Duration.minutes(0) : flags.wait,
158-
apiversion: flags['api-version'],
159-
orgConfig,
160-
alias: flags.alias,
161-
setDefault: flags['set-default'],
162-
tracksSource: flags['track-source'],
163-
};
164147

148+
const createCommandOptions = await buildScratchOrgRequest(
149+
flags,
150+
flags['client-id'] ? await this.clientSecretPrompt() : undefined
151+
);
165152
let lastStatus: string | undefined;
166153

167154
if (!flags.async) {

src/shared/scratchOrgRequest.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright (c) 2023, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
8+
import * as fs from 'fs';
9+
import { Interfaces } from '@oclif/core';
10+
import { ScratchOrgCreateOptions } from '@salesforce/core';
11+
import { Duration } from '@salesforce/kit';
12+
import EnvCreateScratch from '../commands/org/create/scratch';
13+
/**
14+
* Provide the parsed flags
15+
* Returns the objet necessary to create a scratch org
16+
*/
17+
export const buildScratchOrgRequest = async (
18+
flags: Interfaces.InferredFlags<typeof EnvCreateScratch.flags>,
19+
clientSecret?: string
20+
): Promise<ScratchOrgCreateOptions> => {
21+
const orgConfig = {
22+
...(flags['definition-file']
23+
? (JSON.parse(await fs.promises.readFile(flags['definition-file'], 'utf-8')) as Record<string, unknown>)
24+
: {}),
25+
...(flags.edition ? { edition: flags.edition } : {}),
26+
...(flags.username ? { username: flags.username } : {}),
27+
...(flags.description ? { description: flags.description } : {}),
28+
...(flags.name ? { orgName: flags.name } : {}),
29+
...(flags.release ? { release: flags.release } : {}),
30+
...(flags['source-org'] ? { sourceOrg: flags['source-org'] } : {}),
31+
...(flags['admin-email'] ? { adminEmail: flags['admin-email'] } : {}),
32+
};
33+
34+
const createCommandOptions: ScratchOrgCreateOptions = {
35+
hubOrg: flags['target-dev-hub'],
36+
clientSecret,
37+
connectedAppConsumerKey: flags['client-id'],
38+
durationDays: flags['duration-days'].days,
39+
nonamespace: flags['no-namespace'],
40+
noancestors: flags['no-ancestors'],
41+
wait: flags.async ? Duration.minutes(0) : flags.wait,
42+
apiversion: flags['api-version'],
43+
orgConfig,
44+
alias: flags.alias,
45+
setDefault: flags['set-default'],
46+
tracksSource: flags['track-source'],
47+
};
48+
49+
return createCommandOptions;
50+
};

test/nut/scratchCreate.nut.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,17 @@ describe('env create scratch NUTs', () => {
8484
expect(resp).to.have.all.keys(keys);
8585
expect(resp?.orgId).to.match(/^00D.{15}/);
8686
});
87-
it('creates an org from config file with "override" flags ', () => {
87+
it('creates an org from config file with "override" flags and custom admin email', () => {
8888
const expectedUsername = genUniqueString('%[email protected]');
8989
const resp = execCmd<ScratchCreateResponse>(
90-
`env:create:scratch -f config/project-scratch-def.json --json --username ${expectedUsername} --description "new one" --name TheOrg --wait 60`,
90+
`env:create:scratch -f config/project-scratch-def.json --json --username ${expectedUsername} --description "new one" --name TheOrg --wait 60 --admin-email [email protected]`,
9191
{
9292
ensureExitCode: 0,
9393
}
9494
).jsonOutput?.result;
9595
expect(resp).to.have.all.keys(keys);
9696
expect(resp?.username).to.equal(expectedUsername);
97+
expect(resp?.scratchOrgInfo?.AdminEmail).to.equal('[email protected]');
9798
});
9899
it('creates an org with tracking disabled ', async () => {
99100
const resp = execCmd<ScratchCreateResponse>(

test/shared/scratch-def.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"adminEmail": "[email protected]"
3+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright (c) 2023, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
import { Config, Interfaces } from '@oclif/core';
8+
import { expect } from 'chai';
9+
import { MockTestOrgData, TestContext } from '@salesforce/core/lib/testSetup';
10+
import { buildScratchOrgRequest } from '../../src/shared/scratchOrgRequest';
11+
import EnvCreateScratch from '../../src/commands/org/create/scratch';
12+
13+
class Wrapper extends EnvCreateScratch {
14+
// simple method to return the parsed flags so they can be used in the tests
15+
public async getFlags(): Promise<Interfaces.InferredFlags<typeof Wrapper.flags>> {
16+
return (await this.parse(Wrapper)).flags;
17+
}
18+
}
19+
20+
/** pass in the params in array form, get back the parsed flags */
21+
const paramsToFlags = async (params: string[]): Promise<Interfaces.InferredFlags<typeof EnvCreateScratch.flags>> =>
22+
new Wrapper(params, {} as Config).getFlags();
23+
24+
describe('buildScratchOrgRequest function', () => {
25+
const $$ = new TestContext();
26+
beforeEach(async () => {
27+
const hub = new MockTestOrgData();
28+
hub.isDevHub = true;
29+
await $$.stubAuths(hub);
30+
await $$.stubConfig({ 'target-dev-hub': hub.username });
31+
});
32+
33+
after(() => {
34+
$$.SANDBOX.restore();
35+
});
36+
37+
it('edition as only flag', async () => {
38+
const flags = await paramsToFlags(['--edition', 'developer']);
39+
const result = await buildScratchOrgRequest(flags);
40+
expect(result.durationDays).to.equal(7);
41+
expect(result.orgConfig).to.deep.equal({ edition: 'developer' });
42+
});
43+
44+
describe('source-org', () => {
45+
it('valid source-org as only flag', async () => {
46+
const flags = await paramsToFlags(['--source-org', '00D123456789012']);
47+
const result = await buildScratchOrgRequest(flags);
48+
expect(result.durationDays).to.equal(7);
49+
expect(result.orgConfig).to.deep.equal({ sourceOrg: '00D123456789012' });
50+
});
51+
});
52+
53+
describe('definition file', () => {
54+
it('prop from definitionFile', async () => {
55+
const flags = await paramsToFlags(['--definition-file', 'test/shared/scratch-def.json']);
56+
const result = await buildScratchOrgRequest(flags);
57+
expect(result.durationDays).to.equal(7);
58+
expect(result.orgConfig?.adminEmail).to.deep.equal('[email protected]');
59+
});
60+
it('prop from definitionFile overridden by flag', async () => {
61+
const overriddenEmail = '[email protected]';
62+
const flags = await paramsToFlags([
63+
'--definition-file',
64+
'test/shared/scratch-def.json',
65+
'--admin-email',
66+
overriddenEmail,
67+
]);
68+
const result = await buildScratchOrgRequest(flags);
69+
expect(result.durationDays).to.equal(7);
70+
expect(result.orgConfig?.adminEmail).to.deep.equal(overriddenEmail);
71+
});
72+
});
73+
74+
it('applies secret if given', async () => {
75+
const secret = 'ImASecret';
76+
const flags = await paramsToFlags(['--edition', 'developer']);
77+
const result = await buildScratchOrgRequest(flags, secret);
78+
expect(result.clientSecret).to.equal(secret);
79+
});
80+
81+
it('async becomes 0 min', async () => {
82+
const flags = await paramsToFlags(['--edition', 'developer', '--async']);
83+
const result = await buildScratchOrgRequest(flags);
84+
expect(result.wait?.minutes).to.equal(0);
85+
});
86+
});

0 commit comments

Comments
 (0)