@salesforce/core 3.30.14 → 3.31.7
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/LICENSE.txt +11 -11
- package/README.md +222 -227
- package/lib/config/aliasesConfig.d.ts +12 -12
- package/lib/config/aliasesConfig.js +27 -27
- package/lib/config/authInfoConfig.d.ts +19 -19
- package/lib/config/authInfoConfig.js +34 -34
- package/lib/config/config.d.ts +311 -311
- package/lib/config/config.js +574 -574
- package/lib/config/configAggregator.d.ts +232 -232
- package/lib/config/configAggregator.js +379 -379
- package/lib/config/configFile.d.ts +199 -199
- package/lib/config/configFile.js +340 -340
- package/lib/config/configGroup.d.ts +141 -141
- package/lib/config/configGroup.js +224 -224
- package/lib/config/configStore.d.ts +241 -241
- package/lib/config/configStore.js +352 -352
- package/lib/config/envVars.d.ts +101 -101
- package/lib/config/envVars.js +456 -456
- package/lib/config/orgUsersConfig.d.ts +31 -31
- package/lib/config/orgUsersConfig.js +41 -41
- package/lib/config/sandboxOrgConfig.d.ts +37 -37
- package/lib/config/sandboxOrgConfig.js +50 -50
- package/lib/config/sandboxProcessCache.d.ts +16 -16
- package/lib/config/sandboxProcessCache.js +37 -37
- package/lib/config/tokensConfig.d.ts +10 -10
- package/lib/config/tokensConfig.js +28 -28
- package/lib/config/ttlConfig.d.ts +34 -34
- package/lib/config/ttlConfig.js +54 -54
- package/lib/crypto/crypto.d.ts +54 -54
- package/lib/crypto/crypto.js +220 -220
- package/lib/crypto/keyChain.d.ts +8 -8
- package/lib/crypto/keyChain.js +61 -61
- package/lib/crypto/keyChainImpl.d.ts +116 -116
- package/lib/crypto/keyChainImpl.js +486 -486
- package/lib/crypto/secureBuffer.d.ts +46 -46
- package/lib/crypto/secureBuffer.js +82 -82
- package/lib/deviceOauthService.d.ts +71 -71
- package/lib/deviceOauthService.js +191 -191
- package/lib/exported.d.ts +38 -38
- package/lib/exported.js +118 -118
- package/lib/global.d.ts +70 -70
- package/lib/global.js +109 -109
- package/lib/lifecycleEvents.d.ts +93 -93
- package/lib/lifecycleEvents.js +188 -188
- package/lib/logger.d.ts +381 -381
- package/lib/logger.js +734 -734
- package/lib/messages.d.ts +291 -291
- package/lib/messages.js +543 -543
- package/lib/org/authInfo.d.ts +344 -344
- package/lib/org/authInfo.js +892 -892
- package/lib/org/authRemover.d.ts +88 -88
- package/lib/org/authRemover.js +182 -182
- package/lib/org/connection.d.ts +197 -197
- package/lib/org/connection.js +395 -395
- package/lib/org/index.d.ts +6 -6
- package/lib/org/index.js +28 -28
- package/lib/org/org.d.ts +558 -558
- package/lib/org/org.js +1267 -1267
- package/lib/org/orgConfigProperties.d.ts +69 -69
- package/lib/org/orgConfigProperties.js +136 -136
- package/lib/org/permissionSetAssignment.d.ts +35 -35
- package/lib/org/permissionSetAssignment.js +125 -125
- package/lib/org/scratchOrgCache.d.ts +20 -20
- package/lib/org/scratchOrgCache.js +32 -32
- package/lib/org/scratchOrgCreate.d.ts +54 -54
- package/lib/org/scratchOrgCreate.js +216 -216
- package/lib/org/scratchOrgErrorCodes.d.ts +10 -10
- package/lib/org/scratchOrgErrorCodes.js +88 -88
- package/lib/org/scratchOrgFeatureDeprecation.d.ts +26 -26
- package/lib/org/scratchOrgFeatureDeprecation.js +109 -109
- package/lib/org/scratchOrgInfoApi.d.ts +68 -68
- package/lib/org/scratchOrgInfoApi.js +413 -413
- package/lib/org/scratchOrgInfoGenerator.d.ts +64 -64
- package/lib/org/scratchOrgInfoGenerator.js +241 -241
- package/lib/org/scratchOrgLifecycleEvents.d.ts +10 -10
- package/lib/org/scratchOrgLifecycleEvents.js +40 -40
- package/lib/org/scratchOrgSettingsGenerator.d.ts +78 -78
- package/lib/org/scratchOrgSettingsGenerator.js +276 -276
- package/lib/org/scratchOrgTypes.d.ts +43 -43
- package/lib/org/scratchOrgTypes.js +8 -8
- package/lib/org/user.d.ts +187 -187
- package/lib/org/user.js +448 -448
- package/lib/schema/printer.d.ts +79 -79
- package/lib/schema/printer.js +260 -260
- package/lib/schema/validator.d.ts +70 -70
- package/lib/schema/validator.js +169 -169
- package/lib/sfError.d.ts +73 -73
- package/lib/sfError.js +136 -136
- package/lib/sfProject.d.ts +357 -357
- package/lib/sfProject.js +671 -671
- package/lib/stateAggregator/accessors/aliasAccessor.d.ts +98 -98
- package/lib/stateAggregator/accessors/aliasAccessor.js +145 -145
- package/lib/stateAggregator/accessors/orgAccessor.d.ts +101 -101
- package/lib/stateAggregator/accessors/orgAccessor.js +240 -240
- package/lib/stateAggregator/accessors/sandboxAccessor.d.ts +8 -8
- package/lib/stateAggregator/accessors/sandboxAccessor.js +27 -27
- package/lib/stateAggregator/accessors/tokenAccessor.d.ts +63 -63
- package/lib/stateAggregator/accessors/tokenAccessor.js +79 -79
- package/lib/stateAggregator/index.d.ts +4 -4
- package/lib/stateAggregator/index.js +26 -26
- package/lib/stateAggregator/stateAggregator.d.ts +25 -25
- package/lib/stateAggregator/stateAggregator.js +45 -45
- package/lib/status/myDomainResolver.d.ts +66 -66
- package/lib/status/myDomainResolver.js +124 -124
- package/lib/status/pollingClient.d.ts +85 -85
- package/lib/status/pollingClient.js +115 -115
- package/lib/status/streamingClient.d.ts +244 -244
- package/lib/status/streamingClient.js +436 -436
- package/lib/status/types.d.ts +89 -89
- package/lib/status/types.js +17 -17
- package/lib/testSetup.d.ts +553 -530
- package/lib/testSetup.js +871 -727
- package/lib/util/cache.d.ts +11 -11
- package/lib/util/cache.js +69 -69
- package/lib/util/checkLightningDomain.d.ts +1 -1
- package/lib/util/checkLightningDomain.js +28 -28
- package/lib/util/directoryWriter.d.ts +12 -12
- package/lib/util/directoryWriter.js +53 -53
- package/lib/util/getJwtAudienceUrl.d.ts +4 -4
- package/lib/util/getJwtAudienceUrl.js +18 -18
- package/lib/util/internal.d.ts +58 -58
- package/lib/util/internal.js +118 -118
- package/lib/util/jsonXmlTools.d.ts +14 -14
- package/lib/util/jsonXmlTools.js +38 -38
- package/lib/util/mapKeys.d.ts +14 -14
- package/lib/util/mapKeys.js +51 -51
- package/lib/util/sfdc.d.ts +52 -52
- package/lib/util/sfdc.js +85 -85
- package/lib/util/sfdcUrl.d.ts +72 -72
- package/lib/util/sfdcUrl.js +215 -215
- package/lib/util/structuredWriter.d.ts +9 -9
- package/lib/util/structuredWriter.js +2 -2
- package/lib/util/zipWriter.d.ts +16 -16
- package/lib/util/zipWriter.js +67 -67
- package/lib/webOAuthServer.d.ts +156 -156
- package/lib/webOAuthServer.js +388 -388
- package/messages/auth.md +37 -37
- package/messages/config.md +156 -156
- package/messages/connection.md +30 -30
- package/messages/core.json +20 -20
- package/messages/core.md +67 -67
- package/messages/encryption.md +85 -85
- package/messages/envVars.md +303 -303
- package/messages/org.md +63 -63
- package/messages/permissionSetAssignment.md +31 -31
- package/messages/scratchOrgCreate.md +23 -23
- package/messages/scratchOrgErrorCodes.md +115 -115
- package/messages/scratchOrgFeatureDeprecation.md +11 -11
- package/messages/scratchOrgInfoApi.md +15 -15
- package/messages/scratchOrgInfoGenerator.md +23 -23
- package/messages/streaming.md +23 -23
- package/messages/user.md +35 -35
- package/package.json +97 -97
package/lib/org/org.js
CHANGED
|
@@ -1,1268 +1,1268 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/*
|
|
3
|
-
* Copyright (c) 2020, salesforce.com, inc.
|
|
4
|
-
* All rights reserved.
|
|
5
|
-
* Licensed under the BSD 3-Clause license.
|
|
6
|
-
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
7
|
-
*/
|
|
8
|
-
/* eslint-disable class-methods-use-this */
|
|
9
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.Org = exports.SandboxEvents = exports.OrgTypes = void 0;
|
|
11
|
-
const path_1 = require("path");
|
|
12
|
-
const fs = require("fs");
|
|
13
|
-
const kit_1 = require("@salesforce/kit");
|
|
14
|
-
const ts_types_1 = require("@salesforce/ts-types");
|
|
15
|
-
const config_1 = require("../config/config");
|
|
16
|
-
const configAggregator_1 = require("../config/configAggregator");
|
|
17
|
-
const orgUsersConfig_1 = require("../config/orgUsersConfig");
|
|
18
|
-
const global_1 = require("../global");
|
|
19
|
-
const lifecycleEvents_1 = require("../lifecycleEvents");
|
|
20
|
-
const logger_1 = require("../logger");
|
|
21
|
-
const sfError_1 = require("../sfError");
|
|
22
|
-
const sfdc_1 = require("../util/sfdc");
|
|
23
|
-
const webOAuthServer_1 = require("../webOAuthServer");
|
|
24
|
-
const messages_1 = require("../messages");
|
|
25
|
-
const stateAggregator_1 = require("../stateAggregator");
|
|
26
|
-
const pollingClient_1 = require("../status/pollingClient");
|
|
27
|
-
const connection_1 = require("./connection");
|
|
28
|
-
const authInfo_1 = require("./authInfo");
|
|
29
|
-
const scratchOrgCreate_1 = require("./scratchOrgCreate");
|
|
30
|
-
const orgConfigProperties_1 = require("./orgConfigProperties");
|
|
31
|
-
messages_1.Messages.importMessagesDirectory(__dirname);
|
|
32
|
-
const messages = messages_1.Messages.load('@salesforce/core', 'org', [
|
|
33
|
-
'deleteOrgHubError',
|
|
34
|
-
'insufficientAccessToDelete',
|
|
35
|
-
'missingAuthUsername',
|
|
36
|
-
'noDevHubFound',
|
|
37
|
-
'notADevHub',
|
|
38
|
-
'noUsernameFound',
|
|
39
|
-
'sandboxDeleteFailed',
|
|
40
|
-
'sandboxInfoCreateFailed',
|
|
41
|
-
'sandboxNotFound',
|
|
42
|
-
'scratchOrgNotFound',
|
|
43
|
-
'AuthInfoOrgIdUndefined',
|
|
44
|
-
'sandboxCreateNotComplete',
|
|
45
|
-
'SandboxProcessNotFoundBySandboxName',
|
|
46
|
-
'MultiSandboxProcessNotFoundBySandboxName',
|
|
47
|
-
]);
|
|
48
|
-
var OrgTypes;
|
|
49
|
-
(function (OrgTypes) {
|
|
50
|
-
OrgTypes["Scratch"] = "scratch";
|
|
51
|
-
OrgTypes["Sandbox"] = "sandbox";
|
|
52
|
-
})(OrgTypes = exports.OrgTypes || (exports.OrgTypes = {}));
|
|
53
|
-
var SandboxEvents;
|
|
54
|
-
(function (SandboxEvents) {
|
|
55
|
-
SandboxEvents["EVENT_STATUS"] = "status";
|
|
56
|
-
SandboxEvents["EVENT_ASYNC_RESULT"] = "asyncResult";
|
|
57
|
-
SandboxEvents["EVENT_RESULT"] = "result";
|
|
58
|
-
SandboxEvents["EVENT_AUTH"] = "auth";
|
|
59
|
-
SandboxEvents["EVENT_RESUME"] = "resume";
|
|
60
|
-
})(SandboxEvents = exports.SandboxEvents || (exports.SandboxEvents = {}));
|
|
61
|
-
/**
|
|
62
|
-
* Provides a way to manage a locally authenticated Org.
|
|
63
|
-
*
|
|
64
|
-
* **See** {@link AuthInfo}
|
|
65
|
-
*
|
|
66
|
-
* **See** {@link Connection}
|
|
67
|
-
*
|
|
68
|
-
* **See** {@link Aliases}
|
|
69
|
-
*
|
|
70
|
-
* **See** {@link Config}
|
|
71
|
-
*
|
|
72
|
-
* ```
|
|
73
|
-
* // Email username
|
|
74
|
-
* const org1: Org = await Org.create({ aliasOrUsername: 'foo@example.com' });
|
|
75
|
-
* // The target-org config property
|
|
76
|
-
* const org2: Org = await Org.create();
|
|
77
|
-
* // Full Connection
|
|
78
|
-
* const org3: Org = await Org.create({
|
|
79
|
-
* connection: await Connection.create({
|
|
80
|
-
* authInfo: await AuthInfo.create({ username: 'username' })
|
|
81
|
-
* })
|
|
82
|
-
* });
|
|
83
|
-
* ```
|
|
84
|
-
*
|
|
85
|
-
* **See** https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_cli_usernames_orgs.htm
|
|
86
|
-
*/
|
|
87
|
-
class Org extends kit_1.AsyncOptionalCreatable {
|
|
88
|
-
/**
|
|
89
|
-
* @ignore
|
|
90
|
-
*/
|
|
91
|
-
constructor(options) {
|
|
92
|
-
super(options);
|
|
93
|
-
this.status = Org.Status.UNKNOWN;
|
|
94
|
-
this.options = options ?? {};
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* create a sandbox from a production org
|
|
98
|
-
* 'this' needs to be a production org with sandbox licenses available
|
|
99
|
-
*
|
|
100
|
-
* @param sandboxReq SandboxRequest options to create the sandbox with
|
|
101
|
-
* @param options Wait: The amount of time to wait before timing out, Interval: The time interval between polling
|
|
102
|
-
*/
|
|
103
|
-
async createSandbox(sandboxReq, options = {
|
|
104
|
-
wait: kit_1.Duration.minutes(6),
|
|
105
|
-
async: false,
|
|
106
|
-
interval: kit_1.Duration.seconds(30),
|
|
107
|
-
}) {
|
|
108
|
-
this.logger.debug(`CreateSandbox called with SandboxRequest: ${JSON.stringify(sandboxReq, undefined, 2)}`);
|
|
109
|
-
const createResult = await this.connection.tooling.create('SandboxInfo', sandboxReq);
|
|
110
|
-
this.logger.debug(`Return from calling tooling.create: ${JSON.stringify(createResult, undefined, 2)}`);
|
|
111
|
-
if (Array.isArray(createResult) || !createResult.success) {
|
|
112
|
-
throw messages.createError('sandboxInfoCreateFailed', [JSON.stringify(createResult)]);
|
|
113
|
-
}
|
|
114
|
-
const sandboxCreationProgress = await this.querySandboxProcessBySandboxInfoId(createResult.id);
|
|
115
|
-
this.logger.debug(`Return from calling singleRecordQuery with tooling: ${JSON.stringify(sandboxCreationProgress, undefined, 2)}`);
|
|
116
|
-
const isAsync = !!options.async;
|
|
117
|
-
if (isAsync) {
|
|
118
|
-
// The user didn't want us to poll, so simply return the status
|
|
119
|
-
await lifecycleEvents_1.Lifecycle.getInstance().emit(SandboxEvents.EVENT_ASYNC_RESULT, sandboxCreationProgress);
|
|
120
|
-
return sandboxCreationProgress;
|
|
121
|
-
}
|
|
122
|
-
const [wait, pollInterval] = this.validateWaitOptions(options);
|
|
123
|
-
this.logger.debug(`create - pollStatusAndAuth sandboxProcessObj ${JSON.stringify(sandboxCreationProgress, undefined, 2)}, max wait time of ${wait.minutes} minutes`);
|
|
124
|
-
return this.pollStatusAndAuth({
|
|
125
|
-
sandboxProcessObj: sandboxCreationProgress,
|
|
126
|
-
wait,
|
|
127
|
-
pollInterval,
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
*
|
|
132
|
-
* @param sandboxReq SandboxRequest options to create the sandbox with
|
|
133
|
-
* @param sourceSandboxName the name of the sandbox that your new sandbox will be based on
|
|
134
|
-
* @param options Wait: The amount of time to wait before timing out, defaults to 0, Interval: The time interval between polling defaults to 30 seconds
|
|
135
|
-
* @returns {SandboxProcessObject} the newly created sandbox process object
|
|
136
|
-
*/
|
|
137
|
-
async cloneSandbox(sandboxReq, sourceSandboxName, options) {
|
|
138
|
-
sandboxReq.SourceId = (await this.querySandboxProcessBySandboxName(sourceSandboxName)).SandboxInfoId;
|
|
139
|
-
this.logger.debug('Clone sandbox sourceId %s', sandboxReq.SourceId);
|
|
140
|
-
return this.createSandbox(sandboxReq, options);
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* resume a sandbox creation from a production org
|
|
144
|
-
* 'this' needs to be a production org with sandbox licenses available
|
|
145
|
-
*
|
|
146
|
-
* @param resumeSandboxRequest SandboxRequest options to create the sandbox with
|
|
147
|
-
* @param options Wait: The amount of time to wait (default: 30 minutes) before timing out,
|
|
148
|
-
* Interval: The time interval (default: 30 seconds) between polling
|
|
149
|
-
*/
|
|
150
|
-
async resumeSandbox(resumeSandboxRequest, options = {
|
|
151
|
-
wait: kit_1.Duration.minutes(0),
|
|
152
|
-
async: false,
|
|
153
|
-
interval: kit_1.Duration.seconds(30),
|
|
154
|
-
}) {
|
|
155
|
-
this.logger.debug(`ResumeSandbox called with ResumeSandboxRequest: ${JSON.stringify(resumeSandboxRequest, undefined, 2)}`);
|
|
156
|
-
let sandboxCreationProgress;
|
|
157
|
-
// seed the sandboxCreationProgress via the resumeSandboxRequest options
|
|
158
|
-
if (resumeSandboxRequest.SandboxName) {
|
|
159
|
-
sandboxCreationProgress = await this.querySandboxProcessBySandboxName(resumeSandboxRequest.SandboxName);
|
|
160
|
-
}
|
|
161
|
-
else if (resumeSandboxRequest.SandboxProcessObjId) {
|
|
162
|
-
sandboxCreationProgress = await this.querySandboxProcessById(resumeSandboxRequest.SandboxProcessObjId);
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
throw messages.createError('sandboxNotFound', [
|
|
166
|
-
resumeSandboxRequest.SandboxName ?? resumeSandboxRequest.SandboxProcessObjId,
|
|
167
|
-
]);
|
|
168
|
-
}
|
|
169
|
-
this.logger.debug(`Return from calling singleRecordQuery with tooling: ${JSON.stringify(sandboxCreationProgress, undefined, 2)}`);
|
|
170
|
-
await lifecycleEvents_1.Lifecycle.getInstance().emit(SandboxEvents.EVENT_RESUME, sandboxCreationProgress);
|
|
171
|
-
const [wait, pollInterval] = this.validateWaitOptions(options);
|
|
172
|
-
// if wait is 0, return the sandboxCreationProgress immediately
|
|
173
|
-
if (wait.seconds === 0) {
|
|
174
|
-
if (sandboxCreationProgress.Status === 'Completed') {
|
|
175
|
-
// check to see if sandbox can authenticate via sandboxAuth endpoint
|
|
176
|
-
const sandboxInfo = await this.sandboxSignupComplete(sandboxCreationProgress);
|
|
177
|
-
if (sandboxInfo) {
|
|
178
|
-
await lifecycleEvents_1.Lifecycle.getInstance().emit(SandboxEvents.EVENT_AUTH, sandboxInfo);
|
|
179
|
-
try {
|
|
180
|
-
this.logger.debug(`sandbox signup complete with ${JSON.stringify(sandboxInfo, undefined, 2)}`);
|
|
181
|
-
await this.writeSandboxAuthFile(sandboxCreationProgress, sandboxInfo);
|
|
182
|
-
return sandboxCreationProgress;
|
|
183
|
-
}
|
|
184
|
-
catch (err) {
|
|
185
|
-
// eat the error, we don't want to throw an error if we can't write the file
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
await lifecycleEvents_1.Lifecycle.getInstance().emit(SandboxEvents.EVENT_ASYNC_RESULT, sandboxCreationProgress);
|
|
190
|
-
throw messages.createError('sandboxCreateNotComplete');
|
|
191
|
-
}
|
|
192
|
-
this.logger.debug(`resume - pollStatusAndAuth sandboxProcessObj ${JSON.stringify(sandboxCreationProgress, undefined, 2)}, max wait time of ${wait.minutes} minutes`);
|
|
193
|
-
return this.pollStatusAndAuth({
|
|
194
|
-
sandboxProcessObj: sandboxCreationProgress,
|
|
195
|
-
wait,
|
|
196
|
-
pollInterval,
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Creates a scratchOrg
|
|
201
|
-
* 'this' needs to be a valid dev-hub
|
|
202
|
-
*
|
|
203
|
-
* @param {options} ScratchOrgCreateOptions
|
|
204
|
-
* @returns {ScratchOrgCreateResult}
|
|
205
|
-
*/
|
|
206
|
-
async scratchOrgCreate(options) {
|
|
207
|
-
return (0, scratchOrgCreate_1.scratchOrgCreate)({ ...options, hubOrg: this });
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Reports sandbox org creation status. If the org is ready, authenticates to the org.
|
|
211
|
-
*
|
|
212
|
-
* @param {sandboxname} string the sandbox name
|
|
213
|
-
* @param options Wait: The amount of time to wait before timing out, Interval: The time interval between polling
|
|
214
|
-
* @returns {SandboxProcessObject} the sandbox process object
|
|
215
|
-
*/
|
|
216
|
-
async sandboxStatus(sandboxname, options) {
|
|
217
|
-
return this.authWithRetriesByName(sandboxname, options);
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* Clean all data files in the org's data path. Usually <workspace>/.sfdx/orgs/<username>.
|
|
221
|
-
*
|
|
222
|
-
* @param orgDataPath A relative path other than "orgs/".
|
|
223
|
-
* @param throwWhenRemoveFails Should the remove org operations throw an error on failure?
|
|
224
|
-
*/
|
|
225
|
-
async cleanLocalOrgData(orgDataPath, throwWhenRemoveFails = false) {
|
|
226
|
-
let dataPath;
|
|
227
|
-
try {
|
|
228
|
-
dataPath = await this.getLocalDataDir(orgDataPath);
|
|
229
|
-
this.logger.debug(`cleaning data for path: ${dataPath}`);
|
|
230
|
-
}
|
|
231
|
-
catch (err) {
|
|
232
|
-
if (err instanceof Error && err.name === 'InvalidProjectWorkspaceError') {
|
|
233
|
-
// If we aren't in a project dir, we can't clean up data files.
|
|
234
|
-
// If the user unlink this org outside of the workspace they used it in,
|
|
235
|
-
// data files will be left over.
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
throw err;
|
|
239
|
-
}
|
|
240
|
-
return this.manageDelete(async () => fs.promises.rmdir(dataPath), dataPath, throwWhenRemoveFails);
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* @ignore
|
|
244
|
-
*/
|
|
245
|
-
async retrieveOrgUsersConfig() {
|
|
246
|
-
return orgUsersConfig_1.OrgUsersConfig.create(orgUsersConfig_1.OrgUsersConfig.getOptions(this.getOrgId()));
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
|
-
* Cleans up all org related artifacts including users, sandbox config(if a sandbox and auth file.
|
|
250
|
-
*
|
|
251
|
-
* @param throwWhenRemoveFails Determines if the call should throw an error or fail silently.
|
|
252
|
-
*/
|
|
253
|
-
async remove(throwWhenRemoveFails = false) {
|
|
254
|
-
// If deleting via the access token there shouldn't be any auth config files
|
|
255
|
-
// so just return;
|
|
256
|
-
if (this.getConnection().isUsingAccessToken()) {
|
|
257
|
-
return Promise.resolve();
|
|
258
|
-
}
|
|
259
|
-
await this.removeSandboxConfig();
|
|
260
|
-
await this.removeUsers(throwWhenRemoveFails);
|
|
261
|
-
await this.removeUsersConfig();
|
|
262
|
-
// An attempt to remove this org's auth file occurs in this.removeUsersConfig. That's because this org's usersname is also
|
|
263
|
-
// included in the OrgUser config file.
|
|
264
|
-
//
|
|
265
|
-
// So, just in case no users are added to this org we will try the remove again.
|
|
266
|
-
await this.removeAuth();
|
|
267
|
-
}
|
|
268
|
-
/**
|
|
269
|
-
* Check if org is a sandbox org by checking its SandboxOrgConfig.
|
|
270
|
-
*
|
|
271
|
-
*/
|
|
272
|
-
async isSandbox() {
|
|
273
|
-
return (await stateAggregator_1.StateAggregator.getInstance()).sandboxes.hasFile(this.getOrgId());
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* Check that this org is a scratch org by asking the dev hub if it knows about it.
|
|
277
|
-
*
|
|
278
|
-
* **Throws** *{@link SfError}{ name: 'NotADevHubError' }* Not a Dev Hub.
|
|
279
|
-
*
|
|
280
|
-
* **Throws** *{@link SfError}{ name: 'NoResultsError' }* No results.
|
|
281
|
-
*
|
|
282
|
-
* @param devHubUsernameOrAlias The username or alias of the dev hub org.
|
|
283
|
-
*/
|
|
284
|
-
async checkScratchOrg(devHubUsernameOrAlias) {
|
|
285
|
-
let aliasOrUsername = devHubUsernameOrAlias;
|
|
286
|
-
if (!aliasOrUsername) {
|
|
287
|
-
aliasOrUsername = this.configAggregator.getPropertyValue(orgConfigProperties_1.OrgConfigProperties.TARGET_DEV_HUB);
|
|
288
|
-
}
|
|
289
|
-
const devHubConnection = (await Org.create({ aliasOrUsername })).getConnection();
|
|
290
|
-
const thisOrgAuthConfig = this.getConnection().getAuthInfoFields();
|
|
291
|
-
const trimmedId = sfdc_1.sfdc.trimTo15(thisOrgAuthConfig.orgId);
|
|
292
|
-
const DEV_HUB_SOQL = `SELECT CreatedDate,Edition,ExpirationDate FROM ActiveScratchOrg WHERE ScratchOrg='${trimmedId}'`;
|
|
293
|
-
try {
|
|
294
|
-
const results = await devHubConnection.query(DEV_HUB_SOQL);
|
|
295
|
-
if (results.records.length !== 1) {
|
|
296
|
-
throw new sfError_1.SfError('No results', 'NoResultsError');
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
catch (err) {
|
|
300
|
-
if (err instanceof Error && err.name === 'INVALID_TYPE') {
|
|
301
|
-
throw messages.createError('notADevHub', [devHubConnection.getUsername()]);
|
|
302
|
-
}
|
|
303
|
-
throw err;
|
|
304
|
-
}
|
|
305
|
-
return thisOrgAuthConfig;
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* Returns the Org object or null if this org is not affiliated with a Dev Hub (according to the local config).
|
|
309
|
-
*/
|
|
310
|
-
async getDevHubOrg() {
|
|
311
|
-
if (this.isDevHubOrg()) {
|
|
312
|
-
return this;
|
|
313
|
-
}
|
|
314
|
-
else if (this.getField(Org.Fields.DEV_HUB_USERNAME)) {
|
|
315
|
-
const devHubUsername = (0, ts_types_1.ensureString)(this.getField(Org.Fields.DEV_HUB_USERNAME));
|
|
316
|
-
return Org.create({
|
|
317
|
-
connection: await connection_1.Connection.create({
|
|
318
|
-
authInfo: await authInfo_1.AuthInfo.create({ username: devHubUsername }),
|
|
319
|
-
}),
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
/**
|
|
324
|
-
* Returns `true` if the org is a Dev Hub.
|
|
325
|
-
*
|
|
326
|
-
* **Note** This relies on a cached value in the auth file. If that property
|
|
327
|
-
* is not cached, this method will **always return false even if the org is a
|
|
328
|
-
* dev hub**. If you need accuracy, use the {@link Org.determineIfDevHubOrg} method.
|
|
329
|
-
*/
|
|
330
|
-
isDevHubOrg() {
|
|
331
|
-
const isDevHub = this.getField(Org.Fields.IS_DEV_HUB);
|
|
332
|
-
if ((0, ts_types_1.isBoolean)(isDevHub)) {
|
|
333
|
-
return isDevHub;
|
|
334
|
-
}
|
|
335
|
-
else {
|
|
336
|
-
return false;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
/**
|
|
340
|
-
* Will delete 'this' instance remotely and any files locally
|
|
341
|
-
*
|
|
342
|
-
* @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
|
|
343
|
-
*/
|
|
344
|
-
async deleteFrom(controllingOrg) {
|
|
345
|
-
if (typeof controllingOrg === 'string') {
|
|
346
|
-
controllingOrg = await Org.create({
|
|
347
|
-
aggregator: this.configAggregator,
|
|
348
|
-
aliasOrUsername: controllingOrg,
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
if (await this.isSandbox()) {
|
|
352
|
-
await this.deleteSandbox(controllingOrg);
|
|
353
|
-
}
|
|
354
|
-
else {
|
|
355
|
-
await this.deleteScratchOrg(controllingOrg);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
/**
|
|
359
|
-
* Will delete 'this' instance remotely and any files locally
|
|
360
|
-
*/
|
|
361
|
-
async delete() {
|
|
362
|
-
if (await this.isSandbox()) {
|
|
363
|
-
await this.deleteSandbox();
|
|
364
|
-
}
|
|
365
|
-
else {
|
|
366
|
-
await this.deleteScratchOrg();
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
/**
|
|
370
|
-
* Returns `true` if the org is a Dev Hub.
|
|
371
|
-
*
|
|
372
|
-
* Use a cached value. If the cached value is not set, then check access to the
|
|
373
|
-
* ScratchOrgInfo object to determine if the org is a dev hub.
|
|
374
|
-
*
|
|
375
|
-
* @param forceServerCheck Ignore the cached value and go straight to the server
|
|
376
|
-
* which will be required if the org flips on the dev hub after the value is already
|
|
377
|
-
* cached locally.
|
|
378
|
-
*/
|
|
379
|
-
async determineIfDevHubOrg(forceServerCheck = false) {
|
|
380
|
-
const cachedIsDevHub = this.getField(Org.Fields.IS_DEV_HUB);
|
|
381
|
-
if (!forceServerCheck && (0, ts_types_1.isBoolean)(cachedIsDevHub)) {
|
|
382
|
-
return cachedIsDevHub;
|
|
383
|
-
}
|
|
384
|
-
if (this.isDevHubOrg()) {
|
|
385
|
-
return true;
|
|
386
|
-
}
|
|
387
|
-
this.logger.debug('isDevHub is not cached - querying server...');
|
|
388
|
-
const conn = this.getConnection();
|
|
389
|
-
let isDevHub = false;
|
|
390
|
-
try {
|
|
391
|
-
await conn.query('SELECT Id FROM ScratchOrgInfo limit 1');
|
|
392
|
-
isDevHub = true;
|
|
393
|
-
}
|
|
394
|
-
catch (err) {
|
|
395
|
-
/* Not a dev hub */
|
|
396
|
-
}
|
|
397
|
-
const username = (0, ts_types_1.ensure)(this.getUsername());
|
|
398
|
-
const authInfo = await authInfo_1.AuthInfo.create({ username });
|
|
399
|
-
await authInfo.save({ isDevHub });
|
|
400
|
-
// Reset the connection with the updated auth file
|
|
401
|
-
this.connection = await connection_1.Connection.create({ authInfo });
|
|
402
|
-
return isDevHub;
|
|
403
|
-
}
|
|
404
|
-
/**
|
|
405
|
-
* Returns `true` if the org is a scratch org.
|
|
406
|
-
*
|
|
407
|
-
* **Note** This relies on a cached value in the auth file. If that property
|
|
408
|
-
* is not cached, this method will **always return false even if the org is a
|
|
409
|
-
* scratch org**. If you need accuracy, use the {@link Org.determineIfScratch} method.
|
|
410
|
-
*/
|
|
411
|
-
isScratch() {
|
|
412
|
-
const isScratch = this.getField(Org.Fields.IS_SCRATCH);
|
|
413
|
-
if ((0, ts_types_1.isBoolean)(isScratch)) {
|
|
414
|
-
return isScratch;
|
|
415
|
-
}
|
|
416
|
-
else {
|
|
417
|
-
return false;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
/**
|
|
421
|
-
* Returns `true` if the org uses source tracking.
|
|
422
|
-
* Side effect: updates files where the property doesn't currently exist
|
|
423
|
-
*/
|
|
424
|
-
async tracksSource() {
|
|
425
|
-
// use the property if it exists
|
|
426
|
-
const tracksSource = this.getField(Org.Fields.TRACKS_SOURCE);
|
|
427
|
-
if ((0, ts_types_1.isBoolean)(tracksSource)) {
|
|
428
|
-
return tracksSource;
|
|
429
|
-
}
|
|
430
|
-
// scratch orgs with no property use tracking by default
|
|
431
|
-
if (await this.determineIfScratch()) {
|
|
432
|
-
// save true for next time to avoid checking again
|
|
433
|
-
await this.setTracksSource(true);
|
|
434
|
-
return true;
|
|
435
|
-
}
|
|
436
|
-
if (await this.determineIfSandbox()) {
|
|
437
|
-
// does the sandbox know about the SourceMember object?
|
|
438
|
-
const supportsSourceMembers = await this.supportsSourceTracking();
|
|
439
|
-
await this.setTracksSource(supportsSourceMembers);
|
|
440
|
-
return supportsSourceMembers;
|
|
441
|
-
}
|
|
442
|
-
// any other non-sandbox, non-scratch orgs won't use tracking
|
|
443
|
-
await this.setTracksSource(false);
|
|
444
|
-
return false;
|
|
445
|
-
}
|
|
446
|
-
/**
|
|
447
|
-
* Set the tracking property on the org's auth file
|
|
448
|
-
*
|
|
449
|
-
* @param value true or false (whether the org should use source tracking or not)
|
|
450
|
-
*/
|
|
451
|
-
async setTracksSource(value) {
|
|
452
|
-
const originalAuth = await authInfo_1.AuthInfo.create({ username: this.getUsername() });
|
|
453
|
-
return originalAuth.handleAliasAndDefaultSettings({
|
|
454
|
-
setDefault: false,
|
|
455
|
-
setDefaultDevHub: false,
|
|
456
|
-
setTracksSource: value,
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
/**
|
|
460
|
-
* Returns `true` if the org is a scratch org.
|
|
461
|
-
*
|
|
462
|
-
* Use a cached value. If the cached value is not set, then check
|
|
463
|
-
* `Organization.IsSandbox == true && Organization.TrialExpirationDate != null`
|
|
464
|
-
* using {@link Org.retrieveOrganizationInformation}.
|
|
465
|
-
*/
|
|
466
|
-
async determineIfScratch() {
|
|
467
|
-
let cache = this.getField(Org.Fields.IS_SCRATCH);
|
|
468
|
-
if (!cache) {
|
|
469
|
-
await this.updateLocalInformation();
|
|
470
|
-
cache = this.getField(Org.Fields.IS_SCRATCH);
|
|
471
|
-
}
|
|
472
|
-
return cache;
|
|
473
|
-
}
|
|
474
|
-
/**
|
|
475
|
-
* Returns `true` if the org is a sandbox.
|
|
476
|
-
*
|
|
477
|
-
* Use a cached value. If the cached value is not set, then check
|
|
478
|
-
* `Organization.IsSandbox == true && Organization.TrialExpirationDate == null`
|
|
479
|
-
* using {@link Org.retrieveOrganizationInformation}.
|
|
480
|
-
*/
|
|
481
|
-
async determineIfSandbox() {
|
|
482
|
-
let cache = this.getField(Org.Fields.IS_SANDBOX);
|
|
483
|
-
if (!cache) {
|
|
484
|
-
await this.updateLocalInformation();
|
|
485
|
-
cache = this.getField(Org.Fields.IS_SANDBOX);
|
|
486
|
-
}
|
|
487
|
-
return cache;
|
|
488
|
-
}
|
|
489
|
-
/**
|
|
490
|
-
* Retrieve a handful of fields from the Organization table in Salesforce. If this does not have the
|
|
491
|
-
* data you need, just use {@link Connection.singleRecordQuery} with `SELECT <needed fields> FROM Organization`.
|
|
492
|
-
*
|
|
493
|
-
* @returns org information
|
|
494
|
-
*/
|
|
495
|
-
async retrieveOrganizationInformation() {
|
|
496
|
-
return this.getConnection().singleRecordQuery('SELECT Name, InstanceName, IsSandbox, TrialExpirationDate, NamespacePrefix FROM Organization');
|
|
497
|
-
}
|
|
498
|
-
/**
|
|
499
|
-
* Some organization information is locally cached, such as if the org name or if it is a scratch org.
|
|
500
|
-
* This method populates/updates the filesystem from information retrieved from the org.
|
|
501
|
-
*/
|
|
502
|
-
async updateLocalInformation() {
|
|
503
|
-
const username = this.getUsername();
|
|
504
|
-
if (username) {
|
|
505
|
-
const organization = await this.retrieveOrganizationInformation();
|
|
506
|
-
const isScratch = organization.IsSandbox && Boolean(organization.TrialExpirationDate);
|
|
507
|
-
const isSandbox = organization.IsSandbox && !organization.TrialExpirationDate;
|
|
508
|
-
const stateAggregator = await stateAggregator_1.StateAggregator.getInstance();
|
|
509
|
-
stateAggregator.orgs.update(username, {
|
|
510
|
-
[Org.Fields.NAME]: organization.Name,
|
|
511
|
-
[Org.Fields.INSTANCE_NAME]: organization.InstanceName,
|
|
512
|
-
[Org.Fields.NAMESPACE_PREFIX]: organization.NamespacePrefix,
|
|
513
|
-
[Org.Fields.IS_SANDBOX]: isSandbox,
|
|
514
|
-
[Org.Fields.IS_SCRATCH]: isScratch,
|
|
515
|
-
[Org.Fields.TRIAL_EXPIRATION_DATE]: organization.TrialExpirationDate,
|
|
516
|
-
});
|
|
517
|
-
await stateAggregator.orgs.write(username);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
/**
|
|
521
|
-
* Refreshes the auth for this org's instance by calling HTTP GET on the baseUrl of the connection object.
|
|
522
|
-
*/
|
|
523
|
-
async refreshAuth() {
|
|
524
|
-
this.logger.debug('Refreshing auth for org.');
|
|
525
|
-
const requestInfo = {
|
|
526
|
-
url: this.getConnection().baseUrl(),
|
|
527
|
-
method: 'GET',
|
|
528
|
-
};
|
|
529
|
-
const conn = this.getConnection();
|
|
530
|
-
await conn.request(requestInfo);
|
|
531
|
-
}
|
|
532
|
-
/**
|
|
533
|
-
* Reads and returns the content of all user auth files for this org as an array.
|
|
534
|
-
*/
|
|
535
|
-
async readUserAuthFiles() {
|
|
536
|
-
const config = await this.retrieveOrgUsersConfig();
|
|
537
|
-
const contents = await config.read();
|
|
538
|
-
const thisUsername = (0, ts_types_1.ensure)(this.getUsername());
|
|
539
|
-
const usernames = (0, ts_types_1.ensureJsonArray)(contents.usernames ?? [thisUsername]);
|
|
540
|
-
return Promise.all(usernames.map((username) => {
|
|
541
|
-
if (username === thisUsername) {
|
|
542
|
-
return authInfo_1.AuthInfo.create({
|
|
543
|
-
username: this.getConnection().getUsername(),
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
else {
|
|
547
|
-
return authInfo_1.AuthInfo.create({ username: (0, ts_types_1.ensureString)(username) });
|
|
548
|
-
}
|
|
549
|
-
}));
|
|
550
|
-
}
|
|
551
|
-
/**
|
|
552
|
-
* Adds a username to the user config for this org. For convenience `this` object is returned.
|
|
553
|
-
*
|
|
554
|
-
* ```
|
|
555
|
-
* const org: Org = await Org.create({
|
|
556
|
-
* connection: await Connection.create({
|
|
557
|
-
* authInfo: await AuthInfo.create('foo@example.com')
|
|
558
|
-
* })
|
|
559
|
-
* });
|
|
560
|
-
* const userAuth: AuthInfo = await AuthInfo.create({
|
|
561
|
-
* username: 'bar@example.com'
|
|
562
|
-
* });
|
|
563
|
-
* await org.addUsername(userAuth);
|
|
564
|
-
* ```
|
|
565
|
-
*
|
|
566
|
-
* @param {AuthInfo | string} auth The AuthInfo for the username to add.
|
|
567
|
-
*/
|
|
568
|
-
async addUsername(auth) {
|
|
569
|
-
if (!auth) {
|
|
570
|
-
throw new sfError_1.SfError('Missing auth info', 'MissingAuthInfo');
|
|
571
|
-
}
|
|
572
|
-
const authInfo = (0, ts_types_1.isString)(auth) ? await authInfo_1.AuthInfo.create({ username: auth }) : auth;
|
|
573
|
-
this.logger.debug(`adding username ${authInfo.getFields().username}`);
|
|
574
|
-
const orgConfig = await this.retrieveOrgUsersConfig();
|
|
575
|
-
const contents = await orgConfig.read();
|
|
576
|
-
// TODO: This is kind of screwy because contents values can be `AnyJson | object`...
|
|
577
|
-
// needs config refactoring to improve
|
|
578
|
-
const usernames = contents.usernames ?? [];
|
|
579
|
-
if (!(0, ts_types_1.isArray)(usernames)) {
|
|
580
|
-
throw new sfError_1.SfError('Usernames is not an array', 'UnexpectedDataFormat');
|
|
581
|
-
}
|
|
582
|
-
let shouldUpdate = false;
|
|
583
|
-
const thisUsername = (0, ts_types_1.ensure)(this.getUsername());
|
|
584
|
-
if (!usernames.includes(thisUsername)) {
|
|
585
|
-
usernames.push(thisUsername);
|
|
586
|
-
shouldUpdate = true;
|
|
587
|
-
}
|
|
588
|
-
const username = authInfo.getFields().username;
|
|
589
|
-
if (username) {
|
|
590
|
-
usernames.push(username);
|
|
591
|
-
shouldUpdate = true;
|
|
592
|
-
}
|
|
593
|
-
if (shouldUpdate) {
|
|
594
|
-
orgConfig.set('usernames', usernames);
|
|
595
|
-
await orgConfig.write();
|
|
596
|
-
}
|
|
597
|
-
return this;
|
|
598
|
-
}
|
|
599
|
-
/**
|
|
600
|
-
* Removes a username from the user config for this object. For convenience `this` object is returned.
|
|
601
|
-
*
|
|
602
|
-
* **Throws** *{@link SfError}{ name: 'MissingAuthInfoError' }* Auth info is missing.
|
|
603
|
-
*
|
|
604
|
-
* @param {AuthInfo | string} auth The AuthInfo containing the username to remove.
|
|
605
|
-
*/
|
|
606
|
-
async removeUsername(auth) {
|
|
607
|
-
if (!auth) {
|
|
608
|
-
throw new sfError_1.SfError('Missing auth info', 'MissingAuthInfoError');
|
|
609
|
-
}
|
|
610
|
-
const authInfo = (0, ts_types_1.isString)(auth) ? await authInfo_1.AuthInfo.create({ username: auth }) : auth;
|
|
611
|
-
this.logger.debug(`removing username ${authInfo.getFields().username}`);
|
|
612
|
-
const orgConfig = await this.retrieveOrgUsersConfig();
|
|
613
|
-
const contents = await orgConfig.read();
|
|
614
|
-
const targetUser = authInfo.getFields().username;
|
|
615
|
-
const usernames = (contents.usernames ?? []);
|
|
616
|
-
contents.usernames = usernames.filter((username) => username !== targetUser);
|
|
617
|
-
await orgConfig.write();
|
|
618
|
-
return this;
|
|
619
|
-
}
|
|
620
|
-
/**
|
|
621
|
-
* set the sandbox config related to this given org
|
|
622
|
-
*
|
|
623
|
-
* @param orgId {string} orgId of the sandbox
|
|
624
|
-
* @param config {SandboxFields} config of the sandbox
|
|
625
|
-
*/
|
|
626
|
-
async setSandboxConfig(orgId, config) {
|
|
627
|
-
(await stateAggregator_1.StateAggregator.getInstance()).sandboxes.set(orgId, config);
|
|
628
|
-
return this;
|
|
629
|
-
}
|
|
630
|
-
/**
|
|
631
|
-
* get the sandbox config for the given orgId
|
|
632
|
-
*
|
|
633
|
-
* @param orgId {string} orgId of the sandbox
|
|
634
|
-
*/
|
|
635
|
-
async getSandboxConfig(orgId) {
|
|
636
|
-
return (await stateAggregator_1.StateAggregator.getInstance()).sandboxes.read(orgId);
|
|
637
|
-
}
|
|
638
|
-
/**
|
|
639
|
-
* Retrieves the highest api version that is supported by the target server instance. If the apiVersion configured for
|
|
640
|
-
* Sfdx is greater than the one returned in this call an api version mismatch occurs. In the case of the CLI that
|
|
641
|
-
* results in a warning.
|
|
642
|
-
*/
|
|
643
|
-
async retrieveMaxApiVersion() {
|
|
644
|
-
return this.getConnection().retrieveMaxApiVersion();
|
|
645
|
-
}
|
|
646
|
-
/**
|
|
647
|
-
* Returns the admin username used to create the org.
|
|
648
|
-
*/
|
|
649
|
-
getUsername() {
|
|
650
|
-
return this.getConnection().getUsername();
|
|
651
|
-
}
|
|
652
|
-
/**
|
|
653
|
-
* Returns the orgId for this org.
|
|
654
|
-
*/
|
|
655
|
-
getOrgId() {
|
|
656
|
-
return this.orgId ?? this.getField(Org.Fields.ORG_ID);
|
|
657
|
-
}
|
|
658
|
-
/**
|
|
659
|
-
* Returns for the config aggregator.
|
|
660
|
-
*/
|
|
661
|
-
getConfigAggregator() {
|
|
662
|
-
return this.configAggregator;
|
|
663
|
-
}
|
|
664
|
-
/**
|
|
665
|
-
* Returns an org field. Returns undefined if the field is not set or invalid.
|
|
666
|
-
*/
|
|
667
|
-
getField(key) {
|
|
668
|
-
/* eslint-disable @typescript-eslint/ban-ts-comment, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment */
|
|
669
|
-
// @ts-ignore Legacy. We really shouldn't be doing this.
|
|
670
|
-
const ownProp = this[key];
|
|
671
|
-
if (ownProp && typeof ownProp !== 'function')
|
|
672
|
-
return ownProp;
|
|
673
|
-
// @ts-ignore
|
|
674
|
-
return this.getConnection().getAuthInfoFields()[key];
|
|
675
|
-
/* eslint-enable @typescript-eslint/ban-ts-comment, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment */
|
|
676
|
-
}
|
|
677
|
-
/**
|
|
678
|
-
* Returns a map of requested fields.
|
|
679
|
-
*/
|
|
680
|
-
getFields(keys) {
|
|
681
|
-
const json = {};
|
|
682
|
-
return keys.reduce((map, key) => {
|
|
683
|
-
map[key] = this.getField(key);
|
|
684
|
-
return map;
|
|
685
|
-
}, json);
|
|
686
|
-
}
|
|
687
|
-
/**
|
|
688
|
-
* Returns the JSForce connection for the org.
|
|
689
|
-
*/
|
|
690
|
-
getConnection() {
|
|
691
|
-
return this.connection;
|
|
692
|
-
}
|
|
693
|
-
async supportsSourceTracking() {
|
|
694
|
-
if (this.isScratch()) {
|
|
695
|
-
return true;
|
|
696
|
-
}
|
|
697
|
-
try {
|
|
698
|
-
await this.getConnection().tooling.sobject('SourceMember').describe();
|
|
699
|
-
return true;
|
|
700
|
-
}
|
|
701
|
-
catch (err) {
|
|
702
|
-
if (err.message.includes('The requested resource does not exist')) {
|
|
703
|
-
return false;
|
|
704
|
-
}
|
|
705
|
-
throw err;
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
/**
|
|
709
|
-
* query SandboxProcess via sandbox name
|
|
710
|
-
*
|
|
711
|
-
* @param name SandboxName to query for
|
|
712
|
-
* @private
|
|
713
|
-
*/
|
|
714
|
-
async querySandboxProcessBySandboxName(name) {
|
|
715
|
-
return this.querySandboxProcess(`SandboxName='${name}'`);
|
|
716
|
-
}
|
|
717
|
-
/**
|
|
718
|
-
* query SandboxProcess via SandboxInfoId
|
|
719
|
-
*
|
|
720
|
-
* @param id SandboxInfoId to query for
|
|
721
|
-
* @private
|
|
722
|
-
*/
|
|
723
|
-
async querySandboxProcessBySandboxInfoId(id) {
|
|
724
|
-
return this.querySandboxProcess(`SandboxInfoId='${id}'`);
|
|
725
|
-
}
|
|
726
|
-
/**
|
|
727
|
-
* query SandboxProcess via Id
|
|
728
|
-
*
|
|
729
|
-
* @param id SandboxProcessId to query for
|
|
730
|
-
* @private
|
|
731
|
-
*/
|
|
732
|
-
async querySandboxProcessById(id) {
|
|
733
|
-
return this.querySandboxProcess(`Id='${id}'`);
|
|
734
|
-
}
|
|
735
|
-
/**
|
|
736
|
-
* Initialize async components.
|
|
737
|
-
*/
|
|
738
|
-
async init() {
|
|
739
|
-
const stateAggregator = await stateAggregator_1.StateAggregator.getInstance();
|
|
740
|
-
this.logger = await logger_1.Logger.child('Org');
|
|
741
|
-
this.configAggregator = this.options.aggregator ? this.options.aggregator : await configAggregator_1.ConfigAggregator.create();
|
|
742
|
-
if (!this.options.connection) {
|
|
743
|
-
if (this.options.aliasOrUsername == null) {
|
|
744
|
-
this.configAggregator = this.getConfigAggregator();
|
|
745
|
-
const aliasOrUsername = this.options.isDevHub
|
|
746
|
-
? this.configAggregator.getPropertyValue(orgConfigProperties_1.OrgConfigProperties.TARGET_DEV_HUB)
|
|
747
|
-
: this.configAggregator.getPropertyValue(orgConfigProperties_1.OrgConfigProperties.TARGET_ORG);
|
|
748
|
-
this.options.aliasOrUsername = aliasOrUsername ?? undefined;
|
|
749
|
-
}
|
|
750
|
-
const username = stateAggregator.aliases.resolveUsername(this.options.aliasOrUsername);
|
|
751
|
-
if (!username) {
|
|
752
|
-
throw messages.createError('noUsernameFound');
|
|
753
|
-
}
|
|
754
|
-
this.connection = await connection_1.Connection.create({
|
|
755
|
-
// If no username is provided or resolvable from an alias, AuthInfo will throw an SfError.
|
|
756
|
-
authInfo: await authInfo_1.AuthInfo.create({ username, isDevHub: this.options.isDevHub }),
|
|
757
|
-
});
|
|
758
|
-
}
|
|
759
|
-
else {
|
|
760
|
-
this.connection = this.options.connection;
|
|
761
|
-
}
|
|
762
|
-
this.orgId = this.getField(Org.Fields.ORG_ID);
|
|
763
|
-
}
|
|
764
|
-
/**
|
|
765
|
-
* **Throws** *{@link SfError}{ name: 'NotSupportedError' }* Throws an unsupported error.
|
|
766
|
-
*/
|
|
767
|
-
// eslint-disable-next-line class-methods-use-this
|
|
768
|
-
getDefaultOptions() {
|
|
769
|
-
throw new sfError_1.SfError('Not Supported', 'NotSupportedError');
|
|
770
|
-
}
|
|
771
|
-
async getLocalDataDir(orgDataPath) {
|
|
772
|
-
const rootFolder = await config_1.Config.resolveRootFolder(false);
|
|
773
|
-
return (0, path_1.join)(rootFolder, global_1.Global.SFDX_STATE_FOLDER, orgDataPath ? orgDataPath : 'orgs');
|
|
774
|
-
}
|
|
775
|
-
/**
|
|
776
|
-
* Gets the sandboxProcessObject and then polls for it to complete.
|
|
777
|
-
*
|
|
778
|
-
* @param sandboxProcessName sanbox process name
|
|
779
|
-
* @param options { wait?: Duration; interval?: Duration }
|
|
780
|
-
* @returns {SandboxProcessObject} The SandboxProcessObject for the sandbox
|
|
781
|
-
*/
|
|
782
|
-
async authWithRetriesByName(sandboxProcessName, options) {
|
|
783
|
-
return this.authWithRetries(await this.queryLatestSandboxProcessBySandboxName(sandboxProcessName), options);
|
|
784
|
-
}
|
|
785
|
-
/**
|
|
786
|
-
* Polls the sandbox org for the sandboxProcessObject.
|
|
787
|
-
*
|
|
788
|
-
* @param sandboxProcessObj: The in-progress sandbox signup request
|
|
789
|
-
* @param options { wait?: Duration; interval?: Duration }
|
|
790
|
-
* @returns {SandboxProcessObject}
|
|
791
|
-
*/
|
|
792
|
-
async authWithRetries(sandboxProcessObj, options = {
|
|
793
|
-
wait: kit_1.Duration.minutes(0),
|
|
794
|
-
interval: kit_1.Duration.seconds(30),
|
|
795
|
-
}) {
|
|
796
|
-
const [wait, pollInterval] = this.validateWaitOptions(options);
|
|
797
|
-
this.logger.debug(`AuthWithRetries sandboxProcessObj ${JSON.stringify(sandboxProcessObj, undefined, 2)}, max wait time of ${wait.minutes} minutes`);
|
|
798
|
-
return this.pollStatusAndAuth({
|
|
799
|
-
sandboxProcessObj,
|
|
800
|
-
wait,
|
|
801
|
-
pollInterval,
|
|
802
|
-
});
|
|
803
|
-
}
|
|
804
|
-
/**
|
|
805
|
-
* Query the sandbox for the SandboxProcessObject by sandbox name
|
|
806
|
-
*
|
|
807
|
-
* @param sandboxName The name of the sandbox to query
|
|
808
|
-
* @returns {SandboxProcessObject} The SandboxProcessObject for the sandbox
|
|
809
|
-
*/
|
|
810
|
-
async queryLatestSandboxProcessBySandboxName(sandboxNameIn) {
|
|
811
|
-
const { tooling } = this.getConnection();
|
|
812
|
-
this.logger.debug('QueryLatestSandboxProcessBySandboxName called with SandboxName: %s ', sandboxNameIn);
|
|
813
|
-
const queryStr = `SELECT Id, Status, SandboxName, SandboxInfoId, LicenseType, CreatedDate, CopyProgress, SandboxOrganization, SourceId, Description, EndDate FROM SandboxProcess WHERE SandboxName='${sandboxNameIn}' AND Status != 'D' ORDER BY CreatedDate DESC LIMIT 1`;
|
|
814
|
-
const queryResult = await tooling.query(queryStr);
|
|
815
|
-
this.logger.debug('Return from calling queryToolingApi: %s ', queryResult);
|
|
816
|
-
if (queryResult?.records?.length === 1) {
|
|
817
|
-
return queryResult.records[0];
|
|
818
|
-
}
|
|
819
|
-
else if (queryResult.records && queryResult.records.length > 1) {
|
|
820
|
-
throw messages.createError('MultiSandboxProcessNotFoundBySandboxName', [sandboxNameIn]);
|
|
821
|
-
}
|
|
822
|
-
else {
|
|
823
|
-
throw messages.createError('SandboxProcessNotFoundBySandboxName', [sandboxNameIn]);
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
// eslint-disable-next-line class-methods-use-this
|
|
827
|
-
async queryProduction(org, field, value) {
|
|
828
|
-
return org.connection.singleRecordQuery(`SELECT SandboxInfoId FROM SandboxProcess WHERE ${field} ='${value}' AND Status NOT IN ('D', 'E')`, { tooling: true });
|
|
829
|
-
}
|
|
830
|
-
async destroySandbox(org, id) {
|
|
831
|
-
return org.getConnection().tooling.delete('SandboxInfo', id);
|
|
832
|
-
}
|
|
833
|
-
async destroyScratchOrg(org, id) {
|
|
834
|
-
return org.getConnection().delete('ActiveScratchOrg', id);
|
|
835
|
-
}
|
|
836
|
-
/**
|
|
837
|
-
* this method will delete the sandbox org from the production org and clean up any local files
|
|
838
|
-
*
|
|
839
|
-
* @param prodOrg - Production org associated with this sandbox
|
|
840
|
-
* @private
|
|
841
|
-
*/
|
|
842
|
-
async deleteSandbox(prodOrg) {
|
|
843
|
-
const sandbox = await this.getSandboxConfig(this.getOrgId());
|
|
844
|
-
prodOrg ?? (prodOrg = await Org.create({
|
|
845
|
-
aggregator: this.configAggregator,
|
|
846
|
-
aliasOrUsername: sandbox?.prodOrgUsername,
|
|
847
|
-
}));
|
|
848
|
-
let sandboxInfoId = sandbox?.sandboxInfoId;
|
|
849
|
-
if (!sandboxInfoId) {
|
|
850
|
-
let result;
|
|
851
|
-
try {
|
|
852
|
-
// grab sandboxName from config or try to calculate from the sandbox username
|
|
853
|
-
const sandboxName = sandbox?.sandboxName ?? (this.getUsername() ?? '').split(`${prodOrg.getUsername()}.`)[1];
|
|
854
|
-
if (!sandboxName) {
|
|
855
|
-
this.logger.debug('Sandbox name is not available');
|
|
856
|
-
// jump to query by orgId
|
|
857
|
-
throw new Error();
|
|
858
|
-
}
|
|
859
|
-
this.logger.debug(`attempting to locate sandbox with sandbox ${sandboxName}`);
|
|
860
|
-
try {
|
|
861
|
-
result = await this.queryProduction(prodOrg, 'SandboxName', sandboxName);
|
|
862
|
-
}
|
|
863
|
-
catch (err) {
|
|
864
|
-
this.logger.debug(`Failed to find sandbox with sandbox name: ${sandboxName}`);
|
|
865
|
-
// jump to query by orgId
|
|
866
|
-
throw err;
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
catch {
|
|
870
|
-
// if an error is thrown, don't panic yet. we'll try querying by orgId
|
|
871
|
-
const trimmedId = sfdc_1.sfdc.trimTo15(this.getOrgId());
|
|
872
|
-
this.logger.debug(`defaulting to trimming id from ${this.getOrgId()} to ${trimmedId}`);
|
|
873
|
-
try {
|
|
874
|
-
result = await this.queryProduction(prodOrg, 'SandboxOrganization', trimmedId);
|
|
875
|
-
sandboxInfoId = result.SandboxInfoId;
|
|
876
|
-
}
|
|
877
|
-
catch {
|
|
878
|
-
// eating exceptions when trying to find sandbox process record by orgId
|
|
879
|
-
// allows idempotent cleanup of sandbox orgs
|
|
880
|
-
this.logger.debug(`Failed find a SandboxProcess for the sandbox org: ${this.getOrgId()}`);
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
if (sandboxInfoId) {
|
|
885
|
-
const deleteResult = await this.destroySandbox(prodOrg, sandboxInfoId);
|
|
886
|
-
this.logger.debug('Return from calling tooling.delete: ', deleteResult);
|
|
887
|
-
}
|
|
888
|
-
// cleanup remaining artifacts
|
|
889
|
-
await this.remove();
|
|
890
|
-
}
|
|
891
|
-
/**
|
|
892
|
-
* If this Org is a scratch org, calling this method will delete the scratch org from the DevHub and clean up any local files
|
|
893
|
-
*
|
|
894
|
-
* @param devHub - optional DevHub Org of the to-be-deleted scratch org
|
|
895
|
-
* @private
|
|
896
|
-
*/
|
|
897
|
-
async deleteScratchOrg(devHub) {
|
|
898
|
-
// if we didn't get a devHub, we'll get it from the this org
|
|
899
|
-
devHub ?? (devHub = await this.getDevHubOrg());
|
|
900
|
-
if (!devHub) {
|
|
901
|
-
throw messages.createError('noDevHubFound');
|
|
902
|
-
}
|
|
903
|
-
if (devHub.getOrgId() === this.getOrgId()) {
|
|
904
|
-
// we're attempting to delete a DevHub
|
|
905
|
-
throw messages.createError('deleteOrgHubError');
|
|
906
|
-
}
|
|
907
|
-
try {
|
|
908
|
-
const devHubConn = devHub.getConnection();
|
|
909
|
-
const username = this.getUsername();
|
|
910
|
-
const activeScratchOrgRecordId = (await devHubConn.singleRecordQuery(`SELECT Id FROM ActiveScratchOrg WHERE SignupUsername='${username}'`)).Id;
|
|
911
|
-
this.logger.trace(`found matching ActiveScratchOrg with SignupUsername: ${username}. Deleting...`);
|
|
912
|
-
await this.destroyScratchOrg(devHub, activeScratchOrgRecordId);
|
|
913
|
-
await this.remove();
|
|
914
|
-
}
|
|
915
|
-
catch (err) {
|
|
916
|
-
this.logger.info(err instanceof Error ? err.message : err);
|
|
917
|
-
if (err instanceof Error && (err.name === 'INVALID_TYPE' || err.name === 'INSUFFICIENT_ACCESS_OR_READONLY')) {
|
|
918
|
-
// most likely from devHubConn.delete
|
|
919
|
-
this.logger.info('Insufficient privilege to access ActiveScratchOrgs.');
|
|
920
|
-
throw messages.createError('insufficientAccessToDelete');
|
|
921
|
-
}
|
|
922
|
-
if (err instanceof Error && err.name === connection_1.SingleRecordQueryErrors.NoRecords) {
|
|
923
|
-
// most likely from singleRecordQuery
|
|
924
|
-
this.logger.info('The above error can be the result of deleting an expired or already deleted org.');
|
|
925
|
-
this.logger.info('attempting to cleanup the auth file');
|
|
926
|
-
await this.removeAuth();
|
|
927
|
-
throw messages.createError('scratchOrgNotFound');
|
|
928
|
-
}
|
|
929
|
-
throw err;
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
/**
|
|
933
|
-
* Delete an auth info file from the local file system and any related cache information for
|
|
934
|
-
* this Org. You don't want to call this method directly. Instead consider calling Org.remove()
|
|
935
|
-
*/
|
|
936
|
-
async removeAuth() {
|
|
937
|
-
const stateAggregator = await stateAggregator_1.StateAggregator.getInstance();
|
|
938
|
-
const username = this.getUsername();
|
|
939
|
-
// If there is no username, it has already been removed from the globalInfo.
|
|
940
|
-
// We can skip the unset and just ensure that globalInfo is updated.
|
|
941
|
-
if (username) {
|
|
942
|
-
this.logger.debug(`Removing auth for user: ${username}`);
|
|
943
|
-
this.logger.debug(`Clearing auth cache for user: ${username}`);
|
|
944
|
-
await stateAggregator.orgs.remove(username);
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
/**
|
|
948
|
-
* Deletes the users config file
|
|
949
|
-
*/
|
|
950
|
-
async removeUsersConfig() {
|
|
951
|
-
const config = await this.retrieveOrgUsersConfig();
|
|
952
|
-
if (await config.exists()) {
|
|
953
|
-
this.logger.debug(`Removing org users config at: ${config.getPath()}`);
|
|
954
|
-
await config.unlink();
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
async manageDelete(cb, dirPath, throwWhenRemoveFails) {
|
|
958
|
-
return cb().catch((e) => {
|
|
959
|
-
if (throwWhenRemoveFails) {
|
|
960
|
-
throw e;
|
|
961
|
-
}
|
|
962
|
-
else {
|
|
963
|
-
this.logger.warn(`failed to read directory ${dirPath}`);
|
|
964
|
-
return;
|
|
965
|
-
}
|
|
966
|
-
});
|
|
967
|
-
}
|
|
968
|
-
/**
|
|
969
|
-
* Remove the org users auth file.
|
|
970
|
-
*
|
|
971
|
-
* @param throwWhenRemoveFails true if manageDelete should throw or not if the deleted fails.
|
|
972
|
-
*/
|
|
973
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
974
|
-
async removeUsers(throwWhenRemoveFails) {
|
|
975
|
-
const stateAggregator = await stateAggregator_1.StateAggregator.getInstance();
|
|
976
|
-
this.logger.debug(`Removing users associate with org: ${this.getOrgId()}`);
|
|
977
|
-
const config = await this.retrieveOrgUsersConfig();
|
|
978
|
-
this.logger.debug(`using path for org users: ${config.getPath()}`);
|
|
979
|
-
const authInfos = await this.readUserAuthFiles();
|
|
980
|
-
await Promise.all(authInfos
|
|
981
|
-
.map((auth) => auth.getFields().username)
|
|
982
|
-
.map(async (username) => {
|
|
983
|
-
const aliasKeys = (username && stateAggregator.aliases.getAll(username)) ?? [];
|
|
984
|
-
stateAggregator.aliases.unsetAll(username);
|
|
985
|
-
const orgForUser = username === this.getUsername()
|
|
986
|
-
? this
|
|
987
|
-
: await Org.create({
|
|
988
|
-
connection: await connection_1.Connection.create({ authInfo: await authInfo_1.AuthInfo.create({ username }) }),
|
|
989
|
-
});
|
|
990
|
-
const orgType = this.isDevHubOrg() ? orgConfigProperties_1.OrgConfigProperties.TARGET_DEV_HUB : orgConfigProperties_1.OrgConfigProperties.TARGET_ORG;
|
|
991
|
-
const configInfo = orgForUser.configAggregator.getInfo(orgType);
|
|
992
|
-
const needsConfigUpdate = (configInfo.isGlobal() || configInfo.isLocal()) &&
|
|
993
|
-
(configInfo.value === username || aliasKeys.includes(configInfo.value));
|
|
994
|
-
return [
|
|
995
|
-
orgForUser.removeAuth(),
|
|
996
|
-
needsConfigUpdate ? config_1.Config.update(configInfo.isGlobal(), orgType, undefined) : undefined,
|
|
997
|
-
].filter(Boolean);
|
|
998
|
-
}));
|
|
999
|
-
await stateAggregator.aliases.write();
|
|
1000
|
-
}
|
|
1001
|
-
async removeSandboxConfig() {
|
|
1002
|
-
const stateAggregator = await stateAggregator_1.StateAggregator.getInstance();
|
|
1003
|
-
await stateAggregator.sandboxes.remove(this.getOrgId());
|
|
1004
|
-
}
|
|
1005
|
-
async writeSandboxAuthFile(sandboxProcessObj, sandboxRes) {
|
|
1006
|
-
this.logger.debug(`writeSandboxAuthFile sandboxProcessObj: ${JSON.stringify(sandboxProcessObj)}, sandboxRes: ${JSON.stringify(sandboxRes)}`);
|
|
1007
|
-
if (sandboxRes.authUserName) {
|
|
1008
|
-
const productionAuthFields = this.connection.getAuthInfoFields();
|
|
1009
|
-
this.logger.debug('Result from getAuthInfoFields: AuthFields', productionAuthFields);
|
|
1010
|
-
// let's do headless auth via jwt (if we have privateKey) or web auth
|
|
1011
|
-
const oauth2Options = {
|
|
1012
|
-
loginUrl: sandboxRes.loginUrl,
|
|
1013
|
-
instanceUrl: sandboxRes.instanceUrl,
|
|
1014
|
-
username: sandboxRes.authUserName,
|
|
1015
|
-
};
|
|
1016
|
-
// If we don't have a privateKey then we assume it's web auth.
|
|
1017
|
-
if (!productionAuthFields.privateKey) {
|
|
1018
|
-
oauth2Options.redirectUri = `http://localhost:${await webOAuthServer_1.WebOAuthServer.determineOauthPort()}/OauthRedirect`;
|
|
1019
|
-
oauth2Options.authCode = sandboxRes.authCode;
|
|
1020
|
-
}
|
|
1021
|
-
else {
|
|
1022
|
-
oauth2Options.privateKey = productionAuthFields.privateKey;
|
|
1023
|
-
oauth2Options.clientId = productionAuthFields.clientId;
|
|
1024
|
-
}
|
|
1025
|
-
const authInfo = await authInfo_1.AuthInfo.create({
|
|
1026
|
-
username: sandboxRes.authUserName,
|
|
1027
|
-
oauth2Options,
|
|
1028
|
-
parentUsername: productionAuthFields.username,
|
|
1029
|
-
});
|
|
1030
|
-
this.logger.debug('Creating AuthInfo for sandbox', sandboxRes.authUserName, productionAuthFields.username, oauth2Options);
|
|
1031
|
-
// save auth info for new sandbox
|
|
1032
|
-
await authInfo.save();
|
|
1033
|
-
const sandboxOrgId = authInfo.getFields().orgId;
|
|
1034
|
-
if (!sandboxOrgId) {
|
|
1035
|
-
throw messages.createError('AuthInfoOrgIdUndefined');
|
|
1036
|
-
}
|
|
1037
|
-
// set the sandbox config value
|
|
1038
|
-
const sfSandbox = {
|
|
1039
|
-
sandboxUsername: sandboxRes.authUserName,
|
|
1040
|
-
sandboxOrgId,
|
|
1041
|
-
prodOrgUsername: this.getUsername(),
|
|
1042
|
-
sandboxName: sandboxProcessObj.SandboxName,
|
|
1043
|
-
sandboxProcessId: sandboxProcessObj.Id,
|
|
1044
|
-
sandboxInfoId: sandboxProcessObj.SandboxInfoId,
|
|
1045
|
-
timestamp: new Date().toISOString(),
|
|
1046
|
-
};
|
|
1047
|
-
await this.setSandboxConfig(sandboxOrgId, sfSandbox);
|
|
1048
|
-
await (await stateAggregator_1.StateAggregator.getInstance()).sandboxes.write(sandboxOrgId);
|
|
1049
|
-
await lifecycleEvents_1.Lifecycle.getInstance().emit(SandboxEvents.EVENT_RESULT, {
|
|
1050
|
-
sandboxProcessObj,
|
|
1051
|
-
sandboxRes,
|
|
1052
|
-
});
|
|
1053
|
-
}
|
|
1054
|
-
else {
|
|
1055
|
-
// no authed sandbox user, error
|
|
1056
|
-
throw messages.createError('missingAuthUsername', [sandboxProcessObj.SandboxName]);
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
async pollStatusAndAuth(options) {
|
|
1060
|
-
this.logger.debug('PollStatusAndAuth called with SandboxProcessObject', options.sandboxProcessObj, options.wait.minutes, options.pollInterval.seconds);
|
|
1061
|
-
let remainingWait = options.wait;
|
|
1062
|
-
let waitingOnAuth = false;
|
|
1063
|
-
const pollingClient = await pollingClient_1.PollingClient.create({
|
|
1064
|
-
poll: async () => {
|
|
1065
|
-
const sandboxProcessObj = await this.querySandboxProcessBySandboxInfoId(options.sandboxProcessObj.SandboxInfoId);
|
|
1066
|
-
// check to see if sandbox can authenticate via sandboxAuth endpoint
|
|
1067
|
-
const sandboxInfo = await this.sandboxSignupComplete(sandboxProcessObj);
|
|
1068
|
-
if (sandboxInfo) {
|
|
1069
|
-
await lifecycleEvents_1.Lifecycle.getInstance().emit(SandboxEvents.EVENT_AUTH, sandboxInfo);
|
|
1070
|
-
try {
|
|
1071
|
-
this.logger.debug('sandbox signup complete with', sandboxInfo);
|
|
1072
|
-
await this.writeSandboxAuthFile(sandboxProcessObj, sandboxInfo);
|
|
1073
|
-
return { completed: true, payload: sandboxProcessObj };
|
|
1074
|
-
}
|
|
1075
|
-
catch (err) {
|
|
1076
|
-
const error = err;
|
|
1077
|
-
this.logger.debug('Exception while calling writeSandboxAuthFile', err);
|
|
1078
|
-
if (error?.name === 'JwtAuthError' && error?.stack?.includes("user hasn't approved")) {
|
|
1079
|
-
waitingOnAuth = true;
|
|
1080
|
-
}
|
|
1081
|
-
else {
|
|
1082
|
-
throw sfError_1.SfError.wrap(error);
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
await lifecycleEvents_1.Lifecycle.getInstance().emit(SandboxEvents.EVENT_STATUS, {
|
|
1087
|
-
sandboxProcessObj,
|
|
1088
|
-
remainingWait: remainingWait.seconds,
|
|
1089
|
-
interval: options.pollInterval.seconds,
|
|
1090
|
-
waitingOnAuth,
|
|
1091
|
-
});
|
|
1092
|
-
remainingWait = kit_1.Duration.seconds(remainingWait.seconds - options.pollInterval.seconds);
|
|
1093
|
-
return { completed: false, payload: sandboxProcessObj };
|
|
1094
|
-
},
|
|
1095
|
-
frequency: options.pollInterval,
|
|
1096
|
-
timeout: options.wait,
|
|
1097
|
-
});
|
|
1098
|
-
return pollingClient.subscribe();
|
|
1099
|
-
}
|
|
1100
|
-
/**
|
|
1101
|
-
* query SandboxProcess using supplied where clause
|
|
1102
|
-
*
|
|
1103
|
-
* @param where clause to query for
|
|
1104
|
-
* @private
|
|
1105
|
-
*/
|
|
1106
|
-
async querySandboxProcess(where) {
|
|
1107
|
-
const queryStr = `SELECT Id, Status, SandboxName, SandboxInfoId, LicenseType, CreatedDate, CopyProgress, SandboxOrganization, SourceId, Description, EndDate FROM SandboxProcess WHERE ${where} AND Status != 'D'`;
|
|
1108
|
-
return this.connection.singleRecordQuery(queryStr, {
|
|
1109
|
-
tooling: true,
|
|
1110
|
-
});
|
|
1111
|
-
}
|
|
1112
|
-
/**
|
|
1113
|
-
* determines if the sandbox has successfully been created
|
|
1114
|
-
*
|
|
1115
|
-
* @param sandboxProcessObj sandbox signup progress
|
|
1116
|
-
* @private
|
|
1117
|
-
*/
|
|
1118
|
-
async sandboxSignupComplete(sandboxProcessObj) {
|
|
1119
|
-
this.logger.debug('sandboxSignupComplete called with SandboxProcessObject', sandboxProcessObj);
|
|
1120
|
-
if (!sandboxProcessObj.EndDate) {
|
|
1121
|
-
return;
|
|
1122
|
-
}
|
|
1123
|
-
try {
|
|
1124
|
-
// call server side /sandboxAuth API to auth the sandbox org user with the connected app
|
|
1125
|
-
const authFields = this.connection.getAuthInfoFields();
|
|
1126
|
-
const callbackUrl = `http://localhost:${await webOAuthServer_1.WebOAuthServer.determineOauthPort()}/OauthRedirect`;
|
|
1127
|
-
const sandboxReq = {
|
|
1128
|
-
// the sandbox signup has been completed on production, we have production clientId by this point
|
|
1129
|
-
clientId: authFields.clientId,
|
|
1130
|
-
sandboxName: sandboxProcessObj.SandboxName,
|
|
1131
|
-
callbackUrl,
|
|
1132
|
-
};
|
|
1133
|
-
this.logger.debug('Calling sandboxAuth with SandboxUserAuthRequest', sandboxReq);
|
|
1134
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
1135
|
-
const url = `${this.connection.tooling._baseUrl()}/sandboxAuth`;
|
|
1136
|
-
const params = {
|
|
1137
|
-
method: 'POST',
|
|
1138
|
-
url,
|
|
1139
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1140
|
-
body: JSON.stringify(sandboxReq),
|
|
1141
|
-
};
|
|
1142
|
-
const result = await this.connection.tooling.request(params);
|
|
1143
|
-
this.logger.debug('Result of calling sandboxAuth', result);
|
|
1144
|
-
return result;
|
|
1145
|
-
}
|
|
1146
|
-
catch (err) {
|
|
1147
|
-
const error = err;
|
|
1148
|
-
// There are cases where the endDate is set before the sandbox has actually completed.
|
|
1149
|
-
// In that case, the sandboxAuth call will throw a specific exception.
|
|
1150
|
-
if (error?.name === 'INVALID_STATUS') {
|
|
1151
|
-
this.logger.debug('Error while authenticating the user', error?.toString());
|
|
1152
|
-
}
|
|
1153
|
-
else {
|
|
1154
|
-
// If it fails for any unexpected reason, just pass that through
|
|
1155
|
-
throw sfError_1.SfError.wrap(error);
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
validateWaitOptions(options) {
|
|
1160
|
-
const wait = options.wait ?? kit_1.Duration.minutes(30);
|
|
1161
|
-
const interval = options.interval ?? kit_1.Duration.seconds(30);
|
|
1162
|
-
let pollInterval = options.async ? kit_1.Duration.seconds(0) : interval;
|
|
1163
|
-
// pollInterval cannot be > wait.
|
|
1164
|
-
pollInterval = pollInterval.seconds > wait.seconds ? wait : pollInterval;
|
|
1165
|
-
return [wait, pollInterval];
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
exports.Org = Org;
|
|
1169
|
-
(function (Org) {
|
|
1170
|
-
/**
|
|
1171
|
-
* Scratch Org status.
|
|
1172
|
-
*/
|
|
1173
|
-
let Status;
|
|
1174
|
-
(function (Status) {
|
|
1175
|
-
/**
|
|
1176
|
-
* The scratch org is active.
|
|
1177
|
-
*/
|
|
1178
|
-
Status["ACTIVE"] = "ACTIVE";
|
|
1179
|
-
/**
|
|
1180
|
-
* The scratch org has expired.
|
|
1181
|
-
*/
|
|
1182
|
-
Status["EXPIRED"] = "EXPIRED";
|
|
1183
|
-
/**
|
|
1184
|
-
* The org is a scratch Org but no dev hub is indicated.
|
|
1185
|
-
*/
|
|
1186
|
-
Status["UNKNOWN"] = "UNKNOWN";
|
|
1187
|
-
/**
|
|
1188
|
-
* The dev hub configuration is reporting an active Scratch org but the AuthInfo cannot be found.
|
|
1189
|
-
*/
|
|
1190
|
-
Status["MISSING"] = "MISSING";
|
|
1191
|
-
})(Status = Org.Status || (Org.Status = {}));
|
|
1192
|
-
/**
|
|
1193
|
-
* Org Fields.
|
|
1194
|
-
*/
|
|
1195
|
-
// A subset of fields from AuthInfoFields and properties that are specific to Org,
|
|
1196
|
-
// and properties that are defined on Org itself.
|
|
1197
|
-
let Fields;
|
|
1198
|
-
(function (Fields) {
|
|
1199
|
-
/**
|
|
1200
|
-
* The org alias.
|
|
1201
|
-
*/
|
|
1202
|
-
// From AuthInfo
|
|
1203
|
-
Fields["ALIAS"] = "alias";
|
|
1204
|
-
Fields["CREATED"] = "created";
|
|
1205
|
-
// From Organization
|
|
1206
|
-
Fields["NAME"] = "name";
|
|
1207
|
-
Fields["NAMESPACE_PREFIX"] = "namespacePrefix";
|
|
1208
|
-
Fields["INSTANCE_NAME"] = "instanceName";
|
|
1209
|
-
Fields["TRIAL_EXPIRATION_DATE"] = "trailExpirationDate";
|
|
1210
|
-
/**
|
|
1211
|
-
* The Salesforce instance the org was created on. e.g. `cs42`.
|
|
1212
|
-
*/
|
|
1213
|
-
Fields["CREATED_ORG_INSTANCE"] = "createdOrgInstance";
|
|
1214
|
-
/**
|
|
1215
|
-
* The username of the dev hub org that created this org. Only populated for scratch orgs.
|
|
1216
|
-
*/
|
|
1217
|
-
Fields["DEV_HUB_USERNAME"] = "devHubUsername";
|
|
1218
|
-
/**
|
|
1219
|
-
* The full url of the instance the org lives on.
|
|
1220
|
-
*/
|
|
1221
|
-
Fields["INSTANCE_URL"] = "instanceUrl";
|
|
1222
|
-
/**
|
|
1223
|
-
* Is the current org a dev hub org. e.g. They have access to the `ScratchOrgInfo` object.
|
|
1224
|
-
*/
|
|
1225
|
-
Fields["IS_DEV_HUB"] = "isDevHub";
|
|
1226
|
-
/**
|
|
1227
|
-
* Is the current org a scratch org. e.g. Organization has IsSandbox == true and TrialExpirationDate != null.
|
|
1228
|
-
*/
|
|
1229
|
-
Fields["IS_SCRATCH"] = "isScratch";
|
|
1230
|
-
/**
|
|
1231
|
-
* Is the current org a dev hub org. e.g. Organization has IsSandbox == true and TrialExpirationDate == null.
|
|
1232
|
-
*/
|
|
1233
|
-
Fields["IS_SANDBOX"] = "isSandbox";
|
|
1234
|
-
/**
|
|
1235
|
-
* The login url of the org. e.g. `https://login.salesforce.com` or `https://test.salesforce.com`.
|
|
1236
|
-
*/
|
|
1237
|
-
Fields["LOGIN_URL"] = "loginUrl";
|
|
1238
|
-
/**
|
|
1239
|
-
* The org ID.
|
|
1240
|
-
*/
|
|
1241
|
-
Fields["ORG_ID"] = "orgId";
|
|
1242
|
-
/**
|
|
1243
|
-
* The `OrgStatus` of the org.
|
|
1244
|
-
*/
|
|
1245
|
-
Fields["STATUS"] = "status";
|
|
1246
|
-
/**
|
|
1247
|
-
* The snapshot used to create the scratch org.
|
|
1248
|
-
*/
|
|
1249
|
-
Fields["SNAPSHOT"] = "snapshot";
|
|
1250
|
-
/**
|
|
1251
|
-
* true: the org supports and wants source tracking
|
|
1252
|
-
* false: the org opted out of tracking or can't support it
|
|
1253
|
-
*/
|
|
1254
|
-
Fields["TRACKS_SOURCE"] = "tracksSource";
|
|
1255
|
-
// Should it be on org? Leave it off for now, as it might
|
|
1256
|
-
// be confusing to the consumer what this actually is.
|
|
1257
|
-
// USERNAMES = 'usernames',
|
|
1258
|
-
// Keep separation of concerns. I think these should be on a "user" that belongs to the org.
|
|
1259
|
-
// Org can have a list of user objects that belong to it? Should connection be on user and org.getConnection()
|
|
1260
|
-
// gets the orgs current user for the process? Maybe we just want to keep with the Org only model for
|
|
1261
|
-
// the end of time?
|
|
1262
|
-
// USER_ID = 'userId',
|
|
1263
|
-
// USERNAME = 'username',
|
|
1264
|
-
// PASSWORD = 'password',
|
|
1265
|
-
// USER_PROFILE_NAME = 'userProfileName'
|
|
1266
|
-
})(Fields = Org.Fields || (Org.Fields = {}));
|
|
1267
|
-
})(Org = exports.Org || (exports.Org = {}));
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2020, salesforce.com, inc.
|
|
4
|
+
* All rights reserved.
|
|
5
|
+
* Licensed under the BSD 3-Clause license.
|
|
6
|
+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
7
|
+
*/
|
|
8
|
+
/* eslint-disable class-methods-use-this */
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.Org = exports.SandboxEvents = exports.OrgTypes = void 0;
|
|
11
|
+
const path_1 = require("path");
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
const kit_1 = require("@salesforce/kit");
|
|
14
|
+
const ts_types_1 = require("@salesforce/ts-types");
|
|
15
|
+
const config_1 = require("../config/config");
|
|
16
|
+
const configAggregator_1 = require("../config/configAggregator");
|
|
17
|
+
const orgUsersConfig_1 = require("../config/orgUsersConfig");
|
|
18
|
+
const global_1 = require("../global");
|
|
19
|
+
const lifecycleEvents_1 = require("../lifecycleEvents");
|
|
20
|
+
const logger_1 = require("../logger");
|
|
21
|
+
const sfError_1 = require("../sfError");
|
|
22
|
+
const sfdc_1 = require("../util/sfdc");
|
|
23
|
+
const webOAuthServer_1 = require("../webOAuthServer");
|
|
24
|
+
const messages_1 = require("../messages");
|
|
25
|
+
const stateAggregator_1 = require("../stateAggregator");
|
|
26
|
+
const pollingClient_1 = require("../status/pollingClient");
|
|
27
|
+
const connection_1 = require("./connection");
|
|
28
|
+
const authInfo_1 = require("./authInfo");
|
|
29
|
+
const scratchOrgCreate_1 = require("./scratchOrgCreate");
|
|
30
|
+
const orgConfigProperties_1 = require("./orgConfigProperties");
|
|
31
|
+
messages_1.Messages.importMessagesDirectory(__dirname);
|
|
32
|
+
const messages = messages_1.Messages.load('@salesforce/core', 'org', [
|
|
33
|
+
'deleteOrgHubError',
|
|
34
|
+
'insufficientAccessToDelete',
|
|
35
|
+
'missingAuthUsername',
|
|
36
|
+
'noDevHubFound',
|
|
37
|
+
'notADevHub',
|
|
38
|
+
'noUsernameFound',
|
|
39
|
+
'sandboxDeleteFailed',
|
|
40
|
+
'sandboxInfoCreateFailed',
|
|
41
|
+
'sandboxNotFound',
|
|
42
|
+
'scratchOrgNotFound',
|
|
43
|
+
'AuthInfoOrgIdUndefined',
|
|
44
|
+
'sandboxCreateNotComplete',
|
|
45
|
+
'SandboxProcessNotFoundBySandboxName',
|
|
46
|
+
'MultiSandboxProcessNotFoundBySandboxName',
|
|
47
|
+
]);
|
|
48
|
+
var OrgTypes;
|
|
49
|
+
(function (OrgTypes) {
|
|
50
|
+
OrgTypes["Scratch"] = "scratch";
|
|
51
|
+
OrgTypes["Sandbox"] = "sandbox";
|
|
52
|
+
})(OrgTypes = exports.OrgTypes || (exports.OrgTypes = {}));
|
|
53
|
+
var SandboxEvents;
|
|
54
|
+
(function (SandboxEvents) {
|
|
55
|
+
SandboxEvents["EVENT_STATUS"] = "status";
|
|
56
|
+
SandboxEvents["EVENT_ASYNC_RESULT"] = "asyncResult";
|
|
57
|
+
SandboxEvents["EVENT_RESULT"] = "result";
|
|
58
|
+
SandboxEvents["EVENT_AUTH"] = "auth";
|
|
59
|
+
SandboxEvents["EVENT_RESUME"] = "resume";
|
|
60
|
+
})(SandboxEvents = exports.SandboxEvents || (exports.SandboxEvents = {}));
|
|
61
|
+
/**
|
|
62
|
+
* Provides a way to manage a locally authenticated Org.
|
|
63
|
+
*
|
|
64
|
+
* **See** {@link AuthInfo}
|
|
65
|
+
*
|
|
66
|
+
* **See** {@link Connection}
|
|
67
|
+
*
|
|
68
|
+
* **See** {@link Aliases}
|
|
69
|
+
*
|
|
70
|
+
* **See** {@link Config}
|
|
71
|
+
*
|
|
72
|
+
* ```
|
|
73
|
+
* // Email username
|
|
74
|
+
* const org1: Org = await Org.create({ aliasOrUsername: 'foo@example.com' });
|
|
75
|
+
* // The target-org config property
|
|
76
|
+
* const org2: Org = await Org.create();
|
|
77
|
+
* // Full Connection
|
|
78
|
+
* const org3: Org = await Org.create({
|
|
79
|
+
* connection: await Connection.create({
|
|
80
|
+
* authInfo: await AuthInfo.create({ username: 'username' })
|
|
81
|
+
* })
|
|
82
|
+
* });
|
|
83
|
+
* ```
|
|
84
|
+
*
|
|
85
|
+
* **See** https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_cli_usernames_orgs.htm
|
|
86
|
+
*/
|
|
87
|
+
class Org extends kit_1.AsyncOptionalCreatable {
|
|
88
|
+
/**
|
|
89
|
+
* @ignore
|
|
90
|
+
*/
|
|
91
|
+
constructor(options) {
|
|
92
|
+
super(options);
|
|
93
|
+
this.status = Org.Status.UNKNOWN;
|
|
94
|
+
this.options = options ?? {};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* create a sandbox from a production org
|
|
98
|
+
* 'this' needs to be a production org with sandbox licenses available
|
|
99
|
+
*
|
|
100
|
+
* @param sandboxReq SandboxRequest options to create the sandbox with
|
|
101
|
+
* @param options Wait: The amount of time to wait before timing out, Interval: The time interval between polling
|
|
102
|
+
*/
|
|
103
|
+
async createSandbox(sandboxReq, options = {
|
|
104
|
+
wait: kit_1.Duration.minutes(6),
|
|
105
|
+
async: false,
|
|
106
|
+
interval: kit_1.Duration.seconds(30),
|
|
107
|
+
}) {
|
|
108
|
+
this.logger.debug(`CreateSandbox called with SandboxRequest: ${JSON.stringify(sandboxReq, undefined, 2)}`);
|
|
109
|
+
const createResult = await this.connection.tooling.create('SandboxInfo', sandboxReq);
|
|
110
|
+
this.logger.debug(`Return from calling tooling.create: ${JSON.stringify(createResult, undefined, 2)}`);
|
|
111
|
+
if (Array.isArray(createResult) || !createResult.success) {
|
|
112
|
+
throw messages.createError('sandboxInfoCreateFailed', [JSON.stringify(createResult)]);
|
|
113
|
+
}
|
|
114
|
+
const sandboxCreationProgress = await this.querySandboxProcessBySandboxInfoId(createResult.id);
|
|
115
|
+
this.logger.debug(`Return from calling singleRecordQuery with tooling: ${JSON.stringify(sandboxCreationProgress, undefined, 2)}`);
|
|
116
|
+
const isAsync = !!options.async;
|
|
117
|
+
if (isAsync) {
|
|
118
|
+
// The user didn't want us to poll, so simply return the status
|
|
119
|
+
await lifecycleEvents_1.Lifecycle.getInstance().emit(SandboxEvents.EVENT_ASYNC_RESULT, sandboxCreationProgress);
|
|
120
|
+
return sandboxCreationProgress;
|
|
121
|
+
}
|
|
122
|
+
const [wait, pollInterval] = this.validateWaitOptions(options);
|
|
123
|
+
this.logger.debug(`create - pollStatusAndAuth sandboxProcessObj ${JSON.stringify(sandboxCreationProgress, undefined, 2)}, max wait time of ${wait.minutes} minutes`);
|
|
124
|
+
return this.pollStatusAndAuth({
|
|
125
|
+
sandboxProcessObj: sandboxCreationProgress,
|
|
126
|
+
wait,
|
|
127
|
+
pollInterval,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
*
|
|
132
|
+
* @param sandboxReq SandboxRequest options to create the sandbox with
|
|
133
|
+
* @param sourceSandboxName the name of the sandbox that your new sandbox will be based on
|
|
134
|
+
* @param options Wait: The amount of time to wait before timing out, defaults to 0, Interval: The time interval between polling defaults to 30 seconds
|
|
135
|
+
* @returns {SandboxProcessObject} the newly created sandbox process object
|
|
136
|
+
*/
|
|
137
|
+
async cloneSandbox(sandboxReq, sourceSandboxName, options) {
|
|
138
|
+
sandboxReq.SourceId = (await this.querySandboxProcessBySandboxName(sourceSandboxName)).SandboxInfoId;
|
|
139
|
+
this.logger.debug('Clone sandbox sourceId %s', sandboxReq.SourceId);
|
|
140
|
+
return this.createSandbox(sandboxReq, options);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* resume a sandbox creation from a production org
|
|
144
|
+
* 'this' needs to be a production org with sandbox licenses available
|
|
145
|
+
*
|
|
146
|
+
* @param resumeSandboxRequest SandboxRequest options to create the sandbox with
|
|
147
|
+
* @param options Wait: The amount of time to wait (default: 30 minutes) before timing out,
|
|
148
|
+
* Interval: The time interval (default: 30 seconds) between polling
|
|
149
|
+
*/
|
|
150
|
+
async resumeSandbox(resumeSandboxRequest, options = {
|
|
151
|
+
wait: kit_1.Duration.minutes(0),
|
|
152
|
+
async: false,
|
|
153
|
+
interval: kit_1.Duration.seconds(30),
|
|
154
|
+
}) {
|
|
155
|
+
this.logger.debug(`ResumeSandbox called with ResumeSandboxRequest: ${JSON.stringify(resumeSandboxRequest, undefined, 2)}`);
|
|
156
|
+
let sandboxCreationProgress;
|
|
157
|
+
// seed the sandboxCreationProgress via the resumeSandboxRequest options
|
|
158
|
+
if (resumeSandboxRequest.SandboxName) {
|
|
159
|
+
sandboxCreationProgress = await this.querySandboxProcessBySandboxName(resumeSandboxRequest.SandboxName);
|
|
160
|
+
}
|
|
161
|
+
else if (resumeSandboxRequest.SandboxProcessObjId) {
|
|
162
|
+
sandboxCreationProgress = await this.querySandboxProcessById(resumeSandboxRequest.SandboxProcessObjId);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
throw messages.createError('sandboxNotFound', [
|
|
166
|
+
resumeSandboxRequest.SandboxName ?? resumeSandboxRequest.SandboxProcessObjId,
|
|
167
|
+
]);
|
|
168
|
+
}
|
|
169
|
+
this.logger.debug(`Return from calling singleRecordQuery with tooling: ${JSON.stringify(sandboxCreationProgress, undefined, 2)}`);
|
|
170
|
+
await lifecycleEvents_1.Lifecycle.getInstance().emit(SandboxEvents.EVENT_RESUME, sandboxCreationProgress);
|
|
171
|
+
const [wait, pollInterval] = this.validateWaitOptions(options);
|
|
172
|
+
// if wait is 0, return the sandboxCreationProgress immediately
|
|
173
|
+
if (wait.seconds === 0) {
|
|
174
|
+
if (sandboxCreationProgress.Status === 'Completed') {
|
|
175
|
+
// check to see if sandbox can authenticate via sandboxAuth endpoint
|
|
176
|
+
const sandboxInfo = await this.sandboxSignupComplete(sandboxCreationProgress);
|
|
177
|
+
if (sandboxInfo) {
|
|
178
|
+
await lifecycleEvents_1.Lifecycle.getInstance().emit(SandboxEvents.EVENT_AUTH, sandboxInfo);
|
|
179
|
+
try {
|
|
180
|
+
this.logger.debug(`sandbox signup complete with ${JSON.stringify(sandboxInfo, undefined, 2)}`);
|
|
181
|
+
await this.writeSandboxAuthFile(sandboxCreationProgress, sandboxInfo);
|
|
182
|
+
return sandboxCreationProgress;
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
// eat the error, we don't want to throw an error if we can't write the file
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
await lifecycleEvents_1.Lifecycle.getInstance().emit(SandboxEvents.EVENT_ASYNC_RESULT, sandboxCreationProgress);
|
|
190
|
+
throw messages.createError('sandboxCreateNotComplete');
|
|
191
|
+
}
|
|
192
|
+
this.logger.debug(`resume - pollStatusAndAuth sandboxProcessObj ${JSON.stringify(sandboxCreationProgress, undefined, 2)}, max wait time of ${wait.minutes} minutes`);
|
|
193
|
+
return this.pollStatusAndAuth({
|
|
194
|
+
sandboxProcessObj: sandboxCreationProgress,
|
|
195
|
+
wait,
|
|
196
|
+
pollInterval,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Creates a scratchOrg
|
|
201
|
+
* 'this' needs to be a valid dev-hub
|
|
202
|
+
*
|
|
203
|
+
* @param {options} ScratchOrgCreateOptions
|
|
204
|
+
* @returns {ScratchOrgCreateResult}
|
|
205
|
+
*/
|
|
206
|
+
async scratchOrgCreate(options) {
|
|
207
|
+
return (0, scratchOrgCreate_1.scratchOrgCreate)({ ...options, hubOrg: this });
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Reports sandbox org creation status. If the org is ready, authenticates to the org.
|
|
211
|
+
*
|
|
212
|
+
* @param {sandboxname} string the sandbox name
|
|
213
|
+
* @param options Wait: The amount of time to wait before timing out, Interval: The time interval between polling
|
|
214
|
+
* @returns {SandboxProcessObject} the sandbox process object
|
|
215
|
+
*/
|
|
216
|
+
async sandboxStatus(sandboxname, options) {
|
|
217
|
+
return this.authWithRetriesByName(sandboxname, options);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Clean all data files in the org's data path. Usually <workspace>/.sfdx/orgs/<username>.
|
|
221
|
+
*
|
|
222
|
+
* @param orgDataPath A relative path other than "orgs/".
|
|
223
|
+
* @param throwWhenRemoveFails Should the remove org operations throw an error on failure?
|
|
224
|
+
*/
|
|
225
|
+
async cleanLocalOrgData(orgDataPath, throwWhenRemoveFails = false) {
|
|
226
|
+
let dataPath;
|
|
227
|
+
try {
|
|
228
|
+
dataPath = await this.getLocalDataDir(orgDataPath);
|
|
229
|
+
this.logger.debug(`cleaning data for path: ${dataPath}`);
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
if (err instanceof Error && err.name === 'InvalidProjectWorkspaceError') {
|
|
233
|
+
// If we aren't in a project dir, we can't clean up data files.
|
|
234
|
+
// If the user unlink this org outside of the workspace they used it in,
|
|
235
|
+
// data files will be left over.
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
throw err;
|
|
239
|
+
}
|
|
240
|
+
return this.manageDelete(async () => fs.promises.rmdir(dataPath), dataPath, throwWhenRemoveFails);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* @ignore
|
|
244
|
+
*/
|
|
245
|
+
async retrieveOrgUsersConfig() {
|
|
246
|
+
return orgUsersConfig_1.OrgUsersConfig.create(orgUsersConfig_1.OrgUsersConfig.getOptions(this.getOrgId()));
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Cleans up all org related artifacts including users, sandbox config(if a sandbox and auth file.
|
|
250
|
+
*
|
|
251
|
+
* @param throwWhenRemoveFails Determines if the call should throw an error or fail silently.
|
|
252
|
+
*/
|
|
253
|
+
async remove(throwWhenRemoveFails = false) {
|
|
254
|
+
// If deleting via the access token there shouldn't be any auth config files
|
|
255
|
+
// so just return;
|
|
256
|
+
if (this.getConnection().isUsingAccessToken()) {
|
|
257
|
+
return Promise.resolve();
|
|
258
|
+
}
|
|
259
|
+
await this.removeSandboxConfig();
|
|
260
|
+
await this.removeUsers(throwWhenRemoveFails);
|
|
261
|
+
await this.removeUsersConfig();
|
|
262
|
+
// An attempt to remove this org's auth file occurs in this.removeUsersConfig. That's because this org's usersname is also
|
|
263
|
+
// included in the OrgUser config file.
|
|
264
|
+
//
|
|
265
|
+
// So, just in case no users are added to this org we will try the remove again.
|
|
266
|
+
await this.removeAuth();
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Check if org is a sandbox org by checking its SandboxOrgConfig.
|
|
270
|
+
*
|
|
271
|
+
*/
|
|
272
|
+
async isSandbox() {
|
|
273
|
+
return (await stateAggregator_1.StateAggregator.getInstance()).sandboxes.hasFile(this.getOrgId());
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Check that this org is a scratch org by asking the dev hub if it knows about it.
|
|
277
|
+
*
|
|
278
|
+
* **Throws** *{@link SfError}{ name: 'NotADevHubError' }* Not a Dev Hub.
|
|
279
|
+
*
|
|
280
|
+
* **Throws** *{@link SfError}{ name: 'NoResultsError' }* No results.
|
|
281
|
+
*
|
|
282
|
+
* @param devHubUsernameOrAlias The username or alias of the dev hub org.
|
|
283
|
+
*/
|
|
284
|
+
async checkScratchOrg(devHubUsernameOrAlias) {
|
|
285
|
+
let aliasOrUsername = devHubUsernameOrAlias;
|
|
286
|
+
if (!aliasOrUsername) {
|
|
287
|
+
aliasOrUsername = this.configAggregator.getPropertyValue(orgConfigProperties_1.OrgConfigProperties.TARGET_DEV_HUB);
|
|
288
|
+
}
|
|
289
|
+
const devHubConnection = (await Org.create({ aliasOrUsername })).getConnection();
|
|
290
|
+
const thisOrgAuthConfig = this.getConnection().getAuthInfoFields();
|
|
291
|
+
const trimmedId = sfdc_1.sfdc.trimTo15(thisOrgAuthConfig.orgId);
|
|
292
|
+
const DEV_HUB_SOQL = `SELECT CreatedDate,Edition,ExpirationDate FROM ActiveScratchOrg WHERE ScratchOrg='${trimmedId}'`;
|
|
293
|
+
try {
|
|
294
|
+
const results = await devHubConnection.query(DEV_HUB_SOQL);
|
|
295
|
+
if (results.records.length !== 1) {
|
|
296
|
+
throw new sfError_1.SfError('No results', 'NoResultsError');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
catch (err) {
|
|
300
|
+
if (err instanceof Error && err.name === 'INVALID_TYPE') {
|
|
301
|
+
throw messages.createError('notADevHub', [devHubConnection.getUsername()]);
|
|
302
|
+
}
|
|
303
|
+
throw err;
|
|
304
|
+
}
|
|
305
|
+
return thisOrgAuthConfig;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Returns the Org object or null if this org is not affiliated with a Dev Hub (according to the local config).
|
|
309
|
+
*/
|
|
310
|
+
async getDevHubOrg() {
|
|
311
|
+
if (this.isDevHubOrg()) {
|
|
312
|
+
return this;
|
|
313
|
+
}
|
|
314
|
+
else if (this.getField(Org.Fields.DEV_HUB_USERNAME)) {
|
|
315
|
+
const devHubUsername = (0, ts_types_1.ensureString)(this.getField(Org.Fields.DEV_HUB_USERNAME));
|
|
316
|
+
return Org.create({
|
|
317
|
+
connection: await connection_1.Connection.create({
|
|
318
|
+
authInfo: await authInfo_1.AuthInfo.create({ username: devHubUsername }),
|
|
319
|
+
}),
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Returns `true` if the org is a Dev Hub.
|
|
325
|
+
*
|
|
326
|
+
* **Note** This relies on a cached value in the auth file. If that property
|
|
327
|
+
* is not cached, this method will **always return false even if the org is a
|
|
328
|
+
* dev hub**. If you need accuracy, use the {@link Org.determineIfDevHubOrg} method.
|
|
329
|
+
*/
|
|
330
|
+
isDevHubOrg() {
|
|
331
|
+
const isDevHub = this.getField(Org.Fields.IS_DEV_HUB);
|
|
332
|
+
if ((0, ts_types_1.isBoolean)(isDevHub)) {
|
|
333
|
+
return isDevHub;
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Will delete 'this' instance remotely and any files locally
|
|
341
|
+
*
|
|
342
|
+
* @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
|
|
343
|
+
*/
|
|
344
|
+
async deleteFrom(controllingOrg) {
|
|
345
|
+
if (typeof controllingOrg === 'string') {
|
|
346
|
+
controllingOrg = await Org.create({
|
|
347
|
+
aggregator: this.configAggregator,
|
|
348
|
+
aliasOrUsername: controllingOrg,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
if (await this.isSandbox()) {
|
|
352
|
+
await this.deleteSandbox(controllingOrg);
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
await this.deleteScratchOrg(controllingOrg);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Will delete 'this' instance remotely and any files locally
|
|
360
|
+
*/
|
|
361
|
+
async delete() {
|
|
362
|
+
if (await this.isSandbox()) {
|
|
363
|
+
await this.deleteSandbox();
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
await this.deleteScratchOrg();
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Returns `true` if the org is a Dev Hub.
|
|
371
|
+
*
|
|
372
|
+
* Use a cached value. If the cached value is not set, then check access to the
|
|
373
|
+
* ScratchOrgInfo object to determine if the org is a dev hub.
|
|
374
|
+
*
|
|
375
|
+
* @param forceServerCheck Ignore the cached value and go straight to the server
|
|
376
|
+
* which will be required if the org flips on the dev hub after the value is already
|
|
377
|
+
* cached locally.
|
|
378
|
+
*/
|
|
379
|
+
async determineIfDevHubOrg(forceServerCheck = false) {
|
|
380
|
+
const cachedIsDevHub = this.getField(Org.Fields.IS_DEV_HUB);
|
|
381
|
+
if (!forceServerCheck && (0, ts_types_1.isBoolean)(cachedIsDevHub)) {
|
|
382
|
+
return cachedIsDevHub;
|
|
383
|
+
}
|
|
384
|
+
if (this.isDevHubOrg()) {
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
this.logger.debug('isDevHub is not cached - querying server...');
|
|
388
|
+
const conn = this.getConnection();
|
|
389
|
+
let isDevHub = false;
|
|
390
|
+
try {
|
|
391
|
+
await conn.query('SELECT Id FROM ScratchOrgInfo limit 1');
|
|
392
|
+
isDevHub = true;
|
|
393
|
+
}
|
|
394
|
+
catch (err) {
|
|
395
|
+
/* Not a dev hub */
|
|
396
|
+
}
|
|
397
|
+
const username = (0, ts_types_1.ensure)(this.getUsername());
|
|
398
|
+
const authInfo = await authInfo_1.AuthInfo.create({ username });
|
|
399
|
+
await authInfo.save({ isDevHub });
|
|
400
|
+
// Reset the connection with the updated auth file
|
|
401
|
+
this.connection = await connection_1.Connection.create({ authInfo });
|
|
402
|
+
return isDevHub;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Returns `true` if the org is a scratch org.
|
|
406
|
+
*
|
|
407
|
+
* **Note** This relies on a cached value in the auth file. If that property
|
|
408
|
+
* is not cached, this method will **always return false even if the org is a
|
|
409
|
+
* scratch org**. If you need accuracy, use the {@link Org.determineIfScratch} method.
|
|
410
|
+
*/
|
|
411
|
+
isScratch() {
|
|
412
|
+
const isScratch = this.getField(Org.Fields.IS_SCRATCH);
|
|
413
|
+
if ((0, ts_types_1.isBoolean)(isScratch)) {
|
|
414
|
+
return isScratch;
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Returns `true` if the org uses source tracking.
|
|
422
|
+
* Side effect: updates files where the property doesn't currently exist
|
|
423
|
+
*/
|
|
424
|
+
async tracksSource() {
|
|
425
|
+
// use the property if it exists
|
|
426
|
+
const tracksSource = this.getField(Org.Fields.TRACKS_SOURCE);
|
|
427
|
+
if ((0, ts_types_1.isBoolean)(tracksSource)) {
|
|
428
|
+
return tracksSource;
|
|
429
|
+
}
|
|
430
|
+
// scratch orgs with no property use tracking by default
|
|
431
|
+
if (await this.determineIfScratch()) {
|
|
432
|
+
// save true for next time to avoid checking again
|
|
433
|
+
await this.setTracksSource(true);
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
if (await this.determineIfSandbox()) {
|
|
437
|
+
// does the sandbox know about the SourceMember object?
|
|
438
|
+
const supportsSourceMembers = await this.supportsSourceTracking();
|
|
439
|
+
await this.setTracksSource(supportsSourceMembers);
|
|
440
|
+
return supportsSourceMembers;
|
|
441
|
+
}
|
|
442
|
+
// any other non-sandbox, non-scratch orgs won't use tracking
|
|
443
|
+
await this.setTracksSource(false);
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Set the tracking property on the org's auth file
|
|
448
|
+
*
|
|
449
|
+
* @param value true or false (whether the org should use source tracking or not)
|
|
450
|
+
*/
|
|
451
|
+
async setTracksSource(value) {
|
|
452
|
+
const originalAuth = await authInfo_1.AuthInfo.create({ username: this.getUsername() });
|
|
453
|
+
return originalAuth.handleAliasAndDefaultSettings({
|
|
454
|
+
setDefault: false,
|
|
455
|
+
setDefaultDevHub: false,
|
|
456
|
+
setTracksSource: value,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Returns `true` if the org is a scratch org.
|
|
461
|
+
*
|
|
462
|
+
* Use a cached value. If the cached value is not set, then check
|
|
463
|
+
* `Organization.IsSandbox == true && Organization.TrialExpirationDate != null`
|
|
464
|
+
* using {@link Org.retrieveOrganizationInformation}.
|
|
465
|
+
*/
|
|
466
|
+
async determineIfScratch() {
|
|
467
|
+
let cache = this.getField(Org.Fields.IS_SCRATCH);
|
|
468
|
+
if (!cache) {
|
|
469
|
+
await this.updateLocalInformation();
|
|
470
|
+
cache = this.getField(Org.Fields.IS_SCRATCH);
|
|
471
|
+
}
|
|
472
|
+
return cache;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Returns `true` if the org is a sandbox.
|
|
476
|
+
*
|
|
477
|
+
* Use a cached value. If the cached value is not set, then check
|
|
478
|
+
* `Organization.IsSandbox == true && Organization.TrialExpirationDate == null`
|
|
479
|
+
* using {@link Org.retrieveOrganizationInformation}.
|
|
480
|
+
*/
|
|
481
|
+
async determineIfSandbox() {
|
|
482
|
+
let cache = this.getField(Org.Fields.IS_SANDBOX);
|
|
483
|
+
if (!cache) {
|
|
484
|
+
await this.updateLocalInformation();
|
|
485
|
+
cache = this.getField(Org.Fields.IS_SANDBOX);
|
|
486
|
+
}
|
|
487
|
+
return cache;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Retrieve a handful of fields from the Organization table in Salesforce. If this does not have the
|
|
491
|
+
* data you need, just use {@link Connection.singleRecordQuery} with `SELECT <needed fields> FROM Organization`.
|
|
492
|
+
*
|
|
493
|
+
* @returns org information
|
|
494
|
+
*/
|
|
495
|
+
async retrieveOrganizationInformation() {
|
|
496
|
+
return this.getConnection().singleRecordQuery('SELECT Name, InstanceName, IsSandbox, TrialExpirationDate, NamespacePrefix FROM Organization');
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Some organization information is locally cached, such as if the org name or if it is a scratch org.
|
|
500
|
+
* This method populates/updates the filesystem from information retrieved from the org.
|
|
501
|
+
*/
|
|
502
|
+
async updateLocalInformation() {
|
|
503
|
+
const username = this.getUsername();
|
|
504
|
+
if (username) {
|
|
505
|
+
const organization = await this.retrieveOrganizationInformation();
|
|
506
|
+
const isScratch = organization.IsSandbox && Boolean(organization.TrialExpirationDate);
|
|
507
|
+
const isSandbox = organization.IsSandbox && !organization.TrialExpirationDate;
|
|
508
|
+
const stateAggregator = await stateAggregator_1.StateAggregator.getInstance();
|
|
509
|
+
stateAggregator.orgs.update(username, {
|
|
510
|
+
[Org.Fields.NAME]: organization.Name,
|
|
511
|
+
[Org.Fields.INSTANCE_NAME]: organization.InstanceName,
|
|
512
|
+
[Org.Fields.NAMESPACE_PREFIX]: organization.NamespacePrefix,
|
|
513
|
+
[Org.Fields.IS_SANDBOX]: isSandbox,
|
|
514
|
+
[Org.Fields.IS_SCRATCH]: isScratch,
|
|
515
|
+
[Org.Fields.TRIAL_EXPIRATION_DATE]: organization.TrialExpirationDate,
|
|
516
|
+
});
|
|
517
|
+
await stateAggregator.orgs.write(username);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Refreshes the auth for this org's instance by calling HTTP GET on the baseUrl of the connection object.
|
|
522
|
+
*/
|
|
523
|
+
async refreshAuth() {
|
|
524
|
+
this.logger.debug('Refreshing auth for org.');
|
|
525
|
+
const requestInfo = {
|
|
526
|
+
url: this.getConnection().baseUrl(),
|
|
527
|
+
method: 'GET',
|
|
528
|
+
};
|
|
529
|
+
const conn = this.getConnection();
|
|
530
|
+
await conn.request(requestInfo);
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Reads and returns the content of all user auth files for this org as an array.
|
|
534
|
+
*/
|
|
535
|
+
async readUserAuthFiles() {
|
|
536
|
+
const config = await this.retrieveOrgUsersConfig();
|
|
537
|
+
const contents = await config.read();
|
|
538
|
+
const thisUsername = (0, ts_types_1.ensure)(this.getUsername());
|
|
539
|
+
const usernames = (0, ts_types_1.ensureJsonArray)(contents.usernames ?? [thisUsername]);
|
|
540
|
+
return Promise.all(usernames.map((username) => {
|
|
541
|
+
if (username === thisUsername) {
|
|
542
|
+
return authInfo_1.AuthInfo.create({
|
|
543
|
+
username: this.getConnection().getUsername(),
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
return authInfo_1.AuthInfo.create({ username: (0, ts_types_1.ensureString)(username) });
|
|
548
|
+
}
|
|
549
|
+
}));
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Adds a username to the user config for this org. For convenience `this` object is returned.
|
|
553
|
+
*
|
|
554
|
+
* ```
|
|
555
|
+
* const org: Org = await Org.create({
|
|
556
|
+
* connection: await Connection.create({
|
|
557
|
+
* authInfo: await AuthInfo.create('foo@example.com')
|
|
558
|
+
* })
|
|
559
|
+
* });
|
|
560
|
+
* const userAuth: AuthInfo = await AuthInfo.create({
|
|
561
|
+
* username: 'bar@example.com'
|
|
562
|
+
* });
|
|
563
|
+
* await org.addUsername(userAuth);
|
|
564
|
+
* ```
|
|
565
|
+
*
|
|
566
|
+
* @param {AuthInfo | string} auth The AuthInfo for the username to add.
|
|
567
|
+
*/
|
|
568
|
+
async addUsername(auth) {
|
|
569
|
+
if (!auth) {
|
|
570
|
+
throw new sfError_1.SfError('Missing auth info', 'MissingAuthInfo');
|
|
571
|
+
}
|
|
572
|
+
const authInfo = (0, ts_types_1.isString)(auth) ? await authInfo_1.AuthInfo.create({ username: auth }) : auth;
|
|
573
|
+
this.logger.debug(`adding username ${authInfo.getFields().username}`);
|
|
574
|
+
const orgConfig = await this.retrieveOrgUsersConfig();
|
|
575
|
+
const contents = await orgConfig.read();
|
|
576
|
+
// TODO: This is kind of screwy because contents values can be `AnyJson | object`...
|
|
577
|
+
// needs config refactoring to improve
|
|
578
|
+
const usernames = contents.usernames ?? [];
|
|
579
|
+
if (!(0, ts_types_1.isArray)(usernames)) {
|
|
580
|
+
throw new sfError_1.SfError('Usernames is not an array', 'UnexpectedDataFormat');
|
|
581
|
+
}
|
|
582
|
+
let shouldUpdate = false;
|
|
583
|
+
const thisUsername = (0, ts_types_1.ensure)(this.getUsername());
|
|
584
|
+
if (!usernames.includes(thisUsername)) {
|
|
585
|
+
usernames.push(thisUsername);
|
|
586
|
+
shouldUpdate = true;
|
|
587
|
+
}
|
|
588
|
+
const username = authInfo.getFields().username;
|
|
589
|
+
if (username) {
|
|
590
|
+
usernames.push(username);
|
|
591
|
+
shouldUpdate = true;
|
|
592
|
+
}
|
|
593
|
+
if (shouldUpdate) {
|
|
594
|
+
orgConfig.set('usernames', usernames);
|
|
595
|
+
await orgConfig.write();
|
|
596
|
+
}
|
|
597
|
+
return this;
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Removes a username from the user config for this object. For convenience `this` object is returned.
|
|
601
|
+
*
|
|
602
|
+
* **Throws** *{@link SfError}{ name: 'MissingAuthInfoError' }* Auth info is missing.
|
|
603
|
+
*
|
|
604
|
+
* @param {AuthInfo | string} auth The AuthInfo containing the username to remove.
|
|
605
|
+
*/
|
|
606
|
+
async removeUsername(auth) {
|
|
607
|
+
if (!auth) {
|
|
608
|
+
throw new sfError_1.SfError('Missing auth info', 'MissingAuthInfoError');
|
|
609
|
+
}
|
|
610
|
+
const authInfo = (0, ts_types_1.isString)(auth) ? await authInfo_1.AuthInfo.create({ username: auth }) : auth;
|
|
611
|
+
this.logger.debug(`removing username ${authInfo.getFields().username}`);
|
|
612
|
+
const orgConfig = await this.retrieveOrgUsersConfig();
|
|
613
|
+
const contents = await orgConfig.read();
|
|
614
|
+
const targetUser = authInfo.getFields().username;
|
|
615
|
+
const usernames = (contents.usernames ?? []);
|
|
616
|
+
contents.usernames = usernames.filter((username) => username !== targetUser);
|
|
617
|
+
await orgConfig.write();
|
|
618
|
+
return this;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* set the sandbox config related to this given org
|
|
622
|
+
*
|
|
623
|
+
* @param orgId {string} orgId of the sandbox
|
|
624
|
+
* @param config {SandboxFields} config of the sandbox
|
|
625
|
+
*/
|
|
626
|
+
async setSandboxConfig(orgId, config) {
|
|
627
|
+
(await stateAggregator_1.StateAggregator.getInstance()).sandboxes.set(orgId, config);
|
|
628
|
+
return this;
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* get the sandbox config for the given orgId
|
|
632
|
+
*
|
|
633
|
+
* @param orgId {string} orgId of the sandbox
|
|
634
|
+
*/
|
|
635
|
+
async getSandboxConfig(orgId) {
|
|
636
|
+
return (await stateAggregator_1.StateAggregator.getInstance()).sandboxes.read(orgId);
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Retrieves the highest api version that is supported by the target server instance. If the apiVersion configured for
|
|
640
|
+
* Sfdx is greater than the one returned in this call an api version mismatch occurs. In the case of the CLI that
|
|
641
|
+
* results in a warning.
|
|
642
|
+
*/
|
|
643
|
+
async retrieveMaxApiVersion() {
|
|
644
|
+
return this.getConnection().retrieveMaxApiVersion();
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Returns the admin username used to create the org.
|
|
648
|
+
*/
|
|
649
|
+
getUsername() {
|
|
650
|
+
return this.getConnection().getUsername();
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Returns the orgId for this org.
|
|
654
|
+
*/
|
|
655
|
+
getOrgId() {
|
|
656
|
+
return this.orgId ?? this.getField(Org.Fields.ORG_ID);
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Returns for the config aggregator.
|
|
660
|
+
*/
|
|
661
|
+
getConfigAggregator() {
|
|
662
|
+
return this.configAggregator;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Returns an org field. Returns undefined if the field is not set or invalid.
|
|
666
|
+
*/
|
|
667
|
+
getField(key) {
|
|
668
|
+
/* eslint-disable @typescript-eslint/ban-ts-comment, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment */
|
|
669
|
+
// @ts-ignore Legacy. We really shouldn't be doing this.
|
|
670
|
+
const ownProp = this[key];
|
|
671
|
+
if (ownProp && typeof ownProp !== 'function')
|
|
672
|
+
return ownProp;
|
|
673
|
+
// @ts-ignore
|
|
674
|
+
return this.getConnection().getAuthInfoFields()[key];
|
|
675
|
+
/* eslint-enable @typescript-eslint/ban-ts-comment, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment */
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Returns a map of requested fields.
|
|
679
|
+
*/
|
|
680
|
+
getFields(keys) {
|
|
681
|
+
const json = {};
|
|
682
|
+
return keys.reduce((map, key) => {
|
|
683
|
+
map[key] = this.getField(key);
|
|
684
|
+
return map;
|
|
685
|
+
}, json);
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Returns the JSForce connection for the org.
|
|
689
|
+
*/
|
|
690
|
+
getConnection() {
|
|
691
|
+
return this.connection;
|
|
692
|
+
}
|
|
693
|
+
async supportsSourceTracking() {
|
|
694
|
+
if (this.isScratch()) {
|
|
695
|
+
return true;
|
|
696
|
+
}
|
|
697
|
+
try {
|
|
698
|
+
await this.getConnection().tooling.sobject('SourceMember').describe();
|
|
699
|
+
return true;
|
|
700
|
+
}
|
|
701
|
+
catch (err) {
|
|
702
|
+
if (err.message.includes('The requested resource does not exist')) {
|
|
703
|
+
return false;
|
|
704
|
+
}
|
|
705
|
+
throw err;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* query SandboxProcess via sandbox name
|
|
710
|
+
*
|
|
711
|
+
* @param name SandboxName to query for
|
|
712
|
+
* @private
|
|
713
|
+
*/
|
|
714
|
+
async querySandboxProcessBySandboxName(name) {
|
|
715
|
+
return this.querySandboxProcess(`SandboxName='${name}'`);
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* query SandboxProcess via SandboxInfoId
|
|
719
|
+
*
|
|
720
|
+
* @param id SandboxInfoId to query for
|
|
721
|
+
* @private
|
|
722
|
+
*/
|
|
723
|
+
async querySandboxProcessBySandboxInfoId(id) {
|
|
724
|
+
return this.querySandboxProcess(`SandboxInfoId='${id}'`);
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* query SandboxProcess via Id
|
|
728
|
+
*
|
|
729
|
+
* @param id SandboxProcessId to query for
|
|
730
|
+
* @private
|
|
731
|
+
*/
|
|
732
|
+
async querySandboxProcessById(id) {
|
|
733
|
+
return this.querySandboxProcess(`Id='${id}'`);
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Initialize async components.
|
|
737
|
+
*/
|
|
738
|
+
async init() {
|
|
739
|
+
const stateAggregator = await stateAggregator_1.StateAggregator.getInstance();
|
|
740
|
+
this.logger = await logger_1.Logger.child('Org');
|
|
741
|
+
this.configAggregator = this.options.aggregator ? this.options.aggregator : await configAggregator_1.ConfigAggregator.create();
|
|
742
|
+
if (!this.options.connection) {
|
|
743
|
+
if (this.options.aliasOrUsername == null) {
|
|
744
|
+
this.configAggregator = this.getConfigAggregator();
|
|
745
|
+
const aliasOrUsername = this.options.isDevHub
|
|
746
|
+
? this.configAggregator.getPropertyValue(orgConfigProperties_1.OrgConfigProperties.TARGET_DEV_HUB)
|
|
747
|
+
: this.configAggregator.getPropertyValue(orgConfigProperties_1.OrgConfigProperties.TARGET_ORG);
|
|
748
|
+
this.options.aliasOrUsername = aliasOrUsername ?? undefined;
|
|
749
|
+
}
|
|
750
|
+
const username = stateAggregator.aliases.resolveUsername(this.options.aliasOrUsername);
|
|
751
|
+
if (!username) {
|
|
752
|
+
throw messages.createError('noUsernameFound');
|
|
753
|
+
}
|
|
754
|
+
this.connection = await connection_1.Connection.create({
|
|
755
|
+
// If no username is provided or resolvable from an alias, AuthInfo will throw an SfError.
|
|
756
|
+
authInfo: await authInfo_1.AuthInfo.create({ username, isDevHub: this.options.isDevHub }),
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
this.connection = this.options.connection;
|
|
761
|
+
}
|
|
762
|
+
this.orgId = this.getField(Org.Fields.ORG_ID);
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* **Throws** *{@link SfError}{ name: 'NotSupportedError' }* Throws an unsupported error.
|
|
766
|
+
*/
|
|
767
|
+
// eslint-disable-next-line class-methods-use-this
|
|
768
|
+
getDefaultOptions() {
|
|
769
|
+
throw new sfError_1.SfError('Not Supported', 'NotSupportedError');
|
|
770
|
+
}
|
|
771
|
+
async getLocalDataDir(orgDataPath) {
|
|
772
|
+
const rootFolder = await config_1.Config.resolveRootFolder(false);
|
|
773
|
+
return (0, path_1.join)(rootFolder, global_1.Global.SFDX_STATE_FOLDER, orgDataPath ? orgDataPath : 'orgs');
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Gets the sandboxProcessObject and then polls for it to complete.
|
|
777
|
+
*
|
|
778
|
+
* @param sandboxProcessName sanbox process name
|
|
779
|
+
* @param options { wait?: Duration; interval?: Duration }
|
|
780
|
+
* @returns {SandboxProcessObject} The SandboxProcessObject for the sandbox
|
|
781
|
+
*/
|
|
782
|
+
async authWithRetriesByName(sandboxProcessName, options) {
|
|
783
|
+
return this.authWithRetries(await this.queryLatestSandboxProcessBySandboxName(sandboxProcessName), options);
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Polls the sandbox org for the sandboxProcessObject.
|
|
787
|
+
*
|
|
788
|
+
* @param sandboxProcessObj: The in-progress sandbox signup request
|
|
789
|
+
* @param options { wait?: Duration; interval?: Duration }
|
|
790
|
+
* @returns {SandboxProcessObject}
|
|
791
|
+
*/
|
|
792
|
+
async authWithRetries(sandboxProcessObj, options = {
|
|
793
|
+
wait: kit_1.Duration.minutes(0),
|
|
794
|
+
interval: kit_1.Duration.seconds(30),
|
|
795
|
+
}) {
|
|
796
|
+
const [wait, pollInterval] = this.validateWaitOptions(options);
|
|
797
|
+
this.logger.debug(`AuthWithRetries sandboxProcessObj ${JSON.stringify(sandboxProcessObj, undefined, 2)}, max wait time of ${wait.minutes} minutes`);
|
|
798
|
+
return this.pollStatusAndAuth({
|
|
799
|
+
sandboxProcessObj,
|
|
800
|
+
wait,
|
|
801
|
+
pollInterval,
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Query the sandbox for the SandboxProcessObject by sandbox name
|
|
806
|
+
*
|
|
807
|
+
* @param sandboxName The name of the sandbox to query
|
|
808
|
+
* @returns {SandboxProcessObject} The SandboxProcessObject for the sandbox
|
|
809
|
+
*/
|
|
810
|
+
async queryLatestSandboxProcessBySandboxName(sandboxNameIn) {
|
|
811
|
+
const { tooling } = this.getConnection();
|
|
812
|
+
this.logger.debug('QueryLatestSandboxProcessBySandboxName called with SandboxName: %s ', sandboxNameIn);
|
|
813
|
+
const queryStr = `SELECT Id, Status, SandboxName, SandboxInfoId, LicenseType, CreatedDate, CopyProgress, SandboxOrganization, SourceId, Description, EndDate FROM SandboxProcess WHERE SandboxName='${sandboxNameIn}' AND Status != 'D' ORDER BY CreatedDate DESC LIMIT 1`;
|
|
814
|
+
const queryResult = await tooling.query(queryStr);
|
|
815
|
+
this.logger.debug('Return from calling queryToolingApi: %s ', queryResult);
|
|
816
|
+
if (queryResult?.records?.length === 1) {
|
|
817
|
+
return queryResult.records[0];
|
|
818
|
+
}
|
|
819
|
+
else if (queryResult.records && queryResult.records.length > 1) {
|
|
820
|
+
throw messages.createError('MultiSandboxProcessNotFoundBySandboxName', [sandboxNameIn]);
|
|
821
|
+
}
|
|
822
|
+
else {
|
|
823
|
+
throw messages.createError('SandboxProcessNotFoundBySandboxName', [sandboxNameIn]);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
// eslint-disable-next-line class-methods-use-this
|
|
827
|
+
async queryProduction(org, field, value) {
|
|
828
|
+
return org.connection.singleRecordQuery(`SELECT SandboxInfoId FROM SandboxProcess WHERE ${field} ='${value}' AND Status NOT IN ('D', 'E')`, { tooling: true });
|
|
829
|
+
}
|
|
830
|
+
async destroySandbox(org, id) {
|
|
831
|
+
return org.getConnection().tooling.delete('SandboxInfo', id);
|
|
832
|
+
}
|
|
833
|
+
async destroyScratchOrg(org, id) {
|
|
834
|
+
return org.getConnection().delete('ActiveScratchOrg', id);
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* this method will delete the sandbox org from the production org and clean up any local files
|
|
838
|
+
*
|
|
839
|
+
* @param prodOrg - Production org associated with this sandbox
|
|
840
|
+
* @private
|
|
841
|
+
*/
|
|
842
|
+
async deleteSandbox(prodOrg) {
|
|
843
|
+
const sandbox = await this.getSandboxConfig(this.getOrgId());
|
|
844
|
+
prodOrg ?? (prodOrg = await Org.create({
|
|
845
|
+
aggregator: this.configAggregator,
|
|
846
|
+
aliasOrUsername: sandbox?.prodOrgUsername,
|
|
847
|
+
}));
|
|
848
|
+
let sandboxInfoId = sandbox?.sandboxInfoId;
|
|
849
|
+
if (!sandboxInfoId) {
|
|
850
|
+
let result;
|
|
851
|
+
try {
|
|
852
|
+
// grab sandboxName from config or try to calculate from the sandbox username
|
|
853
|
+
const sandboxName = sandbox?.sandboxName ?? (this.getUsername() ?? '').split(`${prodOrg.getUsername()}.`)[1];
|
|
854
|
+
if (!sandboxName) {
|
|
855
|
+
this.logger.debug('Sandbox name is not available');
|
|
856
|
+
// jump to query by orgId
|
|
857
|
+
throw new Error();
|
|
858
|
+
}
|
|
859
|
+
this.logger.debug(`attempting to locate sandbox with sandbox ${sandboxName}`);
|
|
860
|
+
try {
|
|
861
|
+
result = await this.queryProduction(prodOrg, 'SandboxName', sandboxName);
|
|
862
|
+
}
|
|
863
|
+
catch (err) {
|
|
864
|
+
this.logger.debug(`Failed to find sandbox with sandbox name: ${sandboxName}`);
|
|
865
|
+
// jump to query by orgId
|
|
866
|
+
throw err;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
catch {
|
|
870
|
+
// if an error is thrown, don't panic yet. we'll try querying by orgId
|
|
871
|
+
const trimmedId = sfdc_1.sfdc.trimTo15(this.getOrgId());
|
|
872
|
+
this.logger.debug(`defaulting to trimming id from ${this.getOrgId()} to ${trimmedId}`);
|
|
873
|
+
try {
|
|
874
|
+
result = await this.queryProduction(prodOrg, 'SandboxOrganization', trimmedId);
|
|
875
|
+
sandboxInfoId = result.SandboxInfoId;
|
|
876
|
+
}
|
|
877
|
+
catch {
|
|
878
|
+
// eating exceptions when trying to find sandbox process record by orgId
|
|
879
|
+
// allows idempotent cleanup of sandbox orgs
|
|
880
|
+
this.logger.debug(`Failed find a SandboxProcess for the sandbox org: ${this.getOrgId()}`);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
if (sandboxInfoId) {
|
|
885
|
+
const deleteResult = await this.destroySandbox(prodOrg, sandboxInfoId);
|
|
886
|
+
this.logger.debug('Return from calling tooling.delete: ', deleteResult);
|
|
887
|
+
}
|
|
888
|
+
// cleanup remaining artifacts
|
|
889
|
+
await this.remove();
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* If this Org is a scratch org, calling this method will delete the scratch org from the DevHub and clean up any local files
|
|
893
|
+
*
|
|
894
|
+
* @param devHub - optional DevHub Org of the to-be-deleted scratch org
|
|
895
|
+
* @private
|
|
896
|
+
*/
|
|
897
|
+
async deleteScratchOrg(devHub) {
|
|
898
|
+
// if we didn't get a devHub, we'll get it from the this org
|
|
899
|
+
devHub ?? (devHub = await this.getDevHubOrg());
|
|
900
|
+
if (!devHub) {
|
|
901
|
+
throw messages.createError('noDevHubFound');
|
|
902
|
+
}
|
|
903
|
+
if (devHub.getOrgId() === this.getOrgId()) {
|
|
904
|
+
// we're attempting to delete a DevHub
|
|
905
|
+
throw messages.createError('deleteOrgHubError');
|
|
906
|
+
}
|
|
907
|
+
try {
|
|
908
|
+
const devHubConn = devHub.getConnection();
|
|
909
|
+
const username = this.getUsername();
|
|
910
|
+
const activeScratchOrgRecordId = (await devHubConn.singleRecordQuery(`SELECT Id FROM ActiveScratchOrg WHERE SignupUsername='${username}'`)).Id;
|
|
911
|
+
this.logger.trace(`found matching ActiveScratchOrg with SignupUsername: ${username}. Deleting...`);
|
|
912
|
+
await this.destroyScratchOrg(devHub, activeScratchOrgRecordId);
|
|
913
|
+
await this.remove();
|
|
914
|
+
}
|
|
915
|
+
catch (err) {
|
|
916
|
+
this.logger.info(err instanceof Error ? err.message : err);
|
|
917
|
+
if (err instanceof Error && (err.name === 'INVALID_TYPE' || err.name === 'INSUFFICIENT_ACCESS_OR_READONLY')) {
|
|
918
|
+
// most likely from devHubConn.delete
|
|
919
|
+
this.logger.info('Insufficient privilege to access ActiveScratchOrgs.');
|
|
920
|
+
throw messages.createError('insufficientAccessToDelete');
|
|
921
|
+
}
|
|
922
|
+
if (err instanceof Error && err.name === connection_1.SingleRecordQueryErrors.NoRecords) {
|
|
923
|
+
// most likely from singleRecordQuery
|
|
924
|
+
this.logger.info('The above error can be the result of deleting an expired or already deleted org.');
|
|
925
|
+
this.logger.info('attempting to cleanup the auth file');
|
|
926
|
+
await this.removeAuth();
|
|
927
|
+
throw messages.createError('scratchOrgNotFound');
|
|
928
|
+
}
|
|
929
|
+
throw err;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* Delete an auth info file from the local file system and any related cache information for
|
|
934
|
+
* this Org. You don't want to call this method directly. Instead consider calling Org.remove()
|
|
935
|
+
*/
|
|
936
|
+
async removeAuth() {
|
|
937
|
+
const stateAggregator = await stateAggregator_1.StateAggregator.getInstance();
|
|
938
|
+
const username = this.getUsername();
|
|
939
|
+
// If there is no username, it has already been removed from the globalInfo.
|
|
940
|
+
// We can skip the unset and just ensure that globalInfo is updated.
|
|
941
|
+
if (username) {
|
|
942
|
+
this.logger.debug(`Removing auth for user: ${username}`);
|
|
943
|
+
this.logger.debug(`Clearing auth cache for user: ${username}`);
|
|
944
|
+
await stateAggregator.orgs.remove(username);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Deletes the users config file
|
|
949
|
+
*/
|
|
950
|
+
async removeUsersConfig() {
|
|
951
|
+
const config = await this.retrieveOrgUsersConfig();
|
|
952
|
+
if (await config.exists()) {
|
|
953
|
+
this.logger.debug(`Removing org users config at: ${config.getPath()}`);
|
|
954
|
+
await config.unlink();
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
async manageDelete(cb, dirPath, throwWhenRemoveFails) {
|
|
958
|
+
return cb().catch((e) => {
|
|
959
|
+
if (throwWhenRemoveFails) {
|
|
960
|
+
throw e;
|
|
961
|
+
}
|
|
962
|
+
else {
|
|
963
|
+
this.logger.warn(`failed to read directory ${dirPath}`);
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Remove the org users auth file.
|
|
970
|
+
*
|
|
971
|
+
* @param throwWhenRemoveFails true if manageDelete should throw or not if the deleted fails.
|
|
972
|
+
*/
|
|
973
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
974
|
+
async removeUsers(throwWhenRemoveFails) {
|
|
975
|
+
const stateAggregator = await stateAggregator_1.StateAggregator.getInstance();
|
|
976
|
+
this.logger.debug(`Removing users associate with org: ${this.getOrgId()}`);
|
|
977
|
+
const config = await this.retrieveOrgUsersConfig();
|
|
978
|
+
this.logger.debug(`using path for org users: ${config.getPath()}`);
|
|
979
|
+
const authInfos = await this.readUserAuthFiles();
|
|
980
|
+
await Promise.all(authInfos
|
|
981
|
+
.map((auth) => auth.getFields().username)
|
|
982
|
+
.map(async (username) => {
|
|
983
|
+
const aliasKeys = (username && stateAggregator.aliases.getAll(username)) ?? [];
|
|
984
|
+
stateAggregator.aliases.unsetAll(username);
|
|
985
|
+
const orgForUser = username === this.getUsername()
|
|
986
|
+
? this
|
|
987
|
+
: await Org.create({
|
|
988
|
+
connection: await connection_1.Connection.create({ authInfo: await authInfo_1.AuthInfo.create({ username }) }),
|
|
989
|
+
});
|
|
990
|
+
const orgType = this.isDevHubOrg() ? orgConfigProperties_1.OrgConfigProperties.TARGET_DEV_HUB : orgConfigProperties_1.OrgConfigProperties.TARGET_ORG;
|
|
991
|
+
const configInfo = orgForUser.configAggregator.getInfo(orgType);
|
|
992
|
+
const needsConfigUpdate = (configInfo.isGlobal() || configInfo.isLocal()) &&
|
|
993
|
+
(configInfo.value === username || aliasKeys.includes(configInfo.value));
|
|
994
|
+
return [
|
|
995
|
+
orgForUser.removeAuth(),
|
|
996
|
+
needsConfigUpdate ? config_1.Config.update(configInfo.isGlobal(), orgType, undefined) : undefined,
|
|
997
|
+
].filter(Boolean);
|
|
998
|
+
}));
|
|
999
|
+
await stateAggregator.aliases.write();
|
|
1000
|
+
}
|
|
1001
|
+
async removeSandboxConfig() {
|
|
1002
|
+
const stateAggregator = await stateAggregator_1.StateAggregator.getInstance();
|
|
1003
|
+
await stateAggregator.sandboxes.remove(this.getOrgId());
|
|
1004
|
+
}
|
|
1005
|
+
async writeSandboxAuthFile(sandboxProcessObj, sandboxRes) {
|
|
1006
|
+
this.logger.debug(`writeSandboxAuthFile sandboxProcessObj: ${JSON.stringify(sandboxProcessObj)}, sandboxRes: ${JSON.stringify(sandboxRes)}`);
|
|
1007
|
+
if (sandboxRes.authUserName) {
|
|
1008
|
+
const productionAuthFields = this.connection.getAuthInfoFields();
|
|
1009
|
+
this.logger.debug('Result from getAuthInfoFields: AuthFields', productionAuthFields);
|
|
1010
|
+
// let's do headless auth via jwt (if we have privateKey) or web auth
|
|
1011
|
+
const oauth2Options = {
|
|
1012
|
+
loginUrl: sandboxRes.loginUrl,
|
|
1013
|
+
instanceUrl: sandboxRes.instanceUrl,
|
|
1014
|
+
username: sandboxRes.authUserName,
|
|
1015
|
+
};
|
|
1016
|
+
// If we don't have a privateKey then we assume it's web auth.
|
|
1017
|
+
if (!productionAuthFields.privateKey) {
|
|
1018
|
+
oauth2Options.redirectUri = `http://localhost:${await webOAuthServer_1.WebOAuthServer.determineOauthPort()}/OauthRedirect`;
|
|
1019
|
+
oauth2Options.authCode = sandboxRes.authCode;
|
|
1020
|
+
}
|
|
1021
|
+
else {
|
|
1022
|
+
oauth2Options.privateKey = productionAuthFields.privateKey;
|
|
1023
|
+
oauth2Options.clientId = productionAuthFields.clientId;
|
|
1024
|
+
}
|
|
1025
|
+
const authInfo = await authInfo_1.AuthInfo.create({
|
|
1026
|
+
username: sandboxRes.authUserName,
|
|
1027
|
+
oauth2Options,
|
|
1028
|
+
parentUsername: productionAuthFields.username,
|
|
1029
|
+
});
|
|
1030
|
+
this.logger.debug('Creating AuthInfo for sandbox', sandboxRes.authUserName, productionAuthFields.username, oauth2Options);
|
|
1031
|
+
// save auth info for new sandbox
|
|
1032
|
+
await authInfo.save();
|
|
1033
|
+
const sandboxOrgId = authInfo.getFields().orgId;
|
|
1034
|
+
if (!sandboxOrgId) {
|
|
1035
|
+
throw messages.createError('AuthInfoOrgIdUndefined');
|
|
1036
|
+
}
|
|
1037
|
+
// set the sandbox config value
|
|
1038
|
+
const sfSandbox = {
|
|
1039
|
+
sandboxUsername: sandboxRes.authUserName,
|
|
1040
|
+
sandboxOrgId,
|
|
1041
|
+
prodOrgUsername: this.getUsername(),
|
|
1042
|
+
sandboxName: sandboxProcessObj.SandboxName,
|
|
1043
|
+
sandboxProcessId: sandboxProcessObj.Id,
|
|
1044
|
+
sandboxInfoId: sandboxProcessObj.SandboxInfoId,
|
|
1045
|
+
timestamp: new Date().toISOString(),
|
|
1046
|
+
};
|
|
1047
|
+
await this.setSandboxConfig(sandboxOrgId, sfSandbox);
|
|
1048
|
+
await (await stateAggregator_1.StateAggregator.getInstance()).sandboxes.write(sandboxOrgId);
|
|
1049
|
+
await lifecycleEvents_1.Lifecycle.getInstance().emit(SandboxEvents.EVENT_RESULT, {
|
|
1050
|
+
sandboxProcessObj,
|
|
1051
|
+
sandboxRes,
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
else {
|
|
1055
|
+
// no authed sandbox user, error
|
|
1056
|
+
throw messages.createError('missingAuthUsername', [sandboxProcessObj.SandboxName]);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
async pollStatusAndAuth(options) {
|
|
1060
|
+
this.logger.debug('PollStatusAndAuth called with SandboxProcessObject', options.sandboxProcessObj, options.wait.minutes, options.pollInterval.seconds);
|
|
1061
|
+
let remainingWait = options.wait;
|
|
1062
|
+
let waitingOnAuth = false;
|
|
1063
|
+
const pollingClient = await pollingClient_1.PollingClient.create({
|
|
1064
|
+
poll: async () => {
|
|
1065
|
+
const sandboxProcessObj = await this.querySandboxProcessBySandboxInfoId(options.sandboxProcessObj.SandboxInfoId);
|
|
1066
|
+
// check to see if sandbox can authenticate via sandboxAuth endpoint
|
|
1067
|
+
const sandboxInfo = await this.sandboxSignupComplete(sandboxProcessObj);
|
|
1068
|
+
if (sandboxInfo) {
|
|
1069
|
+
await lifecycleEvents_1.Lifecycle.getInstance().emit(SandboxEvents.EVENT_AUTH, sandboxInfo);
|
|
1070
|
+
try {
|
|
1071
|
+
this.logger.debug('sandbox signup complete with', sandboxInfo);
|
|
1072
|
+
await this.writeSandboxAuthFile(sandboxProcessObj, sandboxInfo);
|
|
1073
|
+
return { completed: true, payload: sandboxProcessObj };
|
|
1074
|
+
}
|
|
1075
|
+
catch (err) {
|
|
1076
|
+
const error = err;
|
|
1077
|
+
this.logger.debug('Exception while calling writeSandboxAuthFile', err);
|
|
1078
|
+
if (error?.name === 'JwtAuthError' && error?.stack?.includes("user hasn't approved")) {
|
|
1079
|
+
waitingOnAuth = true;
|
|
1080
|
+
}
|
|
1081
|
+
else {
|
|
1082
|
+
throw sfError_1.SfError.wrap(error);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
await lifecycleEvents_1.Lifecycle.getInstance().emit(SandboxEvents.EVENT_STATUS, {
|
|
1087
|
+
sandboxProcessObj,
|
|
1088
|
+
remainingWait: remainingWait.seconds,
|
|
1089
|
+
interval: options.pollInterval.seconds,
|
|
1090
|
+
waitingOnAuth,
|
|
1091
|
+
});
|
|
1092
|
+
remainingWait = kit_1.Duration.seconds(remainingWait.seconds - options.pollInterval.seconds);
|
|
1093
|
+
return { completed: false, payload: sandboxProcessObj };
|
|
1094
|
+
},
|
|
1095
|
+
frequency: options.pollInterval,
|
|
1096
|
+
timeout: options.wait,
|
|
1097
|
+
});
|
|
1098
|
+
return pollingClient.subscribe();
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* query SandboxProcess using supplied where clause
|
|
1102
|
+
*
|
|
1103
|
+
* @param where clause to query for
|
|
1104
|
+
* @private
|
|
1105
|
+
*/
|
|
1106
|
+
async querySandboxProcess(where) {
|
|
1107
|
+
const queryStr = `SELECT Id, Status, SandboxName, SandboxInfoId, LicenseType, CreatedDate, CopyProgress, SandboxOrganization, SourceId, Description, EndDate FROM SandboxProcess WHERE ${where} AND Status != 'D'`;
|
|
1108
|
+
return this.connection.singleRecordQuery(queryStr, {
|
|
1109
|
+
tooling: true,
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* determines if the sandbox has successfully been created
|
|
1114
|
+
*
|
|
1115
|
+
* @param sandboxProcessObj sandbox signup progress
|
|
1116
|
+
* @private
|
|
1117
|
+
*/
|
|
1118
|
+
async sandboxSignupComplete(sandboxProcessObj) {
|
|
1119
|
+
this.logger.debug('sandboxSignupComplete called with SandboxProcessObject', sandboxProcessObj);
|
|
1120
|
+
if (!sandboxProcessObj.EndDate) {
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
try {
|
|
1124
|
+
// call server side /sandboxAuth API to auth the sandbox org user with the connected app
|
|
1125
|
+
const authFields = this.connection.getAuthInfoFields();
|
|
1126
|
+
const callbackUrl = `http://localhost:${await webOAuthServer_1.WebOAuthServer.determineOauthPort()}/OauthRedirect`;
|
|
1127
|
+
const sandboxReq = {
|
|
1128
|
+
// the sandbox signup has been completed on production, we have production clientId by this point
|
|
1129
|
+
clientId: authFields.clientId,
|
|
1130
|
+
sandboxName: sandboxProcessObj.SandboxName,
|
|
1131
|
+
callbackUrl,
|
|
1132
|
+
};
|
|
1133
|
+
this.logger.debug('Calling sandboxAuth with SandboxUserAuthRequest', sandboxReq);
|
|
1134
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
1135
|
+
const url = `${this.connection.tooling._baseUrl()}/sandboxAuth`;
|
|
1136
|
+
const params = {
|
|
1137
|
+
method: 'POST',
|
|
1138
|
+
url,
|
|
1139
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1140
|
+
body: JSON.stringify(sandboxReq),
|
|
1141
|
+
};
|
|
1142
|
+
const result = await this.connection.tooling.request(params);
|
|
1143
|
+
this.logger.debug('Result of calling sandboxAuth', result);
|
|
1144
|
+
return result;
|
|
1145
|
+
}
|
|
1146
|
+
catch (err) {
|
|
1147
|
+
const error = err;
|
|
1148
|
+
// There are cases where the endDate is set before the sandbox has actually completed.
|
|
1149
|
+
// In that case, the sandboxAuth call will throw a specific exception.
|
|
1150
|
+
if (error?.name === 'INVALID_STATUS') {
|
|
1151
|
+
this.logger.debug('Error while authenticating the user', error?.toString());
|
|
1152
|
+
}
|
|
1153
|
+
else {
|
|
1154
|
+
// If it fails for any unexpected reason, just pass that through
|
|
1155
|
+
throw sfError_1.SfError.wrap(error);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
validateWaitOptions(options) {
|
|
1160
|
+
const wait = options.wait ?? kit_1.Duration.minutes(30);
|
|
1161
|
+
const interval = options.interval ?? kit_1.Duration.seconds(30);
|
|
1162
|
+
let pollInterval = options.async ? kit_1.Duration.seconds(0) : interval;
|
|
1163
|
+
// pollInterval cannot be > wait.
|
|
1164
|
+
pollInterval = pollInterval.seconds > wait.seconds ? wait : pollInterval;
|
|
1165
|
+
return [wait, pollInterval];
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
exports.Org = Org;
|
|
1169
|
+
(function (Org) {
|
|
1170
|
+
/**
|
|
1171
|
+
* Scratch Org status.
|
|
1172
|
+
*/
|
|
1173
|
+
let Status;
|
|
1174
|
+
(function (Status) {
|
|
1175
|
+
/**
|
|
1176
|
+
* The scratch org is active.
|
|
1177
|
+
*/
|
|
1178
|
+
Status["ACTIVE"] = "ACTIVE";
|
|
1179
|
+
/**
|
|
1180
|
+
* The scratch org has expired.
|
|
1181
|
+
*/
|
|
1182
|
+
Status["EXPIRED"] = "EXPIRED";
|
|
1183
|
+
/**
|
|
1184
|
+
* The org is a scratch Org but no dev hub is indicated.
|
|
1185
|
+
*/
|
|
1186
|
+
Status["UNKNOWN"] = "UNKNOWN";
|
|
1187
|
+
/**
|
|
1188
|
+
* The dev hub configuration is reporting an active Scratch org but the AuthInfo cannot be found.
|
|
1189
|
+
*/
|
|
1190
|
+
Status["MISSING"] = "MISSING";
|
|
1191
|
+
})(Status = Org.Status || (Org.Status = {}));
|
|
1192
|
+
/**
|
|
1193
|
+
* Org Fields.
|
|
1194
|
+
*/
|
|
1195
|
+
// A subset of fields from AuthInfoFields and properties that are specific to Org,
|
|
1196
|
+
// and properties that are defined on Org itself.
|
|
1197
|
+
let Fields;
|
|
1198
|
+
(function (Fields) {
|
|
1199
|
+
/**
|
|
1200
|
+
* The org alias.
|
|
1201
|
+
*/
|
|
1202
|
+
// From AuthInfo
|
|
1203
|
+
Fields["ALIAS"] = "alias";
|
|
1204
|
+
Fields["CREATED"] = "created";
|
|
1205
|
+
// From Organization
|
|
1206
|
+
Fields["NAME"] = "name";
|
|
1207
|
+
Fields["NAMESPACE_PREFIX"] = "namespacePrefix";
|
|
1208
|
+
Fields["INSTANCE_NAME"] = "instanceName";
|
|
1209
|
+
Fields["TRIAL_EXPIRATION_DATE"] = "trailExpirationDate";
|
|
1210
|
+
/**
|
|
1211
|
+
* The Salesforce instance the org was created on. e.g. `cs42`.
|
|
1212
|
+
*/
|
|
1213
|
+
Fields["CREATED_ORG_INSTANCE"] = "createdOrgInstance";
|
|
1214
|
+
/**
|
|
1215
|
+
* The username of the dev hub org that created this org. Only populated for scratch orgs.
|
|
1216
|
+
*/
|
|
1217
|
+
Fields["DEV_HUB_USERNAME"] = "devHubUsername";
|
|
1218
|
+
/**
|
|
1219
|
+
* The full url of the instance the org lives on.
|
|
1220
|
+
*/
|
|
1221
|
+
Fields["INSTANCE_URL"] = "instanceUrl";
|
|
1222
|
+
/**
|
|
1223
|
+
* Is the current org a dev hub org. e.g. They have access to the `ScratchOrgInfo` object.
|
|
1224
|
+
*/
|
|
1225
|
+
Fields["IS_DEV_HUB"] = "isDevHub";
|
|
1226
|
+
/**
|
|
1227
|
+
* Is the current org a scratch org. e.g. Organization has IsSandbox == true and TrialExpirationDate != null.
|
|
1228
|
+
*/
|
|
1229
|
+
Fields["IS_SCRATCH"] = "isScratch";
|
|
1230
|
+
/**
|
|
1231
|
+
* Is the current org a dev hub org. e.g. Organization has IsSandbox == true and TrialExpirationDate == null.
|
|
1232
|
+
*/
|
|
1233
|
+
Fields["IS_SANDBOX"] = "isSandbox";
|
|
1234
|
+
/**
|
|
1235
|
+
* The login url of the org. e.g. `https://login.salesforce.com` or `https://test.salesforce.com`.
|
|
1236
|
+
*/
|
|
1237
|
+
Fields["LOGIN_URL"] = "loginUrl";
|
|
1238
|
+
/**
|
|
1239
|
+
* The org ID.
|
|
1240
|
+
*/
|
|
1241
|
+
Fields["ORG_ID"] = "orgId";
|
|
1242
|
+
/**
|
|
1243
|
+
* The `OrgStatus` of the org.
|
|
1244
|
+
*/
|
|
1245
|
+
Fields["STATUS"] = "status";
|
|
1246
|
+
/**
|
|
1247
|
+
* The snapshot used to create the scratch org.
|
|
1248
|
+
*/
|
|
1249
|
+
Fields["SNAPSHOT"] = "snapshot";
|
|
1250
|
+
/**
|
|
1251
|
+
* true: the org supports and wants source tracking
|
|
1252
|
+
* false: the org opted out of tracking or can't support it
|
|
1253
|
+
*/
|
|
1254
|
+
Fields["TRACKS_SOURCE"] = "tracksSource";
|
|
1255
|
+
// Should it be on org? Leave it off for now, as it might
|
|
1256
|
+
// be confusing to the consumer what this actually is.
|
|
1257
|
+
// USERNAMES = 'usernames',
|
|
1258
|
+
// Keep separation of concerns. I think these should be on a "user" that belongs to the org.
|
|
1259
|
+
// Org can have a list of user objects that belong to it? Should connection be on user and org.getConnection()
|
|
1260
|
+
// gets the orgs current user for the process? Maybe we just want to keep with the Org only model for
|
|
1261
|
+
// the end of time?
|
|
1262
|
+
// USER_ID = 'userId',
|
|
1263
|
+
// USERNAME = 'username',
|
|
1264
|
+
// PASSWORD = 'password',
|
|
1265
|
+
// USER_PROFILE_NAME = 'userProfileName'
|
|
1266
|
+
})(Fields = Org.Fields || (Org.Fields = {}));
|
|
1267
|
+
})(Org = exports.Org || (exports.Org = {}));
|
|
1268
1268
|
//# sourceMappingURL=org.js.map
|