UNPKG

@zowe/imperative

Version:
875 lines (874 loc) 53.2 kB
"use strict"; /* * This program and the accompanying materials are made available under the terms of the * Eclipse Public License v2.0 which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-v20.html * * SPDX-License-Identifier: EPL-2.0 * * Copyright Contributors to the Zowe Project. * */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CommandProcessor = void 0; const CommandUtils_1 = require("./utils/CommandUtils"); const messages_1 = require("../../messages"); const SharedOptions_1 = require("./utils/SharedOptions"); const error_1 = require("../../error"); const SyntaxValidator_1 = require("./syntax/SyntaxValidator"); const CommandResponse_1 = require("./response/CommandResponse"); const logger_1 = require("../../logger"); const expect_1 = require("../../expect"); const util_1 = require("util"); const utilities_1 = require("../../utilities"); const nodePath = require("path"); const os = require("os"); const ChainedHandlerUtils_1 = require("./ChainedHandlerUtils"); const constants_1 = require("../../constants"); const CliUtils_1 = require("../../utilities/src/CliUtils"); const WebHelpManager_1 = require("./help/WebHelpManager"); const ConfigUtils_1 = require("../../config/src/ConfigUtils"); const ConfigConstants_1 = require("../../config/src/ConfigConstants"); const Censor_1 = require("../../censor/src/Censor"); const EnvironmentalVariableSettings_1 = require("../../imperative/src/env/EnvironmentalVariableSettings"); const ConnectionPropsForSessCfg_1 = require("../../rest/src/session/ConnectionPropsForSessCfg"); /** * The command processor for imperative - accepts the command definition for the command being issued (and a pre-built) * response object and validates syntax, loads profiles, instantiates handlers, & invokes the handlers. * @export * @class CommandProcessor */ class CommandProcessor { /** * Creates an instance of CommandProcessor. * @param {ICommandProcessorParms} params - See the interface for details. * @memberof CommandProcessor */ constructor(params) { /** * Imperative Logger instance for logging from the command processor. * @private * @type {Logger} * @memberof CommandProcessor */ this.mLogger = logger_1.Logger.getImperativeLogger(); expect_1.ImperativeExpect.toNotBeNullOrUndefined(params, `${CommandProcessor.ERROR_TAG} No parameters supplied to constructor.`); this.mDefinition = params.definition; expect_1.ImperativeExpect.toNotBeNullOrUndefined(this.mDefinition, `${CommandProcessor.ERROR_TAG} No command definition supplied.`); this.mFullDefinition = params.fullDefinition == null ? this.mDefinition : params.fullDefinition; this.mHelpGenerator = params.helpGenerator; expect_1.ImperativeExpect.toNotBeNullOrUndefined(this.mHelpGenerator, `${CommandProcessor.ERROR_TAG} No help generator supplied.`); if (this.mDefinition.type === "command" && this.mDefinition.chainedHandlers == null) { expect_1.ImperativeExpect.keysToBeDefinedAndNonBlank(this.mDefinition, ["handler"], `${CommandProcessor.ERROR_TAG} ` + `The definition supplied is of type "command", ` + `but no handler was specified.`); } this.mCommandRootName = params.rootCommandName; this.mCommandLine = params.commandLine; this.mEnvVariablePrefix = params.envVariablePrefix; this.mPromptPhrase = params.promptPhrase; this.mConfig = params.config; this.mDaemonContext = params.daemonContext; expect_1.ImperativeExpect.keysToBeDefinedAndNonBlank(params, ["promptPhrase"], `${CommandProcessor.ERROR_TAG} No prompt phrase supplied.`); expect_1.ImperativeExpect.keysToBeDefinedAndNonBlank(params, ["rootCommandName"], `${CommandProcessor.ERROR_TAG} No root command supplied.`); expect_1.ImperativeExpect.keysToBeDefinedAndNonBlank(params, ["envVariablePrefix"], `${CommandProcessor.ERROR_TAG} No ENV variable prefix supplied.`); // TODO - check if the command definition passed actually exists within the full command definition tree passed } /** * Accessor for the root command name * @readonly * @type {string} * @memberof CommandProcessor */ get rootCommand() { return this.mCommandRootName; } /** * Accessor for the command line * @readonly * @type {string} * @memberof CommandProcessor */ get commandLine() { return this.mCommandLine; } /** * Accessor for the environment variable prefix * @readonly * @type {string} * @memberof CommandProcessor */ get envVariablePrefix() { return this.mEnvVariablePrefix; } /** * Accessor for the prompt phrase * @readonly * @type {string} * @memberof CommandProcessor */ get promptPhrase() { return this.mPromptPhrase; } /** * Accessor for the help generator passed to this instance of the command processor * @readonly * @type {IHelpGenerator} * @memberof CommandProcessor */ get helpGenerator() { return this.mHelpGenerator; } /** * Accessor for the app config * @readonly * @type {Config} * @memberof CommandProcessor */ get config() { return this.mConfig; } /** * Obtain a copy of the command definition * @return {ICommandDefinition}: The Zowe Commands definition document. */ get definition() { return JSON.parse(JSON.stringify(this.mDefinition)); } /** * Obtain a copy of the command definition * @return {ICommandDefinition}: The Zowe Commands definition document. */ get fullDefinition() { return JSON.parse(JSON.stringify(this.mFullDefinition)); } /** * Generates the help for the command definition passed. * @param {CommandResponse} response - The command response object * @memberof CommandProcessor */ help(response) { expect_1.ImperativeExpect.toNotBeNullOrUndefined(response, `${CommandProcessor.ERROR_TAG} help(): No command response object supplied.`); this.log.info(`Building help text for command "${this.definition.name}"...`); const help = this.helpGenerator.buildHelp(); response.data.setObj(help); response.console.log(Buffer.from(help)); response.data.setMessage(`The help was constructed for command: ${this.mDefinition.name}.`); return this.finishResponse(response); } /** * Generates the help for the command definition passed. * @param {string} inContext - Name of page for group/command to jump to * @param {CommandResponse} response - The command response object * @memberof CommandProcessor */ webHelp(inContext, response) { expect_1.ImperativeExpect.toNotBeNullOrUndefined(response, `${CommandProcessor.ERROR_TAG} help(): No command response object supplied.`); this.log.info(`Launching web help for command "${this.definition.name}"...`); try { WebHelpManager_1.WebHelpManager.instance.openHelp(inContext, response); response.data.setMessage(`The web help was launched for command: ${this.definition.name}`); } catch (err) { this.log.error(err); response.console.error(err); response.failed(); response.setError({ msg: err.message, stack: err.stack }); } return this.finishResponse(response); } /** * Validates the input arguments/options for the command (Performs additional validation outside of what Yargs * already provides - ideally, we would like to maintain control over all errors and messages for consistency). * @param {ICommandArguments} commandArguments: The input command arguments from the command line. * @param {CommandResponse} responseObject: Response object to print. * @return {Promise<ICommandValidatorResponse>}: Promise to be fulfilled when validation is complete. */ validate(commandArguments, responseObject) { return __awaiter(this, void 0, void 0, function* () { expect_1.ImperativeExpect.toNotBeNullOrUndefined(commandArguments, `${CommandProcessor.ERROR_TAG} validate(): No command arguments supplied.`); expect_1.ImperativeExpect.toNotBeNullOrUndefined(responseObject, `${CommandProcessor.ERROR_TAG} validate(): No response object supplied.`); this.log.info(`Performing syntax validation for command "${this.definition.name}"...`); return new SyntaxValidator_1.SyntaxValidator(this.mDefinition, this.mFullDefinition).validate(responseObject, commandArguments); }); } /** * Invoke the command handler. Locates and requires the module specified by the command definition document, * creates a new object, creates a response object, and invokes the handler. The handler is responsible for * fulfilling the promise when complete. * @param {IInvokeCommandParms} params - The parameters passed to the invoke function * @return {Promise<ICommandResponse>} - The promise that is fulfilled. A rejection if the promise indicates a * truly exceptional condition (should not occur). */ invoke(params) { return __awaiter(this, void 0, void 0, function* () { var _a, _b; // Ensure parameters are correct expect_1.ImperativeExpect.toNotBeNullOrUndefined(params, `${CommandProcessor.ERROR_TAG} invoke(): No parameters supplied.`); expect_1.ImperativeExpect.toNotBeNullOrUndefined(params.arguments, `${CommandProcessor.ERROR_TAG} invoke(): No command arguments supplied.`); params.responseFormat = params.responseFormat == null ? "default" : params.responseFormat; const responseOptions = ["default", "json"]; expect_1.ImperativeExpect.toBeOneOf(params.responseFormat, responseOptions, `${CommandProcessor.ERROR_TAG} invoke(): Response format must be one of the following: ${responseOptions.join(",")}`); expect_1.ImperativeExpect.toBeAnArray(params.arguments._, `${CommandProcessor.ERROR_TAG} invoke(): The command arguments object supplied does not contain an array of args.`); if (this.definition.chainedHandlers == null) { expect_1.ImperativeExpect.toNotBeNullOrUndefined(this.definition.handler, `${CommandProcessor.ERROR_TAG} invoke(): Cannot invoke the command "${this.definition.name}"` + `. It has no handler and no chained handlers.`); expect_1.ImperativeExpect.toNotBeEqual(this.definition.handler.trim(), "", `${CommandProcessor.ERROR_TAG} invoke(): Cannot invoke the handler for command "${this.definition.name}". The handler is blank.`); } // Set up the censored options Censor_1.Censor.setCensoredOptions({ profiles: (_a = utilities_1.ImperativeConfig.instance.loadedConfig) === null || _a === void 0 ? void 0 : _a.profiles, config: utilities_1.ImperativeConfig.instance.config, commandDefinition: this.definition, commandArguments: params.arguments }); let commandLine = utilities_1.ImperativeConfig.instance.commandLine || this.commandLine; for (const secureArg of Censor_1.Censor.CENSORED_OPTIONS) { let regex; if (secureArg.length > 1) { regex = new RegExp(`--${secureArg} ([^\\s]+)`, "gi"); } else { regex = new RegExp(`-${secureArg} ([^\\s]+)`, "gi"); } if (commandLine.search(regex) >= 0) { if (secureArg.length > 1) { commandLine = commandLine.replace(regex, `--${secureArg} ${Censor_1.Censor.CENSOR_RESPONSE}`); } else { commandLine = commandLine.replace(regex, `-${secureArg} ${Censor_1.Censor.CENSOR_RESPONSE}`); } } } // this.log.info(`post commandLine issued:\n\n${TextUtils.prettyJson(commandLine)}`); // Log the invoke this.log.info(`Invoking command "${this.definition.name}"...`); this.log.info(`Command issued:\n\n${utilities_1.TextUtils.prettyJson(this.rootCommand + " " + commandLine)}`); this.log.trace(`Invoke parameters:\n${(0, util_1.inspect)(params, { depth: null })}`); this.log.trace(`Command definition:\n${(0, util_1.inspect)(this.definition, { depth: null })}`); const prepareResponse = this.constructResponseObject(params); prepareResponse.succeeded(); // Prepare for command processing - load profiles, stdin, etc. let preparedArgs; try { // Build the response object, base args object, and the entire array of options for this command // Assume that the command succeed, it will be marked otherwise under the appropriate failure conditions if (((_b = this.mDaemonContext) === null || _b === void 0 ? void 0 : _b.response) != null) { // NOTE(Kelosky): we adjust `cwd` and do not restore it, so that multiple simultaneous requests from the same // directory will operate without unexpected chdir taking place. Multiple simultaneous requests from different // directories may cause unpredictable results if (this.mDaemonContext.response.cwd != null) { process.chdir(this.mDaemonContext.response.cwd); } // Define environment variables received from daemon if (this.mDaemonContext.response.env != null) { // Delete environment variables that start with CLI prefix for (const envVarName of Object.keys(process.env)) { if (envVarName.startsWith(`${this.mEnvVariablePrefix}_`)) { delete process.env[envVarName]; } } // Load new environment variables for (const [k, v] of Object.entries(this.mDaemonContext.response.env)) { process.env[k] = v; } // Load environment variables from env file utilities_1.EnvFileUtils.resetEnvironmentForApp(); } // reload config for daemon client directory yield utilities_1.ImperativeConfig.instance.config.reload(); } this.log.info(`Preparing (loading profiles, reading stdin, etc.) execution of "${this.definition.name}" command...`); preparedArgs = yield this.prepare(prepareResponse, params.arguments); } catch (prepareErr) { // Indicate that the command has failed prepareResponse.failed(); // Construct the main error header/message const err = `${prepareErr.message || "Internal Error: No cause message present."}`; this.log.error(err); prepareResponse.data.setMessage(err); prepareResponse.console.errorHeader("Command Preparation Failed"); prepareResponse.console.error(err); // Start constructing the error object for the response const impErr = { msg: err }; // If details are present and of type "string", output the additional details if (prepareErr.details != null && prepareErr.details.additionalDetails != null && typeof prepareErr.details.additionalDetails === "string") { prepareResponse.console.errorHeader("Error Details"); prepareResponse.console.error(prepareErr.details.additionalDetails); impErr.additionalDetails = prepareErr.details.additionalDetails; } // Set the error response object and finish the command response prepareResponse.setError(impErr); return this.finishResponse(prepareResponse); } // Recreate the response object with the update params from prepare. params.arguments = preparedArgs; const response = this.constructResponseObject(params); response.succeeded(); // prompt the user for any requested fields if (this.promptPhrase != null) { try { // prompt for any requested positionals if (this.definition.positionals != null && this.definition.positionals.length > 0) { for (const positional of this.definition.positionals) { // convert if positional is an array designated by "..." const positionalName = positional.name.replace("...", ""); // check if value provided if (preparedArgs[positionalName] != null) { // string processing if (typeof preparedArgs[positionalName] === "string" && preparedArgs[positionalName].toUpperCase() === this.promptPhrase.toUpperCase()) { // prompt has been requested for a positional this.log.debug("Prompting for positional %s which was requested by passing the value %s", positionalName, this.promptPhrase); preparedArgs[positionalName] = yield response.console.prompt(`"${positionalName}" Description: ` + `${positional.description}\nPlease enter "${positionalName}":`, { hideText: true, secToWait: 0 }); } // array processing else if (preparedArgs[positionalName] != null && Array.isArray(preparedArgs[positionalName]) && preparedArgs[positionalName][0] != null && typeof preparedArgs[positionalName][0] === "string" && preparedArgs[positionalName][0].toUpperCase() === this.promptPhrase.toUpperCase()) { // prompt has been requested for a positional this.log.debug("Prompting for positional %s which was requested by passing the value %s", preparedArgs[positionalName][0], this.promptPhrase); preparedArgs[positionalName][0] = yield response.console.prompt(`"${positionalName}" Description: ` + `${positional.description}\nPlease enter "${positionalName}":`, { hideText: true, secToWait: 0 }); // prompting enters as string but need to place it in array const array = preparedArgs[positionalName][0].split(" "); preparedArgs[positionalName] = array; } } } } // prompt for any requested --options if (this.definition.options != null && this.definition.options.length > 0) { for (const option of this.definition.options) { // check if value provided if (preparedArgs[option.name] != null) { // string processing if (typeof preparedArgs[option.name] === "string" && preparedArgs[option.name].toUpperCase() === this.promptPhrase.toUpperCase()) { // prompt has been requested for an --option this.log.debug("Prompting for option %s which was requested by passing the value %s", option.name, this.promptPhrase); preparedArgs[option.name] = yield response.console.prompt(`"${option.name}" Description: ` + `${option.description}\nPlease enter "${option.name}":`, { hideText: true, secToWait: 0 }); const camelCase = CliUtils_1.CliUtils.getOptionFormat(option.name).camelCase; preparedArgs[camelCase] = preparedArgs[option.name]; if (option.aliases != null) { for (const alias of option.aliases) { // set each alias of the args object as well preparedArgs[alias] = preparedArgs[option.name]; } } } // array processing else if (Array.isArray(preparedArgs[option.name]) && preparedArgs[option.name][0] != null && typeof preparedArgs[option.name][0] === "string" && preparedArgs[option.name][0].toUpperCase() === this.promptPhrase.toUpperCase()) { // prompt has been requested for an --option this.log.debug("Prompting for option %s which was requested by passing the value %s", option.name, this.promptPhrase); preparedArgs[option.name][0] = yield response.console.prompt(`"${option.name}" Description: ` + `${option.description}\nPlease enter "${option.name}":`, { hideText: true, secToWait: 0 }); const array = preparedArgs[option.name][0].split(" "); preparedArgs[option.name] = array; const camelCase = CliUtils_1.CliUtils.getOptionFormat(option.name).camelCase; preparedArgs[camelCase] = preparedArgs[option.name]; if (option.aliases != null) { for (const alias of option.aliases) { // set each alias of the args object as well preparedArgs[alias] = preparedArgs[option.name]; } } } } } } } catch (e) { const errMsg = `Unexpected prompting error`; const errReason = errMsg + ": " + e.message + e.stack; this.log.error(`Prompting for command "${this.definition.name}" has failed unexpectedly: ${errReason}`); response.data.setMessage(errReason); response.console.errorHeader(errMsg); response.console.error(e.message); response.setError({ msg: errMsg, additionalDetails: e.message }); response.failed(); return this.finishResponse(response); } } // Validate that the syntax is correct for the command let validator; try { validator = yield this.validate(preparedArgs, response); } catch (e) { const errMsg = `Unexpected syntax validation error`; const errReason = errMsg + ": " + e.message; this.log.error(`Validation for command "${this.definition.name}" has failed unexpectedly: ${errReason}`); response.data.setMessage(errReason); response.console.errorHeader(errMsg); response.console.error(e.message); response.setError({ msg: errMsg, additionalDetails: e.message }); response.failed(); return this.finishResponse(response); } // Check if the syntax is valid - if not return immediately. if (!validator.valid) { this.invalidSyntaxNotification(params, response); return this.finishResponse(response); } // Invoke the handler this.log.info(`Invoking process method of handler for "${this.mDefinition.name}" command.`); if (this.definition.handler != null) { // single handler - no chained handlers const handler = this.attemptHandlerLoad(response, this.definition.handler); if (handler == null) { // if the handler load failed return this.finishResponse(response); } const handlerParms = { response, arguments: preparedArgs, positionals: preparedArgs._, definition: this.definition, fullDefinition: this.fullDefinition, stdin: this.getStdinStream() }; try { if (handlerParms.arguments.showInputsOnly) { this.showInputsOnly(handlerParms); } else { yield handler.process(handlerParms); } } catch (processErr) { this.handleHandlerError(processErr, response, this.definition.handler); CliUtils_1.CliUtils.showMsgWhenDeprecated(handlerParms); // Return the failed response to the caller return this.finishResponse(response); } this.log.info(`Handler for command "${this.definition.name}" succeeded.`); response.succeeded(); response.endProgressBar(); CliUtils_1.CliUtils.showMsgWhenDeprecated(handlerParms); // Return the response to the caller return this.finishResponse(response); } else if (this.definition.chainedHandlers != null) { // chained handlers - no single handler const chainedResponses = []; let chainedResponse; let bufferedStdOut = Buffer.from([]); let bufferedStdErr = Buffer.from([]); if (preparedArgs.showInputsOnly) { this.showInputsOnly({ response, arguments: preparedArgs, positionals: preparedArgs._, definition: this.definition, fullDefinition: this.fullDefinition, stdin: this.getStdinStream() }); response.succeeded(); response.endProgressBar(); return this.finishResponse(response); } else { this.log.debug("Attempting to invoke %d chained handlers for command: '%s'", this.definition.chainedHandlers.length, this.definition.name); for (let chainedHandlerIndex = 0; chainedHandlerIndex < this.definition.chainedHandlers.length; chainedHandlerIndex++) { const chainedHandler = this.definition.chainedHandlers[chainedHandlerIndex]; this.log.debug("Loading chained handler '%s' (%d of %d)", chainedHandler.handler, chainedHandlerIndex + 1, this.definition.chainedHandlers.length); const handler = this.attemptHandlerLoad(response, chainedHandler.handler); if (handler == null) { // if the handler load failed this.log.fatal("failed to load a chained handler! aborting chained handler sequence."); return this.finishResponse(response); } this.log.debug("Constructing new response object for handler '%s': silent?: %s. json?: %s", chainedHandler.handler, chainedHandler.silent + "", params.arguments[constants_1.Constants.JSON_OPTION] + ""); chainedResponse = this.constructResponseObject({ arguments: params.arguments, silent: chainedHandler.silent, responseFormat: params.arguments[constants_1.Constants.JSON_OPTION] ? "json" : "default" }); // make sure the new chained response preserves output chainedResponse.bufferStdout(bufferedStdOut); chainedResponse.bufferStderr(bufferedStdErr); try { yield handler.process({ response: chainedResponse, arguments: ChainedHandlerUtils_1.ChainedHandlerService.getArguments(this.mCommandRootName, this.definition.chainedHandlers, chainedHandlerIndex, chainedResponses, preparedArgs, this.log), positionals: preparedArgs._, definition: this.definition, fullDefinition: this.fullDefinition, stdin: this.getStdinStream(), isChained: true }); const builtResponse = chainedResponse.buildJsonResponse(); chainedResponses.push(builtResponse.data); // save the stdout and stderr to pass to the next chained handler (if any) bufferedStdOut = builtResponse.stdout; bufferedStdErr = builtResponse.stderr; } catch (processErr) { this.handleHandlerError(processErr, chainedResponse, chainedHandler.handler); // Return the failed response to the caller return this.finishResponse(chainedResponse); } } } this.log.info(`Chained handlers for command "${this.definition.name}" succeeded.`); response.succeeded(); response.endProgressBar(); // Return the response to the caller return this.finishResponse(chainedResponse); } }); } /** * Print parameters in place for --dry-run in effect * @private * @param {IHandlerParameters} commandParameters * @returns * @memberof CommandProcessor */ showInputsOnly(commandParameters) { var _a, _b, _c, _d, _e, _f; /** * Determine if we should display secure values. If the ENV variable is set to true, * then we will display the secure values. If the ENV variable is set to false, * then we will display the non-secure values. * - GitHub Copilot AI */ let showSecure = false; // ZOWE_SHOW_SECURE_ARGS const SECRETS_ENV = `${utilities_1.ImperativeConfig.instance.envVariablePrefix}${EnvironmentalVariableSettings_1.EnvironmentalVariableSettings.ENV_SHOW_SECURE_SUFFIX}`; const env = EnvironmentalVariableSettings_1.EnvironmentalVariableSettings.read(utilities_1.ImperativeConfig.instance.envVariablePrefix).showSecureArgs.value.toUpperCase(); if (env === "TRUE" || env === "1") { showSecure = true; } /** * Begin building response object */ const showInputsOnly = { commandValues: {} }; const sessCfg = {}; ConnectionPropsForSessCfg_1.ConnectionPropsForSessCfg.resolveSessCfgProps(sessCfg, commandParameters.arguments); showInputsOnly.authenticationType = sessCfg.type; showInputsOnly.authTypeOrder = sessCfg.authTypeOrder.toString(); /** * Append profile information */ showInputsOnly.requiredProfiles = (_a = commandParameters.definition.profile) === null || _a === void 0 ? void 0 : _a.required; showInputsOnly.optionalProfiles = (_b = commandParameters.definition.profile) === null || _b === void 0 ? void 0 : _b.optional; showInputsOnly.locations = []; const configSecureProps = []; /** * Need to get the secure properties from the config */ const combinedProfiles = [...(_c = showInputsOnly.requiredProfiles) !== null && _c !== void 0 ? _c : [], ...(_d = showInputsOnly.optionalProfiles) !== null && _d !== void 0 ? _d : []]; combinedProfiles.forEach((profile) => { const name = ConfigUtils_1.ConfigUtils.getActiveProfileName(profile, commandParameters.arguments); // get profile name const props = this.mConfig.api.secure.securePropsForProfile(name); // get secure props for (const propName of props) { configSecureProps.push(...Object.values(CliUtils_1.CliUtils.getOptionFormat(propName))); // add multiple cases to list } }); /** * Construct a Set of secure fields from Zowe Team Config API. */ const secureInputs = new Set([...configSecureProps]); let censored = false; /** * Only attempt to show the input if it is in the command definition */ for (let i = 0; i < ((_e = commandParameters.definition.options) === null || _e === void 0 ? void 0 : _e.length); i++) { const name = commandParameters.definition.options[i].name; if (commandParameters.arguments[name] != null) { if (showSecure || !secureInputs.has(name)) { showInputsOnly.commandValues[name] = commandParameters.arguments[name]; } else { showInputsOnly.commandValues[name] = ConfigConstants_1.ConfigConstants.SECURE_VALUE; censored = true; } } } /** * Add profile location info */ (_f = this.mConfig) === null || _f === void 0 ? void 0 : _f.layers.forEach((layer) => { if (layer.exists) { showInputsOnly.locations.push(layer.path); } }); /** * Show warning if we censored output and we were not instructed to show secure values */ if (censored && !showSecure) { commandParameters.response.console.errorHeader("Some inputs are not displayed"); commandParameters.response.console.error(`Inputs below may be displayed as '${ConfigConstants_1.ConfigConstants.SECURE_VALUE}'. ` + `Properties identified as secure fields are not displayed by default.\n\n` + `Set the environment variable ` + `${SECRETS_ENV} to 'true' to display secure values in plain text.\n`); } /** * Show the inputs */ commandParameters.response.console.log(utilities_1.TextUtils.prettyJson(showInputsOnly).trim()); commandParameters.response.data.setObj(showInputsOnly); return; } /** * This function outputs the final help text when a syntax validation occurs * in {@link CommandProcessor#invoke} * * @param params The parameters passed to {@link CommandProcessor#invoke} * @param response The response object to output information to. */ invalidSyntaxNotification(params, response) { this.log.error(`Syntax for command "${this.mDefinition.name}" is invalid.`); response.data.setMessage("Command syntax invalid"); response.failed(); let finalHelp = ""; // Display the first example on error if (this.mDefinition.examples && this.mDefinition.examples.length > 0) { let exampleText = utilities_1.TextUtils.wordWrap(`- ${this.mDefinition.examples[0].description}:\n\n`, undefined, " "); exampleText += ` $ ${this.rootCommand} ${CommandUtils_1.CommandUtils.getFullCommandName(this.mDefinition, this.mFullDefinition)} ${this.mDefinition.examples[0].options}\n`; finalHelp += `\nExample:\n\n${exampleText}`; } if (params.arguments._.length > 0) { finalHelp += `\nUse "${this.rootCommand} ${params.arguments._.join(" ")} --help" to view command description, usage, and options.`; } else { finalHelp += `\nUse "${this.mDefinition.name} --help" to view command description, usage, and options.`; } response.console.error(finalHelp); } /** * Prepare for command execution. Actions such as reading stdin, auto-loading profiles, etc. will occur here before * the command handler is invoked. * @param {CommandResponse} response: The response object for command messaging. * @param {yargs.Arguments} commandArguments: The arguments specified on the command line. * @return {Promise<ICommandArguments>}: Promise to fulfill when complete. */ prepare(response, commandArguments) { return __awaiter(this, void 0, void 0, function* () { // Construct the imperative arguments - replacement/wrapper for Yargs to insulate handlers against any // changes made to Yargs let args = CliUtils_1.CliUtils.buildBaseArgs(commandArguments); this.log.trace(`Base set of arguments from Yargs parse:\n${(0, util_1.inspect)(args)}`); let allOpts = this.definition.options != null ? this.definition.options : []; allOpts = this.definition.positionals != null ? allOpts.concat(this.definition.positionals) : allOpts; this.log.trace(`Set of options and positionals defined on the command:\n${(0, util_1.inspect)(allOpts)}`); // Extract options supplied via environment variables - we must do this before we load profiles to // allow the user to specify the profile to load via environment variable. Then merge with already // supplied args from Yargs const envArgs = CliUtils_1.CliUtils.extractEnvForOptions(this.envVariablePrefix, allOpts); this.log.trace(`Arguments extracted from :\n${(0, util_1.inspect)(args)}`); args = CliUtils_1.CliUtils.mergeArguments(envArgs, args); this.log.trace(`Arguments merged :\n${(0, util_1.inspect)(args)}`); // Extract arguments from stdin this.log.trace(`Reading stdin for "${this.definition.name}" command...`); yield SharedOptions_1.SharedOptions.readStdinIfRequested(commandArguments, response, this.definition.type); // Build an object that contains all the options loaded from config if (this.mConfig != null) { // Merge the arguments from the config into the CLI args const profArgs = CliUtils_1.CliUtils.getOptValuesFromConfig(this.mConfig, this.definition.profile, args, allOpts); this.log.trace(`Arguments extracted from the config:\n${(0, util_1.inspect)(profArgs)}`); args = CliUtils_1.CliUtils.mergeArguments(profArgs, args); } // Set the default value for all options if defaultValue was specified on the command // definition and the option was not specified for (const option of allOpts) { if (option.defaultValue != null && args[option.name] == null && !args[constants_1.Constants.DISABLE_DEFAULTS_OPTION]) { const defaultedArgs = CliUtils_1.CliUtils.setOptionValue(option.name, "aliases" in option ? option.aliases : [], option.defaultValue); args = CliUtils_1.CliUtils.mergeArguments(args, defaultedArgs); } } // Ensure that throughout this process we didn't nuke "_" or "$0" args.$0 = commandArguments.$0; args._ = commandArguments._; // Log for debugging this.log.trace(`Full argument object constructed:\n${(0, util_1.inspect)(args)}`); return args; }); } /** * Internal accessor for the logger instance. * @readonly * @private * @type {Logger} * @memberof CommandProcessor */ get log() { return this.mLogger; } /** * Build the response object for the command based on the invoke parameters. The command response object is * passed to the handlers to allow them to perform console messages, response JSON construction, progress bars, etc. * @private * @param {IInvokeCommandParms} params * @returns {CommandResponse} * @memberof CommandProcessor */ constructResponseObject(params) { var _a; this.log.trace(`Constructing response object for "${this.definition.name}" command...`); return new CommandResponse_1.CommandResponse({ definition: this.definition, args: params.arguments, silent: params.silent == null ? false : params.silent, responseFormat: params.responseFormat, stream: (_a = utilities_1.ImperativeConfig.instance.daemonContext) === null || _a === void 0 ? void 0 : _a.stream }); } /** * Get stdin stream for the command handler to use. In daemon mode this is * a stream of data received from the daemon client. Otherwise it defaults * to `process.stdin`. * @returns Readable stream containing stdin data */ getStdinStream() { var _a; return ((_a = this.mDaemonContext) === null || _a === void 0 ? void 0 : _a.stdinStream) || process.stdin; } /** * Attempt to load a handler * @param {CommandResponse} response - command response to use to log errors in case of failure * @param {string} handlerPath - the specified path to the handler, we will attempt to load this * @returns {ICommandHandler} */ attemptHandlerLoad(response, handlerPath) { try { this.log.info(`Requiring handler "${handlerPath}" for command "${handlerPath}"...`); const commandHandler = require(handlerPath); const handler = new commandHandler.default(); this.log.info(`Handler "${handlerPath}" for command "${this.definition.name}" successfully loaded/required.`); return handler; } catch (handlerErr) { this.log.error(`Failed to load/require handler "${handlerPath}" for command "${this.definition.name}".`); this.log.error(`Error details: ${handlerErr.message}`); this.log.error("Diagnostic information:\n" + "Platform: '%s', Architecture: '%s', Process.argv: '%s'\n" + "Environmental variables: '%s'", os.platform(), os.arch(), process.argv.join(" "), JSON.stringify(process.env, null, 2)); const errorMessage = utilities_1.TextUtils.formatMessage(messages_1.couldNotInstantiateCommandHandler.message, { commandHandler: nodePath.normalize(handlerPath) || "\"undefined or not specified\"", definitionName: this.definition.name }); response.failed(); response.console.errorHeader("Handler Instantiation Failed"); response.console.error(errorMessage); response.data.setMessage(errorMessage); response.console.errorHeader("Error Details"); response.console.error(handlerErr.message); response.setError({ msg: errorMessage, additionalDetails: handlerErr.message }); return undefined; } } /** * Finish the response by building the response object and optionally outputting the JSON response depending on the * modes selected. * @private * @param {CommandResponse} response * @returns {ICommandResponse} * @memberof CommandProcessor */ finishResponse(response) { const json = response.buildJsonResponse(); if (!response.silent) { switch (response.responseFormat) { case "json": response.writeJsonResponse(); break; case "default": // Do nothing - already written along the way break; default: throw new error_1.ImperativeError({ msg: `${CommandProcessor.ERROR_TAG} ` + `The response format specified ("${response.responseFormat}") is not valid.` }); } } this.log.info(`Command "${this.definition.name}" completed with success flag: "${json.success}"`); this.log.trace(`Command "${this.definition.name}" finished. Response object:\n${(0, util_1.inspect)(json, { depth: null })}`); response.endStream(); return json; } /** * Respond to an error encountered when invoking a command handler * @param {Error | string} handlerErr - the error that was encountered * @param {CommandResponse} response - a response object to print error messages to * @param {string} handlerPath - path to the handler with which an error was encountered */ handleHandlerError(handlerErr, response, handlerPath) { // Mark the command as failed this.log.error(`Handler for command "${this.definition.name}" failed.`); response.failed(); response.endProgressBar(); this.log.error("Diagnostic information:\n" + "Platform: '%s', Architecture: '%s', Process.argv: '%s'\n" + "Node versions: '%s'" + "Environmental variables: '%s'", os.platform(), os.arch(), process.argv.join(" "), JSON.stringify(process.versions, null, 2), JSON.stringify(process.env, null, 2)); // If this is an instance of an imperative error, then we are good to go and can formulate the response. // If it is an Error object, then something truly unexpected occurred in the handler. // If there is no error response (or something else), then the command was rejected with the reject method. if (handlerErr instanceof error_1.ImperativeError) { this.log.error(`Handler for ${this.mDefinition.name} rejected by thrown ImperativeError.`); response.setError(handlerErr.details); // display primary user message response.console.error(utilities_1.TextUtils.chalk.red("Unable to perform this operation due to the following problem.")); // Remove http status in 'message', since the same information was placed in additionalDetails. response.console.error(utilities_1.TextUtils.chalk.red(handlerErr.message.replace(/Rest API failure with HTTP\(S\) status \d\d\d\n/, ""))); // display server response const responseTitle = "Response From Service"; if (handlerErr.causeErrors) { try { const causeErrorsJson = JSON.parse(handlerErr.causeErrors); response.console.error("\n" + utilities_1.TextUtils.chalk.bold.yellow(responseTitle)); response.console.error(utilities_1.TextUtils.prettyJson(causeErrorsJson, undefined, false, "")); } catch (parseErr) { // causeErrors was not JSON. const causeErrString = handlerErr.causeErrors.toString(); if (causeErrString.length > 0) { // output the text value of causeErrors response.console.error("\n" + utilities_1.TextUtils.chalk.bold.yellow(responseTitle)); response.console.error(causeErrString); } } } // display diagnostic information const diagInfo = handlerErr.details.additionalDetails; if ((diagInfo === null || diagInfo === void 0 ? void 0 : diagInfo.length) > 0) {