@salesforce/core 3.7.2 → 3.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +79 -4
- package/LICENSE.txt +1 -1
- package/lib/config/configStore.js +0 -1
- package/lib/exported.d.ts +1 -1
- package/lib/exported.js +4 -2
- package/lib/lifecycleEvents.d.ts +38 -1
- package/lib/lifecycleEvents.js +73 -2
- package/lib/logger.js +1 -0
- package/lib/org/authInfo.d.ts +2 -6
- package/lib/org/authInfo.js +1 -1
- package/lib/org/connection.js +5 -3
- package/lib/org/org.d.ts +121 -9
- package/lib/org/org.js +356 -22
- package/lib/sfdxError.d.ts +1 -1
- package/lib/sfdxError.js +1 -0
- package/lib/status/pollingClient.d.ts +0 -1
- package/lib/status/streamingClient.d.ts +2 -3
- package/lib/status/streamingClient.js +10 -16
- package/lib/status/types.d.ts +89 -0
- package/lib/status/types.js +18 -0
- package/lib/testSetup.d.ts +3 -2
- package/lib/testSetup.js +4 -4
- package/lib/util/sfdcUrl.d.ts +2 -1
- package/lib/util/sfdcUrl.js +14 -6
- package/messages/core.json +3 -3
- package/messages/core.md +1 -1
- package/messages/org.md +36 -0
- package/package.json +15 -23
package/lib/org/org.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.Org = void 0;
|
|
9
|
+
exports.Org = exports.SandboxEvents = exports.OrgTypes = void 0;
|
|
10
10
|
const path_1 = require("path");
|
|
11
11
|
const kit_1 = require("@salesforce/kit");
|
|
12
12
|
const ts_types_1 = require("@salesforce/ts-types");
|
|
@@ -21,11 +21,37 @@ const fs_1 = require("../util/fs");
|
|
|
21
21
|
const sfdc_1 = require("../util/sfdc");
|
|
22
22
|
const globalInfo_1 = require("../globalInfo");
|
|
23
23
|
const messages_1 = require("../messages");
|
|
24
|
+
const lifecycleEvents_1 = require("../lifecycleEvents");
|
|
25
|
+
const webOAuthServer_1 = require("../webOAuthServer");
|
|
24
26
|
const connection_1 = require("./connection");
|
|
25
27
|
const authInfo_1 = require("./authInfo");
|
|
26
28
|
const orgConfigProperties_1 = require("./orgConfigProperties");
|
|
27
29
|
messages_1.Messages.importMessagesDirectory(__dirname);
|
|
28
|
-
const messages = messages_1.Messages.load('@salesforce/core', 'org', [
|
|
30
|
+
const messages = messages_1.Messages.load('@salesforce/core', 'org', [
|
|
31
|
+
'deleteOrgHubError',
|
|
32
|
+
'insufficientAccessToDelete',
|
|
33
|
+
'missingAuthUsername',
|
|
34
|
+
'noDevHubFound',
|
|
35
|
+
'notADevHub',
|
|
36
|
+
'noUsernameFound',
|
|
37
|
+
'orgPollingTimeout',
|
|
38
|
+
'sandboxDeleteFailed',
|
|
39
|
+
'sandboxInfoCreateFailed',
|
|
40
|
+
'sandboxNotFound',
|
|
41
|
+
'scratchOrgNotFound',
|
|
42
|
+
]);
|
|
43
|
+
var OrgTypes;
|
|
44
|
+
(function (OrgTypes) {
|
|
45
|
+
OrgTypes["Scratch"] = "scratch";
|
|
46
|
+
OrgTypes["Sandbox"] = "sandbox";
|
|
47
|
+
})(OrgTypes = exports.OrgTypes || (exports.OrgTypes = {}));
|
|
48
|
+
var SandboxEvents;
|
|
49
|
+
(function (SandboxEvents) {
|
|
50
|
+
SandboxEvents["EVENT_STATUS"] = "status";
|
|
51
|
+
SandboxEvents["EVENT_ASYNC_RESULT"] = "asyncResult";
|
|
52
|
+
SandboxEvents["EVENT_RESULT"] = "result";
|
|
53
|
+
SandboxEvents["EVENT_AUTH"] = "auth";
|
|
54
|
+
})(SandboxEvents = exports.SandboxEvents || (exports.SandboxEvents = {}));
|
|
29
55
|
/**
|
|
30
56
|
* Provides a way to manage a locally authenticated Org.
|
|
31
57
|
*
|
|
@@ -61,6 +87,33 @@ class Org extends kit_1.AsyncOptionalCreatable {
|
|
|
61
87
|
this.status = Org.Status.UNKNOWN;
|
|
62
88
|
this.options = options || {};
|
|
63
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* create a sandbox from a production org
|
|
92
|
+
* 'this' needs to be a production org with sandbox licenses available
|
|
93
|
+
*
|
|
94
|
+
* @param sandboxReq SandboxRequest options to create the sandbox with
|
|
95
|
+
* @param options Wait: The amount of time to wait before timing out, Interval: The time interval between polling
|
|
96
|
+
*/
|
|
97
|
+
async createSandbox(sandboxReq, options) {
|
|
98
|
+
var _a;
|
|
99
|
+
this.logger.debug('CreateSandbox called with SandboxRequest: %s ', sandboxReq);
|
|
100
|
+
const createResult = await this.connection.tooling.create('SandboxInfo', sandboxReq);
|
|
101
|
+
this.logger.debug('Return from calling tooling.create: %s ', createResult);
|
|
102
|
+
if (Array.isArray(createResult) || !createResult.success) {
|
|
103
|
+
throw messages.createError('sandboxInfoCreateFailed', [JSON.stringify(createResult)]);
|
|
104
|
+
}
|
|
105
|
+
const sandboxCreationProgress = await this.querySandboxProcess(createResult.id);
|
|
106
|
+
this.logger.debug('Return from calling singleRecordQuery with tooling: %s', sandboxCreationProgress);
|
|
107
|
+
const retries = options.wait ? options.wait.seconds / kit_1.Duration.seconds(30).seconds : 0;
|
|
108
|
+
this.logger.debug('pollStatusAndAuth sandboxProcessObj %s, maxPollingRetries %i', sandboxCreationProgress, retries);
|
|
109
|
+
const pollInterval = (_a = options.interval) !== null && _a !== void 0 ? _a : kit_1.Duration.seconds(30);
|
|
110
|
+
return this.pollStatusAndAuth({
|
|
111
|
+
sandboxProcessObj: sandboxCreationProgress,
|
|
112
|
+
retries,
|
|
113
|
+
shouldPoll: retries > 0,
|
|
114
|
+
pollInterval,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
64
117
|
/**
|
|
65
118
|
* Clean all data files in the org's data path. Usually <workspace>/.sfdx/orgs/<username>.
|
|
66
119
|
*
|
|
@@ -75,7 +128,7 @@ class Org extends kit_1.AsyncOptionalCreatable {
|
|
|
75
128
|
this.logger.debug(`cleaning data for path: ${dataPath}`);
|
|
76
129
|
}
|
|
77
130
|
catch (err) {
|
|
78
|
-
if (err.name === 'InvalidProjectWorkspaceError') {
|
|
131
|
+
if (err instanceof Error && err.name === 'InvalidProjectWorkspaceError') {
|
|
79
132
|
// If we aren't in a project dir, we can't clean up data files.
|
|
80
133
|
// If the user unlink this org outside of the workspace they used it in,
|
|
81
134
|
// data files will be left over.
|
|
@@ -113,6 +166,13 @@ class Org extends kit_1.AsyncOptionalCreatable {
|
|
|
113
166
|
// So, just in case no users are added to this org we will try the remove again.
|
|
114
167
|
await this.removeAuth();
|
|
115
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Check if org is a sandbox org by checking its SandboxOrgConfig.
|
|
171
|
+
*
|
|
172
|
+
*/
|
|
173
|
+
async isSandbox() {
|
|
174
|
+
return !!(await this.getSandboxOrgConfigField(sandboxOrgConfig_1.SandboxOrgConfig.Fields.PROD_ORG_USERNAME));
|
|
175
|
+
}
|
|
116
176
|
/**
|
|
117
177
|
* Check that this org is a scratch org by asking the dev hub if it knows about it.
|
|
118
178
|
*
|
|
@@ -138,7 +198,7 @@ class Org extends kit_1.AsyncOptionalCreatable {
|
|
|
138
198
|
}
|
|
139
199
|
}
|
|
140
200
|
catch (err) {
|
|
141
|
-
if (err.name === 'INVALID_TYPE') {
|
|
201
|
+
if (err instanceof Error && err.name === 'INVALID_TYPE') {
|
|
142
202
|
throw messages.createError('notADevHub', [devHubConnection.getUsername()]);
|
|
143
203
|
}
|
|
144
204
|
throw err;
|
|
@@ -177,6 +237,36 @@ class Org extends kit_1.AsyncOptionalCreatable {
|
|
|
177
237
|
return false;
|
|
178
238
|
}
|
|
179
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Will delete 'this' instance remotely and any files locally
|
|
242
|
+
*
|
|
243
|
+
* @param controllingOrg username or Org that 'this.devhub' or 'this.production' refers to. AKA a DevHub for a scratch org, or a Production Org for a sandbox
|
|
244
|
+
*/
|
|
245
|
+
async deleteFrom(controllingOrg) {
|
|
246
|
+
if (typeof controllingOrg === 'string') {
|
|
247
|
+
controllingOrg = await Org.create({
|
|
248
|
+
aggregator: this.configAggregator,
|
|
249
|
+
aliasOrUsername: controllingOrg,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
if (await this.isSandbox()) {
|
|
253
|
+
await this.deleteSandbox(controllingOrg);
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
await this.deleteScratchOrg(controllingOrg);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Will delete 'this' instance remotely and any files locally
|
|
261
|
+
*/
|
|
262
|
+
async delete() {
|
|
263
|
+
if (await this.isSandbox()) {
|
|
264
|
+
await this.deleteSandbox();
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
await this.deleteScratchOrg();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
180
270
|
/**
|
|
181
271
|
* Returns `true` if the org is a Dev Hub.
|
|
182
272
|
*
|
|
@@ -243,22 +333,6 @@ class Org extends kit_1.AsyncOptionalCreatable {
|
|
|
243
333
|
}
|
|
244
334
|
return cache;
|
|
245
335
|
}
|
|
246
|
-
/**
|
|
247
|
-
* Returns `true` if the org is a sandbox.
|
|
248
|
-
*
|
|
249
|
-
* **Note** This relies on a cached value in the auth file. If that property
|
|
250
|
-
* is not cached, this method will **always return false even if the org is a
|
|
251
|
-
* sandbox**. If you need accuracy, use the {@link Org.determineIfDevHubOrg} method.
|
|
252
|
-
*/
|
|
253
|
-
isSandbox() {
|
|
254
|
-
const isSandbox = this.getField(Org.Fields.IS_SANDBOX);
|
|
255
|
-
if (ts_types_1.isBoolean(isSandbox)) {
|
|
256
|
-
return isSandbox;
|
|
257
|
-
}
|
|
258
|
-
else {
|
|
259
|
-
return false;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
336
|
/**
|
|
263
337
|
* Returns `true` if the org is a sandbox.
|
|
264
338
|
*
|
|
@@ -502,7 +576,7 @@ class Org extends kit_1.AsyncOptionalCreatable {
|
|
|
502
576
|
}
|
|
503
577
|
this.connection = await connection_1.Connection.create({
|
|
504
578
|
// If no username is provided or resolvable from an alias, AuthInfo will throw an SfdxError.
|
|
505
|
-
authInfo: await authInfo_1.AuthInfo.create({ username }),
|
|
579
|
+
authInfo: await authInfo_1.AuthInfo.create({ username, isDevHub: this.options.isDevHub }),
|
|
506
580
|
});
|
|
507
581
|
}
|
|
508
582
|
else {
|
|
@@ -516,6 +590,105 @@ class Org extends kit_1.AsyncOptionalCreatable {
|
|
|
516
590
|
getDefaultOptions() {
|
|
517
591
|
throw new sfdxError_1.SfdxError('Not Supported', 'NotSupportedError');
|
|
518
592
|
}
|
|
593
|
+
async queryProduction(org, field, value) {
|
|
594
|
+
return org.connection.singleRecordQuery(`SELECT SandboxInfoId FROM SandboxProcess WHERE ${field} ='${value}' AND Status NOT IN ('D', 'E')`, { tooling: true });
|
|
595
|
+
}
|
|
596
|
+
async destorySandbox(org, id) {
|
|
597
|
+
return org.getConnection().tooling.delete('SandboxInfo', id);
|
|
598
|
+
}
|
|
599
|
+
async destoryScratchOrg(org, id) {
|
|
600
|
+
return org.getConnection().delete('ActiveScratchOrg', id);
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* this method will delete the sandbox org from the production org and clean up any local files
|
|
604
|
+
*
|
|
605
|
+
* @param prodOrg - Production org associated with this sandbox
|
|
606
|
+
* @private
|
|
607
|
+
*/
|
|
608
|
+
async deleteSandbox(prodOrg) {
|
|
609
|
+
prodOrg !== null && prodOrg !== void 0 ? prodOrg : (prodOrg = await Org.create({
|
|
610
|
+
aggregator: this.configAggregator,
|
|
611
|
+
aliasOrUsername: await this.getSandboxOrgConfigField(sandboxOrgConfig_1.SandboxOrgConfig.Fields.PROD_ORG_USERNAME),
|
|
612
|
+
}));
|
|
613
|
+
let result;
|
|
614
|
+
// attempt to locate sandbox id by username
|
|
615
|
+
try {
|
|
616
|
+
// try to calculate sandbox name from the production org
|
|
617
|
+
// production org: admin@integrationtesthub.org
|
|
618
|
+
// this.getUsername: admin@integrationtesthub.org.dev1
|
|
619
|
+
// sandboxName in Production: dev1
|
|
620
|
+
const sandboxName = (this.getUsername() || '').split(`${prodOrg.getUsername()}.`)[1];
|
|
621
|
+
if (!sandboxName) {
|
|
622
|
+
this.logger.debug('Could not construct a sandbox name');
|
|
623
|
+
throw new Error();
|
|
624
|
+
}
|
|
625
|
+
this.logger.debug(`attempting to locate sandbox with username ${sandboxName}`);
|
|
626
|
+
result = await this.queryProduction(prodOrg, 'SandboxName', sandboxName);
|
|
627
|
+
if (!result) {
|
|
628
|
+
this.logger.debug(`Failed to find sandbox with username: ${sandboxName}`);
|
|
629
|
+
throw new Error();
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
catch {
|
|
633
|
+
// if an error is thrown, don't panic yet. we'll try querying by orgId
|
|
634
|
+
const trimmedId = sfdc_1.sfdc.trimTo15(this.getOrgId());
|
|
635
|
+
this.logger.debug(`defaulting to trimming id from ${this.getOrgId()} to ${trimmedId}`);
|
|
636
|
+
try {
|
|
637
|
+
result = await this.queryProduction(prodOrg, 'SandboxOrganization', trimmedId);
|
|
638
|
+
}
|
|
639
|
+
catch {
|
|
640
|
+
throw messages.createError('sandboxNotFound', [trimmedId]);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
// const deleteResult = await prodOrg.connection.tooling.delete('SandboxInfo', result.SandboxInfoId);
|
|
644
|
+
const deleteResult = await this.destorySandbox(prodOrg, result.SandboxInfoId);
|
|
645
|
+
this.logger.debug('Return from calling tooling.delete: %o ', deleteResult);
|
|
646
|
+
await this.remove();
|
|
647
|
+
if (Array.isArray(deleteResult) || !deleteResult.success) {
|
|
648
|
+
throw messages.createError('sandboxDeleteFailed', [JSON.stringify(deleteResult)]);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* If this Org is a scratch org, calling this method will delete the scratch org from the DevHub and clean up any local files
|
|
653
|
+
*
|
|
654
|
+
* @param devHub - optional DevHub Org of the to-be-deleted scratch org
|
|
655
|
+
* @private
|
|
656
|
+
*/
|
|
657
|
+
async deleteScratchOrg(devHub) {
|
|
658
|
+
// if we didn't get a devHub, we'll get it from the this org
|
|
659
|
+
devHub !== null && devHub !== void 0 ? devHub : (devHub = await this.getDevHubOrg());
|
|
660
|
+
if (!devHub) {
|
|
661
|
+
throw messages.createError('noDevHubFound');
|
|
662
|
+
}
|
|
663
|
+
if (devHub.getOrgId() === this.getOrgId()) {
|
|
664
|
+
// we're attempting to delete a DevHub
|
|
665
|
+
throw messages.createError('deleteOrgHubError');
|
|
666
|
+
}
|
|
667
|
+
try {
|
|
668
|
+
const devHubConn = devHub.getConnection();
|
|
669
|
+
const username = this.getUsername();
|
|
670
|
+
const activeScratchOrgRecordId = (await devHubConn.singleRecordQuery(`SELECT Id FROM ActiveScratchOrg WHERE SignupUsername='${username}'`)).Id;
|
|
671
|
+
this.logger.trace(`found matching ActiveScratchOrg with SignupUsername: ${username}. Deleting...`);
|
|
672
|
+
await this.destoryScratchOrg(devHub, activeScratchOrgRecordId);
|
|
673
|
+
await this.remove();
|
|
674
|
+
}
|
|
675
|
+
catch (err) {
|
|
676
|
+
this.logger.info(err instanceof Error ? err.message : err);
|
|
677
|
+
if (err instanceof Error && (err.name === 'INVALID_TYPE' || err.name === 'INSUFFICIENT_ACCESS_OR_READONLY')) {
|
|
678
|
+
// most likely from devHubConn.delete
|
|
679
|
+
this.logger.info('Insufficient privilege to access ActiveScratchOrgs.');
|
|
680
|
+
throw messages.createError('insufficientAccessToDelete');
|
|
681
|
+
}
|
|
682
|
+
if (err instanceof Error && err.name === connection_1.SingleRecordQueryErrors.NoRecords) {
|
|
683
|
+
// most likely from singleRecordQuery
|
|
684
|
+
this.logger.info('The above error can be the result of deleting an expired or already deleted org.');
|
|
685
|
+
this.logger.info('attempting to cleanup the auth file');
|
|
686
|
+
await this.removeAuth();
|
|
687
|
+
throw messages.createError('scratchOrgNotFound');
|
|
688
|
+
}
|
|
689
|
+
throw err;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
519
692
|
/**
|
|
520
693
|
* Delete an auth info file from the local file system and any related cache information for
|
|
521
694
|
* this Org. You don't want to call this method directly. Instead consider calling Org.remove()
|
|
@@ -548,7 +721,7 @@ class Org extends kit_1.AsyncOptionalCreatable {
|
|
|
548
721
|
async retrieveSandboxOrgConfig() {
|
|
549
722
|
return await sandboxOrgConfig_1.SandboxOrgConfig.create(sandboxOrgConfig_1.SandboxOrgConfig.getOptions(this.getOrgId()));
|
|
550
723
|
}
|
|
551
|
-
manageDelete(cb, dirPath, throwWhenRemoveFails) {
|
|
724
|
+
async manageDelete(cb, dirPath, throwWhenRemoveFails) {
|
|
552
725
|
return cb().catch((e) => {
|
|
553
726
|
if (throwWhenRemoveFails) {
|
|
554
727
|
throw e;
|
|
@@ -610,6 +783,167 @@ class Org extends kit_1.AsyncOptionalCreatable {
|
|
|
610
783
|
await this.manageDelete(async () => await sandboxOrgConfig.unlink(), sandboxOrgConfig.getPath(), throwWhenRemoveFails);
|
|
611
784
|
}
|
|
612
785
|
}
|
|
786
|
+
async writeSandboxAuthFile(sandboxProcessObj, sandboxRes) {
|
|
787
|
+
this.logger.debug('writeSandboxAuthFile sandboxProcessObj: %s, sandboxRes: %s', sandboxProcessObj, sandboxRes);
|
|
788
|
+
if (sandboxRes.authUserName) {
|
|
789
|
+
const productionAuthFields = this.connection.getAuthInfoFields();
|
|
790
|
+
this.logger.debug('Result from getAuthInfoFields: AuthFields %s', productionAuthFields);
|
|
791
|
+
// let's do headless auth via jwt (if we have privateKey) or web auth
|
|
792
|
+
const oauth2Options = {
|
|
793
|
+
loginUrl: sandboxRes.loginUrl,
|
|
794
|
+
instanceUrl: sandboxRes.instanceUrl,
|
|
795
|
+
username: sandboxRes.authUserName,
|
|
796
|
+
};
|
|
797
|
+
// If we don't have a privateKey then we assume it's web auth.
|
|
798
|
+
if (!productionAuthFields.privateKey) {
|
|
799
|
+
oauth2Options.redirectUri = `http://localhost:${await webOAuthServer_1.WebOAuthServer.determineOauthPort()}/OauthRedirect`;
|
|
800
|
+
oauth2Options.authCode = sandboxRes.authCode;
|
|
801
|
+
}
|
|
802
|
+
const authInfo = await authInfo_1.AuthInfo.create({
|
|
803
|
+
username: sandboxRes.authUserName,
|
|
804
|
+
oauth2Options,
|
|
805
|
+
parentUsername: productionAuthFields.username,
|
|
806
|
+
});
|
|
807
|
+
await authInfo.save();
|
|
808
|
+
const sandboxOrg = await Org.create({ aliasOrUsername: authInfo.getUsername() });
|
|
809
|
+
await sandboxOrg.setSandboxOrgConfigField(sandboxOrgConfig_1.SandboxOrgConfig.Fields.PROD_ORG_USERNAME,
|
|
810
|
+
// we couldn't get this far into the process without a production org so username will be there
|
|
811
|
+
productionAuthFields.username);
|
|
812
|
+
await lifecycleEvents_1.Lifecycle.getInstance().emit(SandboxEvents.EVENT_RESULT, {
|
|
813
|
+
sandboxProcessObj,
|
|
814
|
+
sandboxRes,
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
else {
|
|
818
|
+
// no authed sandbox user, error
|
|
819
|
+
throw messages.createError('missingAuthUsername', [sandboxProcessObj.SandboxName]);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Polls for the new sandbox to be created - and will write the associated auth files
|
|
824
|
+
*
|
|
825
|
+
* @private
|
|
826
|
+
* @param options
|
|
827
|
+
* sandboxProcessObj: The in-progress sandbox signup request
|
|
828
|
+
* retries: the number of retries to poll for every 30s
|
|
829
|
+
* shouldPoll: wait for polling, or just return
|
|
830
|
+
* pollInterval: Duration to sleep between poll events, default 30 seconds
|
|
831
|
+
*/
|
|
832
|
+
async pollStatusAndAuth(options) {
|
|
833
|
+
const { sandboxProcessObj, retries, shouldPoll, pollInterval } = options;
|
|
834
|
+
this.logger.debug('PollStatusAndAuth called with SandboxProcessObject%s, retries %s', sandboxProcessObj, retries);
|
|
835
|
+
const lifecycle = lifecycleEvents_1.Lifecycle.getInstance();
|
|
836
|
+
let pollFinished = false;
|
|
837
|
+
let waitingOnAuth = false;
|
|
838
|
+
const sandboxInfo = await this.sandboxSignupComplete(sandboxProcessObj);
|
|
839
|
+
if (sandboxInfo) {
|
|
840
|
+
await lifecycleEvents_1.Lifecycle.getInstance().emit(SandboxEvents.EVENT_AUTH, sandboxInfo);
|
|
841
|
+
try {
|
|
842
|
+
this.logger.debug('sandbox signup complete with %s', sandboxInfo);
|
|
843
|
+
await this.writeSandboxAuthFile(sandboxProcessObj, sandboxInfo);
|
|
844
|
+
pollFinished = true;
|
|
845
|
+
}
|
|
846
|
+
catch (err) {
|
|
847
|
+
this.logger.debug('Exception while calling writeSandboxAuthFile %s', err);
|
|
848
|
+
if ((err === null || err === void 0 ? void 0 : err.name) === 'JWTAuthError' && (err === null || err === void 0 ? void 0 : err.stack.includes("user hasn't approved"))) {
|
|
849
|
+
waitingOnAuth = true;
|
|
850
|
+
}
|
|
851
|
+
else {
|
|
852
|
+
throw sfdxError_1.SfdxError.wrap(err);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
if (!pollFinished) {
|
|
857
|
+
if (retries > 0) {
|
|
858
|
+
// emit the signup progress of the sandbox and query the production org again after waiting the interval
|
|
859
|
+
await Promise.all([
|
|
860
|
+
await lifecycle.emit(SandboxEvents.EVENT_STATUS, {
|
|
861
|
+
sandboxProcessObj,
|
|
862
|
+
interval: pollInterval.seconds,
|
|
863
|
+
retries,
|
|
864
|
+
waitingOnAuth,
|
|
865
|
+
}),
|
|
866
|
+
await kit_1.sleep(pollInterval),
|
|
867
|
+
]);
|
|
868
|
+
const polledSandboxProcessObj = await this.querySandboxProcess(sandboxProcessObj.SandboxInfoId);
|
|
869
|
+
return this.pollStatusAndAuth({
|
|
870
|
+
sandboxProcessObj: polledSandboxProcessObj,
|
|
871
|
+
retries: retries - 1,
|
|
872
|
+
shouldPoll,
|
|
873
|
+
pollInterval,
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
else {
|
|
877
|
+
if (shouldPoll) {
|
|
878
|
+
// timed out on retries
|
|
879
|
+
throw messages.createError('orgPollingTimeout', [sandboxProcessObj.Status]);
|
|
880
|
+
}
|
|
881
|
+
else {
|
|
882
|
+
// The user didn't want us to poll, so simply return the status
|
|
883
|
+
// simply report status and exit
|
|
884
|
+
await lifecycle.emit(SandboxEvents.EVENT_ASYNC_RESULT, sandboxProcessObj);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
return sandboxProcessObj;
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* query SandboxProcess via SandboxInfoId
|
|
892
|
+
*
|
|
893
|
+
* @param id SandboxInfoId to query for
|
|
894
|
+
* @private
|
|
895
|
+
*/
|
|
896
|
+
async querySandboxProcess(id) {
|
|
897
|
+
const queryStr = `SELECT Id, Status, SandboxName, SandboxInfoId, LicenseType, CreatedDate, CopyProgress, SandboxOrganization, SourceId, Description, EndDate FROM SandboxProcess WHERE SandboxInfoId='${id}' AND Status != 'D'`;
|
|
898
|
+
return await this.connection.singleRecordQuery(queryStr, {
|
|
899
|
+
tooling: true,
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* determines if the sandbox has successfully been created
|
|
904
|
+
*
|
|
905
|
+
* @param sandboxProcessObj sandbox signup progeress
|
|
906
|
+
* @private
|
|
907
|
+
*/
|
|
908
|
+
async sandboxSignupComplete(sandboxProcessObj) {
|
|
909
|
+
this.logger.debug('sandboxSignupComplete called with SandboxProcessObject %s', sandboxProcessObj);
|
|
910
|
+
if (!sandboxProcessObj.EndDate) {
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
try {
|
|
914
|
+
// call server side /sandboxAuth API to auth the sandbox org user with the connected app
|
|
915
|
+
const authFields = this.connection.getAuthInfoFields();
|
|
916
|
+
const callbackUrl = `http://localhost:${await webOAuthServer_1.WebOAuthServer.determineOauthPort()}/OauthRedirect`;
|
|
917
|
+
const sandboxReq = {
|
|
918
|
+
// the sandbox signup has been completed on production, we have production clientId by this point
|
|
919
|
+
clientId: authFields.clientId,
|
|
920
|
+
sandboxName: sandboxProcessObj.SandboxName,
|
|
921
|
+
callbackUrl,
|
|
922
|
+
};
|
|
923
|
+
this.logger.debug('Calling sandboxAuth with SandboxUserAuthRequest %s', sandboxReq);
|
|
924
|
+
const url = `${this.connection.tooling._baseUrl()}/sandboxAuth`;
|
|
925
|
+
const params = {
|
|
926
|
+
method: 'POST',
|
|
927
|
+
url,
|
|
928
|
+
headers: { 'Content-Type': 'application/json' },
|
|
929
|
+
body: JSON.stringify(sandboxReq),
|
|
930
|
+
};
|
|
931
|
+
const result = await this.connection.tooling.request(params);
|
|
932
|
+
this.logger.debug('Result of calling sandboxAuth %s', result);
|
|
933
|
+
return result;
|
|
934
|
+
}
|
|
935
|
+
catch (err) {
|
|
936
|
+
// There are cases where the endDate is set before the sandbox has actually completed.
|
|
937
|
+
// In that case, the sandboxAuth call will throw a specific exception.
|
|
938
|
+
if ((err === null || err === void 0 ? void 0 : err.name) === 'INVALID_STATUS') {
|
|
939
|
+
this.logger.debug('Error while authenticating the user %s', err === null || err === void 0 ? void 0 : err.toString());
|
|
940
|
+
}
|
|
941
|
+
else {
|
|
942
|
+
// If it fails for any unexpected reason, just pass that through
|
|
943
|
+
throw sfdxError_1.SfdxError.wrap(err);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
613
947
|
}
|
|
614
948
|
exports.Org = Org;
|
|
615
949
|
(function (Org) {
|
package/lib/sfdxError.d.ts
CHANGED
|
@@ -50,7 +50,7 @@ export declare class SfdxError extends NamedError {
|
|
|
50
50
|
* @param err The error to convert.
|
|
51
51
|
*/
|
|
52
52
|
static wrap(err: Error | string): SfdxError;
|
|
53
|
-
get code(): string;
|
|
53
|
+
get code(): string | undefined | any;
|
|
54
54
|
set code(code: string);
|
|
55
55
|
/**
|
|
56
56
|
* Sets the context of the error. For convenience `this` object is returned.
|
package/lib/sfdxError.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { AsyncOptionalCreatable, Duration, Env } from '@salesforce/kit/lib';
|
|
2
2
|
import { AnyJson } from '@salesforce/ts-types/lib';
|
|
3
|
-
import type { Client as CometClient, StreamProcessor } from 'sfdx-faye';
|
|
4
3
|
import { Org } from '../org/org';
|
|
5
|
-
|
|
4
|
+
import { CometClient, CometSubscription, Message, StatusResult, StreamingExtension, StreamProcessor } from './types';
|
|
5
|
+
export { CometClient, CometSubscription, Message, StatusResult, StreamingExtension, StreamProcessor };
|
|
6
6
|
/**
|
|
7
7
|
* Inner streaming client interface. This implements the Cometd behavior.
|
|
8
8
|
* Also allows for mocking the functional behavior.
|
|
@@ -187,7 +187,6 @@ export declare namespace StreamingClient {
|
|
|
187
187
|
handshakeTimeout: Duration;
|
|
188
188
|
channel: string;
|
|
189
189
|
streamingImpl: StreamingClientIfc;
|
|
190
|
-
private envDep;
|
|
191
190
|
/**
|
|
192
191
|
* Constructor for DefaultStreamingOptions
|
|
193
192
|
*
|
|
@@ -11,12 +11,12 @@ exports.StreamingClient = exports.CometClient = void 0;
|
|
|
11
11
|
const url_1 = require("url");
|
|
12
12
|
const lib_1 = require("@salesforce/kit/lib");
|
|
13
13
|
const lib_2 = require("@salesforce/ts-types/lib");
|
|
14
|
-
const Faye = require("
|
|
14
|
+
const Faye = require("faye");
|
|
15
15
|
const logger_1 = require("../logger");
|
|
16
16
|
const sfdxError_1 = require("../sfdxError");
|
|
17
17
|
const messages_1 = require("../messages");
|
|
18
|
-
|
|
19
|
-
Object.defineProperty(exports, "CometClient", { enumerable: true, get: function () { return
|
|
18
|
+
const types_1 = require("./types");
|
|
19
|
+
Object.defineProperty(exports, "CometClient", { enumerable: true, get: function () { return types_1.CometClient; } });
|
|
20
20
|
messages_1.Messages.importMessagesDirectory(__dirname);
|
|
21
21
|
const messages = messages_1.Messages.load('@salesforce/core', 'streaming', [
|
|
22
22
|
'waitParamValidValueError',
|
|
@@ -337,6 +337,10 @@ exports.StreamingClient = StreamingClient;
|
|
|
337
337
|
* @see {@link StatusResult}
|
|
338
338
|
*/
|
|
339
339
|
constructor(org, channel, streamProcessor, envDep = lib_1.env) {
|
|
340
|
+
if (envDep) {
|
|
341
|
+
const logger = logger_1.Logger.childFromRoot('StreamingClient');
|
|
342
|
+
logger.warn('envDep is deprecated');
|
|
343
|
+
}
|
|
340
344
|
if (!streamProcessor) {
|
|
341
345
|
throw new sfdxError_1.SfdxError('Missing stream processor', 'MissingArg');
|
|
342
346
|
}
|
|
@@ -346,7 +350,6 @@ exports.StreamingClient = StreamingClient;
|
|
|
346
350
|
if (!channel) {
|
|
347
351
|
throw new sfdxError_1.SfdxError('Missing streaming channel', 'MissingArg');
|
|
348
352
|
}
|
|
349
|
-
this.envDep = envDep;
|
|
350
353
|
this.org = org;
|
|
351
354
|
this.apiVersion = org.getConnection().getApiVersion();
|
|
352
355
|
if (channel.startsWith('/system')) {
|
|
@@ -361,23 +364,14 @@ exports.StreamingClient = StreamingClient;
|
|
|
361
364
|
this.handshakeTimeout = StreamingClient.DefaultOptions.DEFAULT_HANDSHAKE_TIMEOUT;
|
|
362
365
|
this.streamingImpl = {
|
|
363
366
|
getCometClient: (url) => {
|
|
364
|
-
|
|
365
|
-
return new Faye.Client(url
|
|
366
|
-
// This parameter ensures all cookies regardless of path are included in subsequent requests. Otherwise
|
|
367
|
-
// only cookies with the path "/" and "/cometd" are known to be included.
|
|
368
|
-
// if SFDX_ENABLE_FAYE_COOKIES_ALLOW_ALL_PATHS is *not* set the default to true.
|
|
369
|
-
cookiesAllowAllPaths: x === undefined
|
|
370
|
-
? true
|
|
371
|
-
: this.envDep.getBoolean(StreamingClient.DefaultOptions.SFDX_ENABLE_FAYE_COOKIES_ALLOW_ALL_PATHS),
|
|
372
|
-
// WARNING - The allows request/response exchanges to be written to the log instance which includes
|
|
373
|
-
// header and cookie information.
|
|
374
|
-
enableRequestResponseLogging: this.envDep.getBoolean(StreamingClient.DefaultOptions.SFDX_ENABLE_FAYE_REQUEST_RESPONSE_LOGGING),
|
|
375
|
-
});
|
|
367
|
+
// @ts-ignore
|
|
368
|
+
return new Faye.Client(url);
|
|
376
369
|
},
|
|
377
370
|
setLogger: (logLine) => {
|
|
378
371
|
// @ts-ignore
|
|
379
372
|
Faye.logger = {};
|
|
380
373
|
['info', 'error', 'fatal', 'warn', 'debug'].forEach((element) => {
|
|
374
|
+
// @ts-ignore
|
|
381
375
|
lib_1.set(Faye.logger, element, logLine);
|
|
382
376
|
});
|
|
383
377
|
},
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
import { AnyFunction, AnyJson, JsonMap } from '@salesforce/ts-types';
|
|
4
|
+
export declare type Message = JsonMap;
|
|
5
|
+
export declare type Callback<T = unknown> = (...args: any[]) => T;
|
|
6
|
+
export interface StatusResult {
|
|
7
|
+
/**
|
|
8
|
+
* If the result of the streaming or polling client is expected to return a result
|
|
9
|
+
*/
|
|
10
|
+
payload?: AnyJson;
|
|
11
|
+
/**
|
|
12
|
+
* Indicates to the streaming or polling client that the subscriber has what its needs. If `true` the client will end
|
|
13
|
+
* the messaging exchanges with the endpoint.
|
|
14
|
+
*/
|
|
15
|
+
completed: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The subscription object returned from the cometd subscribe object.
|
|
19
|
+
*/
|
|
20
|
+
export interface CometSubscription {
|
|
21
|
+
callback(callback: () => void): void;
|
|
22
|
+
errback(callback: (error: Error) => void): void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Types for defining extensions.
|
|
26
|
+
*/
|
|
27
|
+
export interface StreamingExtension {
|
|
28
|
+
/**
|
|
29
|
+
* Extension for outgoing message.
|
|
30
|
+
*
|
|
31
|
+
* @param message The message.
|
|
32
|
+
* @param callback The callback to invoke after the message is processed.
|
|
33
|
+
*/
|
|
34
|
+
outgoing?: (message: JsonMap, callback: AnyFunction) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Extension for the incoming message.
|
|
37
|
+
*
|
|
38
|
+
* @param message The message.
|
|
39
|
+
* @param callback The callback to invoke after the message is processed.
|
|
40
|
+
*/
|
|
41
|
+
incoming?: (message: JsonMap, callback: AnyFunction) => void;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Function type for processing messages
|
|
45
|
+
*/
|
|
46
|
+
export declare type StreamProcessor = (message: JsonMap) => StatusResult;
|
|
47
|
+
/**
|
|
48
|
+
* Comet client interface. The is to allow for mocking the inner streaming Cometd implementation.
|
|
49
|
+
* The Faye implementation is used by default but it could be used to adapt another Cometd impl.
|
|
50
|
+
*/
|
|
51
|
+
export declare abstract class CometClient extends EventEmitter {
|
|
52
|
+
/**
|
|
53
|
+
* Disable polling features.
|
|
54
|
+
*
|
|
55
|
+
* @param label Polling feature label.
|
|
56
|
+
*/
|
|
57
|
+
abstract disable(label: string): void;
|
|
58
|
+
/**
|
|
59
|
+
* Add a custom extension to the underlying client.
|
|
60
|
+
*
|
|
61
|
+
* @param extension The json function for the extension.
|
|
62
|
+
*/
|
|
63
|
+
abstract addExtension(extension: StreamingExtension): void;
|
|
64
|
+
/**
|
|
65
|
+
* Sets an http header name/value.
|
|
66
|
+
*
|
|
67
|
+
* @param name The header name.
|
|
68
|
+
* @param value The header value.
|
|
69
|
+
*/
|
|
70
|
+
abstract setHeader(name: string, value: string): void;
|
|
71
|
+
/**
|
|
72
|
+
* handshake with the streaming channel
|
|
73
|
+
*
|
|
74
|
+
* @param callback Callback for the handshake when it successfully completes. The handshake should throw
|
|
75
|
+
* errors when errors are encountered.
|
|
76
|
+
*/
|
|
77
|
+
abstract handshake(callback: () => void): void;
|
|
78
|
+
/**
|
|
79
|
+
* Subscribes to Comet topics. Subscribe should perform a handshake if one hasn't been performed yet.
|
|
80
|
+
*
|
|
81
|
+
* @param channel The topic to subscribe to.
|
|
82
|
+
* @param callback The callback to execute once a message has been received.
|
|
83
|
+
*/
|
|
84
|
+
abstract subscribe(channel: string, callback: (message: JsonMap) => void): CometSubscription;
|
|
85
|
+
/**
|
|
86
|
+
* Method to call to disconnect the client from the server.
|
|
87
|
+
*/
|
|
88
|
+
abstract disconnect(): void;
|
|
89
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CometClient = void 0;
|
|
4
|
+
/*
|
|
5
|
+
* Copyright (c) 2020, salesforce.com, inc.
|
|
6
|
+
* All rights reserved.
|
|
7
|
+
* Licensed under the BSD 3-Clause license.
|
|
8
|
+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
9
|
+
*/
|
|
10
|
+
const events_1 = require("events");
|
|
11
|
+
/**
|
|
12
|
+
* Comet client interface. The is to allow for mocking the inner streaming Cometd implementation.
|
|
13
|
+
* The Faye implementation is used by default but it could be used to adapt another Cometd impl.
|
|
14
|
+
*/
|
|
15
|
+
class CometClient extends events_1.EventEmitter {
|
|
16
|
+
}
|
|
17
|
+
exports.CometClient = CometClient;
|
|
18
|
+
//# sourceMappingURL=types.js.map
|