@salesforce/core 4.0.0 → 4.1.0

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.
Files changed (151) hide show
  1. package/LICENSE.txt +1 -1
  2. package/README.md +93 -44
  3. package/lib/config/authInfoConfig.d.ts +19 -0
  4. package/lib/config/authInfoConfig.js +35 -0
  5. package/lib/config/config.d.ts +87 -22
  6. package/lib/config/config.js +117 -65
  7. package/lib/config/configAggregator.d.ts +41 -35
  8. package/lib/config/configAggregator.js +102 -73
  9. package/lib/config/configFile.d.ts +2 -2
  10. package/lib/config/configFile.js +38 -29
  11. package/lib/config/configStore.d.ts +9 -9
  12. package/lib/config/configStore.js +17 -15
  13. package/lib/config/envVars.d.ts +15 -9
  14. package/lib/config/envVars.js +71 -47
  15. package/lib/config/orgUsersConfig.js +2 -0
  16. package/lib/config/sandboxOrgConfig.js +2 -0
  17. package/lib/config/sandboxProcessCache.d.ts +16 -0
  18. package/lib/config/sandboxProcessCache.js +38 -0
  19. package/lib/config/tokensConfig.d.ts +10 -0
  20. package/lib/config/tokensConfig.js +29 -0
  21. package/lib/config/ttlConfig.d.ts +34 -0
  22. package/lib/config/ttlConfig.js +50 -0
  23. package/lib/crypto/crypto.js +15 -22
  24. package/lib/crypto/keyChain.js +2 -3
  25. package/lib/crypto/keyChainImpl.d.ts +5 -3
  26. package/lib/crypto/keyChainImpl.js +58 -61
  27. package/lib/crypto/secureBuffer.d.ts +1 -1
  28. package/lib/deviceOauthService.d.ts +3 -3
  29. package/lib/deviceOauthService.js +27 -25
  30. package/lib/exported.d.ts +15 -12
  31. package/lib/exported.js +28 -16
  32. package/lib/global.d.ts +11 -3
  33. package/lib/global.js +39 -12
  34. package/lib/lifecycleEvents.d.ts +1 -1
  35. package/lib/lifecycleEvents.js +3 -0
  36. package/lib/logger.d.ts +19 -9
  37. package/lib/logger.js +112 -86
  38. package/lib/messages.d.ts +53 -36
  39. package/lib/messages.js +81 -91
  40. package/lib/org/authInfo.d.ts +56 -20
  41. package/lib/org/authInfo.js +232 -131
  42. package/lib/org/authRemover.d.ts +8 -7
  43. package/lib/org/authRemover.js +32 -28
  44. package/lib/org/connection.d.ts +13 -37
  45. package/lib/org/connection.js +78 -124
  46. package/lib/org/index.js +5 -1
  47. package/lib/org/org.d.ts +151 -48
  48. package/lib/org/org.js +468 -225
  49. package/lib/org/orgConfigProperties.d.ts +64 -3
  50. package/lib/org/orgConfigProperties.js +96 -4
  51. package/lib/org/permissionSetAssignment.js +4 -13
  52. package/lib/org/scratchOrgCache.d.ts +20 -0
  53. package/lib/org/scratchOrgCache.js +33 -0
  54. package/lib/org/scratchOrgCreate.d.ts +28 -17
  55. package/lib/org/scratchOrgCreate.js +125 -53
  56. package/lib/org/scratchOrgErrorCodes.d.ts +9 -3
  57. package/lib/org/scratchOrgErrorCodes.js +34 -17
  58. package/lib/org/scratchOrgFeatureDeprecation.js +1 -6
  59. package/lib/org/scratchOrgInfoApi.d.ts +21 -47
  60. package/lib/org/scratchOrgInfoApi.js +129 -63
  61. package/lib/org/scratchOrgInfoGenerator.d.ts +6 -5
  62. package/lib/org/scratchOrgInfoGenerator.js +76 -62
  63. package/lib/org/scratchOrgLifecycleEvents.d.ts +10 -0
  64. package/lib/org/scratchOrgLifecycleEvents.js +41 -0
  65. package/lib/org/scratchOrgSettingsGenerator.d.ts +44 -21
  66. package/lib/org/scratchOrgSettingsGenerator.js +165 -98
  67. package/lib/org/scratchOrgTypes.d.ts +43 -0
  68. package/lib/org/scratchOrgTypes.js +9 -0
  69. package/lib/org/user.d.ts +1 -1
  70. package/lib/org/user.js +25 -34
  71. package/lib/schema/printer.d.ts +6 -0
  72. package/lib/schema/printer.js +34 -31
  73. package/lib/schema/validator.d.ts +12 -10
  74. package/lib/schema/validator.js +56 -76
  75. package/lib/{sfdxError.d.ts → sfError.d.ts} +12 -20
  76. package/lib/{sfdxError.js → sfError.js} +40 -30
  77. package/lib/{sfdxProject.d.ts → sfProject.d.ts} +75 -35
  78. package/lib/sfProject.js +651 -0
  79. package/lib/stateAggregator/accessors/aliasAccessor.d.ts +129 -0
  80. package/lib/stateAggregator/accessors/aliasAccessor.js +263 -0
  81. package/lib/stateAggregator/accessors/orgAccessor.d.ts +101 -0
  82. package/lib/stateAggregator/accessors/orgAccessor.js +240 -0
  83. package/lib/stateAggregator/accessors/sandboxAccessor.d.ts +8 -0
  84. package/lib/stateAggregator/accessors/sandboxAccessor.js +28 -0
  85. package/lib/stateAggregator/accessors/tokenAccessor.d.ts +63 -0
  86. package/lib/stateAggregator/accessors/tokenAccessor.js +80 -0
  87. package/lib/stateAggregator/index.d.ts +4 -0
  88. package/lib/stateAggregator/index.js +27 -0
  89. package/lib/stateAggregator/stateAggregator.d.ts +25 -0
  90. package/lib/stateAggregator/stateAggregator.js +46 -0
  91. package/lib/status/myDomainResolver.d.ts +1 -1
  92. package/lib/status/myDomainResolver.js +4 -4
  93. package/lib/status/pollingClient.js +4 -4
  94. package/lib/status/streamingClient.d.ts +2 -2
  95. package/lib/status/streamingClient.js +58 -63
  96. package/lib/status/types.d.ts +2 -2
  97. package/lib/testSetup.d.ts +204 -75
  98. package/lib/testSetup.js +468 -164
  99. package/lib/util/cache.d.ts +2 -2
  100. package/lib/util/cache.js +6 -6
  101. package/lib/util/checkLightningDomain.js +3 -4
  102. package/lib/util/directoryWriter.d.ts +12 -0
  103. package/lib/util/directoryWriter.js +54 -0
  104. package/lib/util/getJwtAudienceUrl.js +1 -1
  105. package/lib/util/internal.d.ts +28 -2
  106. package/lib/util/internal.js +65 -8
  107. package/lib/util/jsonXmlTools.js +2 -4
  108. package/lib/util/mapKeys.d.ts +9 -9
  109. package/lib/util/mapKeys.js +13 -9
  110. package/lib/util/sfdc.d.ts +51 -51
  111. package/lib/util/sfdc.js +74 -79
  112. package/lib/util/sfdcUrl.d.ts +5 -19
  113. package/lib/util/sfdcUrl.js +40 -49
  114. package/lib/util/structuredWriter.d.ts +9 -0
  115. package/lib/util/structuredWriter.js +3 -0
  116. package/lib/util/zipWriter.d.ts +8 -6
  117. package/lib/util/zipWriter.js +13 -13
  118. package/lib/webOAuthServer.d.ts +20 -6
  119. package/lib/webOAuthServer.js +102 -56
  120. package/messageTransformer/messageTransformer.ts +93 -0
  121. package/messages/auth.md +9 -1
  122. package/messages/config.md +42 -6
  123. package/messages/connection.md +8 -0
  124. package/messages/core.md +10 -0
  125. package/messages/envVars.md +37 -3
  126. package/messages/org.md +21 -1
  127. package/messages/scratchOrgCreate.md +2 -6
  128. package/messages/scratchOrgErrorCodes.md +17 -1
  129. package/messages/scratchOrgInfoApi.md +9 -0
  130. package/messages/scratchOrgInfoGenerator.md +9 -1
  131. package/package.json +123 -46
  132. package/CHANGELOG.md +0 -1244
  133. package/lib/config/keychainConfig.d.ts +0 -19
  134. package/lib/config/keychainConfig.js +0 -43
  135. package/lib/globalInfo/accessors/aliasAccessor.d.ts +0 -83
  136. package/lib/globalInfo/accessors/aliasAccessor.js +0 -130
  137. package/lib/globalInfo/accessors/orgAccessor.d.ts +0 -13
  138. package/lib/globalInfo/accessors/orgAccessor.js +0 -45
  139. package/lib/globalInfo/accessors/tokenAccessor.d.ts +0 -13
  140. package/lib/globalInfo/accessors/tokenAccessor.js +0 -35
  141. package/lib/globalInfo/globalInfoConfig.d.ts +0 -36
  142. package/lib/globalInfo/globalInfoConfig.js +0 -105
  143. package/lib/globalInfo/index.d.ts +0 -6
  144. package/lib/globalInfo/index.js +0 -29
  145. package/lib/globalInfo/sfdxDataHandler.d.ts +0 -43
  146. package/lib/globalInfo/sfdxDataHandler.js +0 -217
  147. package/lib/globalInfo/types.d.ts +0 -39
  148. package/lib/globalInfo/types.js +0 -10
  149. package/lib/sfdxProject.js +0 -557
  150. package/lib/util/fs.d.ts +0 -201
  151. package/lib/util/fs.js +0 -378
