Skip to content

Commit a1d2d31

Browse files
authored
Merge pull request #539 from forcedotcom/sm/sf-create-scratch
Sm/sf-create-scratch
2 parents bef7325 + 066a2bb commit a1d2d31

11 files changed

+125
-66
lines changed

src/exported.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,12 @@ export { PermissionSetAssignment, PermissionSetAssignmentFields } from './org/pe
106106

107107
export { ScratchOrgCreateOptions, ScratchOrgCreateResult, scratchOrgCreate } from './org/scratchOrgCreate';
108108

109-
export { ScratchOrgInfo } from './org/scratchOrgInfoApi';
109+
export { ScratchOrgInfo } from './org/scratchOrgTypes';
110+
export {
111+
ScratchOrgLifecycleEvent,
112+
scratchOrgLifecycleEventName,
113+
scratchOrgLifecycleStages,
114+
} from './org/scratchOrgLifecycleEvents';
110115

111116
// Utility sub-modules
112117
export * from './util/sfdc';

src/org/scratchOrgCreate.ts

+14-9
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,20 @@ import { Messages } from '../messages';
1010
import { Logger } from '../logger';
1111
import { ConfigAggregator } from '../config/configAggregator';
1212
import { SfProject } from '../sfProject';
13-
import { SfError } from '../sfError';
13+
import { Lifecycle } from '../lifecycleEvents';
1414
import { Org } from './org';
1515
import {
1616
authorizeScratchOrg,
1717
requestScratchOrgCreation,
1818
pollForScratchOrgInfo,
1919
deploySettingsAndResolveUrl,
20-
ScratchOrgInfo,
2120
} from './scratchOrgInfoApi';
21+
import { ScratchOrgInfo } from './scratchOrgTypes';
2222
import SettingsGenerator from './scratchOrgSettingsGenerator';
2323
import { generateScratchOrgInfo, getScratchOrgInfoPayload } from './scratchOrgInfoGenerator';
2424
import { AuthFields, AuthInfo } from './authInfo';
2525
import { Connection } from './connection';
26+
import { emit } from './scratchOrgLifecycleEvents';
2627

