@zowe/imperative
Version:
framework for building configurable CLIs
875 lines • 53 kB
JavaScript
"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 rest_1 = require("../../rest");
/**
* 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 = {};
rest_1.ConnectionPropsForSessCfg.resolveSessCfgProps(sessCfg, commandParameters.arguments);
showInputsOnly.authenticationType = sessCfg.type;
/**
* 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) {
response.console.error(utilities_1.TextUtils.chalk.bold.yellow("\nDiagnostic Information"));
response.console.error(diagInf