@@ -1,15 +1,17 @@
1
1
  "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AuthInfo = exports.DEFAULT_CONNECTED_APP_INFO = void 0;
4
2
  /*
5
3
  * Copyright (c) 2020, salesforce.com, inc.
6
4
  * All rights reserved.
7
5
  * Licensed under the BSD 3-Clause license.
8
6
  * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
9
7
  */
8
+ /* eslint-disable class-methods-use-this */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.AuthInfo = exports.DEFAULT_CONNECTED_APP_INFO = void 0;
10
11
  const crypto_1 = require("crypto");
11
12
  const path_1 = require("path");
12
13
  const os = require("os");
14
+ const fs = require("fs");
13
15
  const kit_1 = require("@salesforce/kit");
14
16
  const ts_types_1 = require("@salesforce/ts-types");
15
17
  const jsforce_1 = require("jsforce");
@@ -18,40 +20,15 @@ const jwt = require("jsonwebtoken");
18
20
  const config_1 = require("../config/config");
19
21
  const configAggregator_1 = require("../config/configAggregator");
20
22
  const logger_1 = require("../logger");
21
- const sfdxError_1 = require("../sfdxError");
22
- const fs_1 = require("../util/fs");
23
+ const sfError_1 = require("../sfError");
23
24
  const sfdc_1 = require("../util/sfdc");
24
- const globalInfo_1 = require("../globalInfo");
25
+ const stateAggregator_1 = require("../stateAggregator");
25
26
  const messages_1 = require("../messages");
26
27
  const sfdcUrl_1 = require("../util/sfdcUrl");
27
28
  const connection_1 = require("./connection");
28
29
  const orgConfigProperties_1 = require("./orgConfigProperties");
