UNPKG

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
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