salesforce-alm
Version:
This package contains tools, and APIs, for an improved salesforce.com developer experience.
298 lines (296 loc) • 13.6 kB
JavaScript
;
/*
* 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