UNPKG

20 kBJavaScriptView Raw
1"use strict";
2/*
3 * Copyright (c) 2018, salesforce.com, inc.
4 * All rights reserved.
5 * SPDX-License-Identifier: BSD-3-Clause
6 * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
7 */
8Object.defineProperty(exports, "__esModule", { value: true });
9const kit_1 = require("@salesforce/kit");
10const ts_types_1 = require("@salesforce/ts-types");
11const path_1 = require("path");
12const authInfo_1 = require("./authInfo");
13const aliases_1 = require("./config/aliases");
14const config_1 = require("./config/config");
15const configAggregator_1 = require("./config/configAggregator");
16const orgUsersConfig_1 = require("./config/orgUsersConfig");
17const connection_1 = require("./connection");
18const global_1 = require("./global");
19const logger_1 = require("./logger");
20const sfdxError_1 = require("./sfdxError");
21const fs_1 = require("./util/fs");
22const sfdc_1 = require("./util/sfdc");
23/**
24 * Provides a way to manage a locally authenticated Org.
25 *
26 * **See** {@link AuthInfo}
27 *
28 * **See** {@link Connection}
29 *
30 * **See** {@link Aliases}
31 *
32 * **See** {@link Config}
33 *
34 * ```
35 * // Email username
36 * const org1: Org = await Org.create({ aliasOrUsername: 'foo@example.com' });
37 * // The defaultusername config property
38 * const org2: Org = await Org.create({});
39 * // Full Connection
40 * const org3: Org = await Org.create({
41 * connection: await Connection.create({
42 * authInfo: await AuthInfo.create({ username: 'username' })
43 * })
44 * });
45 * ```
46 *
47 * **See** https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_cli_usernames_orgs.htm
48 */
49class Org extends kit_1.AsyncCreatable {
50 /**
51 * @ignore
52 */
53 constructor(options) {
54 super(options);
55 // tslint:disable-next-line:no-unused-variable
56 this.status = Org.Status.UNKNOWN;
57 this.options = options;
58 }
59 /**
60 * Clean all data files in the org's data path. Usually <workspace>/.sfdx/orgs/<username>.
61 * @param orgDataPath A relative path other than "orgs/".
62 * @param throwWhenRemoveFails Should the remove org operations throw an error on failure?
63 */
64 async cleanLocalOrgData(orgDataPath, throwWhenRemoveFails = false) {
65 let dataPath;
66 try {
67 const rootFolder = await config_1.Config.resolveRootFolder(false);
68 dataPath = path_1.join(rootFolder, global_1.Global.STATE_FOLDER, orgDataPath ? orgDataPath : 'orgs');
69 this.logger.debug(`cleaning data for path: ${dataPath}`);
70 }
71 catch (err) {
72 if (err.name === 'InvalidProjectWorkspace') {
73 // If we aren't in a project dir, we can't clean up data files.
74 // If the user unlink this org outside of the workspace they used it in,
75 // data files will be left over.
76 return;
77 }
78 throw err;
79 }
80 return this.manageDelete(async () => await fs_1.fs.remove(dataPath), dataPath, throwWhenRemoveFails);
81 }
82 /**
83 * @ignore
84 */
85 async retrieveOrgUsersConfig() {
86 return await orgUsersConfig_1.OrgUsersConfig.create(orgUsersConfig_1.OrgUsersConfig.getOptions(this.getOrgId()));
87 }
88 /**
89 * Removes the scratch org config file at $HOME/.sfdx/[name].json, any project level org
90 * files, all user auth files for the org, matching default config settings, and any
91 * matching aliases.
92 * @param throwWhenRemoveFails Determines if the call should throw an error or fail silently.
93 */
94 async remove(throwWhenRemoveFails = false) {
95 // If deleting via the access token there shouldn't be any auth config files
96 // so just return;
97 if (this.getConnection().isUsingAccessToken()) {
98 return Promise.resolve();
99 }
100 const auths = await this.readUserAuthFiles();
101 const aliases = await aliases_1.Aliases.create(aliases_1.Aliases.getDefaultOptions());
102 this.logger.info(`Cleaning up usernames in org: ${this.getOrgId()}`);
103 for (const auth of auths) {
104 const username = auth.getFields().username;
105 const aliasKeys = (username && aliases.getKeysByValue(username)) || [];
106 aliases.unsetAll(aliasKeys);
107 let orgForUser;
108 if (username === this.getUsername()) {
109 orgForUser = this;
110 }
111 else {
112 const _info = await authInfo_1.AuthInfo.create({ username });
113 const connection = await connection_1.Connection.create({ authInfo: _info });
114 orgForUser = await Org.create({ connection });
115 }
116 const orgType = this.isDevHubOrg() ? config_1.Config.DEFAULT_DEV_HUB_USERNAME : config_1.Config.DEFAULT_USERNAME;
117 const configInfo = await orgForUser.configAggregator.getInfo(orgType);
118 if ((configInfo.value === username || aliasKeys.includes(configInfo.value)) &&
119 (configInfo.isGlobal() || configInfo.isLocal())) {
120 await config_1.Config.update(configInfo.isGlobal(), orgType, undefined);
121 }
122 const orgUsers = await this.retrieveOrgUsersConfig();
123 await this.manageDelete(async () => await orgUsers.unlink(), orgUsers.getPath(), throwWhenRemoveFails);
124 }
125 await aliases.write();
126 }
127 /**
128 * Check that this org is a scratch org by asking the dev hub if it knows about it.
129 *
130 * **Throws** *{@link SfdxError}{ name: 'NotADevHub' }* Not a Dev Hub.
131 *
132 * **Throws** *{@link SfdxError}{ name: 'NoResults' }* No results.
133 *
134 * @param devHubUsername The username of the dev hub org.
135 */
136 async checkScratchOrg(devHubUsername) {
137 let targetDevHub = devHubUsername;
138 if (!targetDevHub) {
139 targetDevHub = ts_types_1.asString(this.configAggregator.getPropertyValue(config_1.Config.DEFAULT_DEV_HUB_USERNAME));
140 }
141 const devHubConnection = await connection_1.Connection.create({
142 authInfo: await authInfo_1.AuthInfo.create({ username: targetDevHub })
143 });
144 const thisOrgAuthConfig = this.getConnection().getAuthInfoFields();
145 const trimmedId = sfdc_1.sfdc.trimTo15(thisOrgAuthConfig.orgId);
146 const DEV_HUB_SOQL = `SELECT CreatedDate,Edition,ExpirationDate FROM ActiveScratchOrg WHERE ScratchOrg=\'${trimmedId}\'`;
147 let results;
148 try {
149 results = await devHubConnection.query(DEV_HUB_SOQL);
150 }
151 catch (err) {
152 if (err.name === 'INVALID_TYPE') {
153 throw sfdxError_1.SfdxError.create('@salesforce/core', 'org', 'NotADevHub', [devHubConnection.getUsername()]);
154 }
155 throw err;
156 }
157 if (ts_types_1.getNumber(results, 'records.length') !== 1) {
158 throw new sfdxError_1.SfdxError('No results', 'NoResults');
159 }
160 return thisOrgAuthConfig;
161 }
162 /**
163 * Returns the Org object or null if this org is not affiliated with a Dev Hub (according to the local config).
164 */
165 async getDevHubOrg() {
166 if (this.isDevHubOrg()) {
167 return this;
168 }
169 else if (this.getField(Org.Fields.DEV_HUB_USERNAME)) {
170 const devHubUsername = ts_types_1.ensureString(this.getField(Org.Fields.DEV_HUB_USERNAME));
171 return Org.create({
172 connection: await connection_1.Connection.create({
173 authInfo: await authInfo_1.AuthInfo.create({ username: devHubUsername })
174 })
175 });
176 }
177 }
178 /**
179 * Returns `true` if the org is a Dev Hub.
180 *
181 * **Note** This relies on a cached value in the auth file. If that property
182 * is not cached, this method will **always return false even if the org is a
183 * dev hub**. If you need accuracy, use the {@link Org.determineIfDevHubOrg} method.
184 */
185 isDevHubOrg() {
186 const isDevHub = this.getField(Org.Fields.IS_DEV_HUB);
187 if (ts_types_1.isBoolean(isDevHub)) {
188 return isDevHub;
189 }
190 else {
191 return false;
192 }
193 }
194 /**
195 * Returns `true` if the org is a Dev Hub.
196 *
197 * Use a cached value. If the cached value is not set, then check access to the
198 * ScratchOrgInfo object to determine if the org is a dev hub.
199 *
200 * @param forceServerCheck Ignore the cached value and go straight to the server
201 * which will be required if the org flips on the dev hub after the value is already
202 * cached locally.
203 */
204 async determineIfDevHubOrg(forceServerCheck = false) {
205 const cachedIsDevHub = this.getField(Org.Fields.IS_DEV_HUB);
206 if (!forceServerCheck && ts_types_1.isBoolean(cachedIsDevHub)) {
207 return cachedIsDevHub;
208 }
209 if (this.isDevHubOrg()) {
210 return true;
211 }
212 this.logger.debug('isDevHub is not cached - querying server...');
213 const conn = this.getConnection();
214 let isDevHub = false;
215 try {
216 await conn.query('SELECT Id FROM ScratchOrgInfo');
217 isDevHub = true;
218 }
219 catch (err) {
220 /* Not a dev hub */
221 }
222 const username = ts_types_1.ensure(this.getUsername());
223 const auth = await authInfo_1.AuthInfo.create({ username });
224 await auth.save({ isDevHub });
225 authInfo_1.AuthInfo.clearCache(username);
226 // Reset the connection with the updated auth file
227 this.connection = await connection_1.Connection.create({
228 authInfo: await authInfo_1.AuthInfo.create({ username })
229 });
230 return isDevHub;
231 }
232 /**
233 * Refreshes the auth for this org's instance by calling HTTP GET on the baseUrl of the connection object.
234 */
235 async refreshAuth() {
236 this.logger.debug('Refreshing auth for org.');
237 const requestInfo = {
238 url: this.getConnection().baseUrl(),
239 method: 'GET'
240 };
241 const conn = this.getConnection();
242 await conn.request(requestInfo);
243 }
244 /**
245 * Reads and returns the content of all user auth files for this org as an array.
246 */
247 async readUserAuthFiles() {
248 const config = await this.retrieveOrgUsersConfig();
249 const contents = await config.read();
250 const thisUsername = ts_types_1.ensure(this.getUsername());
251 const usernames = ts_types_1.ensureJsonArray(contents.usernames || [thisUsername]);
252 return Promise.all(usernames.map(username => {
253 if (username === thisUsername) {
254 return authInfo_1.AuthInfo.create({
255 username: this.getConnection().getUsername()
256 });
257 }
258 else {
259 return authInfo_1.AuthInfo.create({ username: ts_types_1.ensureString(username) });
260 }
261 }));
262 }
263 /**
264 * Adds a username to the user config for this org. For convenience `this` object is returned.
265 *
266 * ```
267 * const org: Org = await Org.create({
268 * connection: await Connection.create({
269 * authInfo: await AuthInfo.create('foo@example.com')
270 * })
271 * });
272 * const userAuth: AuthInfo = await AuthInfo.create({
273 * username: 'bar@example.com'
274 * });
275 * await org.addUsername(userAuth);
276 * ```
277 *
278 * @param {AuthInfo | string} auth The AuthInfo for the username to add.
279 */
280 async addUsername(auth) {
281 if (!auth) {
282 throw new sfdxError_1.SfdxError('Missing auth info', 'MissingAuthInfo');
283 }
284 const _auth = ts_types_1.isString(auth) ? await authInfo_1.AuthInfo.create({ username: auth }) : auth;
285 this.logger.debug(`adding username ${_auth.getFields().username}`);
286 const orgConfig = await this.retrieveOrgUsersConfig();
287 const contents = await orgConfig.read();
288 // TODO: This is kind of screwy because contents values can be `AnyJson | object`...
289 // needs config refactoring to improve
290 const usernames = contents.usernames || [];
291 if (!ts_types_1.isArray(usernames)) {
292 throw new sfdxError_1.SfdxError('Usernames is not an array', 'UnexpectedDataFormat');
293 }
294 let shouldUpdate = false;
295 const thisUsername = ts_types_1.ensure(this.getUsername());
296 if (!usernames.includes(thisUsername)) {
297 usernames.push(thisUsername);
298 shouldUpdate = true;
299 }
300 const username = _auth.getFields().username;
301 if (username) {
302 usernames.push(username);
303 shouldUpdate = true;
304 }
305 if (shouldUpdate) {
306 orgConfig.set('usernames', usernames);
307 await orgConfig.write();
308 }
309 return this;
310 }
311 /**
312 * Removes a username from the user config for this object. For convenience `this` object is returned.
313 *
314 * **Throws** *{@link SfdxError}{ name: 'MissingAuthInfo' }* Auth info is missing.
315 *
316 * @param {AuthInfo | string} auth The AuthInfo containing the username to remove.
317 */
318 async removeUsername(auth) {
319 if (!auth) {
320 throw new sfdxError_1.SfdxError('Missing auth info', 'MissingAuthInfo');
321 }
322 const _auth = ts_types_1.isString(auth) ? await authInfo_1.AuthInfo.create({ username: auth }) : auth;
323 this.logger.debug(`removing username ${_auth.getFields().username}`);
324 const orgConfig = await this.retrieveOrgUsersConfig();
325 const contents = await orgConfig.read();
326 const targetUser = _auth.getFields().username;
327 const usernames = (contents.usernames || []);
328 contents.usernames = usernames.filter(username => username !== targetUser);
329 await orgConfig.write();
330 return this;
331 }
332 /**
333 * Retrieves the highest api version that is supported by the target server instance. If the apiVersion configured for
334 * Sfdx is greater than the one returned in this call an api version mismatch occurs. In the case of the CLI that
335 * results in a warning.
336 */
337 async retrieveMaxApiVersion() {
338 return await this.getConnection().retrieveMaxApiVersion();
339 }
340 /**
341 * Returns the admin username used to create the org.
342 */
343 getUsername() {
344 return this.getConnection().getUsername();
345 }
346 /**
347 * Returns the orgId for this org.
348 */
349 getOrgId() {
350 return this.getField(Org.Fields.ORG_ID);
351 }
352 /**
353 * Returns for the config aggregator.
354 */
355 getConfigAggregator() {
356 return this.configAggregator;
357 }
358 /**
359 * Returns an org field. Returns undefined if the field is not set or invalid.
360 */
361 getField(key) {
362 // @ts-ignore TODO: Need to refactor storage of these values on both Org and AuthFields
363 return this[key] || this.getConnection().getAuthInfoFields()[key];
364 }
365 /**
366 * Returns a map of requested fields.
367 */
368 getFields(keys) {
369 const json = {};
370 return keys.reduce((map, key) => {
371 map[key] = this.getField(key);
372 return map;
373 }, json);
374 }
375 /**
376 * Returns the JSForce connection for the org.
377 */
378 getConnection() {
379 return this.connection;
380 }
381 /**
382 * Initialize async components.
383 */
384 async init() {
385 this.logger = await logger_1.Logger.child('Org');
386 this.configAggregator = this.options.aggregator ? this.options.aggregator : await configAggregator_1.ConfigAggregator.create();
387 if (!this.options.connection) {
388 if (this.options.aliasOrUsername == null) {
389 this.configAggregator = this.getConfigAggregator();
390 const aliasOrUsername = this.options.isDevHub
391 ? ts_types_1.getString(this.configAggregator.getInfo(config_1.Config.DEFAULT_DEV_HUB_USERNAME), 'value')
392 : ts_types_1.getString(this.configAggregator.getInfo(config_1.Config.DEFAULT_USERNAME), 'value');
393 this.options.aliasOrUsername = aliasOrUsername || undefined;
394 }
395 const username = this.options.aliasOrUsername;
396 this.connection = await connection_1.Connection.create({
397 // If no username is provided or resolvable from an alias, AuthInfo will throw an SfdxError.
398 authInfo: await authInfo_1.AuthInfo.create({
399 username: (username != null && (await aliases_1.Aliases.fetch(username))) || username
400 })
401 });
402 }
403 else {
404 this.connection = this.options.connection;
405 }
406 }
407 /**
408 * **Throws** *{@link SfdxError} Throws and unsupported error.
409 */
410 getDefaultOptions() {
411 throw new sfdxError_1.SfdxError('Not Supported');
412 }
413 manageDelete(cb, dirPath, throwWhenRemoveFails) {
414 return cb().catch(e => {
415 if (throwWhenRemoveFails) {
416 throw e;
417 }
418 else {
419 this.logger.warn(`failed to read directory ${dirPath}`);
420 return;
421 }
422 });
423 }
424}
425exports.Org = Org;
426(function (Org) {
427 /**
428 * Scratch Org status.
429 */
430 let Status;
431 (function (Status) {
432 /**
433 * The scratch org is active.
434 */
435 Status["ACTIVE"] = "ACTIVE";
436 /**
437 * The scratch org has expired.
438 */
439 Status["EXPIRED"] = "EXPIRED";
440 /**
441 * The org is a scratch Org but no dev hub is indicated.
442 */
443 Status["UNKNOWN"] = "UNKNOWN";
444 /**
445 * The dev hub configuration is reporting an active Scratch org but the AuthInfo cannot be found.
446 */
447 Status["MISSING"] = "MISSING";
448 })(Status = Org.Status || (Org.Status = {}));
449 /**
450 * Org Fields.
451 */
452 // A subset of fields from AuthInfoFields and properties that are specific to Org,
453 // and properties that are defined on Org itself.
454 let Fields;
455 (function (Fields) {
456 /**
457 * The org alias.
458 */
459 // From AuthInfo
460 Fields["ALIAS"] = "alias";
461 Fields["CREATED"] = "created";
462 /**
463 * The Salesforce instance the org was created on. e.g. `cs42`.
464 */
465 Fields["CREATED_ORG_INSTANCE"] = "createdOrgInstance";
466 /**
467 * The username of the dev hub org that created this org. Only populated for scratch orgs.
468 */
469 Fields["DEV_HUB_USERNAME"] = "devHubUsername";
470 /**
471 * The full url of the instance the org lives on.
472 */
473 Fields["INSTANCE_URL"] = "instanceUrl";
474 /**
475 * Is the current org a dev hub org. e.g. They have access to the `ScratchOrgInfo` object.
476 */
477 Fields["IS_DEV_HUB"] = "isDevHub";
478 /**
479 * The login url of the org. e.g. `https://login.salesforce.com` or `https://test.salesforce.com`.
480 */
481 Fields["LOGIN_URL"] = "loginUrl";
482 /**
483 * The org ID.
484 */
485 Fields["ORG_ID"] = "orgId";
486 /**
487 * The `OrgStatus` of the org.
488 */
489 Fields["STATUS"] = "status";
490 /**
491 * The snapshot used to create the scratch org.
492 */
493 Fields["SNAPSHOT"] = "snapshot";
494 // Should it be on org? Leave it off for now, as it might
495 // be confusing to the consumer what this actually is.
496 // USERNAMES = 'usernames',
497 // Keep separation of concerns. I think these should be on a "user" that belongs to the org.
498 // Org can have a list of user objects that belong to it? Should connection be on user and org.getConnection()
499 // gets the orgs current user for the process? Maybe we just want to keep with the Org only model for
500 // the end of time?
501 // USER_ID = 'userId',
502 // USERNAME = 'username',
503 // PASSWORD = 'password',
504 // USER_PROFILE_NAME = 'userProfileName'
505 })(Fields = Org.Fields || (Org.Fields = {}));
506})(Org = exports.Org || (exports.Org = {}));
507//# sourceMappingURL=org.js.map
\No newline at end of file