sfdx-hardis
Version:
Swiss-army-knife Toolbox for Salesforce. Allows you to define a complete CD/CD Pipeline. Orchestrate base commands and assist users with interactive wizards
717 lines • 34.2 kB
JavaScript
import { MetadataUtils } from '../metadata-utils/index.js';
import { prompts } from './prompts.js';
import c from 'chalk';
import fs from 'fs-extra';
import * as path from 'path';
import { createTempDir, elapseEnd, elapseStart, execCommand, execSfdxJson, isCI, uxLog } from './index.js';
import { WebSocketClient } from '../websocketClient.js';
import { getConfig, setConfig } from '../../config/index.js';
import * as EmailValidator from 'email-validator';
import sortArray from 'sort-array';
import { AuthInfo, Connection, SfError } from '@salesforce/core';
import { importData } from './dataUtils.js';
import { soqlQuery, soqlQueryTooling } from './apiUtils.js';
import { isSfdxProject } from './projectUtils.js';
import { deployMetadatas, smartDeploy, forceSourcePush } from './deployUtils.js';
import { PACKAGE_ROOT_DIR } from '../../settings.js';
import { clearCache } from '../cache/index.js';
export async function listProfiles(conn) {
if (conn in [null, undefined]) {
return [];
}
const profileRes = await soqlQuery('SELECT Id,Name FROM Profile ORDER BY Name', conn);
return profileRes.records;
}
// Get record type id, with cache management
const recordTypeIdCache = {};
export async function getRecordTypeId(recordTypeInfo, conn) {
const cacheKey = JSON.stringify(recordTypeInfo);
if (recordTypeIdCache[cacheKey]) {
return recordTypeIdCache[cacheKey];
}
const recordTypeQueryRes = await soqlQuery(`SELECT Id FROM RecordType WHERE SobjectType='${recordTypeInfo.sObjectType}' AND` +
` DeveloperName='${recordTypeInfo.developerName}'` +
` LIMIT 1`, conn);
if (recordTypeQueryRes.records[0].Id) {
recordTypeIdCache[cacheKey] = recordTypeQueryRes.records[0].Id;
return recordTypeQueryRes.records[0].Id;
}
return null;
}
// Prompt profile(s) for selection
/*
Example calls from command class:
const profiles = await promptProfiles(flags['target-org'].getConnection(),{multiselect: true, initialSelection: ["System Administrator","Administrateur Système"]});
const profile = await promptProfiles(flags['target-org'].getConnection(),{multiselect: false, initialSelection: ["System Administrator","Administrateur Système"]});
*/
export async function promptProfiles(conn, options = {
multiselect: false,
initialSelection: [],
returnField: 'Name',
message: 'Please select profile(s)',
allowSelectAll: true,
allowSelectAllErrorMessage: 'You can not select all profiles',
allowSelectMine: true,
allowSelectMineErrorMessage: 'You can not select the profile your user is assigned to',
returnApiName: false
}) {
const profiles = await listProfiles(conn);
let selectedProfiles = [];
// Profiles returned by active connection
if (profiles.length > 0) {
const profilesSelection = await prompts({
type: options.multiselect ? 'multiselect' : 'select',
message: options.message || 'Please select profile(s)',
description: 'Select one or more Salesforce profiles for the operation',
name: 'value',
choices: profiles.map((profile) => {
return {
title: profile.Name,
value: options.returnField === 'record' ? profile : options.returnField === 'Id' ? profile.Id : profile.Name,
};
}),
});
// Verify that all profiles are not selected if allowSelectAll === false
if (options.allowSelectAll === false && profilesSelection.value.length === profiles.length) {
throw new SfError(options.allowSelectAllErrorMessage);
}
// Verify that current user profile is not selected
if (options.allowSelectMine === false) {
if (!['record', 'Id'].includes(options.returnField)) {
throw new SfError("You can not use option allowSelectMine:false if you don't use record or Id as return value");
}
const userRes = await soqlQuery(`SELECT ProfileId FROM User WHERE Id='${(await conn.identity()).user_id}' LIMIT 1`, conn);
const profileId = userRes.records[0]['ProfileId'];
if (profilesSelection.value.filter((profileSelected) => profileSelected === profileId || profileSelected?.Id === profileId).length > 0) {
throw new SfError(options.allowSelectMineErrorMessage);
}
}
selectedProfiles = profilesSelection.value || [];
}
else {
// Manual input of comma separated profiles
const profilesSelection = await prompts({
type: 'text',
message: options.message || 'Please input profile name',
description: 'Enter the Salesforce profile name manually',
placeholder: 'Ex: System Administrator',
name: 'value',
initial: options?.initialSelection[0] || null,
});
selectedProfiles = options.multiselect ? profilesSelection.value.split(',') : profilesSelection.value;
}
if (options.returnApiName) {
const apiNames = [];
for (const profileName of selectedProfiles) {
// Map standard profile names to their API names
const standardProfileMapping = {
'System Administrator': 'Admin',
'Standard User': 'Standard',
'Solution Manager': 'SolutionManager',
'Marketing User': 'MarketingUser',
'Contract Manager': 'ContractManager',
'Read Only': 'ReadOnly'
};
if (Object.keys(standardProfileMapping).includes(profileName.trim())) {
apiNames.push(standardProfileMapping[profileName.trim()]);
}
else {
uxLog("log", this, 'Profile not found in predefined standard profiles, querying ApiName (FullName)...');
const results = await soqlQueryTooling(`SELECT Id, Name, FullName FROM Profile WHERE Name='${profileName.trim()}'`, conn);
if (results.records.length > 0) { // Fullname limits the results to 1 row only.
apiNames.push(results.records[0].FullName);
}
else {
uxLog("warning", this, `Profile '${profileName.trim()}' not found`);
}
}
}
return apiNames;
}
return selectedProfiles;
}
export async function promptOrg(commandThis, options = { devHub: false, setDefault: true, scratch: false, devSandbox: false, promptMessage: null, quickOrgList: false, defaultOrgUsername: null, useCache: true }) {
// List all local orgs and request to user
// Access flags via commandThis, fallback to options if not present
const defaultOrgUsername = options.defaultOrgUsername || '';
const orgListResult = await MetadataUtils.listLocalOrgs(options.devSandbox === true ? 'sandbox' : 'any', { quickOrgList: options.quickOrgList, useCache: options.useCache });
let orgList = [
{
username: '🌍 Login to another org',
otherOrg: true,
descriptionForUi: 'Connect in Web Browser to a Sandbox, a Production Org, a Dev Org or a Scratch Org',
},
...sortArray(orgListResult?.scratchOrgs || [], {
by: ['instanceUrl', 'devHubUsername', 'username', 'alias'],
order: ['asc', 'asc', 'asc'],
}),
...sortArray(orgListResult?.nonScratchOrgs || [], {
by: ['instanceUrl', 'username', 'alias',],
order: ['asc', 'asc', 'asc'],
}),
{
username: "😱 I already authenticated my org but I don't see it !",
clearCache: true,
descriptionForUi: 'It might be a sfdx-hardis cache issue, reset it and try again !',
},
{ username: '❌ Cancel', cancel: true, descriptionForUi: 'Get out of here 😊' },
];
// Filter if we want to list only the scratch attached to current devhub
if (options.scratch === true) {
const configGetRes = await execSfdxJson('sf config get target-dev-hub', this, {
output: false,
fail: true,
});
const hubOrgUsername = configGetRes?.result[0]?.value || '';
orgList = orgList.filter((org) => org.status === 'Active' && org.devHubUsername === hubOrgUsername);
}
const defaultOrg = orgList.find((org) => org.username === defaultOrgUsername) || null;
// Prompt user
/* jscpd:ignore-start */
const orgResponse = await prompts({
type: 'select',
name: 'org',
message: c.cyanBright(options.promptMessage || 'Please select an org'),
description: 'Choose a Salesforce org from the list of authenticated orgs',
default: defaultOrg || '',
choices: orgList.map((org) => {
let title = org.instanceUrl || org.username || org.alias || "ERROR";
if (org.alias && title !== org.alias) {
title += ` (${org.alias})`;
}
const description = `Connected with ${org.username || org.alias || 'unknown user'} ` +
(org.devHubUsername ? ` (Hub: ${org.devHubUsername})` : '');
return {
title: title.replace("https://", ""),
description: org.descriptionForUi ? org.descriptionForUi : description || '-',
value: org,
};
}),
});
/* jscpd:ignore-end */
let org = orgResponse.org;
// Cancel
if (org.cancel === true) {
uxLog("error", commandThis, c.red('Cancelled.'));
process.exit(0);
}
// Connect to new org
if (org.otherOrg === true) {
await commandThis.config.runHook('auth', {
checkAuth: true,
Command: commandThis,
devHub: options.devHub === true,
setDefault: options.setDefault !== false,
});
const justConnectedOrg = globalThis.justConnectedOrg;
if (justConnectedOrg) {
return justConnectedOrg;
}
return {};
}
// Reset cache and try again
if (org.clearCache === true) {
await clearCache();
return await promptOrg(commandThis, options);
}
// Token is expired: login again to refresh it
if (org?.connectedStatus === 'RefreshTokenAuthError' || org?.connectedStatus?.includes('expired')) {
uxLog("action", this, c.yellow(`⚠️ Your authentication has expired. Please log in again using the web browser.`));
const loginCommand = 'sf org login web' + ` --instance-url ${org.instanceUrl}`;
const loginResult = await execSfdxJson(loginCommand, this, { fail: true, output: false });
org = loginResult.result;
}
if (options.setDefault === true) {
// Set default username
const setDefaultOrgCommand = `sf config set ` +
`${options.devHub ? 'target-dev-hub' : 'target-org'}=${org.username}` +
(!isSfdxProject() ? ' --global' : '');
await execSfdxJson(setDefaultOrgCommand, commandThis, {
fail: true,
output: false,
});
// If devHub , set alias of project devHub from config file
const config = await getConfig('project');
if (options.devHub && config.devHubAlias) {
const setAliasCommand = `sf alias set ${config.devHubAlias}=${org.username}`;
await execSfdxJson(setAliasCommand, commandThis, {
fail: true,
output: false,
});
}
WebSocketClient.sendRefreshStatusMessage();
// Update local user .sfdx-hardis.yml file with response if scratch has been selected
if (org.username.includes('scratch')) {
await setConfig('user', {
scratchOrgAlias: org.alias || null,
scratchOrgUsername: org.username || org.alias,
});
}
else {
const configUser = await getConfig('user');
if (configUser.scratchOrgAlias || configUser.scratchOrgUsername) {
await setConfig('user', {
scratchOrgAlias: null,
scratchOrgUsername: null,
});
}
}
}
// uxLog(commandThis, c.gray(JSON.stringify(org, null, 2)));
uxLog("log", commandThis, c.grey(`Selected Org ${c.green(org.username)} - ${c.green(org.instanceUrl)}`));
return orgResponse.org;
}
export async function promptOrgList(options = {}) {
const orgListResult = await MetadataUtils.listLocalOrgs('any');
const orgListSorted = sortArray(orgListResult?.nonScratchOrgs || [], {
by: ['instanceUrl', 'username', 'alias',],
order: ['asc', 'asc', 'asc'],
});
// Prompt user
const orgResponse = await prompts({
type: 'multiselect',
name: 'orgs',
message: c.cyanBright(options.promptMessage || 'Please select orgs'),
description: 'Choose multiple Salesforce orgs from the list of authenticated orgs',
choices: orgListSorted.map((org) => {
const title = org.instanceUrl || org.username || org.alias || "ERROR";
const description = `Connected with ${org.username || org.alias || 'unknown user'} ` +
(org.devHubUsername ? ` (Hub: ${org.devHubUsername})` : '');
return {
title: title,
description: org.descriptionForUi ? org.descriptionForUi : description || '-',
value: org,
};
}),
});
return orgResponse.orgs;
}
export async function makeSureOrgIsConnected(targetOrg) {
// Get connected Status and instance URL
let connectedStatus;
let instanceUrl;
let orgResult;
if (typeof targetOrg !== 'string') {
instanceUrl = targetOrg.instanceUrl;
connectedStatus = targetOrg.connectedStatus;
targetOrg = targetOrg.username;
orgResult = targetOrg;
}
else {
const displayOrgCommand = `sf org display --target-org ${targetOrg}`;
const displayResult = await execSfdxJson(displayOrgCommand, this, {
fail: false,
output: false,
});
connectedStatus = displayResult?.result?.connectedStatus || "error";
instanceUrl = displayResult?.result?.instanceUrl || "error";
orgResult = displayResult.result;
}
// Org is connected
if (connectedStatus === "Connected") {
return orgResult;
}
// Authentication is necessary
if (connectedStatus?.includes("expired")) {
uxLog("action", this, c.yellow("Your auth token has expired. You need to authenticate again.\n(Be patient after logging in; it can take a while 😑)"));
// Delete rotten authentication json file in case there has been a sandbox refresh
const homeSfdxDir = path.join(process.env.HOME || process.env.USERPROFILE || "~", '.sfdx');
const authFile = path.join(homeSfdxDir, `${targetOrg}.json`);
if (fs.existsSync(authFile)) {
try {
await fs.unlink(authFile);
uxLog("log", this, c.cyan(`Deleted potentially rotten auth file ${c.green(authFile)}`));
}
catch (e) {
uxLog("warning", this, c.red(`Error while deleting potentially rotten auth file ${c.green(authFile)}: ${e.message}\nYou might need to delete it manually.`));
}
}
// Authenticate again
const loginCommand = 'sf org login web' + ` --instance-url ${instanceUrl}`;
const loginRes = await execSfdxJson(loginCommand, this, { fail: true, output: false });
return loginRes.result;
}
// We shouldn't be here 😊
uxLog("warning", this, c.yellow("What are we doing here? Please create an issue with the following text: " + instanceUrl + ":" + connectedStatus));
}
export async function promptOrgUsernameDefault(commandThis, defaultOrg, options = { devHub: false, setDefault: true, message: "", quickOrgList: true }) {
const defaultOrgRes = await prompts({
type: 'confirm',
message: options.message || `Do you want to use org ${defaultOrg} ?`,
description: 'Confirms whether to use the currently configured default org or select a different one',
});
if (defaultOrgRes.value === true) {
return defaultOrg;
}
else {
const selectedOrg = await promptOrg(commandThis, options);
return selectedOrg.username;
}
}
export async function promptUserEmail(promptMessage = null) {
const userConfig = await getConfig('user');
const promptResponse = await prompts({
type: 'text',
name: 'value',
initial: userConfig.userEmail || '',
message: c.cyanBright(promptMessage || 'Please input your email address'),
description: 'Your email address will be stored locally and used for CI/CD operations',
placeholder: 'Ex: john.doe@company.com',
validate: (value) => EmailValidator.validate(value),
});
const userEmail = promptResponse.value;
// Store email in user .sfdx-hardis.USERNAME.yml file for later reuse
if (userConfig.userEmail !== userEmail) {
await setConfig('user', {
userEmail: userEmail,
});
}
return userEmail;
}
// Authenticate with SfdxUrlStore
export async function authenticateWithSfdxUrlStore(org) {
// Authenticate to scratch org to delete
const authFile = path.join(await createTempDir(), 'sfdxScratchAuth.txt');
const authFileContent = org.scratchOrgSfdxAuthUrl || (org.authFileJson ? JSON.stringify(org.authFileJson) : null);
await fs.writeFile(authFile, authFileContent, 'utf8');
const authCommand = `sf org login sfdx-url --sfdx-url-file ${authFile}`;
await execCommand(authCommand, this, { fail: true, output: false });
}
// Add package installation to project .sfdx-hardis.yml
export async function managePackageConfig(installedPackages, packagesToInstallCompleted, filterStandard = false) {
const config = await getConfig('project');
let projectPackages = config.installedPackages || [];
let updated = false;
const promptPackagesToInstall = [];
for (const installedPackage of installedPackages) {
// Filter standard packages
const matchInstalled = packagesToInstallCompleted.filter((pckg) => pckg.SubscriberPackageId === installedPackage.SubscriberPackageId);
const matchLocal = projectPackages.filter((projectPackage) => installedPackage.SubscriberPackageId === projectPackage.SubscriberPackageId);
// Upgrade version of already installed package
if (matchInstalled.length > 0 && matchLocal.length > 0) {
projectPackages = projectPackages.map((projectPackage) => {
if (installedPackage.SubscriberPackageId === projectPackage.SubscriberPackageId) {
const projectPackageId = projectPackage.Id || null;
projectPackage = Object.assign(projectPackage, installedPackage);
if (projectPackageId) {
projectPackage.Id = projectPackageId;
}
}
return projectPackage;
});
uxLog("action", this, c.cyan(`Updated package ${c.green(installedPackage.SubscriberPackageName)} with version id ${c.green(installedPackage.SubscriberPackageVersionId)}`));
updated = true;
}
else if (matchInstalled.length > 0 && matchLocal.length === 0) {
// Check if not filtered package
if (filterStandard &&
[
"License Management App",
"Sales Cloud",
"Sales Insights",
"Salesforce Chatter Dashboards 1.0",
"Salesforce Chatter Dashboards",
"Salesforce Connected Apps",
"Salesforce Mobile Apps",
"Salesforce.com CRM Dashboards",
"SalesforceA Connected Apps",
"Trail Tracker"
].includes(installedPackage.SubscriberPackageName)) {
uxLog("action", this, c.cyan(`Skipped ${installedPackage.SubscriberPackageName} as it is a Salesforce standard package`));
continue;
}
promptPackagesToInstall.push(installedPackage);
}
}
const promptPackagesRes = await prompts({
type: "multiselect",
name: 'value',
message: c.cyanBright('Please select packages to add to your project configuration'),
description: 'Select packages to add to your project configuration for automatic installation during scratch org creation and/or deployments',
choices: promptPackagesToInstall.map((pckg) => {
return {
title: `${pckg.SubscriberPackageName} (${pckg.SubscriberPackageVersionNumber})`,
value: pckg,
};
}),
});
const selectedPackages = promptPackagesRes.value || [];
for (const installedPackage of selectedPackages) {
// Request user about automatic installation during scratch orgs and deployments
const installResponse = await prompts({
type: 'select',
name: 'value',
message: c.cyanBright(`Please select the install configuration for ${c.bold(installedPackage.SubscriberPackageName)}`),
description: 'Configure how this package should be automatically installed during CI/CD operations',
choices: [
{
title: `Deploy automatically ${c.bold(installedPackage.SubscriberPackageName)} on integration/production orgs only`,
value: 'deploy',
},
{
title: `Install automatically ${c.bold(installedPackage.SubscriberPackageName)} on scratch orgs only`,
value: 'scratch',
},
{
title: `Both: Install & deploy automatically ${c.bold(installedPackage.SubscriberPackageName)}`,
value: 'scratch-deploy',
},
{
title: `Do not configure ${c.bold(installedPackage.SubscriberPackageName)} installation / deployment`,
value: 'none',
},
],
});
installedPackage.installOnScratchOrgs = installResponse.value.includes('scratch');
installedPackage.installDuringDeployments = installResponse.value.includes('deploy');
if (installResponse.value !== 'none' && installResponse.value != null) {
projectPackages.push(installedPackage);
updated = true;
}
}
if (updated) {
uxLog("action", this, c.cyan('Updated package configuration in .sfdx-hardis.yml config file'));
const configFile = await setConfig('project', { installedPackages: projectPackages });
WebSocketClient.sendReportFileMessage(`${configFile}#installedPackages`, "Package config in .sfdx-hardis.yml", "report");
}
}
export async function installPackages(installedPackages, orgAlias) {
const packages = installedPackages || [];
elapseStart('Install all packages');
await MetadataUtils.installPackagesOnOrg(packages, orgAlias, this, 'scratch');
elapseEnd('Install all packages');
}
export async function initOrgMetadatas(configInfo, orgUsername, orgAlias, projectScratchDef, debugMode, options = {}) {
// Push or deploy according to config (default: push)
if ((isCI && process.env.CI_SCRATCH_MODE === 'deploy') || process.env.DEBUG_DEPLOY === 'true') {
// if CI, use sf project deploy start to make sure package.xml is consistent
uxLog("action", this, c.cyan(`Deploying project sources to org ${c.green(orgAlias)}...`));
const packageXmlFile = process.env.PACKAGE_XML_TO_DEPLOY || configInfo.packageXmlToDeploy || fs.existsSync('./manifest/package.xml')
? './manifest/package.xml'
: './config/package.xml';
await smartDeploy(packageXmlFile, false, 'NoTestRun', debugMode, this, {
targetUsername: orgUsername,
conn: null,
testClasses: ""
});
}
else {
// Use push for local scratch orgs
uxLog("action", this, c.cyan(`Pushing project sources to org ${c.green(orgAlias)}... (You can see progress in Setup -> Deployment Status)`));
// Suspend sharing calc if necessary
const deferSharingCalc = (projectScratchDef.features || []).includes('DeferSharingCalc');
if (deferSharingCalc) {
// Deploy to permission set allowing to update SharingCalc
await deployMetadatas({
deployDir: path.join(path.join(PACKAGE_ROOT_DIR, 'defaults/utils/deferSharingCalc', '.')),
testlevel: 'NoTestRun',
});
// Assign to permission set allowing to update SharingCalc
try {
const assignCommand = `sf org assign permset --name SfdxHardisDeferSharingRecalc --target-org ${orgUsername}`;
await execSfdxJson(assignCommand, this, {
fail: false, // Do not fail in case permission set already exists
output: false,
debug: debugMode,
});
await execCommand('sf texei:sharingcalc:suspend', this, {
fail: false,
output: true,
debug: debugMode,
});
}
catch (e) {
uxLog("warning", this, c.yellow("Issue while assigning SfdxHardisDeferSharingRecalc PS and suspending Sharing Calc, but it's probably ok anyway"));
uxLog("log", this, c.grey(e.message));
}
}
await forceSourcePush(orgAlias, this, debugMode, options);
// Resume sharing calc if necessary
if (deferSharingCalc) {
await execCommand('sf texei:sharingcalc:resume', this, {
fail: false,
output: true,
debug: debugMode,
});
}
}
}
// Assign permission sets to user
export async function initPermissionSetAssignments(permSets, orgUsername) {
uxLog("action", this, c.cyan('Assigning Permission Sets...'));
for (const permSet of permSets) {
uxLog("action", this, c.cyan(`Assigning ${c.bold(permSet.name || permSet)} to org user ${orgUsername}`));
const assignCommand = `sf org assign permset --name ${permSet.name || permSet} --target-org ${orgUsername}`;
const assignResult = await execSfdxJson(assignCommand, this, {
fail: false,
output: false,
});
if (assignResult?.result?.failures?.length > 0 &&
!assignResult?.result?.failures[0].message.includes('Duplicate')) {
uxLog("error", this, c.red(`Error assigning to ${c.bold(permSet.name || permSet)}\n${assignResult?.result?.failures[0].message}`));
}
}
}
// Run initialization apex scripts
export async function initApexScripts(orgInitApexScripts, orgAlias) {
uxLog("action", this, c.cyan('Running apex initialization scripts...'));
// Build list of apex scripts and check their existence
const initApexScripts = orgInitApexScripts.map((scriptName) => {
if (!fs.existsSync(scriptName)) {
throw new SfError(c.red(`[sfdx-hardis][ERROR] Unable to find script ${scriptName}`));
}
return scriptName;
});
// Process apex scripts
for (const apexScript of initApexScripts) {
const apexScriptCommand = `sf apex run --file "${apexScript}" --target-org ${orgAlias}`;
await execCommand(apexScriptCommand, this, {
fail: true,
output: true,
});
}
}
// Loads data in the org
export async function initOrgData(initDataFolder, orgUsername) {
// Init folder (accounts, etc...)
if (fs.existsSync(initDataFolder)) {
uxLog("action", this, c.cyan('Loading sandbox org initialization data...'));
await importData(initDataFolder, this, {
targetUsername: orgUsername,
});
}
else {
uxLog("action", this, c.cyan(`No initialization data: Define a sfdmu workspace in ${initDataFolder} if you need data in your new sandbox orgs`));
}
// Import data packages
const config = await getConfig('user');
const dataPackages = config.dataPackages || [];
for (const dataPackage of dataPackages) {
if (dataPackage.importInSandboxOrgs === true) {
await importData(dataPackage.dataPath, this, {
targetUsername: orgUsername,
});
}
else {
uxLog("log", this, c.grey(`Skipped import of ${dataPackage.dataPath} as importInSandboxOrgs is not defined to true in .sfdx-hardis.yml`));
}
}
}
export async function getOrgAliasUsername(alias) {
const aliasListRes = await execSfdxJson('sf alias list', this, {
output: false,
fail: false,
});
const matchingItems = aliasListRes?.result?.filter((aliasItem) => aliasItem.alias === alias);
if (matchingItems.length > 0) {
return matchingItems[0].value;
}
return null;
}
// Returns true if the org is a sandbox and not a scratch org
export async function isProductionOrg(targetUsername, options) {
// Use jsforce connection is applicable
if (options?.conn?.username && options.conn.username === targetUsername) {
const orgRes = await soqlQuery('SELECT IsSandbox FROM Organization LIMIT 1', options.conn);
return orgRes.records[0].IsSandbox === false;
}
// Use SF Cli command
const orgQuery = `sf data query --query "SELECT IsSandbox FROM Organization LIMIT 1"` +
(targetUsername ? ` --target-org ${targetUsername}` : "");
const orgQueryRes = await execSfdxJson(orgQuery, this, {
output: false,
debug: options.debugMode || false,
fail: true,
});
const orgRes = orgQueryRes?.result?.records || orgQueryRes.records || [];
return orgRes[0].IsSandbox === false;
}
// Returns true if the org is a sandbox and not a scratch org
export async function isSandbox(options) {
if (options.conn) {
const orgRes = await soqlQuery('SELECT IsSandbox,TrialExpirationDate FROM Organization LIMIT 1', options.conn);
return orgRes.records[0].IsSandbox === true && orgRes.records[0].TrialExpirationDate == null;
}
else {
return options?.scratch === false;
}
}
// Returns true if the org is a scratch org and not a sandbox
export async function isScratchOrg(options) {
if (options.conn) {
const orgRes = await soqlQuery('SELECT IsSandbox,TrialExpirationDate FROM Organization LIMIT 1', options.conn);
return orgRes.records[0].IsSandbox === true && orgRes.records[0].TrialExpirationDate !== null;
}
else {
return options?.scratch === true;
}
}
// Set global variables with connections
let tryTechnical = true;
export async function setConnectionVariables(conn, handleTechnical = false) {
if (conn) {
globalThis.jsForceConn = conn;
}
if (handleTechnical && tryTechnical && !(process.env?.SKIP_TECHNICAL_ORG === "true")) {
try {
const techOrgDisplayCommand = 'sf org display --target-org TECHNICAL_ORG --json --verbose';
const orgInfoResult = await execSfdxJson(techOrgDisplayCommand, this, {
fail: false,
output: false,
debug: false,
});
if (orgInfoResult.result && orgInfoResult.result.connectedStatus === 'Connected') {
const authInfo = await AuthInfo.create({
username: orgInfoResult.result.username,
isDefaultUsername: false,
});
const connTechnical = await Connection.create({
authInfo: authInfo,
connectionOptions: {
instanceUrl: orgInfoResult.result.instanceUrl,
accessToken: orgInfoResult.result.accessToken
}
});
const identity = await connTechnical.identity();
uxLog("log", this, c.grey(`Connected to technical org ${c.green(identity.username)}`));
globalThis.jsForceConnTechnical = connTechnical;
}
}
catch (e) {
uxLog("warning", this, c.yellow(`Unable to connect to technical org: ${e}\nThat's ok, we'll use default org 😊`));
globalThis.jsForceConnTechnical = null;
}
}
tryTechnical = false;
}
const FIND_USER_BY_USERNAME_LIKE_CACHE = {};
export async function findUserByUsernameLike(usernameLike, conn) {
if (FIND_USER_BY_USERNAME_LIKE_CACHE[usernameLike]) {
return FIND_USER_BY_USERNAME_LIKE_CACHE[usernameLike];
}
const users = await conn.query(`SELECT Id, Username FROM User WHERE Username LIKE '${usernameLike}%' AND IsActive=true LIMIT 1`);
if (users.records.length > 0) {
FIND_USER_BY_USERNAME_LIKE_CACHE[usernameLike] = users.records[0];
return users.records[0];
}
return null;
}
// Query methods
export async function listOrgSObjects(connection) {
const sObjectsQuery = `SELECT Id, Label, DeveloperName, QualifiedApiName, DurableId, IsTriggerable, IsCustomizable, IsApexTriggerable
FROM EntityDefinition WHERE IsTriggerable = true AND IsCustomizable = true and IsCustomSetting = false ORDER BY DeveloperName`;
const results = await soqlQuery(sObjectsQuery, connection);
uxLog("log", this, c.grey(`Found ${results.records.length} sObjects.`));
return results;
}
export async function listOrgSObjectsFiltered(connection) {
const sObjectResults = await listOrgSObjects(connection);
const sObjectsDict = {};
for (const record of sObjectResults.records) {
if (!record.DeveloperName.endsWith("__Share") && !record.DeveloperName.endsWith("__ChangeEvent")) {
sObjectsDict[record.DeveloperName] = `${record.Label} (${record.QualifiedApiName})`;
}
}
return sObjectsDict;
}
//# sourceMappingURL=orgUtils.js.map