29
- messages_1.Messages.importMessagesDirectory(__dirname);
30
- const messages = messages_1.Messages.load('@salesforce/core', 'core', [
31
- 'authInfoCreationError',
32
- 'authInfoOverwriteError',
33
- 'namedOrgNotFound',
34
- 'orgDataNotAvailableError',
35
- 'orgDataNotAvailableError.actions',
36
- 'refreshTokenAuthError',
37
- 'jwtAuthError',
38
- 'authCodeUsernameRetrievalError',
39
- 'authCodeExchangeError',
40
- ]);
41
- // Extend OAuth2 to add JWT Bearer Token Flow support.
42
- class JwtOAuth2 extends jsforce_1.OAuth2 {
43
- constructor(options) {
44
- super(options);
45
- }
46
- jwtAuthorize(innerToken) {
47
- // @ts-ignore
48
- return super._postParams({
49
- // eslint-disable-next-line camelcase
50
- grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
51
- assertion: innerToken,
52
- });
53
- }
54
- }
30
+ const org_1 = require("./org");
31
+ const messages = new messages_1.Messages('@salesforce/core', 'core', new Map([["authInfoCreationError", "Must pass a username and/or OAuth options when creating an AuthInfo instance."], ["authInfoOverwriteError", "Cannot create an AuthInfo instance that will overwrite existing auth data."], ["authInfoOverwriteError.actions", ["Create the AuthInfo instance using existing auth data by just passing the username. E.g., `AuthInfo.create({ username: 'my@user.org' });`."]], ["authCodeExchangeError", "Error authenticating with auth code due to: %s"], ["authCodeUsernameRetrievalError", "Could not retrieve the username after successful auth code exchange.\n\nDue to: %s"], ["jwtAuthError", "Error authenticating with JWT config due to: %s"], ["jwtAuthErrors", "Error authenticating with JWT.\nErrors encountered:\n%s"], ["refreshTokenAuthError", "Error authenticating with the refresh token due to: %s"], ["orgDataNotAvailableError", "An attempt to refresh the authentication token failed with a 'Data Not Found Error'. The org identified by username %s does not appear to exist. Likely cause is that the org was deleted by another user or has expired."], ["orgDataNotAvailableError.actions", ["Run `sfdx force:org:list --clean` to remove stale org authentications.", "Use `sfdx force:config:set` to update the defaultusername.", "Use `sfdx force:org:create` to create a new org.", "Use `sfdx auth` to authenticate an existing org."]], ["namedOrgNotFound", "No authorization information found for %s."], ["noAliasesFound", "Nothing to set."], ["invalidFormat", "Setting aliases must be in the format <key>=<value> but found: [%s]."], ["invalidJsonCasing", "All JSON input must have heads down camelcase keys. E.g., `{ sfdcLoginUrl: \"https://login.salesforce.com\" }`\nFound \"%s\" at %s"], ["missingClientId", "Client ID is required for JWT authentication."]]));
55
32
  // parses the id field returned from jsForce oauth2 methods to get
56
33
  // user ID and org ID.
