@sfdx-falcon/command
Version:
Contains SFDX-Falcon flavored extensions to @sfdx/command. Part of the SFDX-Falcon Library.
423 lines • 28.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const command_1 = require("@salesforce/command"); // Required by child classe to create a CLI command
const command_2 = require("@salesforce/command"); // Required by child classe to create a CLI command
const core_1 = require("@salesforce/core"); // Messages library that simplifies using external JSON for string reuse.
const path = require("path"); // Helps resolve local paths at runtime.
// Import SFDX-Falcon Libraries
const validator_1 = require("@sfdx-falcon/validator"); // Library. Contains core validation functions to check that local path values don't have invalid chars.
const validator_2 = require("@sfdx-falcon/validator"); // Library. Contains type validation functions.
// Import SFDX-Falcon Classes & Functions
const debug_1 = require("@sfdx-falcon/debug"); // Class. Internal debugging framework for SFDX-Falcon.
const error_1 = require("@sfdx-falcon/error"); // Class. Extends SfdxError to provide specialized error structures for SFDX-Falcon modules.
const status_1 = require("@sfdx-falcon/status"); // Class. Used to communicate results of SFDX-Falcon code execution at a variety of levels.
const status_2 = require("@sfdx-falcon/status"); // Class. Manages progress notifications inside Falcon.
// Set the File Local Debug Namespace
const dbgNs = '@sfdx-falcon:command:standard';
debug_1.SfdxFalconDebug.msg(`${dbgNs}:`, `Debugging initialized for ${dbgNs}`);
//─────────────────────────────────────────────────────────────────────────────────────────────────┐
/**
* Enum. Defines the possible types of SFDX-Falcon Commands.
*/
//─────────────────────────────────────────────────────────────────────────────────────────────────┘
var SfdxFalconCommandType;
(function (SfdxFalconCommandType) {
SfdxFalconCommandType["STANDARD"] = "STANDARD";
SfdxFalconCommandType["GENERATOR"] = "GENERATOR";
SfdxFalconCommandType["UNKNOWN"] = "UNKNOWN";
})(SfdxFalconCommandType = exports.SfdxFalconCommandType || (exports.SfdxFalconCommandType = {}));
//─────────────────────────────────────────────────────────────────────────────┐
// SFDX Core library has the ability to import a JSON file with message strings
// making it easy to separate logic from static output messages. There are
// two steps required to use this.
//
// Step 1: Tell the Messages framework to look for and import a 'messages'
// directory from inside the root of your project.
// Step 2: Create a Messages object representing a message bundle from inside
// your 'messages' directory. The second param represents the name of
// the JSON file you're trying to load.
//
// Note that messages from @salesforce/command, @salesforce/core, or any library
// that is using the messages framework can also be loaded this way by
// specifying the module name as the first parameter of loadMessages().
//─────────────────────────────────────────────────────────────────────────────┘
core_1.Messages.importMessagesDirectory(__dirname);
const baseMessages = core_1.Messages.loadMessages('@sfdx-falcon/command', 'sfdxFalconCommand');
const errorMessages = core_1.Messages.loadMessages('@sfdx-falcon/command', 'sfdxFalconError');
//─────────────────────────────────────────────────────────────────────────────────────────────────┐
/**
* @abstract
* @class SfdxFalconCommand
* @extends SfdxCommand
* @summary Abstract base class class for building Salesforce CLI commands that use the SFDX-Falcon Library.
* @description Classes that extend SfdxFalconCommand will be able to leverage specialized "command
* and control" capabilities that the SFDX-Library adds on top of standard SFDX
* commands.
* @public
*/
//─────────────────────────────────────────────────────────────────────────────────────────────────┘
class SfdxFalconCommand extends command_2.SfdxCommand {
//───────────────────────────────────────────────────────────────────────────┐
/**
* @constructs SfdxFalconCommand
* @param {any} argv Required. Part of the `oclif` command run process.
* Must be passed **unmodified** to the superclass.
* @param {any} config Required. Part of the `oclif` command run process.
* Must be passed **unmodified** to the superclass.
* @param {SfdxFalconCommandType} [commandType] Optional. Specifies the
* type of command being created. Defaults to `STANDARD`.
* @description Constructs an `SfdxFalconCommand` object.
* @private
*/
//───────────────────────────────────────────────────────────────────────────┘
constructor(argv, config, commandType) {
// Call the parent constructor. DO NOT MODIFY `argv` or `config` variables!
super(argv, config);
// Member vars for ALL debug flags
this.falconDebugFlag = new Array(); // Why?
this.falconDebugErrorFlag = false; // Why?
this.falconDebugSuccessFlag = false; // Why?
this.falconDebugDepthFlag = 2; // Why?
// Set the correct Command Type.
this.commandType = (typeof commandType === 'string' && commandType) ? commandType : SfdxFalconCommandType.STANDARD;
// Set the Command Name.
this.commandName = this.id;
// Initialize an SfdxFalconResult object to store the Result of this COMMAND.
this.commandResult =
new status_1.SfdxFalconResult(this.commandName, "COMMAND" /* COMMAND */, { startNow: true,
bubbleError: false,
bubbleFailure: false }); // Let onSuccess() handle failures (no bubbling)
// Initialize COMMAND Result Detail. Some details will be fleshed out by `sfdxFalconCommandInit()`.
this.commandResultDetail = { commandName: this.commandName,
commandType: this.commandType,
commandFlags: null,
commandArgs: null,
commandExitCode: null,
enabledDebuggers: null };
// Attach the Results Detail object to the COMMAND result then debug it.
this.commandResult.setDetail(this.commandResultDetail);
// Get a reference to `package.json` from the package that owns the dervived class.
this.packageJson = this.getDerivedPackageJson();
}
//───────────────────────────────────────────────────────────────────────────┐
/**
* @function run
* @returns {Promise<AnyJson>}
* @description Recieves the results from a Rejected Promise and processes
* them to settle out the ultimate exit status of this
* COMMAND Result.
* @protected
*/
//───────────────────────────────────────────────────────────────────────────┘
async run() {
// Define function-local debug namespace.
const funcName = `run`;
const dbgNsLocal = `${dbgNs}:${funcName}`;
// Initialize the SfdxFalconCommand (required by ALL classes that extend SfdxFalconCommand).
this.sfdxFalconCommandInit();
// Reflect `this` context.
debug_1.SfdxFalconDebug.obj(`${dbgNsLocal}:this:`, this);
// Run the command logic that's defined by the derived class.
const commandResponse = await this.runCommand()
.then((result) => {
debug_1.SfdxFalconDebug.obj(`${dbgNsLocal}:runCommand:result:`, { result: result });
return this.onSuccess(result);
})
.catch((error) => {
debug_1.SfdxFalconDebug.obj(`${dbgNsLocal}:runCommand:error:`, { error: error });
return this.onError(error);
});
debug_1.SfdxFalconDebug.obj(`${dbgNsLocal}:commandResponse:`, { commandResponse: commandResponse });
// Prepare the Command Response for return to the user.
const preparedResponse = this.prepareResponse(commandResponse);
debug_1.SfdxFalconDebug.obj(`${dbgNsLocal}:preparedResponse:`, { preparedResponse: preparedResponse });
// All done. Give the core SfdxCommand our prepared response.
return preparedResponse;
}
//───────────────────────────────────────────────────────────────────────────┐
/**
* @method buildFinalError
* @param {SfdxFalconError} cmdError Required. Error object used as
* the basis for the "friendly error message" being created
* by this method.
* @returns {SfdxError}
* @description Derived classes should override this method in order to build
* a user-friendly error message that is appropriate to the CLI
* Command that they are implementing. The output of this method
* will **always** be used by the `onError()` method of the
* `SfdxFalconCommand` base class to help communicate the
* end-of-command error state to the user.
* @protected
*/
//───────────────────────────────────────────────────────────────────────────┘
buildFinalError(cmdError) {
// Default behavior is to simply return the provided Error, unmodified.
// Derived classes should override this method to provide more helpful
// errors to users.
return cmdError;
}
//───────────────────────────────────────────────────────────────────────────┐
/**
* @function onError
* @param {any} rejectedPromise Required.
* @param {boolean} [showErrorDebug] Optional. Determines if extended
* debugging output the Error Result can be shown.
* @param {boolean} [promptUser=true] Optional. Determines if the user
* will be prompted to display debug info. If `false`, debug info
* will be shown without requiring additional user input.
* @returns {Promise<void>}
* @description Recieves the results from a Rejected Promise and processes
* them to settle out the ultimate exit status of this
* COMMAND Result.
* @private
*/
//───────────────────────────────────────────────────────────────────────────┘
async onError(rejectedPromise, showErrorDebug = true, promptUser = true) {
// Make sure any rejected promises are wrapped as an ERROR Result.
const errorResult = status_1.SfdxFalconResult.wrapRejectedPromise(rejectedPromise, "UNKNOWN" /* UNKNOWN */, 'RejectedPromise');
// Set the Exit Code for this COMMAND Result (failure==1).
this.commandResultDetail.commandExitCode = 1;
// Add the ERROR Result to the COMMAND Result.
this.commandResult.addChild(errorResult);
// Manually mark the COMMAND Result as an Error (since bubbleError is FALSE)
this.commandResult.error(errorResult.errObj);
// Terminate with Error.
// TODO: Need to add a global parameter to store the "show prompt" setting
await this.terminateWithError(showErrorDebug, promptUser);
}
//───────────────────────────────────────────────────────────────────────────┐
/**
* @function onSuccess
* @param {any} resolvedPromise Required.
* @returns {Promise<void>}
* @description Takes any resolved Promise which should be returned by some
* sort of Asynchronous call (implemented in a derived class)
* that does whatever "work" the CLI Command is meant to do and
* makes sure it's wrapped as an SFDX Falcon Result
* @protected
*/
//───────────────────────────────────────────────────────────────────────────┘
async onSuccess(resolvedPromise) {
// Make sure any resolved promises are wrapped as an SfdxFalconResult.
const successResult = status_1.SfdxFalconResult.wrap(resolvedPromise, "UNKNOWN" /* UNKNOWN */, `onSuccess`);
// Set the Exit Code for this COMMAND Result (success==0).
this.commandResultDetail.commandExitCode = 0;
// Add the SFDX-Falcon Result as a Child of the COMMAND Result.
this.commandResult.addChild(successResult);
// Mark the COMMAND Result as completing successfully.
this.commandResult.success();
// If the "falcondebugsuccess" flag was set, render the COMMAND Result
if (this.falconDebugSuccessFlag) {
this.commandResult.displayResult();
}
// Return the unmodified Resolved Promise so it can be processed by `prepareResponse()`.
return resolvedPromise;
}
//───────────────────────────────────────────────────────────────────────────┐
/**
* @function getDerivedPackageJson
* @returns {JsonMap}
* @description Searches the package install location of whatever code has
* implemented a class that's derived from `SfdxFalconCommand`
* for that package's manifest (its `package.json` file), and
* loads it as a `JsonMap` via Node's `require()` method.
* @private
*/
//───────────────────────────────────────────────────────────────────────────┘
getDerivedPackageJson() {
// Set function-local debug namespace.
const funcName = `getDerivedPackageJson`;
const dbgNsLocal = `${dbgNs}:${funcName}`;
// Debug the filenames of this module and its parent+grandparent modules.
// Any errors running this debug code mean that we won't be able to get the
// package.json from the derived class. In that case, just return NULL.
try {
debug_1.SfdxFalconDebug.str(`${dbgNsLocal}:module.filename:`, `${module.filename}`);
debug_1.SfdxFalconDebug.str(`${dbgNsLocal}:module.parent.filename:`, `${module.parent.filename}`);
debug_1.SfdxFalconDebug.str(`${dbgNsLocal}:module.parent.parent.filename:`, `${module.parent.parent.filename}`);
}
catch (parentModuleError) {
debug_1.SfdxFalconDebug.obj(`${dbgNsLocal}:parentModuleError:`, parentModuleError);
return null;
}
// Initialize Package JSON to NULL. If nothing is found, this is what will be returned.
let packageJson = null;
// Set the parameters for the search we're about to run.
const fileToCheck = 'package.json';
let pathToCheck = path.dirname(module.parent.parent.filename);
let loopCount = 0;
const maxLoops = 10;
// Starting from the Path to Check, walk backwards up the path until the desired file is found.
do {
loopCount++;
debug_1.SfdxFalconDebug.str(`${dbgNsLocal}:loopCount:`, loopCount.toString());
try {
debug_1.SfdxFalconDebug.str(`${dbgNsLocal}:pathToCheck:ATTEMPT:`, pathToCheck);
packageJson = require(path.join(pathToCheck, fileToCheck));
debug_1.SfdxFalconDebug.str(`${dbgNsLocal}:pathToCheck:SUCCESS:`, pathToCheck);
debug_1.SfdxFalconDebug.obj(`${dbgNsLocal}:packageJson:`, packageJson);
break;
}
catch (requireError) {
debug_1.SfdxFalconDebug.str(`${dbgNsLocal}:pathToCheck:FAILED:`, pathToCheck);
pathToCheck = path.resolve(pathToCheck, '../');
}
} while (packageJson === null && loopCount < maxLoops);
// Return the Package JSON that we found. If nothing was found, NULL will be returned.
return packageJson;
}
//───────────────────────────────────────────────────────────────────────────┐
/**
* @function sfdxFalconCommandInit
* @returns {void}
* @description Initializes various SfdxFalconCommand structures.
* @private
*/
//───────────────────────────────────────────────────────────────────────────┘
sfdxFalconCommandInit() {
// Define function-local debug namespace.
const funcName = `sfdxFalconCommandInit`;
const dbgNsLocal = `${dbgNs}:${funcName}`;
// Initialize the command response.
this.commandResponse = null;
// Read the inocming values for all COMMON FLAGS. (not all of these will have values)
this.outputDirectory = path.resolve(this.flags.outputdir || '.');
this.projectDirectory = path.resolve(this.flags.projectdir || '.');
this.sourceDirectory = path.resolve(this.flags.sourcedir || '.');
this.targetDirectory = path.resolve(this.flags.targetdir || '.');
this.recipeFile = this.flags.recipefile || '';
this.configFile = this.flags.configfile || '';
this.extendedOptions = JSON.parse((this.flags.extendedOptions || '{}'));
// Read the incoming values for all COMMON ARGS. (not all of these will have values)
this.gitRemoteUri = this.args.GIT_REMOTE_URI || '';
this.gitCloneDirectory = this.args.GIT_CLONE_DIR || '';
// Read the incoming values for all DEBUG flags.
this.falconDebugFlag = this.flags.falcondebug || [];
this.falconDebugErrorFlag = this.flags.falcondebugerror || false;
this.falconDebugSuccessFlag = this.flags.falcondebugsuccess || false;
this.falconDebugDepthFlag = this.flags.falcondebugdepth || 2;
// Specify the top-level SFDX-Falcon debugger namespaces to enable.
const enabledDebuggers = new Array();
// Build an array of the debugger namespaces to enable.
for (const debugNamespace of this.falconDebugFlag) {
enabledDebuggers.push(`${debugNamespace.trim()}`);
}
if (this.falconDebugErrorFlag)
enabledDebuggers.push('FALCON_ERROR');
if (this.falconDebugSuccessFlag)
enabledDebuggers.push('FALCON_SUCCESS');
// Enable the specified debuggers.
debug_1.SfdxFalconDebug.enableDebuggers(enabledDebuggers, this.falconDebugDepthFlag);
// Flesh out the details of the COMMAND Result then debug it.
this.commandResultDetail.commandFlags = this.flags;
this.commandResultDetail.commandArgs = this.args,
this.commandResultDetail.enabledDebuggers = enabledDebuggers;
this.commandResult.debugResult(`After initialization in constructor`, `${dbgNsLocal}`);
// Perform validation of common flags and args.
if (validator_1.CoreValidator.validateLocalPath(this.outputDirectory) === false) {
throw new Error(errorMessages.getMessage('errInvalidDirectory', ['Output ']));
}
if (validator_1.CoreValidator.validateLocalPath(this.projectDirectory) === false) {
throw new Error(errorMessages.getMessage('errInvalidDirectory', ['Project ']));
}
if (validator_1.CoreValidator.validateLocalPath(this.targetDirectory) === false) {
throw new Error(errorMessages.getMessage('errInvalidDirectory', ['Target ']));
}
}
//───────────────────────────────────────────────────────────────────────────┐
/**
* @method prepareResponse
* @param {unknown} finalOutput Required. The ouput that came back
* from the `runCommand()` method, after being processed by
* either `onSuccess()` or `onFailure()`.
* @description Given the output returned by the `runCommand()` method and
* processed by either the `onSuccess()` or `onFailure()` methods,
* prepares the final output which will be returned from the
* `run()` method.
* @private
*/
//───────────────────────────────────────────────────────────────────────────┘
prepareResponse(finalOutput) {
// TODO: Implement this method.
return finalOutput;
}
//───────────────────────────────────────────────────────────────────────────┐
/**
* @method terminateWithError
* @param {boolean} [showErrorDebug=true] Optional. Determines if
* extended debugging output for the terminating Error can be shown.
* @param {boolean} [promptUser=true] Optional. Determines if the user
* will be prompted to display debug info. If `false`, debug info
* will be shown without requiring additional user input.
* @description Kills all ongoing async code (ie. Progress Notifications) and
* possibly renders an Error Debug before throwing an `SfdxError`
* so that the CLI can present user-friendly error info.
* @private
*/
//───────────────────────────────────────────────────────────────────────────┘
async terminateWithError(showErrorDebug = true, promptUser = true) {
// Make sure any outstanding notifications are killed.
status_2.TaskStatus.killAll();
// Make sure that an SfdxFalconResult object was passed to us.
if ((this.commandResult instanceof status_1.SfdxFalconResult) === false) {
throw new Error('ERROR_X01: An unexpected fatal error has occured');
}
// Make sure that the SfdxFalconResult object comes to us with a contained SfdxFalconError Object.
if ((this.commandResult.errObj instanceof error_1.SfdxFalconError) === false) {
throw new Error('ERROR_X02: An unexpected fatal error has occured');
}
// Run the "Display Error Debug Info" process. This may prompt the user to view extended debug info.
await this.commandResult.displayErrorDebugInfo(showErrorDebug, promptUser);
// Create a "Final Error" based on the COMMAND Result's Error object.
let finalError = null;
try {
finalError = this.buildFinalError(this.commandResult.errObj);
if (validator_2.TypeValidator.isInvalidInstance(finalError, Error)) {
finalError = new error_1.SfdxFalconError(`${this.commandName} failed. See error details for additional information.`, `FatalCommandError`, `${dbgNs}:terminateWithError`, null, { devWarning: `Warning: The developer of this plugin did not properly implement the buildFinalError() method. If you are the developer, please fix this.`,
rawErrorObj: this.commandResult.errObj,
finalErrorObj: finalError });
}
}
catch (errorBuilderError) {
finalError = new error_1.SfdxFalconError(`${this.commandName} failed. See error details for additional information.`, `FatalCommandError`, `${dbgNs}:terminateWithError`, null, { devWarning: `Warning: The developer of this plugin did not properly implement the buildFinalError() method. If you are the developer, please fix this.`,
errorBuilderError: errorBuilderError,
rawErrorObj: this.commandResult.errObj,
finalErrorObj: finalError });
}
// Throw the Final Error.
throw finalError;
}
}
exports.SfdxFalconCommand = SfdxFalconCommand;
//───────────────────────────────────────────────────────────────────────────┐
// Define the baseline set of custom FLAGS used by all SFDX-Falcon commands.
// --FALCONDEBUG Command should run in DEBUG mode.
// --FALCONDEBUGERROR Command should run in ERROR DEBUG mode.
// --FALCONDEBUGSUCCESS Command should run in SUCCESS DEBUG mode.
// --FALCONDEBUGDEPTH Object inspection depth when debug is rendered.
//───────────────────────────────────────────────────────────────────────────┘
SfdxFalconCommand.falconBaseflagsConfig = {
falcondebug: command_1.flags.array({
description: baseMessages.getMessage('falcondebug_FlagDescription'),
required: false,
hidden: false,
default: []
}),
falcondebugerror: command_1.flags.boolean({
description: baseMessages.getMessage('falcondebugerror_FlagDescription'),
required: false,
hidden: false
}),
falcondebugsuccess: command_1.flags.boolean({
description: baseMessages.getMessage('falcondebugsuccess_FlagDescription'),
required: false,
hidden: false
}),
falcondebugdepth: command_1.flags.number({
description: baseMessages.getMessage('falcondebugdepth_FlagDescription'),
required: false,
hidden: false,
default: 2
})
};
//# sourceMappingURL=sfdx-falcon-command.js.map