UNPKG

salesforce-alm

Version:

This package contains tools, and APIs, for an improved salesforce.com developer experience.

298 lines (296 loc) 13.6 kB
"use strict"; /* * Copyright (c) 2020, 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 url_1 = require("url"); const kit_1 = require("@salesforce/kit"); const core_1 = require("@salesforce/core"); // Thirdparty const _ = require("lodash"); const ts_retry_promise_1 = require("ts-retry-promise"); // Local const ts_types_1 = require("@salesforce/ts-types"); const almError = require("../core/almError"); const messages = require("../messages"); const srcDevUtil = require("../core/srcDevUtil"); const scratchOrgErrorCodes_1 = require("./scratchOrgErrorCodes"); /** * Returns the url to be used to authorize into the new scratch org * * @param scratchOrgInfoComplete * @param force * @param useLoginUrl * @returns {*} * @private */ 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; }; /** * 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 force - the force api * @param hubOrg - the environment hub org * @param scratchOrg - the scratch org to save to disk * @param clientSecret - The OAuth client secret. May be null for JWT OAuth flow. * @param saveAsDefault {boolean} - whether to save this org as the default for this workspace. * @returns {*} * @private */ exports.authorizeScratchOrg = async (options) => { const { scratchOrgInfoComplete, hubOrg, clientSecret, setAsDefault, signupTargetLoginUrlConfig, alias } = options; const logger = await core_1.Logger.child('authorizeScratchOrg'); logger.debug(`_authorize - 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 isJwtFlow = !!hubOrg.getConnection().getAuthInfoFields().privateKey; const oauth2Options = { loginUrl: _getOrgInstanceAuthority(scratchOrgInfoComplete, hubOrg.getField(core_1.Org.Fields.LOGIN_URL), signupTargetLoginUrlConfig), }; logger.debug(`_authorize - isJwtFlow: ${isJwtFlow}`); let retries = 0; let delay = 1000; let timeout = 1000; if (isJwtFlow && !process.env.SFDX_CLIENT_SECRET) { oauth2Options.privateKeyFile = hubOrg.getConnection().getAuthInfoFields().privateKey; 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; timeout = kit_1.Duration.seconds(timeoutInSeconds).milliseconds; delay = retries ? timeout / retries : 1000; } else { // retry only for jwt for now retries = 0; // Web Server OAuth "auth code exchange" flow if (process.env.SFDX_CLIENT_SECRET) { oauth2Options.clientSecret = process.env.SFDX_CLIENT_SECRET; } else if (clientSecret) { oauth2Options.clientSecret = clientSecret; } oauth2Options.redirectUri = scratchOrgInfoComplete.ConnectedAppCallbackUrl; oauth2Options.authCode = scratchOrgInfoComplete.AuthCode; } logger.debug(`_authorize - oauth2options: ${JSON.stringify(oauth2Options, null, 4)}`); const retryAuthorize = ts_retry_promise_1.retryDecorator(async (options) => core_1.AuthInfo.create(options), { timeout, delay, retries, }); const authInfo = retries ? await retryAuthorize({ username: scratchOrgInfoComplete.SignupUsername, parentUsername: hubOrg.getUsername(), oauth2Options, }).catch((reason) => { logger.error(reason); if (reason.message.startsWith('Timeout after')) { throw core_1.SfdxError.create('salesforce-alm', 'org_create', 'jwtAuthRetryTimedOut', [ scratchOrgInfoComplete.SignupUsername, timeout, retries, ]); } throw reason.lastError || reason; }) : await core_1.AuthInfo.create({ username: scratchOrgInfoComplete.SignupUsername, parentUsername: hubOrg.getUsername(), oauth2Options, }); await authInfo.save({ devHubUsername: hubOrg.getUsername(), created: new Date(scratchOrgInfoComplete.CreatedDate).valueOf().toString(), expirationDate: scratchOrgInfoComplete.ExpirationDate, clientId: scratchOrgInfoComplete.ConnectedAppConsumerKey, createdOrgInstance: scratchOrgInfoComplete.SignupInstance, isDevHub: false, snapshot: scratchOrgInfoComplete.Snapshot, }); if (alias) { logger.debug(`_authorize - setting alias to ${alias}`); await authInfo.setAlias(alias); logger.debug(`_authorize - AuthInfo has alias to ${authInfo.getFields().alias}`); } if (setAsDefault) { await authInfo.setAsDefault({ defaultUsername: true }); } logger.debug(`_authorize - orgConfig.loginUrl: ${authInfo.getFields().loginUrl}`); logger.debug(`_authorize - orgConfig.instanceUrl: ${authInfo.getFields().instanceUrl}`); return authInfo; }; const checkOrgDoesntExist = async (_scratchOrgInfo) => { const usernameKey = Object.keys(_scratchOrgInfo).find((key) => key ? key.toUpperCase() === 'USERNAME' : false); if (!usernameKey) { return; } const username = ts_types_1.ensureString(_.get(_scratchOrgInfo, usernameKey)); if (username && username.length > 0) { try { await core_1.AuthInfo.create({ username: username.toLowerCase() }); } catch (e) { // if an AuthInfo couldn't be created that means no AuthFile exists. if (e.name === 'NamedOrgNotFound') { return; } // Something unexpected throw e; } // An org file already exists throw almError({ keyName: 'C-1007', bundle: 'signup' }); } }; /** * This extracts orgPrefs/settings from the user input and performs a basic scratchOrgInfo request. * * @param scratchOrgInfo - An object containing the fields of the ScratchOrgInfo. * @returns {*|promise} */ exports.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 almError('signupDuplicateSettingsSpecified'); } // See if we need to migrate and warn about using old style orgPreferences if (scratchOrgRequest.orgPreferences) { await settings.migrate(scratchOrgRequest); } const _scratchOrgInfo = srcDevUtil.mapKeys(scratchOrgRequest, _.upperFirst, true); await checkOrgDoesntExist(_scratchOrgInfo); // throw if it does exists. try { return await hubOrg.getConnection().sobject('ScratchOrgInfo').create(_scratchOrgInfo); } catch (err) { if (err.errorCode === 'REQUIRED_FIELD_MISSING') { throw new core_1.SfdxError(messages().getMessage('signupFieldsMissing', err.fields.toString())); } throw err; } }; /** * 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 {BBPromise} */ exports.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 core_1.Logger.child('scratchOrgInfoApi-pollForScratchOrgInfo'); logger.debug(`PollingTimeout in minutes: ${timeout.minutes}`); logger.debug(`pollForScratchOrgInfo this.scratchOrgInfoId: ${scratchOrgInfoId} from devHub ${hubOrg.getUsername()}`); const response = await ts_retry_promise_1.retry(async () => { 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 resultInProgress; } // all other statuses, OR lack of status (e.g. network errors) will cause a retry throw new Error(`Scratch org status is ${resultInProgress.Status}`); }, { retries: 'INFINITELY', timeout: timeout.milliseconds, delay: kit_1.Duration.seconds(2).milliseconds, backoff: 'LINEAR', maxBackOff: kit_1.Duration.seconds(30).milliseconds, }).catch(() => { throw new core_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', ]); }); return scratchOrgErrorCodes_1.checkScratchOrgInfoForErrors(response, hubOrg.getUsername(), logger); }; /** * This authenticates into the newly created org and sets org preferences * * @param scratchOrgInfoResult - an object containing the fields of the ScratchOrgInfo * @param clientSecret - the OAuth client secret. May be null for JWT OAuth flow * @param scratchOrg - The ScratchOrg configuration * @param saveAsDefault - Save the org as the default for commands to run against * @returns {*} */ exports.deploySettingsAndResolveUrl = async (scratchOrgAuthInfo, apiVersion, orgSettings) => { const logger = await core_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}`); let deployDir; try { deployDir = await orgSettings.createDeployDir(); await orgSettings.deploySettingsViaFolder(scratchOrgAuthInfo.getUsername(), deployDir); } finally { // delete the deploy dir if (deployDir && core_1.fs.existsSync(deployDir)) { try { core_1.fs.rmdirSync(deployDir, { recursive: true }); } catch (e) { logger.debug(`Error when trying to clean up settings deploy dir: ${deployDir}. ${e === null || e === void 0 ? void 0 : e.message}`); } } } } if (scratchOrgAuthInfo.getFields().instanceUrl) { logger.debug(`processScratchOrgInfoResult - resultData.instanceUrl: ${JSON.stringify(scratchOrgAuthInfo.getFields().instanceUrl)}`); const options = { timeout: kit_1.Duration.minutes(3), frequency: kit_1.Duration.seconds(10), url: new url_1.URL(scratchOrgAuthInfo.getFields().instanceUrl), }; try { const resolver = await core_1.MyDomainResolver.create(options); await resolver.resolve(); } catch (err) { logger.debug(`processScratchOrgInfoResult - err: ${JSON.stringify(err, null, 4)}`); // this will be an sfdxError if (err instanceof core_1.SfdxError && err.name === 'MyDomainResolverTimeoutError') { const { orgId, username, instanceUrl } = scratchOrgAuthInfo.getFields(); err.setData({ orgId, username, instanceUrl, }); err.message = messages().getMessage('MyDomainResolverTimeoutError', [orgId, username, instanceUrl], 'signup'); logger.debug(`processScratchOrgInfoResult - err data: ${JSON.stringify(err.data, null, 4)}`); } throw err; } return scratchOrgAuthInfo; } }; //# sourceMappingURL=scratchOrgInfoApi.js.map