57
34
  function parseIdUrl(idUrl) {
@@ -66,15 +43,7 @@ function parseIdUrl(idUrl) {
66
43
  }
67
44
  exports.DEFAULT_CONNECTED_APP_INFO = {
68
45
  clientId: 'PlatformCLI',
69
- // Legacy. The connected app info is owned by the thing that
70
- // creates new AuthInfos. Currently that is the auth:* commands which
71
- // aren't owned by this core library. These values need to be here
72
- // for any old auth files where the id and secret aren't stored.
73
- //
74
- // Ideally, this would be removed at some point in the distant future
75
- // when all auth files now have the clientId stored in it.
76
- legacyClientId: 'SalesforceDevelopmentExperience',
77
- legacyClientSecret: '1384510088588713504',
46
+ clientSecret: '',
78
47
  };
79
48
  /**
80
49
  * Handles persistence and fetching of user authentication information using
@@ -121,7 +90,7 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
121
90
  super(options);
122
91
  // Possibly overridden in create
123
92
  this.usingAccessToken = false;
124
- this.options = options || {};
93
+ this.options = options ?? {};
125
94
  }
126
95
  /**
127
96
  * Returns the default instance url
@@ -129,8 +98,8 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
129
98
  * @returns {string}
130
99
  */
131
100
  static getDefaultInstanceUrl() {
132
- const configuredInstanceUrl = configAggregator_1.ConfigAggregator.getValue('instanceUrl').value;
133
- return configuredInstanceUrl || sfdcUrl_1.SfdcUrl.PRODUCTION;
101
+ const configuredInstanceUrl = configAggregator_1.ConfigAggregator.getValue(orgConfigProperties_1.OrgConfigProperties.ORG_INSTANCE_URL)?.value;
102
+ return configuredInstanceUrl ?? sfdcUrl_1.SfdcUrl.PRODUCTION;
134
103
  }
135
104
  /**
136
105
  * Get a list of all authorizations based on auth files stored in the global directory.
@@ -142,20 +111,21 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
142
111
  * @returns {Promise<OrgAuthorization[]>}
143
112
  */
144
113
  static async listAllAuthorizations(orgAuthFilter = (orgAuth) => !!orgAuth) {
145
- var _a;
146
- const globalInfo = await globalInfo_1.GlobalInfo.getInstance();
114
+ const stateAggregator = await stateAggregator_1.StateAggregator.getInstance();
147
115
  const config = (await configAggregator_1.ConfigAggregator.create()).getConfigInfo();
148
- const orgs = Object.values(globalInfo.orgs.getAll());
116
+ const orgs = await stateAggregator.orgs.readAll();
149
117
  const final = [];
150
118
  for (const org of orgs) {
151
119
  const username = (0, ts_types_1.ensureString)(org.username);
152
- const aliases = (_a = globalInfo.aliases.getAll(username)) !== null && _a !== void 0 ? _a : undefined;
120
+ const aliases = stateAggregator.aliases.getAll(username) ?? undefined;
153
121
  // Get a list of configuration values that are set to either the username or one
154
122
  // of the aliases
155
123
  const configs = config
156
124
  .filter((c) => aliases.includes(c.value) || c.value === username)
157
125
  .map((c) => c.key);
158
126
  try {
127
+ // prevent ConfigFile collision bug
128
+ // eslint-disable-next-line no-await-in-loop
159
129
  const authInfo = await AuthInfo.create({ username });
160
130
  const { orgId, instanceUrl, devHubUsername, expirationDate, isDevHub } = authInfo.getFields();
161
131
  final.push({
@@ -164,7 +134,9 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
164
134
  username,
165
135
  instanceUrl,
166
136
  isScratchOrg: Boolean(devHubUsername),
167
- isDevHub: isDevHub || false,
137
+ isDevHub: isDevHub ?? false,
138
+ // eslint-disable-next-line no-await-in-loop
139
+ isSandbox: await stateAggregator.sandboxes.hasFile(orgId),
168
140
  orgId: orgId,
169
141
  accessToken: authInfo.getConnectionOptions().accessToken,
170
142
  oauthMethod: authInfo.isJwt() ? 'jwt' : authInfo.isOauth() ? 'web' : 'token',
@@ -194,7 +166,7 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
194
166
  */
195
167
  static async hasAuthentications() {
196
168
  try {
197
- const auths = (await globalInfo_1.GlobalInfo.getInstance()).orgs.getAll();
169
+ const auths = await (await stateAggregator_1.StateAggregator.getInstance()).orgs.list();
198
170
  return !(0, kit_1.isEmpty)(auths);
199
171
  }
200
172
  catch (err) {
@@ -213,14 +185,14 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
213
185
  static getAuthorizationUrl(options, oauth2) {
214
186
  // Always use a verifier for enhanced security
215
187
  options.useVerifier = true;
216
- const oauth2Verifier = oauth2 || new jsforce_1.OAuth2(options);
188
+ const oauth2Verifier = oauth2 ?? new jsforce_1.OAuth2(options);
217
189
  // The state parameter allows the redirectUri callback listener to ignore request
218
190
  // that don't contain the state value.
219
191
  const params = {
220
192
  state: (0, crypto_1.randomBytes)(Math.ceil(6)).toString('hex'),
221
193
  prompt: 'login',
222
194
  // Default connected app is 'refresh_token api web'
223
- scope: options.scope || kit_1.env.getString('SFDX_AUTH_SCOPES', 'refresh_token api web'),
195
+ scope: options.scope ?? kit_1.env.getString('SFDX_AUTH_SCOPES', 'refresh_token api web'),
224
196
  };
225
197
  return oauth2Verifier.getAuthorizationUrl(params);
226
198
  }
@@ -236,7 +208,7 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
236
208
  static parseSfdxAuthUrl(sfdxAuthUrl) {
237
209
  const match = sfdxAuthUrl.match(/^force:\/\/([a-zA-Z0-9._-]+):([a-zA-Z0-9._-]*):([a-zA-Z0-9._-]+={0,2})@([a-zA-Z0-9._-]+)/);
238
210
  if (!match) {
239
- throw new sfdxError_1.SfdxError('Invalid SFDX auth URL. Must be in the format "force://<clientId>:<clientSecret>:<refreshToken>@<instanceUrl>". Note that the SFDX auth URL uses the "force" protocol, and not "http" or "https". Also note that the "instanceUrl" inside the SFDX auth URL doesn\'t include the protocol ("https://").', 'INVALID_SFDX_AUTH_URL');
211
+ throw new sfError_1.SfError('Invalid SFDX auth URL. Must be in the format "force://<clientId>:<clientSecret>:<refreshToken>@<instanceUrl>". Note that the SFDX auth URL uses the "force" protocol, and not "http" or "https". Also note that the "instanceUrl" inside the SFDX auth URL doesn\'t include the protocol ("https://").', 'INVALID_SFDX_AUTH_URL');
240
212
  }
241
213
  const [, clientId, clientSecret, refreshToken, loginUrl] = match;
242
214
  return {
@@ -246,6 +218,64 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
246
218
  loginUrl: `https://${loginUrl}`,
247
219
  };
248
220
  }
221
+ /**
222
+ * Given a set of decrypted fields and an authInfo, determine if the org belongs to an available
223
+ * dev hub.
224
+ *
225
+ * @param fields
226
+ * @param orgAuthInfo
227
+ */
228
+ static async identifyPossibleScratchOrgs(fields, orgAuthInfo) {
229
+ // fields property is passed in because the consumers of this method have performed the decrypt.
230
+ // This is so we don't have to call authInfo.getFields(true) and decrypt again OR accidentally save an
231
+ // authInfo before it is necessary.
232
+ const logger = await logger_1.Logger.child('Common', { tag: 'identifyPossibleScratchOrgs' });
233
+ // return if we already know the hub org we know it is a devhub or prod-like or no orgId present
234
+ if (fields.isDevHub || fields.devHubUsername || !fields.orgId)
235
+ return;
236
+ logger.debug('getting devHubs');
237
+ // TODO: return if url is not sandbox-like to avoid constantly asking about production orgs
238
+ // TODO: someday we make this easier by asking the org if it is a scratch org
239
+ const hubAuthInfos = await AuthInfo.getDevHubAuthInfos();
240
+ logger.debug(`found ${hubAuthInfos.length} DevHubs`);
241
+ if (hubAuthInfos.length === 0)
242
+ return;
243
+ // ask all those orgs if they know this orgId
244
+ await Promise.all(hubAuthInfos.map(async (hubAuthInfo) => {
245
+ try {
246
+ const soi = await AuthInfo.queryScratchOrg(hubAuthInfo.username, fields.orgId);
247
+ // if any return a result
248
+ logger.debug(`found orgId ${fields.orgId} in devhub ${hubAuthInfo.username}`);
249
+ try {
250
+ await orgAuthInfo.save({
251
+ ...fields,
252
+ devHubUsername: hubAuthInfo.username,
253
+ expirationDate: soi.ExpirationDate,
254
+ isScratch: true,
255
+ });
256
+ logger.debug(`set ${hubAuthInfo.username} as devhub and expirationDate ${soi.ExpirationDate} for scratch org ${orgAuthInfo.getUsername()}`);
257
+ }
258
+ catch (error) {
259
+ logger.debug(`error updating auth file for ${orgAuthInfo.getUsername()}`, error);
260
+ }
261
+ }
262
+ catch (error) {
263
+ logger.error(`Error connecting to devhub ${hubAuthInfo.username}`, error);
264
+ }
265
+ }));
266
+ }
267
+ /**
268
+ * Find all dev hubs available in the local environment.
269
+ */
270
+ static async getDevHubAuthInfos() {
271
+ return AuthInfo.listAllAuthorizations((possibleHub) => possibleHub?.isDevHub ?? false);
272
+ }
273
+ static async queryScratchOrg(devHubUsername, scratchOrgId) {
274
+ const devHubOrg = await org_1.Org.create({ aliasOrUsername: devHubUsername });
275
+ const conn = devHubOrg.getConnection();
276
+ const data = await conn.singleRecordQuery(`select Id, ExpirationDate from ScratchOrgInfo where ScratchOrg = '${(0, sfdc_1.trimTo15)(scratchOrgId)}'`);
277
+ return data;
278
+ }
249
279
  /**
250
280
  * Get the username.
251
281
  */
@@ -287,11 +317,11 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
287
317
  async save(authData) {
288
318
  this.update(authData);
289
319
  const username = (0, ts_types_1.ensure)(this.getUsername());
290
- if (sfdc_1.sfdc.matchesAccessToken(username)) {
320
+ if ((0, sfdc_1.matchesAccessToken)(username)) {
291
321
  this.logger.debug('Username is an accesstoken. Skip saving authinfo to disk.');
292
322
  return this;
293
323
  }
294
- await this.globalInfo.write();
324
+ await this.stateAggregator.orgs.write(username);
295
325
  this.logger.info(`Saved auth info for username: ${username}`);
296
326
  return this;
297
327
  }
@@ -302,13 +332,10 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
302
332
  * @param authData Authorization fields to update.
303
333
  */
304
334
  update(authData) {
305
- // todo move into configstore
306
335
  if (authData && (0, ts_types_1.isPlainObject)(authData)) {
307
- this.username = authData.username || this.username;
308
- const existingFields = this.globalInfo.orgs.get(this.getUsername());
309
- const mergedFields = Object.assign({}, existingFields || {}, authData);
310
- this.globalInfo.orgs.set(this.getUsername(), mergedFields);
311
- this.logger.info(`Updated auth info for username: ${this.getUsername()}`);
336
+ this.username = authData.username ?? this.username;
337
+ this.stateAggregator.orgs.update(this.username, authData);
338
+ this.logger.info(`Updated auth info for username: ${this.username}`);
312
339
  }
313
340
  return this;
314
341
  }
@@ -341,7 +368,7 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
341
368
  // Decrypt a user provided client secret or use the default.
342
369
  opts = {
343
370
  oauth2: {
344
- loginUrl: instanceUrl || sfdcUrl_1.SfdcUrl.PRODUCTION,
371
+ loginUrl: instanceUrl ?? sfdcUrl_1.SfdcUrl.PRODUCTION,
345
372
  clientId: this.getClientId(),
346
373
  redirectUri: this.getRedirectUri(),
347
374
  },
@@ -354,8 +381,7 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
354
381
  return opts;
355
382
  }
356
383
  getClientId() {
357
- var _a;
358
- return ((_a = this.getFields()) === null || _a === void 0 ? void 0 : _a.clientId) || exports.DEFAULT_CONNECTED_APP_INFO.legacyClientId;
384
+ return this.getFields()?.clientId ?? exports.DEFAULT_CONNECTED_APP_INFO.clientId;
359
385
  }
360
386
  getRedirectUri() {
361
387
  return 'http://localhost:1717/OauthRedirect';
@@ -366,7 +392,7 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
366
392
  * @param decrypt Decrypt the fields.
367
393
  */
368
394
  getFields(decrypt) {
369
- return this.globalInfo.orgs.get(this.username, decrypt);
395
+ return this.stateAggregator.orgs.get(this.username, decrypt) ?? {};
370
396
  }
371
397
  /**
372
398
  * Get the org front door (used for web based oauth flows)
@@ -393,11 +419,37 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
393
419
  const instanceUrl = (0, ts_types_1.ensure)(decryptedFields.instanceUrl, 'undefined instanceUrl').replace(/^https?:\/\//, '');
394
420
  let sfdxAuthUrl = 'force://';
395
421
  if (decryptedFields.clientId) {
396
- sfdxAuthUrl += `${decryptedFields.clientId}:${decryptedFields.clientSecret || ''}:`;
422
+ sfdxAuthUrl += `${decryptedFields.clientId}:${decryptedFields.clientSecret ?? ''}:`;
397
423
  }
398
424
  sfdxAuthUrl += `${(0, ts_types_1.ensure)(decryptedFields.refreshToken, 'undefined refreshToken')}@${instanceUrl}`;
399
425
  return sfdxAuthUrl;
400
426
  }
427
+ /**
428
+ * Convenience function to handle typical side effects encountered when dealing with an AuthInfo.
429
+ * Given the values supplied in parameter sideEffects, this function will set auth alias, default auth
430
+ * and default dev hub.
431
+ *
432
+ * @param sideEffects - instance of AuthSideEffects
433
+ */
434
+ async handleAliasAndDefaultSettings(sideEffects) {
435
+ if (sideEffects.alias ||
436
+ sideEffects.setDefault ||
437
+ sideEffects.setDefaultDevHub ||
438
+ typeof sideEffects.setTracksSource === 'boolean') {
439
+ if (sideEffects.alias)
440
+ await this.setAlias(sideEffects.alias);
441
+ if (sideEffects.setDefault)
442
+ await this.setAsDefault({ org: true });
443
+ if (sideEffects.setDefaultDevHub)
444
+ await this.setAsDefault({ devHub: true });
445
+ if (typeof sideEffects.setTracksSource === 'boolean') {
446
+ await this.save({ tracksSource: sideEffects.setTracksSource });
447
+ }
448
+ else {
449
+ await this.save();
450
+ }
451
+ }
452
+ }
401
453
  /**
402
454
  * Set the target-env (default) or the target-dev-hub to the alias if
403
455
  * it exists otherwise to the username. Method will try to set the local
@@ -415,8 +467,8 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
415
467
  config = await config_1.Config.create({ isGlobal: true });
416
468
  }
417
469
  const username = (0, ts_types_1.ensureString)(this.getUsername());
418
- const alias = this.globalInfo.aliases.get(username);
419
- const value = alias !== null && alias !== void 0 ? alias : username;
470
+ const alias = this.stateAggregator.aliases.get(username);
471
+ const value = alias ?? username;
420
472
  if (options.org) {
421
473
  config.set(orgConfigProperties_1.OrgConfigProperties.TARGET_ORG, value);
422
474
  }
@@ -431,16 +483,16 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
431
483
  * @param alias alias to set
432
484
  */
433
485
  async setAlias(alias) {
434
- this.globalInfo.aliases.set(alias, this.getUsername());
486
+ this.stateAggregator.aliases.set(alias, this.getUsername());
487
+ await this.stateAggregator.aliases.write();
435
488
  }
436
489
  /**
437
490
  * Initializes an instance of the AuthInfo class.
438
491
  */
439
492
  async init() {
440
- // We have to set the global instance here because we need synchronous access to it later
441
- this.globalInfo = await globalInfo_1.GlobalInfo.getInstance();
493
+ this.stateAggregator = await stateAggregator_1.StateAggregator.getInstance();
442
494
  const username = this.options.username;
443
- const authOptions = this.options.oauth2Options || this.options.accessTokenOptions;
495
+ const authOptions = this.options.oauth2Options ?? this.options.accessTokenOptions;
444
496
  // Must specify either username and/or options
445
497
  if (!username && !authOptions) {
446
498
  throw messages.createError('authInfoCreationError');
@@ -448,21 +500,21 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
448
500
  // If a username AND oauth options, ensure an authorization for the username doesn't
449
501
  // already exist. Throw if it does so we don't overwrite the authorization.
450
502
  if (username && authOptions) {
451
- const authExists = this.globalInfo.orgs.has(username);
452
- if (authExists) {
503
+ if (await this.stateAggregator.orgs.hasFile(username)) {
453
504
  throw messages.createError('authInfoOverwriteError');
454
505
  }
455
506
  }
456
- const oauthUsername = username || (0, ts_types_1.getString)(authOptions, 'username');
507
+ const oauthUsername = username ?? authOptions?.username;
457
508
  if (oauthUsername) {
458
509
  this.username = oauthUsername;
510
+ await this.stateAggregator.orgs.read(oauthUsername, false, false);
459
511
  } // Else it will be set in initAuthOptions below.
460
512
  // If the username is an access token, use that for auth and don't persist
461
- if ((0, ts_types_1.isString)(oauthUsername) && sfdc_1.sfdc.matchesAccessToken(oauthUsername)) {
513
+ if ((0, ts_types_1.isString)(oauthUsername) && (0, sfdc_1.matchesAccessToken)(oauthUsername)) {
462
514
  // Need to initAuthOptions the logger and authInfoCrypto since we don't call init()
463
515
  this.logger = await logger_1.Logger.child('AuthInfo');
464
516
  const aggregator = await configAggregator_1.ConfigAggregator.create();
465
- const instanceUrl = this.getInstanceUrl(authOptions, aggregator);
517
+ const instanceUrl = this.getInstanceUrl(aggregator, authOptions);
466
518
  this.update({
467
519
  accessToken: oauthUsername,
468
520
  instanceUrl,
@@ -472,16 +524,16 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
472
524
  this.usingAccessToken = true;
473
525
  }
474
526
  // If a username with NO oauth options, ensure authorization already exist.
475
- else if (username && !authOptions && !this.globalInfo.orgs.has(username)) {
527
+ else if (username && !authOptions && !(await this.stateAggregator.orgs.exists(username))) {
476
528
  throw messages.createError('namedOrgNotFound', [username]);
477
529
  }
478
530
  else {
479
531
  await this.initAuthOptions(authOptions);
480
532
  }
481
533
  }
482
- getInstanceUrl(options, aggregator) {
483
- const instanceUrl = (0, ts_types_1.getString)(options, 'instanceUrl') || aggregator.getPropertyValue('instanceUrl');
484
- return instanceUrl || sfdcUrl_1.SfdcUrl.PRODUCTION;
534
+ getInstanceUrl(aggregator, options) {
535
+ const instanceUrl = options?.instanceUrl ?? aggregator.getPropertyValue(orgConfigProperties_1.OrgConfigProperties.ORG_INSTANCE_URL);
536
+ return instanceUrl ?? sfdcUrl_1.SfdcUrl.PRODUCTION;
485
537
  }
486
538
  /**
487
539
  * Initialize this AuthInfo instance with the specified options. If options are not provided, initialize it from cache
@@ -489,7 +541,7 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
489
541
  *
490
542
  * @param options Options to be used for creating an OAuth2 instance.
491
543
  *
492
- * **Throws** *{@link SfdxError}{ name: 'NamedOrgNotFoundError' }* Org information does not exist.
544
+ * **Throws** *{@link SfError}{ name: 'NamedOrgNotFoundError' }* Org information does not exist.
493
545
  * @returns {Promise<AuthInfo>}
494
546
  */
495
547
  async initAuthOptions(options) {
@@ -501,7 +553,7 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
501
553
  if (this.isTokenOptions(options)) {
502
554
  authConfig = options;
503
555
  const userInfo = await this.retrieveUserInfo((0, ts_types_1.ensureString)(options.instanceUrl), (0, ts_types_1.ensureString)(options.accessToken));
504
- this.update({ username: userInfo === null || userInfo === void 0 ? void 0 : userInfo.username, orgId: userInfo === null || userInfo === void 0 ? void 0 : userInfo.organizationId });
556
+ this.update({ username: userInfo?.username, orgId: userInfo?.organizationId });
505
557
  }
506
558
  else {
507
559
  if (this.options.parentUsername) {
@@ -524,30 +576,32 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
524
576
  options.privateKey = (0, path_1.resolve)(options.privateKeyFile);
525
577
  }
526
578
  if (options.privateKey) {
527
- authConfig = await this.buildJwtConfig(options);
579
+ authConfig = await this.authJwt(options);
528
580
  }
529
581
  else if (!options.authCode && options.refreshToken) {
530
582
  // refresh token flow (from sfdxUrl or OAuth refreshFn)
531
583
  authConfig = await this.buildRefreshTokenConfig(options);
532
584
  }
585
+ else if (this.options.oauth2 instanceof jsforce_1.OAuth2) {
586
+ // authcode exchange / web auth flow
587
+ authConfig = await this.exchangeToken(options, this.options.oauth2);
588
+ }
533
589
  else {
534
- if (this.options.oauth2 instanceof jsforce_1.OAuth2) {
535
- // authcode exchange / web auth flow
536
- authConfig = await this.exchangeToken(options, this.options.oauth2);
537
- }
538
- else {
539
- authConfig = await this.exchangeToken(options);
540
- }
590
+ authConfig = await this.exchangeToken(options);
541
591
  }
542
592
  }
593
+ authConfig.isDevHub = await this.determineIfDevHub((0, ts_types_1.ensureString)(authConfig.instanceUrl), (0, ts_types_1.ensureString)(authConfig.accessToken));
594
+ if (authConfig.username)
595
+ await this.stateAggregator.orgs.read(authConfig.username, false, false);
543
596
  // Update the auth fields WITH encryption
544
597
  this.update(authConfig);
545
598
  }
546
599
  return this;
547
600
  }
601
+ // eslint-disable-next-line @typescript-eslint/require-await
548
602
  async loadDecryptedAuthFromConfig(username) {
549
603
  // Fetch from the persisted auth file
550
- const authInfo = this.globalInfo.orgs.get(username, true);
604
+ const authInfo = this.stateAggregator.orgs.get(username, true);
551
605
  if (!authInfo) {
552
606
  throw messages.createError('namedOrgNotFound', [username]);
553
607
  }
@@ -565,7 +619,6 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
565
619
  // A callback function for a connection to refresh an access token. This is used
566
620
  // both for a JWT connection and an OAuth connection.
567
621
  async refreshFn(conn, callback) {
568
- var _a;
569
622
  this.logger.info('Access token has expired. Updating...');
570
623
  try {
571
624
  const fields = this.getFields(true);
@@ -575,35 +628,48 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
575
628
  }
576
629
  catch (err) {
577
630
  const error = err;
578
- if ((_a = error === null || error === void 0 ? void 0 : error.message) === null || _a === void 0 ? void 0 : _a.includes('Data Not Available')) {
631
+ if (error?.message?.includes('Data Not Available')) {
579
632
  // Set cause to keep original stacktrace
580
633
  return await callback(messages.createError('orgDataNotAvailableError', [this.getUsername()], [], error));
581
634
  }
582
635
  return await callback(error);
583
636
  }
584
637
  }
638
+ async readJwtKey(keyFile) {
639
+ return fs.promises.readFile(keyFile, 'utf8');
640
+ }
585
641
  // Build OAuth config for a JWT auth flow
586
- async buildJwtConfig(options) {
587
- const privateKeyContents = await fs_1.fs.readFile((0, ts_types_1.ensure)(options.privateKey), 'utf8');
642
+ async authJwt(options) {
643
+ if (!options.clientId) {
644
+ throw messages.createError('missingClientId');
645
+ }
646
+ const privateKeyContents = await this.readJwtKey((0, ts_types_1.ensureString)(options.privateKey));
588
647
  const { loginUrl = sfdcUrl_1.SfdcUrl.PRODUCTION } = options;
589
648
  const url = new sfdcUrl_1.SfdcUrl(loginUrl);
590
- const createdOrgInstance = (0, ts_types_1.getString)(options, 'createdOrgInstance', '').trim().toLowerCase();
649
+ const createdOrgInstance = (this.getFields().createdOrgInstance ?? '').trim().toLowerCase();
591
650
  const audienceUrl = await url.getJwtAudienceUrl(createdOrgInstance);
592
- const jwtToken = jwt.sign({
593
- iss: options.clientId,
594
- sub: this.getUsername(),
595
- aud: audienceUrl,
596
- exp: Date.now() + 300,
597
- }, privateKeyContents, {
598
- algorithm: 'RS256',
599
- });
600
- const oauth2 = new JwtOAuth2({ loginUrl: options.loginUrl });
601
651
  let authFieldsBuilder;
602
- try {
603
- authFieldsBuilder = (0, ts_types_1.ensureJsonMap)(await oauth2.jwtAuthorize(jwtToken));
652
+ const authErrors = [];
653
+ // given that we can no longer depend on instance names or URls to determine audience, let's try them all
654
+ const loginAndAudienceUrls = (0, sfdcUrl_1.getLoginAudienceCombos)(audienceUrl, loginUrl);
655
+ for (const [login, audience] of loginAndAudienceUrls) {
656
+ try {
657
+ // sequentially, in probabilistic order
658
+ // eslint-disable-next-line no-await-in-loop
659
+ authFieldsBuilder = await this.tryJwtAuth(options.clientId, login, audience, privateKeyContents);
660
+ break;
661
+ }
662
+ catch (err) {
663
+ const error = err;
664
+ const message = error.message.includes('audience')
665
+ ? `${error.message} [audience=${audience} login=${login}]`
666
+ : error.message;
667
+ authErrors.push(message);
668
+ }
604
669
  }
605
- catch (err) {
606
- throw messages.createError('jwtAuthError', [err.message]);
670
+ if (!authFieldsBuilder) {
671
+ // messages.createError expects names to end in `error` and this one says Errors so do it manually.
672
+ throw new sfError_1.SfError(messages.getMessage('jwtAuthErrors', [authErrors.join('\n')]), 'JwtAuthError');
607
673
  }
608
674
  const authFields = {
609
675
  accessToken: (0, ts_types_1.asString)(authFieldsBuilder.access_token),
@@ -620,18 +686,34 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
620
686
  authFields.instanceUrl = instanceUrl;
621
687
  }
622
688
  catch (err) {
623
- this.logger.debug(`Instance URL [${authFieldsBuilder.instance_url}] is not available. DNS lookup failed. Using loginUrl [${options.loginUrl}] instead. This may result in a "Destination URL not reset" error.`);
689
+ this.logger.debug(
690
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
691
+ `Instance URL [${authFieldsBuilder.instance_url}] is not available. DNS lookup failed. Using loginUrl [${options.loginUrl}] instead. This may result in a "Destination URL not reset" error.`);
624
692
  authFields.instanceUrl = options.loginUrl;
625
693
  }
626
694
  return authFields;
627
695
  }
696
+ async tryJwtAuth(clientId, loginUrl, audienceUrl, privateKeyContents) {
697
+ const jwtToken = jwt.sign({
698
+ iss: clientId,
699
+ sub: this.getUsername(),
700
+ aud: audienceUrl,
701
+ exp: Date.now() + 300,
702
+ }, privateKeyContents, {
703
+ algorithm: 'RS256',
704
+ });
705
+ const oauth2 = new jsforce_1.JwtOAuth2({ loginUrl });
706
+ // jsforce has it types as any
707
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
708
+ return (0, ts_types_1.ensureJsonMap)(await oauth2.jwtAuthorize(jwtToken));
709
+ }
628
710
  // Build OAuth config for a refresh token auth flow
629
711
  async buildRefreshTokenConfig(options) {
630
712
  // Ideally, this would be removed at some point in the distant future when all auth files
631
713
  // now have the clientId stored in it.
632
714
  if (!options.clientId) {
633
- options.clientId = exports.DEFAULT_CONNECTED_APP_INFO.legacyClientId;
634
- options.clientSecret = exports.DEFAULT_CONNECTED_APP_INFO.legacyClientSecret;
715
+ options.clientId = exports.DEFAULT_CONNECTED_APP_INFO.clientId;
716
+ options.clientSecret = exports.DEFAULT_CONNECTED_APP_INFO.clientSecret;
635
717
  }
636
718
  if (!options.redirectUri) {
637
719
  options.redirectUri = this.getRedirectUri();
@@ -644,22 +726,20 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
644
726
  catch (err) {
645
727
  throw messages.createError('refreshTokenAuthError', [err.message]);
646
728
  }
729
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
647
730
  // @ts-ignore
648
731
  const { orgId } = parseIdUrl(authFieldsBuilder.id);
649
732
  let username = this.getUsername();
650
733
  if (!username) {
651
- // @ts-ignore
652
734
  const userInfo = await this.retrieveUserInfo(authFieldsBuilder.instance_url, authFieldsBuilder.access_token);
653
- username = (0, ts_types_1.ensureString)(userInfo === null || userInfo === void 0 ? void 0 : userInfo.username);
735
+ username = (0, ts_types_1.ensureString)(userInfo?.username);
654
736
  }
655
737
  return {
656
738
  orgId,
657
739
  username,
658
740
  accessToken: authFieldsBuilder.access_token,
659
- // @ts-ignore TODO: need better typings for jsforce
660
741
  instanceUrl: authFieldsBuilder.instance_url,
661
- // @ts-ignore TODO: need better typings for jsforce
662
- loginUrl: options.loginUrl || authFieldsBuilder.instance_url,
742
+ loginUrl: options.loginUrl ?? authFieldsBuilder.instance_url,
663
743
  refreshToken: options.refreshToken,
664
744
  clientId: options.clientId,
665
745
  clientSecret: options.clientSecret,
@@ -687,24 +767,22 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
687
767
  catch (err) {
688
768
  throw messages.createError('authCodeExchangeError', [err.message]);
689
769
  }
690
- // @ts-ignore TODO: need better typings for jsforce
691
770
  const { orgId } = parseIdUrl(authFields.id);
692
771
  let username = this.getUsername();
693
772
  // Only need to query for the username if it isn't known. For example, a new auth code exchange
694
773
  // rather than refreshing a token on an existing connection.
695
774
  if (!username) {
775
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
696
776
  // @ts-ignore
697
777
  const userInfo = await this.retrieveUserInfo(authFields.instance_url, authFields.access_token);
698
- username = userInfo === null || userInfo === void 0 ? void 0 : userInfo.username;
778
+ username = userInfo?.username;
699
779
  }
700
780
  return {
701
781
  accessToken: authFields.access_token,
702
- // @ts-ignore TODO: need better typings for jsforce
703
782
  instanceUrl: authFields.instance_url,
704
783
  orgId,
705
784
  username,
706
- // @ts-ignore TODO: need better typings for jsforce
707
- loginUrl: options.loginUrl || authFields.instance_url,
785
+ loginUrl: options.loginUrl ?? authFields.instance_url,
708
786
  refreshToken: authFields.refresh_token,
709
787
  clientId: options.clientId,
710
788
  clientSecret: options.clientSecret,
@@ -717,7 +795,7 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
717
795
  const apiVersion = 'v51.0'; // hardcoding to v51.0 just for this call is okay.
718
796
  const instance = (0, ts_types_1.ensure)(instanceUrl);
719
797
  const baseUrl = new sfdcUrl_1.SfdcUrl(instance);
720
- const userInfoUrl = `${baseUrl}services/oauth2/userinfo`;
798
+ const userInfoUrl = `${baseUrl.toString()}services/oauth2/userinfo`;
721
799
  const headers = Object.assign({ Authorization: `Bearer ${accessToken}` }, connection_1.SFDX_HTTP_HEADERS);
722
800
  try {
723
801
  this.logger.info(`Sending request for Username after successful auth code exchange to URL: ${userInfoUrl}`);
@@ -727,7 +805,7 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
727
805
  }
728
806
  else {
729
807
  const userInfoJson = (0, kit_1.parseJsonMap)(response.body);
730
- const url = `${baseUrl}/services/data/${apiVersion}/sobjects/User/${userInfoJson.user_id}`;
808
+ const url = `${baseUrl.toString()}services/data/${apiVersion}/sobjects/User/${userInfoJson.user_id}`;
731
809
  this.logger.info(`Sending request for User SObject after successful auth code exchange to URL: ${url}`);
732
810
  response = await new transport_1.default().httpRequest({ url, method: 'GET', headers });
733
811
  if (response.statusCode >= 400) {
@@ -751,24 +829,47 @@ class AuthInfo extends kit_1.AsyncOptionalCreatable {
751
829
  * @private
752
830
  */
753
831
  throwUserGetException(response) {
754
- var _a;
755
832
  let errorMsg = '';
756
- const bodyAsString = (0, ts_types_1.getString)(response, 'body', JSON.stringify({ message: 'UNKNOWN', errorCode: 'UNKNOWN' }));
833
+ const bodyAsString = response.body ?? JSON.stringify({ message: 'UNKNOWN', errorCode: 'UNKNOWN' });
757
834
  try {
758
835
  const body = (0, kit_1.parseJson)(bodyAsString);
759
836
  if ((0, ts_types_1.isArray)(body)) {
760
- errorMsg = body
761
- .map((line) => { var _a; return (_a = (0, ts_types_1.getString)(line, 'message')) !== null && _a !== void 0 ? _a : (0, ts_types_1.getString)(line, 'errorCode', 'UNKNOWN'); })
762
- .join(os.EOL);
837
+ errorMsg = body.map((line) => line.message ?? line.errorCode ?? 'UNKNOWN').join(os.EOL);
763
838
  }
764
839
  else {
765
- errorMsg = (_a = (0, ts_types_1.getString)(body, 'message')) !== null && _a !== void 0 ? _a : (0, ts_types_1.getString)(body, 'errorCode', 'UNKNOWN');
840
+ errorMsg = body.message ?? body.errorCode ?? 'UNKNOWN';
766
841
  }
767
842
  }
768
843
  catch (err) {
769
844
  errorMsg = `${bodyAsString}`;
770
845
  }
771
- throw new sfdxError_1.SfdxError(errorMsg);
846
+ throw new sfError_1.SfError(errorMsg);
847
+ }
848
+ /**
849
+ * Returns `true` if the org is a Dev Hub.
850
+ *
851
+ * Check access to the ScratchOrgInfo object to determine if the org is a dev hub.
852
+ */
853
+ async determineIfDevHub(instanceUrl, accessToken) {
854
+ // Make a REST call for the ScratchOrgInfo obj directly. Normally this is done via a connection
855
+ // but we don't want to create circular dependencies or lots of snowflakes
856
+ // within this file to support it.
857
+ const apiVersion = 'v51.0'; // hardcoding to v51.0 just for this call is okay.
858
+ const instance = (0, ts_types_1.ensure)(instanceUrl);
859
+ const baseUrl = new sfdcUrl_1.SfdcUrl(instance);
860
+ const scratchOrgInfoUrl = `${baseUrl.toString()}/services/data/${apiVersion}/query?q=SELECT%20Id%20FROM%20ScratchOrgInfo%20limit%201`;
861
+ const headers = Object.assign({ Authorization: `Bearer ${accessToken}` }, connection_1.SFDX_HTTP_HEADERS);
862
+ try {
863
+ const res = await new transport_1.default().httpRequest({ url: scratchOrgInfoUrl, method: 'GET', headers });
864
+ if (res.statusCode >= 400) {
865
+ return false;
866
+ }
867
+ return true;
868
+ }
869
+ catch (err) {
870
+ /* Not a dev hub */
871
+ return false;
872
+ }
772
873
  }
773
874
  }
774
875
  exports.AuthInfo = AuthInfo;