@salesforce/core
Version:
Core libraries to interact with SFDX projects, orgs, and APIs.
355 lines • 15.6 kB
JavaScript
"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