2728
Messages.importMessagesDirectory(__dirname);
2829
const messages = Messages.load('@salesforce/core', 'scratchOrgCreate', [
@@ -106,8 +107,9 @@ const validateWait = (wait: Duration): void => {
106107

107108
export const scratchOrgCreate = async (options: ScratchOrgCreateOptions): Promise<ScratchOrgCreateResult> => {
108109
const logger = await Logger.child('scratchOrgCreate');
109-
logger.debug('scratchOrgCreate');
110110

111+
logger.debug('scratchOrgCreate');
112+
await emit({ stage: 'prepare request' });
111113
const {
112114
hubOrg,
113115
connectedAppConsumerKey,
@@ -151,7 +153,6 @@ export const scratchOrgCreate = async (options: ScratchOrgCreateOptions): Promis
151153

152154
// creates the scratch org info in the devhub
153155
const scratchOrgInfoRequestResult = await requestScratchOrgCreation(hubOrg, scratchOrgInfo, settingsGenerator);
154-
155156
const scratchOrgInfoId = ensureString(getString(scratchOrgInfoRequestResult, 'id'));
156157

157158
logger.debug(`scratch org has recordId ${scratchOrgInfoId}`);
@@ -177,6 +178,8 @@ export const scratchOrgCreate = async (options: ScratchOrgCreateOptions): Promis
177178
logger.debug(`scratch org username ${username}`);
178179

179180
const configAggregator = new ConfigAggregator();
181+
await emit({ stage: 'deploy settings', scratchOrgInfo: scratchOrgInfoResult });
182+
180183
const authInfo = await deploySettingsAndResolveUrl(
181184
scratchOrgAuthInfo,
182185
apiversion ??
@@ -189,6 +192,7 @@ export const scratchOrgCreate = async (options: ScratchOrgCreateOptions): Promis
189192
logger.trace('Settings deployed to org');
190193
/** updating the revision num to zero during org:creation if source members are created during org:create.This only happens for some specific scratch org definition file.*/
191194
await updateRevisionCounterToZero(scratchOrg);
195+
await emit({ stage: 'done', scratchOrgInfo: scratchOrgInfoResult });
192196

193197
return {
194198
username,
@@ -212,15 +216,16 @@ const getSignupTargetLoginUrl = async (): Promise<string | undefined> => {
212216
const updateRevisionCounterToZero = async (scratchOrg: Org): Promise<void> => {
213217
const conn = scratchOrg.getConnection();
214218
const queryResult = await conn.tooling.sobject('SourceMember').find({ RevisionCounter: { $gt: 0 } }, ['Id']);
219+
if (queryResult.length === 0) {
220+
return;
221+
}
215222
try {
216223
await conn.tooling
217224
.sobject('SourceMember')
218225
.update(queryResult.map((record) => ({ Id: record.Id, RevisionCounter: 0 })));
219226
} catch (err) {
220-
const message = messages.getMessage('SourceStatusResetFailureError', [
221-
scratchOrg.getOrgId(),
222-
scratchOrg.getUsername(),
223-
]);
224-
throw new SfError(message, 'SourceStatusResetFailure');
227+
await Lifecycle.getInstance().emitWarning(
228+
messages.getMessage('SourceStatusResetFailureError', [scratchOrg.getOrgId(), scratchOrg.getUsername()])
229+
);
225230
}
226231
};

src/org/scratchOrgErrorCodes.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Optional } from '@salesforce/ts-types';
99
import { Messages } from '../messages';
1010
import { SfError } from '../sfError';
1111
import { Logger } from '../logger';
12-
import { ScratchOrgInfo } from './scratchOrgInfoApi';
12+
import { ScratchOrgInfo } from './scratchOrgTypes';
1313

1414
const WORKSPACE_CONFIG_FILENAME = 'sfdx-project.json';
1515

src/org/scratchOrgInfoApi.ts

+9-38
Original file line numberDiff line numberDiff line change
@@ -22,44 +22,9 @@ import { MyDomainResolver } from '../status/myDomainResolver';
2222
import { AuthInfo } from './authInfo';
2323
import { Org } from './org';
2424
import { checkScratchOrgInfoForErrors } from './scratchOrgErrorCodes';
25-
import SettingsGenerator, { ObjectSetting } from './scratchOrgSettingsGenerator';
26-
export interface ScratchOrgInfo {
27-
AdminEmail?: string;
28-
readonly CreatedDate?: string;
29-
ConnectedAppCallbackUrl?: string;
30-
ConnectedAppConsumerKey?: string;
31-
Country?: string;
32-
Description?: string;
33-
DurationDays?: string;
34-
Edition?: string;
35-
readonly ErrorCode?: string;
36-
readonly ExpirationDate?: string;
37-
Features?: string;
38-
HasSampleData?: boolean;
39-
readonly Id?: string;
40-
Language?: string;
41-
LoginUrl: string;
42-
readonly Name?: string;
43-
Namespace?: string;
44-
OrgName?: string;
45-
Release?: 'Current' | 'Previous' | 'Preview';
46-
readonly ScratchOrg?: string;
47-
SourceOrg?: string;
48-
readonly AuthCode: string;
49-
Snapshot: string;
50-
readonly Status: 'New' | 'Creating' | 'Active' | 'Error' | 'Deleted';
51-
readonly SignupEmail: string;
52-
readonly SignupUsername: string;
53-
readonly SignupInstance: string;
54-
Username: string;
55-
settings?: Record<string, unknown>;
56-
objectSettings?: { [objectName: string]: ObjectSetting };
57-
orgPreferences?: {
58-
enabled: string[];
59-
disabled: string[];
60-
};
61-
}
62-
25+
import SettingsGenerator from './scratchOrgSettingsGenerator';
26+
import { ScratchOrgInfo } from './scratchOrgTypes';
27+
import { emit } from './scratchOrgLifecycleEvents';
6328
export interface JsForceError extends Error {
6429
errorCode: string;
6530
fields: string[];
@@ -225,6 +190,7 @@ export const authorizeScratchOrg = async (options: {
225190
retry?: number;
226191
}): Promise<AuthInfo> => {
227192
const { scratchOrgInfoComplete, hubOrg, clientSecret, signupTargetLoginUrlConfig, retry: maxRetries } = options;
193+
await emit({ stage: 'authenticate', scratchOrgInfo: scratchOrgInfoComplete });
228194
const logger = await Logger.child('authorizeScratchOrg');
229195
logger.debug(`scratchOrgInfoComplete: ${JSON.stringify(scratchOrgInfoComplete, null, 4)}`);
230196

@@ -321,6 +287,8 @@ export const requestScratchOrgCreation = async (
321287

322288
await checkOrgDoesntExist(scratchOrgInfo); // throw if it does exist.
323289
try {
290+
await emit({ stage: 'send request' });
291+
// return await will cause this catch block to run instead of the caller's catch block
324292
return await hubOrg.getConnection().sobject('ScratchOrgInfo').create(scratchOrgInfo);
325293
} catch (error) {
326294
// this is a jsforce error which contains the property "fields" which regular error don't
@@ -356,11 +324,14 @@ export const pollForScratchOrgInfo = async (
356324
logger.debug(`polling client result: ${JSON.stringify(resultInProgress, null, 4)}`);
357325
// Once it's "done" we can return it
358326
if (resultInProgress.Status === 'Active' || resultInProgress.Status === 'Error') {
327+
await emit({ stage: 'available', scratchOrgInfo: resultInProgress as unknown as ScratchOrgInfo });
359328
return {
360329
completed: true,
361330
payload: resultInProgress as unknown as AnyJson,
362331
};
363332
}
333+
await emit({ stage: 'wait for org', scratchOrgInfo: resultInProgress as unknown as ScratchOrgInfo });
334+
364335
logger.debug(`Scratch org status is ${resultInProgress.Status}`);
365336
return {
366337
completed: false,

src/org/scratchOrgInfoGenerator.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { WebOAuthServer } from '../webOAuthServer';
1313
import { Messages } from '../messages';
1414
import { SfError } from '../sfError';
1515
import { Org } from './org';
16-
import { ScratchOrgInfo } from './scratchOrgInfoApi';
16+
import { ScratchOrgInfo } from './scratchOrgTypes';
1717
import { ScratchOrgFeatureDeprecation } from './scratchOrgFeatureDeprecation';
1818

1919
const defaultConnectedAppInfo = {

src/org/scratchOrgLifecycleEvents.ts

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright (c) 2020, 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 { Lifecycle } from '../lifecycleEvents';
8+
import { ScratchOrgInfo } from './scratchOrgTypes';
9+
10+
const emitter = Lifecycle.getInstance();
11+
12+
export const scratchOrgLifecycleEventName = 'scratchOrgLifecycleEvent';
13+
export const scratchOrgLifecycleStages = [
14+
'prepare request',
15+
'send request',
16+
'wait for org',
17+
'available',
18+
'authenticate',
19+
'deploy settings',
20+
'done',
21+
] as const;
22+
export interface ScratchOrgLifecycleEvent {
23+
stage: typeof scratchOrgLifecycleStages[number];
24+
scratchOrgInfo?: ScratchOrgInfo;
25+
}
26+
27+
export const emit = async (event: ScratchOrgLifecycleEvent) => {
28+
emitter.emit(scratchOrgLifecycleEventName, event);
29+
};

src/org/scratchOrgSettingsGenerator.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { JsonAsXml } from '../util/jsonXmlTools';
1515
import { ZipWriter } from '../util/zipWriter';
1616
import { StatusResult } from '../status/types';
1717
import { PollingClient } from '../status/pollingClient';
18-
import { ScratchOrgInfo } from './scratchOrgInfoApi';
18+
import { ScratchOrgInfo, ObjectSetting } from './scratchOrgTypes';
1919
import { Org } from './org';
2020

2121
export enum RequestStatus {
@@ -30,11 +30,6 @@ export enum RequestStatus {
3030

3131
const breakPolling = ['Succeeded', 'SucceededPartial', 'Failed', 'Canceled'];
3232

33-
export interface ObjectSetting extends JsonMap {
34-
sharingModel?: string;
35-
defaultRecordType?: string;
36-
}
37-
3833
interface ObjectToBusinessProcessPicklist {
3934
[key: string]: {
4035
fullName: string;

src/org/scratchOrgTypes.ts

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright (c) 2020, 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 { JsonMap } from '@salesforce/ts-types';
9+
10+
export interface ScratchOrgInfo {
11+
AdminEmail?: string;
12+
readonly CreatedDate?: string;
13+
ConnectedAppCallbackUrl?: string;
14+
ConnectedAppConsumerKey?: string;
15+
Country?: string;
16+
Description?: string;
17+
DurationDays?: string;
18+
Edition?: string;
19+
readonly ErrorCode?: string;
20+
readonly ExpirationDate?: string;
21+
Features?: string;
22+
HasSampleData?: boolean;
23+
readonly Id?: string;
24+
Language?: string;
25+
LoginUrl: string;
26+
readonly Name?: string;
27+
Namespace?: string;
28+
OrgName?: string;
29+
Release?: 'Current' | 'Previous' | 'Preview';
30+
readonly ScratchOrg?: string;
31+
SourceOrg?: string;
32+
readonly AuthCode: string;
33+
Snapshot: string;
34+
readonly Status: 'New' | 'Creating' | 'Active' | 'Error' | 'Deleted';
35+
readonly SignupEmail: string;
36+
readonly SignupUsername: string;
37+
readonly SignupInstance: string;
38+
Username: string;
39+
settings?: Record<string, unknown>;
40+
objectSettings?: { [objectName: string]: ObjectSetting };
41+
orgPreferences?: {
42+
enabled: string[];
43+
disabled: string[];
44+
};
45+
}
46+
47+
export interface ObjectSetting extends JsonMap {
48+
sharingModel?: string;
49+
defaultRecordType?: string;
50+
}

test/unit/org/scratchOrgErrorCodesTest.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { expect } from 'chai';
88
import { assert } from 'sinon';
99
import { Logger } from '../../../src/logger';
1010
import { SfError } from '../../../src/sfError';
11-
import { ScratchOrgInfo } from '../../../src/org/scratchOrgInfoApi';
11+
import { ScratchOrgInfo } from '../../../src/org/scratchOrgTypes';
1212
import { checkScratchOrgInfoForErrors } from '../../../src/org/scratchOrgErrorCodes';
1313

1414
const testUsername = 'foo';

test/unit/org/scratchOrgInfoApiTest.ts

+12-8
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@ import { MyDomainResolver } from '../../../src/status/myDomainResolver';
1717
import SettingsGenerator from '../../../src/org/scratchOrgSettingsGenerator';
1818
import {
1919
requestScratchOrgCreation,
20-
ScratchOrgInfo,
2120
JsForceError,
2221
deploySettingsAndResolveUrl,
2322
pollForScratchOrgInfo,
2423
authorizeScratchOrg,
2524
} from '../../../src/org/scratchOrgInfoApi';
25+
import { ScratchOrgInfo } from '../../../src/org/scratchOrgTypes';
2626
import { Messages } from '../../../src/messages';
27+
import { SfError } from '../../../src/sfError';
2728

2829
Messages.importMessagesDirectory(__dirname);
2930
const messages = Messages.loadMessages('@salesforce/core', 'scratchOrgInfoApi');
@@ -138,9 +139,12 @@ describe('requestScratchOrgCreation', () => {
138139
const err = new Error('MyError');
139140
err.name = 'NamedOrgNotFound';
140141
stubMethod(sandbox, AuthInfo, 'create').rejects(err);
141-
const jsForceError = new Error('JsForce-Error') as JsForceError;
142-
jsForceError.errorCode = 'REQUIRED_FIELD_MISSING';
143-
jsForceError.fields = ['error-field'];
142+
const jsForceError = {
143+
...new Error('JsForce-Error'),
144+
errorCode: 'REQUIRED_FIELD_MISSING',
145+
fields: ['error-field'],
146+
} as JsForceError;
147+
144148
// @ts-ignore
145149
connectionStub.sobject.withArgs('ScratchOrgInfo').returns({
146150
create: sinon.stub().rejects(jsForceError),
@@ -150,10 +154,10 @@ describe('requestScratchOrgCreation', () => {
150154
try {
151155
await shouldThrow(requestScratchOrgCreation(hubOrg, TEMPLATE_SCRATCH_ORG_INFO, settings));
152156
} catch (error) {
153-
expect(error).to.exist;
154-
expect(error.message).to.include(
155-
messages.getMessage('SignupFieldsMissingError', [jsForceError.fields.toString()])
156-
);
157+
const e = error as SfError;
158+
expect(e).to.exist;
159+
expect(e).to.have.property('message');
160+
expect(e.message).to.include(messages.getMessage('SignupFieldsMissingError', [jsForceError.fields.toString()]));
157161
}
158162
});
159163

test/unit/org/scratchOrgSettingsGeneratorTest.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { assert, expect } from 'chai';
1111
import { Org, Connection } from '../../../src/org';
1212
import { sfdc } from '../../../src/util/sfdc';
1313
import { ZipWriter } from '../../../src/util/zipWriter';
14-
import { ScratchOrgInfo } from '../../../src/org/scratchOrgInfoApi';
14+
import { ScratchOrgInfo } from '../../../src/org/scratchOrgTypes';
1515
import SettingsGenerator from '../../../src/org/scratchOrgSettingsGenerator';
1616
import { MockTestOrgData } from '../../../src/testSetup';
1717

0 commit comments

Comments
 (0)