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
200 lines • 8.89 kB
JavaScript
import c from 'chalk';
import fs from 'fs-extra';
import moment from 'moment';
import * as os from 'os';
import * as path from 'path';
import { getConfig, setConfig } from '../../config/index.js';
import { createTempDir, execSfdxJson, isCI, uxLog } from './index.js';
import { LocalTestProvider } from '../keyValueProviders/localtest.js';
import { SfError } from '@salesforce/core';
import { prompts } from './prompts.js';
import { SalesforceProvider } from '../keyValueProviders/salesforce.js';
let keyValueProvider;
export async function getPoolConfig() {
const config = await getConfig('branch');
return config.poolConfig || null;
}
export async function hasPoolConfig() {
const poolConfig = await getPoolConfig();
return poolConfig !== null;
}
// Read scratch org pool remote storage
export async function getPoolStorage(options = {}) {
const providerInitialized = await initializeProvider(options);
if (providerInitialized) {
return await keyValueProvider.getValue(null);
}
return null;
}
// Write scratch org pool remote storage
export async function setPoolStorage(value, options = {}) {
const providerInitialized = await initializeProvider(options);
if (providerInitialized) {
uxLog("other", this, '[pool] ' + c.grey(`Updating pool storage value...`));
const valueSetRes = await keyValueProvider.setValue(null, value);
uxLog("other", this, '[pool] ' + c.grey(`Updated pool storage value.`));
return valueSetRes;
}
return null;
}
// Update ActiveScratchOrgInfo
export async function updateActiveScratchOrg(scratchOrg, keyValues, options = {}) {
const providerInitialized = await initializeProvider(options);
if (providerInitialized) {
return await keyValueProvider.updateActiveScratchOrg(scratchOrg, keyValues);
}
return null;
}
let updatingPool = false;
export async function addScratchOrgToPool(scratchOrg, options = { position: 'last' }) {
if (updatingPool === true) {
uxLog("log", this, c.grey('Already updating scratch org pool. Try again in 2000 ms.'));
await new Promise((resolve) => setTimeout(resolve, 2000));
return await addScratchOrgToPool(scratchOrg, options);
}
else {
updatingPool = true;
const res = await executeAddScratchOrgToPool(scratchOrg, options);
updatingPool = false;
return res;
}
}
// Write scratch org pool remote storage
async function executeAddScratchOrgToPool(scratchOrg, options = { position: 'last' }) {
const poolStorage = await getPoolStorage(options);
// Valid scratch orgs
if (scratchOrg.status === 0) {
const scratchOrgs = poolStorage.scratchOrgs || [];
if (options.position === 'first') {
scratchOrgs.push(scratchOrg);
}
else {
scratchOrgs.unshift(scratchOrg);
}
poolStorage.scratchOrgs = scratchOrgs;
await setPoolStorage(poolStorage, options);
await updateActiveScratchOrg(scratchOrg, {
Description: `Added to pool by ${os.userInfo().username} on ${moment().format('YYYYMMDD_hhmm')}`,
});
}
else {
// Store scratch creation errors
/*
const scratchOrgErrors = poolStorage.scratchOrgErrors || [];
scratchOrgErrors.push(scratchOrg);
poolStorage.scratchOrgErrors = scratchOrgErrors;
await setPoolStorage(poolStorage, options);
*/
uxLog("other", this, '[pool] ' + c.red('Scratch org creation error:\n' + JSON.stringify(scratchOrg)));
}
}
// Fetch a scratch org
export async function fetchScratchOrg(options) {
try {
const scratchOrg = await tryFetchScratchOrg(options);
return scratchOrg;
}
catch (e) {
uxLog("warning", this, c.yellow(`[pool] Unable to fetch scratch org from pool. That's sad because it's faster !\nError: ${e.message}`));
return null;
}
}
export async function tryFetchScratchOrg(options) {
const poolStorage = await getPoolStorage(options);
if (poolStorage === null) {
uxLog("warning", this, '[pool] ' +
c.yellow('No valid scratch pool storage has been reachable. Consider fixing the scratch pool config and auth'));
return null;
}
uxLog("other", this, '[pool] ' + c.cyan('Trying to fetch a scratch org from the scratch orgs pool to improve performance.'));
const scratchOrgs = poolStorage.scratchOrgs || [];
if (scratchOrgs.length > 0) {
const scratchOrg = scratchOrgs.shift();
await updateActiveScratchOrg(scratchOrg, {
Description: `Fetched by ${os.userInfo().username} on ${moment().format('YYYYMMDD_hhmm')}`,
});
// Remove and save
poolStorage.scratchOrgs = scratchOrgs;
await setPoolStorage(poolStorage, options);
// Authenticate to scratch org
uxLog("action", this, '[pool] ' + c.cyan('Authenticating to scratch org from pool...'));
const authTempDir = await createTempDir();
const tmpAuthFile = path.join(authTempDir, 'authFile.txt');
const authFileContent = scratchOrg.scratchOrgSfdxAuthUrl || (scratchOrg.authFileJson ? JSON.stringify(scratchOrg.authFileJson) : null);
if (authFileContent == null) {
uxLog("warning", this, c.yellow(`[pool] Unable to authenticate to org ${scratchOrg.scratchOrgAlias}: ${scratchOrg.scratchOrgUsername} (missing sfdxAuthUrl)`));
return null;
}
await fs.writeFile(tmpAuthFile, authFileContent, 'utf8');
const authCommand = `sf org login sfdx-url --sfdx-url-file ${tmpAuthFile} --set-default --alias ${scratchOrg.scratchOrgAlias}`;
const authRes = await execSfdxJson(authCommand, this, { fail: false, output: false });
if (authRes.status !== 0) {
uxLog("warning", this, c.yellow(`[pool] Unable to authenticate to org ${scratchOrg.scratchOrgAlias}: ${scratchOrg.scratchOrgUsername}\n${c.grey(JSON.stringify(authRes))}`));
return null;
}
// Remove temp auth file
await fs.unlink(tmpAuthFile);
// Store sfdxAuthUrl for next step if we are in CI
if (isCI) {
await setConfig('user', { sfdxAuthUrl: authFileContent });
}
// Display org URL
const openRes = await execSfdxJson(`sf org open --url-only --target-org ${scratchOrg.scratchOrgAlias}`, this, {
fail: false,
output: false,
});
uxLog("action", this, c.cyan(`Open scratch org with URL: ${c.green(openRes?.result?.url)}`));
// Return scratch org
await updateActiveScratchOrg(scratchOrg, {
Description: `Authenticated by ${os.userInfo().username} on ${moment().format('YYYYMMDD_hhmm')}`,
});
return scratchOrg;
}
uxLog("warning", this, '[pool]' +
c.yellow(`No scratch org available in scratch org pool. You may increase ${c.white('poolConfig.maxScratchOrgsNumber')} or schedule call to ${c.white('sf hardis:scratch:pool:refresh')} more often in CI`));
return null;
}
export async function listKeyValueProviders() {
return [SalesforceProvider, LocalTestProvider].map((cls) => new cls());
}
async function initializeProvider(options) {
if (keyValueProvider) {
return true;
}
const poolConfig = await getPoolConfig();
if (poolConfig.storageService) {
keyValueProvider = await instantiateProvider(poolConfig.storageService);
try {
await keyValueProvider.initialize(options);
return true;
}
catch (e) {
// in CI, we should always be able to initialize the provider
if (isCI) {
throw e;
}
uxLog("other", this, '[pool] ' + c.grey('Provider initialization error: ' + e.message));
// If manual, let's ask the user if he/she has credentials to input
const resp = await prompts({
type: 'confirm',
message: 'Scratch org pool credentials are missing, do you want to configure them ?',
description: 'Set up authentication credentials required to access the scratch org pool service',
});
if (resp.value === true) {
await keyValueProvider.userAuthenticate(options);
await keyValueProvider.initialize(options);
return true;
}
return false;
}
}
}
export async function instantiateProvider(storageService) {
const providerClasses = await listKeyValueProviders();
const providerClassRes = providerClasses.filter((cls) => cls.name === storageService);
if (providerClassRes.length === 0) {
throw new SfError(c.red('Unable to find class for storage provider ' + storageService));
}
return providerClassRes[0];
}
//# sourceMappingURL=poolUtils.js.map