UNPKG

sfdx-node

Version:

Utility to wrap the Salesforce CLI for use in NodeJS

179 lines (170 loc) 6.63 kB
const _ = require('lodash'); const Promise = require('bluebird'); const { getAllSFDXCommands } = require('./lib/read-plugins'); let sfdxErrors = []; let hookCounter = 0; const sfdxApi = {}; const realStdoutWrite = process.stdout.write; const realStderrWrite = process.stderr.write; // Catch errors thrown by Salesforce CLI commands process.on('cmdError', errObj => { sfdxErrors.push(errObj); }); /** * Restore the original stdout and stderr write methods. */ const unhookStd = () => { process.stdout.write = realStdoutWrite; process.stderr.write = realStderrWrite; }; /** * Method only returns true if input value is true, boolean or string (case-insensitive), e.g. true, True, TRUE. * Any other value is considered as falsy. * @param {*} inputValue Input value to convert to boolean */ const getBooleanValue = (inputValue) => { // Return as is, if input value is already boolean if (_.isBoolean(inputValue)) { return inputValue; } // Return true if the the input is a string 'true', in whatever case. if (typeof inputValue === 'string' && _.toLower(inputValue) === 'true') { return true; } return false; }; /** * Takes input objects flags & opts, and converts them to argument array as accepted by Salesforce CLI commands * @param {Object} flags Command parameters * @param {Object} opts Special command arguments accepted by a few specific commands, such as force:alias:set */ const transformArgs = (flags = {}, opts = {}) => { const argsObj = { argv: [], quiet: true, rejectOnError: false, }; _.forEach(flags, (flagValue, flagName) => { // convert all parameter names to lower case flagName = _.toLower(flagName); if (flagName === '_quiet') { // Determine if output needs to be suppressed argsObj.quiet = getBooleanValue(flagValue); } else if (flagName === '_rejectonerror') { // Determine if we need to reject in case the Salesforce CLI command fails argsObj.rejectOnError = getBooleanValue(flagValue); } else { // For parameters with boolean values, convert them to a single flag argument (without any value) and push into the args array if (_.isBoolean(flagValue)) { if (flagValue) { argsObj.argv.push(`--${flagName}`); } } else { // For parameters with non-boolean values, push parameter name and value as string into the args array argsObj.argv.push(`--${flagName}`, `${flagValue}`); } } }); // If available, push the special commnand parameters into the args array if (opts && _.isArray(opts.args)) { argsObj.argv = _.concat(argsObj.argv, opts.args); } return argsObj; }; /** * Creates a custom function for a command which accepts command parameters as function arguments. This custom * function actually executes the Salesforce CLI command. * @param {*} cmdId Command ID, e.g. force:alias:set * @param {*} cmdName Command Name as exported by the command module, e.g. AliasSetCommand * @param {*} cmdFile Command module file */ const _createCommand = (cmdId, cmdName, cmdFile) => (flags, opts) => new Promise((resolve, reject) => { const cmdModule = require(cmdFile); let cmd = cmdModule[cmdName] || cmdModule.default; if (!cmd) { const exportedKeys = _.keys(cmdModule); // console.log(exportedKeys); if (exportedKeys.length) { _.forEach(exportedKeys, (key) => { // console.log(key, typeof cmdModule[key], typeof cmdModule[key].run); if (typeof cmdModule[key] === 'function' && typeof cmdModule[key].run === 'function') { cmd = cmdModule[key]; return false; } return true; }); } else if (typeof cmdModule === 'function' && typeof cmdModule.run === 'function') { // console.log(typeof cmdModule, typeof cmdModule.run); cmd = cmdModule; } else { return reject(new Error(`Unable to load command ${cmdId}`)); } } // console.log(typeof cmd, typeof cmd.run); cmd.id = cmdId; const cmdArgs = transformArgs(flags, opts); let currentHookFlag = false; // If output needs to be suppressed, hook into the stdout and stderr streams and increase the counter if (cmdArgs.quiet) { const hookStd = require('hook-std'); hookStd(() => { }); hookCounter++; currentHookFlag = true; } // Empty error array before each SFDX call so that the logs from one command execution do not impact the subsequent commands sfdxErrors = []; cmd.run(cmdArgs.argv) .then((sfdxResult) => { // If output was suppressed, decrease the counter. If counter reaches zero, restore the original stdout and stderr streams. if (cmdArgs.quiet && currentHookFlag && hookCounter > 0) { hookCounter--; currentHookFlag = false; if (hookCounter === 0) { unhookStd(); } } // In case there were any errors and rejection is required, throw the errors if (cmdArgs.rejectOnError && sfdxErrors.length) { throw sfdxErrors; } // Revert exitCode set by SFDX as we don't want CI scripts to exit due to this if (process.exitCode) { process.exitCode = 0; } resolve(sfdxResult); }) .catch((sfdxErr) => { // If output was suppressed, decrease the counter. If counter reaches zero, restore the original stdout and stderr streams. if (cmdArgs.quiet && currentHookFlag && hookCounter > 0) { hookCounter--; currentHookFlag = false; if (hookCounter === 0) { unhookStd(); } } // Revert exitCode set by SFDX as we don't want CI scripts to exit due to this if (process.exitCode) { process.exitCode = 0; } // Ensure that the error is thrown as an array of plain JS objects, in format => { message: "some error", stack: "stack trace for the error" } const errors = require('./lib/process-errors'); reject(errors.processAllErrors(sfdxErr)); }); }); /** * Loops through all the commands and creates progrmmatically executable version of each Salesforce CLI command. */ const buildAllCommands = () => { const commands = getAllSFDXCommands(); _.forEach(commands, (cmdObj) => { const { namespace, topic, methodName, commandId, commandExportedClassName, commandFile } = cmdObj; let commandPath; if (methodName) { commandPath = `${namespace}.${topic}.${methodName}`; } else { commandPath = `${namespace}.${topic}`; } _.set(sfdxApi, commandPath, _createCommand(commandId, commandExportedClassName, commandFile)); }); }; buildAllCommands(); // console.log(sfdxApi); module.exports = sfdxApi;