UNPKG

@salesforce/core

Version:

Core libraries to interact with SFDX projects, orgs, and APIs.

355 lines 15.6 kB
"use strict"; /* * Copyright (c) 2021, salesforce.com, inc. * All rights reserved. * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ Object.defineProperty(exports, "__esModule", { value: true }); exports.deploySettingsAndResolveUrl = exports.pollForScratchOrgInfo = exports.requestScratchOrgCreation = exports.authorizeScratchOrg = void 0; const kit_1 = require("@salesforce/kit"); const ts_types_1 = require("@salesforce/ts-types"); const ts_retry_promise_1 = require("ts-retry-promise"); // Local const org_1 = require("./org"); const logger_1 = require("./logger"); const mapKeys_1 = require("./util/mapKeys"); const authInfo_1 = require("./authInfo"); const messages_1 = require("./messages"); const sfdxError_1 = require("./sfdxError"); const sfdcUrl_1 = require("./util/sfdcUrl"); const pollingClient_1 = require("./status/pollingClient"); const myDomainResolver_1 = require("./status/myDomainResolver"); const scratchOrgErrorCodes_1 = require("./scratchOrgErrorCodes"); messages_1.Messages.importMessagesDirectory(__dirname); const messages = messages_1.Messages.loadMessages('@salesforce/core', 'scratchOrgInfoApi'); /** * Returns the url to be used to authorize into the new scratch org * * @param scratchOrgInfoComplete The completed ScratchOrgInfo * @param hubOrgLoginUrl the hun org login url * @param signupTargetLoginUrlConfig the login url * @returns {string} */ const getOrgInstanceAuthority = function (scratchOrgInfoComplete, hubOrgLoginUrl, signupTargetLoginUrlConfig) { const createdOrgInstance = scratchOrgInfoComplete.SignupInstance; if (createdOrgInstance === 'utf8') { return hubOrgLoginUrl; } let altUrl; // For non-Falcon (ie - instance names not ending in -s) sandboxes, use the instance URL if (createdOrgInstance && !createdOrgInstance.toLowerCase().endsWith('s')) { altUrl = `https://${createdOrgInstance}.salesforce.com`; } else { // For Falcon sandboxes, try the LoginURL instead; createdOrgInstance will not yield a valid URL altUrl = scratchOrgInfoComplete.LoginUrl; } return signupTargetLoginUrlConfig !== null && signupTargetLoginUrlConfig !== void 0 ? signupTargetLoginUrlConfig : altUrl; }; /** * Returns OAuth2Options object * * @param hubOrg the environment hub org * @param clientSecret The OAuth client secret. May be null for JWT OAuth flow. * @param scratchOrgInfoComplete The completed ScratchOrgInfo which should contain an access token. * @param retry auth retry attempts * @param signupTargetLoginUrlConfig the login url * @returns {OAuth2Options, number, number, number} options, retries, timeout, delay */ const buildOAuth2Options = async (options) => { const logger = await logger_1.Logger.child('buildOAuth2Options'); const isJwtFlow = !!options.hubOrg.getConnection().getAuthInfoFields().privateKey; const oauth2Options = { loginUrl: getOrgInstanceAuthority(options.scratchOrgInfoComplete, options.hubOrg.getField(org_1.Org.Fields.LOGIN_URL), options.signupTargetLoginUrlConfig), }; logger.debug(`isJwtFlow: ${isJwtFlow}`); if (isJwtFlow && !process.env.SFDX_CLIENT_SECRET) { oauth2Options.privateKeyFile = options.hubOrg.getConnection().getAuthInfoFields().privateKey; const retries = (options === null || options === void 0 ? void 0 : options.retry) || kit_1.env.getNumber('SFDX_JWT_AUTH_RETRY_ATTEMPTS') || 0; const timeoutInSeconds = kit_1.env.getNumber('SFDX_JWT_AUTH_RETRY_TIMEOUT') || 300; const timeout = kit_1.Duration.seconds(timeoutInSeconds).milliseconds; const delay = retries ? timeout / retries : 1000; return { options: oauth2Options, retries, timeout, delay, }; } else { // Web Server OAuth "auth code exchange" flow if (process.env.SFDX_CLIENT_SECRET) { oauth2Options.clientSecret = process.env.SFDX_CLIENT_SECRET; } else if (options.clientSecret) { oauth2Options.clientSecret = options.clientSecret; } oauth2Options.redirectUri = options.scratchOrgInfoComplete.ConnectedAppCallbackUrl; oauth2Options.authCode = options.scratchOrgInfoComplete.AuthCode; return { options: oauth2Options, retries: 0, }; } }; /** * Returns OAuth2Options object * * @param hubOrg the environment hub org * @param username The OAuth client secret. May be null for JWT OAuth flow. * @param oauth2Options The completed ScratchOrgInfo which should contain an access token. * @param retries auth retry attempts * @param timeout the login url * @param delay the login url * @returns {OAuth2Options, number, number, number} options, retries, timeout, delay */ const getAuthInfo = async (options) => { const logger = await logger_1.Logger.child('getAuthInfo'); const retryAuthorize = ts_retry_promise_1.retryDecorator(async (opts) => authInfo_1.AuthInfo.create(opts), { timeout: options.timeout, delay: options.delay, retries: options.retries, }); if (options.retries) { try { return await retryAuthorize({ username: options.username, parentUsername: options.hubOrg.getUsername(), oauth2Options: options.oauth2Options, }); } catch (err) { const error = err; logger.error(error); throw error.lastError || error; } } else { return authInfo_1.AuthInfo.create({ username: options.username, parentUsername: options.hubOrg.getUsername(), oauth2Options: options.oauth2Options, }); } }; /** * after we successfully signup an org we need to trade the auth token for access and refresh token. * * @param scratchOrgInfoComplete - The completed ScratchOrgInfo which should contain an access token. * @param hubOrg - the environment hub org * @param clientSecret - The OAuth client secret. May be null for JWT OAuth flow. * @param signupTargetLoginUrlConfig - Login url * @param retry - auth retry attempts * @returns {Promise<AuthInfo>} */ const authorizeScratchOrg = async (options) => { var _a; const { scratchOrgInfoComplete, hubOrg, clientSecret, signupTargetLoginUrlConfig, retry: maxRetries } = options; const logger = await logger_1.Logger.child('authorizeScratchOrg'); logger.debug(`scratchOrgInfoComplete: ${JSON.stringify(scratchOrgInfoComplete, null, 4)}`); // if we didn't have it marked as a devhub but just successfully used it as one, this will update the authFile, fix cache, etc if (!hubOrg.isDevHubOrg()) { await hubOrg.determineIfDevHubOrg(true); } const oAuth2Options = await buildOAuth2Options({ hubOrg, clientSecret, scratchOrgInfoComplete, retry: maxRetries, signupTargetLoginUrlConfig, }); const authInfo = await getAuthInfo({ hubOrg, username: scratchOrgInfoComplete.SignupUsername, oauth2Options: oAuth2Options.options, retries: oAuth2Options.retries, timeout: oAuth2Options.timeout, delay: oAuth2Options.delay, }); await authInfo.save({ devHubUsername: hubOrg.getUsername(), created: new Date((_a = scratchOrgInfoComplete.CreatedDate) !== null && _a !== void 0 ? _a : new Date()).valueOf().toString(), expirationDate: scratchOrgInfoComplete.ExpirationDate, clientId: scratchOrgInfoComplete.ConnectedAppConsumerKey, createdOrgInstance: scratchOrgInfoComplete.SignupInstance, isDevHub: false, snapshot: scratchOrgInfoComplete.Snapshot, }); return authInfo; }; exports.authorizeScratchOrg = authorizeScratchOrg; const checkOrgDoesntExist = async (scratchOrgInfo) => { const usernameKey = Object.keys(scratchOrgInfo).find((key) => key.toUpperCase() === 'USERNAME'); if (!usernameKey) { return; } const username = ts_types_1.getString(scratchOrgInfo, usernameKey); if (username && username.length > 0) { try { await authInfo_1.AuthInfo.create({ username: username.toLowerCase() }); } catch (error) { const sfdxError = sfdxError_1.SfdxError.wrap(error); // if an AuthInfo couldn't be created that means no AuthFile exists. if (sfdxError.name === 'NamedOrgNotFound') { return; } // Something unexpected throw sfdxError; } // An org file already exists throw sfdxError_1.SfdxError.create('@salesforce/core', 'scratchOrgErrorCodes', 'C-1007'); } }; /** * This extracts orgPrefs/settings from the user input and performs a basic scratchOrgInfo request. * * @param hubOrg - the environment hub org * @param scratchOrgRequest - An object containing the fields of the ScratchOrgInfo * @param settings - An object containing org settings * @returns {Promise<RecordResult>} */ const requestScratchOrgCreation = async (hubOrg, scratchOrgRequest, settings) => { // If these were present, they were already used to initialize the scratchOrgSettingsGenerator. // They shouldn't be submitted as part of the scratchOrgInfo. delete scratchOrgRequest.settings; delete scratchOrgRequest.objectSettings; // We do not allow you to specify the old and the new way of doing post create settings if (scratchOrgRequest.orgPreferences && settings.hasSettings()) { // This is not allowed throw new sfdxError_1.SfdxError('signupDuplicateSettingsSpecified'); } // deprecated old style orgPreferences if (scratchOrgRequest.orgPreferences) { throw new sfdxError_1.SfdxError(messages.getMessage('deprecatedPrefFormat')); } const scratchOrgInfo = mapKeys_1.default(scratchOrgRequest, kit_1.upperFirst, true); await checkOrgDoesntExist(scratchOrgInfo); // throw if it does exists. try { return await hubOrg.getConnection().sobject('ScratchOrgInfo').create(scratchOrgInfo); } catch (error) { // this is a jsforce error which contains the property "fields" which regular error don't const jsForceError = error; if (jsForceError.errorCode === 'REQUIRED_FIELD_MISSING') { throw new sfdxError_1.SfdxError(messages.getMessage('signupFieldsMissing', [jsForceError.fields.toString()])); } throw sfdxError_1.SfdxError.wrap(jsForceError); } }; exports.requestScratchOrgCreation = requestScratchOrgCreation; /** * This retrieves the ScratchOrgInfo, polling until the status is Active or Error * * @param hubOrg * @param scratchOrgInfoId - the id of the scratchOrgInfo that we are retrieving * @param timeout - A Duration object * @returns {Promise<ScratchOrgInfo>} */ const pollForScratchOrgInfo = async (hubOrg, scratchOrgInfoId, // org:create specifies a default timeout of 6. This longer default is for other consumers timeout = kit_1.Duration.minutes(15)) => { const logger = await logger_1.Logger.child('scratchOrgInfoApi-pollForScratchOrgInfo'); logger.debug(`PollingTimeout in minutes: ${timeout.minutes}`); const pollingOptions = { async poll() { try { const resultInProgress = await hubOrg .getConnection() .sobject('ScratchOrgInfo') .retrieve(scratchOrgInfoId); logger.debug(`polling client result: ${JSON.stringify(resultInProgress, null, 4)}`); // Once it's "done" we can return it if (resultInProgress.Status === 'Active' || resultInProgress.Status === 'Error') { return { completed: true, payload: resultInProgress, }; } logger.debug(`Scratch org status is ${resultInProgress.Status}`); return { completed: false, }; } catch (error) { logger.debug(`An error occurred trying to retrieve scratchOrgInfo for ${scratchOrgInfoId}`); logger.debug(`Error: ${error.message}`); logger.debug('Re-trying deploy check again....'); return { completed: false, }; } }, timeout, frequency: kit_1.Duration.seconds(1), timeoutErrorName: 'ScratchOrgInfoTimeoutError', }; const client = await pollingClient_1.PollingClient.create(pollingOptions); try { const resultInProgress = await client.subscribe(); return scratchOrgErrorCodes_1.checkScratchOrgInfoForErrors(resultInProgress, hubOrg.getUsername(), logger); } catch (error) { const err = error; if (err.message) { throw sfdxError_1.SfdxError.wrap(err); } throw new sfdxError_1.SfdxError(`The scratch org did not complete within ${timeout.minutes} minutes`, 'orgCreationTimeout', [ 'Try your force:org:create command again with a longer --wait value', ]); } }; exports.pollForScratchOrgInfo = pollForScratchOrgInfo; /** * This authenticates into the newly created org and sets org preferences * * @param scratchOrgAuthInfo - an object containing the AuthInfo of the ScratchOrg * @param apiVersion - the target api version * @param orgSettings - The ScratchOrg settings * @param scratchOrg - The scratchOrg Org info * @returns {Promise<Optional<AuthInfo>>} */ const deploySettingsAndResolveUrl = async (scratchOrgAuthInfo, apiVersion, orgSettings, scratchOrg) => { const logger = await logger_1.Logger.child('scratchOrgInfoApi-deploySettingsAndResolveUrl'); if (orgSettings.hasSettings()) { // deploy the settings to the newly created scratch org logger.debug(`deploying scratch org settings with apiVersion ${apiVersion}`); try { await orgSettings.createDeploy(); await orgSettings.deploySettingsViaFolder(scratchOrg, apiVersion); } catch (error) { throw sfdxError_1.SfdxError.wrap(error); } } const { instanceUrl } = scratchOrgAuthInfo.getFields(); if (instanceUrl) { logger.debug(`processScratchOrgInfoResult - resultData.instanceUrl: ${instanceUrl}`); const options = { timeout: kit_1.Duration.minutes(3), frequency: kit_1.Duration.seconds(10), url: new sfdcUrl_1.SfdcUrl(instanceUrl), }; try { const resolver = await myDomainResolver_1.MyDomainResolver.create(options); await resolver.resolve(); } catch (error) { const sfdxError = sfdxError_1.SfdxError.wrap(error); logger.debug('processScratchOrgInfoResult - err: %s', error); if (sfdxError.name === 'MyDomainResolverTimeoutError') { sfdxError.setData({ orgId: scratchOrgAuthInfo.getFields().orgId, username: scratchOrgAuthInfo.getFields().username, instanceUrl, }); logger.debug('processScratchOrgInfoResult - err data: %s', sfdxError.data); } throw sfdxError; } return scratchOrgAuthInfo; } }; exports.deploySettingsAndResolveUrl = deploySettingsAndResolveUrl; //# sourceMappingURL=scratchOrgInfoApi.js.map