@zowe/imperative
Version:
framework for building configurable CLIs
926 lines • 46.1 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.
*
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommandResponse = void 0;
const error_1 = require("../../../error");
const utilities_1 = require("../../../utilities");
const operations_1 = require("../../../operations");
const constants_1 = require("../../../constants");
const expect_1 = require("../../../expect");
const OptionConstants_1 = require("../constants/OptionConstants");
const util_1 = require("util");
const DeepMerge = require("deepmerge");
const ProgressBar = require("progress");
const tty = require("tty");
const DaemonRequest_1 = require("../../../utilities/src/DaemonRequest");
const logger_1 = require("../../../logger");
const censor_1 = require("../../../censor");
const DataObjectParser = require("dataobject-parser");
/**
* Command response object allocated by the command processor and used to construct the handler response object
* passed to the command handlers. The response object contains all the methods necessary for a command handler (and
* the processor) to formulate a command response. Controls buffering of messages and JSON response formats.
*
* Instances of this class are only manipulated by the command processor. See "HandlerResponse" for the handler response
* object.
*
* @export
* @class CommandResponse
* @implements {ICommandResponseApi}
*/
class CommandResponse {
/**
* Creates an instance of CommandResponse.
* @param {ICommandResponseParms} params - See the interface for details.
* @memberof CommandResponse
*/
constructor(params) {
/**
* Enable silent mode - means absolutely NO output will be written to the console/terminal.
* @private
* @type {boolean}
* @memberof CommandResponse
*/
this.mSilent = false;
/**
* Command handler succeeded flag - true if the command succeeded.
* @private
* @type {boolean}
* @memberof CommandResponse
*/
this.mSucceeded = true;
/**
* Command handler exit code. Will be used when exiting the process at the end of command execution
* @private
* @type {number}
* @memberof CommandResponse
*/
this.mExitCode = null;
/**
* The default highlight color for chalk
* @private
* @memberof CommandResponse
*/
this.mPrimaryTextColor = constants_1.Constants.DEFAULT_HIGHLIGHT_COLOR;
/**
* The stderr buffer - collects all messages sent to stderr.
* @private
* @type {Buffer}
* @memberof CommandResponse
*/
this.mStderr = Buffer.alloc(0);
/**
* The stdout buffer - collects all messages sent to stdout.
* @private
* @type {Buffer}
* @memberof CommandResponse
*/
this.mStdout = Buffer.alloc(0);
/**
* The message placed on the response object when the JSON response is built
* @private
* @type {string}
* @memberof CommandResponse
*/
this.mMessage = "";
/**
* The "data" object that is placed on the JSON response object of the command
* @private
* @type {*}
* @memberof CommandResponse
*/
this.mData = {};
/**
* The progress bar spinner chars.
* @private
* @type {string}
* @memberof CommandResponse
*/
this.mProgressBarSpinnerChars = "-oO0)|(0Oo-";
this.mControl = params == null ? {} : params;
this.mArguments = this.mControl.args;
this.mDefinition = this.mControl.definition;
this.mPrimaryTextColor = this.mControl.primaryTextColor == null ? this.mPrimaryTextColor : this.mControl.primaryTextColor;
expect_1.ImperativeExpect.toNotBeEqual(this.mPrimaryTextColor.trim(), "", `${CommandResponse.RESPONSE_ERR_TAG} The primary text color supplied is blank. Must provide a valid color.`);
const formats = ["json", "default"];
this.mResponseFormat = this.mControl.responseFormat == null ? "default" : this.mControl.responseFormat;
expect_1.ImperativeExpect.toBeOneOf(this.mResponseFormat, formats, `${CommandResponse.RESPONSE_ERR_TAG} Response format invalid. Valid formats: "${formats.join(",")}"`);
this.mSilent = this.mControl.silent == null ? false : this.mControl.silent;
this.mProgressBarSpinnerChars = this.mControl.progressBarSpinner == null ? this.mProgressBarSpinnerChars : this.mControl.progressBarSpinner;
this.mStream = params ? params.stream : undefined;
}
get format() {
// Access to "this" from the inner class
// eslint-disable-next-line @typescript-eslint/no-this-alias
const outer = this;
if (this.mFormatApi == null) {
this.mFormatApi = new class {
/**
* Format output data from the command based on the defaults specified OR the parameters specified by
* the user.
* @param {ICommandOutputFormat} format
*/
output(format) {
// The input parameters must not be null and we will make a copy to not alter the original
expect_1.ImperativeExpect.toNotBeNullOrUndefined(format, "No format parameters were supplied");
expect_1.ImperativeExpect.toNotBeNullOrUndefined(format.output, "No output data to format was supplied");
expect_1.ImperativeExpect.toBeOneOf(format.format, OptionConstants_1.OptionConstants.RESPONSE_FORMAT_TYPES, `Output format must be one of the following: ${OptionConstants_1.OptionConstants.RESPONSE_FORMAT_TYPES.toString()}`);
// If the output is an array and the length is 0 or - do nothing
if (Array.isArray(format.output) && format.output.length === 0 ||
Object.keys(format.output).length === 0 && format.output.constructor === Object) {
return;
}
// Create a copy of the params to manipulate
let formatCopy;
try {
formatCopy = JSON.parse(JSON.stringify(format));
}
catch (copyErr) {
outer.console.errorHeader(`Non-formatted output data`);
outer.console.error(`${(0, util_1.inspect)(format.output, { depth: null, compact: true })}`);
throw new error_1.ImperativeError({
msg: `Error copying input parameters. Details: ${copyErr.message}`,
additionalDetails: copyErr
});
}
// Depending on the command definition and arguments, override the format options
if (outer.mDefinition != null && outer.mDefinition.outputFormatOptions != null) {
formatCopy.format = outer.mArguments != null && outer.mArguments.responseFormatType != null ?
outer.mArguments.responseFormatType : formatCopy.format;
formatCopy.fields = outer.mArguments != null && outer.mArguments.responseFormatFilter != null ?
outer.mArguments.responseFormatFilter : formatCopy.fields;
formatCopy.header = outer.mArguments != null && outer.mArguments.responseFormatHeader != null ?
outer.mArguments.responseFormatHeader : formatCopy.header;
}
// Format the output for the command, if an error occurs, output the format error data
// so that the response is still available to the user
try {
this.formatOutput(formatCopy, outer);
}
catch (formatErr) {
outer.console.errorHeader(`Non-formatted output data`);
outer.console.error(`${(0, util_1.inspect)(format.output, { compact: true })}`);
throw formatErr;
}
}
/**
* Formats and prints the data/output passed. The handler dictates (via the ICommandOutputFormat params)
* the default output format. However, if the user has specified any of the response-format options,
* those take precedence over the defaults.
* @private
* @param {ICommandOutputFormat} params - the command format output parameters (see interface for details)
* @param {CommandResponse} response - the command response object
* @param {Arguments} args - the arguments passed on the command line by the user
* @memberof CommandProcessor
*/
formatOutput(params, response) {
// If a single filter is specified, save the field the data was extracted from
const extractedFrom = params.fields != null && params.fields.length === 1 && typeof params.output !== "string" ?
params.fields[0] : undefined;
// If filter fields are present, filter the object
params.output = this.filterProperties(params);
// Process each type according to the data presented from the handler
switch (params.format) {
// Output the data as a string
case "string":
// Stringify if not a string
if (typeof params.output !== "string") {
params.output = JSON.stringify(params.output);
}
// Log the string data
response.console.log(params.output);
break;
// Output the data as a list of strings
case "list":
if (Array.isArray(params.output)) {
// Filter the properties by request and stringify if needed
const list = [];
params.output.forEach((entry) => {
if (typeof entry === "object") {
list.push(JSON.stringify(entry));
}
else {
list.push(entry);
}
});
// Join each array entry on a newline
params.output = list.join("\n");
response.console.log(params.output);
}
else {
throw new error_1.ImperativeError({
msg: this.errorDetails(params, "Arrays", extractedFrom)
});
}
break;
// Output the data as an object or list of objects (prettified)
case "object":
if (Array.isArray(params.output) || typeof params.output === "object") {
// Build the table and catch any errors that may occur from the packages
let pretty;
try {
// Prettify the data
pretty = utilities_1.TextUtils.prettyJson(params.output, undefined, undefined, "");
}
catch (prettyErr) {
throw new error_1.ImperativeError({
msg: `Error formulating pretty JSON for command response. Details: ` +
`${prettyErr.message}`,
additionalDetails: prettyErr
});
}
// Print the output
response.console.log(pretty);
}
else {
throw new error_1.ImperativeError({
msg: this.errorDetails(params, "JSON objects or Arrays", extractedFrom)
});
}
break;
// Output the data as a table
case "table":
if (typeof params.output === "object" || Array.isArray(params.output)) {
// Build the table and catch any errors that may occur from the packages
let table;
try {
// Adjust if required
if (!Array.isArray(params.output)) {
params.output = [params.output];
}
// Build the table
table = utilities_1.TextUtils.getTable(params.output, "yellow", CommandResponse.MAX_COLUMN_WIDTH, params.header != null ? params.header : false);
}
catch (tableErr) {
throw new error_1.ImperativeError({
msg: `Error formulating table for command response. ` +
`Details: ${tableErr.message}`,
additionalDetails: tableErr
});
}
// Print the table
response.console.log(table);
}
else {
throw new error_1.ImperativeError({
msg: this.errorDetails(params, "JSON objects or Arrays", extractedFrom)
});
}
break;
default:
throw new error_1.ImperativeError({
msg: `Invalid output format of "${params.format}" supplied. ` +
`Contact the command handler creators for support.`
});
}
}
/**
* Formulate an error with details for the user to help diagnose the problem.
* @private
* @param {ICommandOutputFormat} params - the format parameters
* @param {string} appliedTo - where this format type can be applied to
* @param {string} extractedFrom - if the data was extracted from a field, specify the field here, so
* that it makes sense to the user.
* @returns {string} - the error string
*/
errorDetails(params, appliedTo, extractedFrom) {
return `The format type of "${params.format}" can only be applied to ${appliedTo}.\n` +
`The data being formatted is of type ` +
`"${Array.isArray(params.output) ? "array" : typeof params.output}".` +
`${extractedFrom != null ? `\nNote that the data being formatted was extracted from property "${extractedFrom}" ` +
`because that field was specified as the single filter.` : ""}`;
}
/**
* Filter fields from top level of the object. Iterates over each property and deletes if not present in the
* "keep" array.
* @private
* @param {*} obj - the object to remove the properties
* @param {string[]} keep - an array of properties to keep on the object
* @memberof CommandProcessor
*/
filterProperties(params) {
// Retain the original object/data if there is nothing to do
let filtered = params.output;
// If there are no filter fields, return the original object/data
if (params.fields != null && params.fields.length > 0) {
// Extract the single filter if required
let singleFilter;
if (params.fields.length === 1 && typeof params.output === "object") {
singleFilter = params.fields[0];
}
// Perform the filtering depending on if a single filter was specified
if (singleFilter != null && !Array.isArray(params.output)) {
// Extract only the single field - this allows a single object property
// to be selected and output without "meta" info (like the prop name)
const dataObjectParser = new DataObjectParser(params.output);
filtered = dataObjectParser.get(singleFilter);
}
else if (singleFilter != null && Array.isArray(params.output) && (params.format === "list" || params.format === "string")) {
// Extract each of the single fields and output as a list of strings
const strings = [];
params.output.forEach((entry) => {
const dataObjectParser = new DataObjectParser(entry);
strings.push(dataObjectParser.get(singleFilter));
});
filtered = strings;
}
else if (Array.isArray(params.output)) {
// Extract all the fields from each entry in the array
filtered = [];
params.output.forEach((entry) => {
filtered.push(this.extractProperties(entry, params.fields, params.format));
});
}
else if (typeof params.output === "object") {
// Extract each field from the object
filtered = this.extractProperties(params.output, params.fields, params.format);
}
}
// Return the original or filtered object/data
return filtered;
}
/**
* Extract the properties from the objects that the user specified
* @private
* @param {*} dataObj - The data object to extract the properties from
* @param {string[]} keepProps - the properties to extract
* @param {OUTPUT_FORMAT} format - the output format
* @returns {*} - the "filtered" object
*/
extractProperties(dataObj, keepProps, format) {
let extracted = dataObj;
if (keepProps != null && keepProps.length > 0 && typeof dataObj === "object") {
extracted = {};
const objParser = new DataObjectParser(dataObj);
const extractedParser = new DataObjectParser(extracted);
for (const extractProp of keepProps) {
// If the response format is table, then extract the data
// and create a property with hyphenated names to allow
// for the user to create a proper table fro nested extractions
const propValue = objParser.get(extractProp);
if (format === "table") {
// Use the dots for the table
extracted[extractProp] = propValue;
}
else {
// Keep the object structure
extractedParser.set(extractProp, propValue);
}
}
}
return extracted;
}
}();
}
return this.mFormatApi;
}
/**
* Accessor for the console API - Handlers will use this API to write console messages.
* @readonly
* @type {IHandlerResponseConsoleApi}
* @memberof CommandResponse
*/
get console() {
// Access to "this" from the inner class
// eslint-disable-next-line @typescript-eslint/no-this-alias
const outer = this;
// Create only a single instance of the console API
if (this.mConsoleApi == null) {
this.mConsoleApi = new class {
/**
* Write a message/data to stdout. Appends a newline character if the input is of type string. If the
* command response indicates JSON format, then the message is automatically buffered.
* @param {(string | Buffer)} message - The message/data to write to stdout (can be a format string).
* @param {...any[]} values - The format values. Ignored if a buffer is passed
* @returns {string} - The formatted data or the original data.toString() if a buffer was passed
*/
log(message, ...values) {
let msg = censor_1.Censor.censorRawData(message.toString(), logger_1.Logger.DEFAULT_CONSOLE_NAME);
if (!Buffer.isBuffer(message)) {
msg = outer.formatMessage(msg.toString(), ...values) + "\n";
}
outer.writeAndBufferStdout(msg);
return msg;
}
/**
* Write a message to stderr. Appends a newline character if the input is of type string. If the
* command response indicates JSON format, then the message is automatically buffered.
* @param {(string | Buffer)} message - The message/data to write to stderr (can be a format string).
* @param {...any[]} values - The format values.
* @returns {string} - The formatted data, or the original data.toString() if a buffer was passed
*/
error(message, ...values) {
let msg = censor_1.Censor.censorRawData(message.toString(), logger_1.Logger.DEFAULT_CONSOLE_NAME);
if (!Buffer.isBuffer(message)) {
msg = outer.formatMessage(msg.toString(), ...values) + "\n";
}
outer.writeAndBufferStderr(msg);
return msg;
}
/**
* Writes a red message header to stderr (followed by a newline). Used to highlight error conditions
* and messages for the user.
* @param {string} message - The message to use as the header.
* @param {string} [delimeter] - The a delimeter to prints.
* @returns {string} - The string that is printed (including the color codes)
*/
errorHeader(message, delimeter = ":") {
let msg = censor_1.Censor.censorRawData(message.toString(), logger_1.Logger.DEFAULT_CONSOLE_NAME);
msg = utilities_1.TextUtils.chalk.red(msg + `${delimeter}\n`);
outer.writeAndBufferStderr(msg);
return msg;
}
/**
* Handles prompting for command input
* @param {string} questionText
* @param {IPromptOptions} [opts]
* @returns {Promise<string>}
*/
prompt(questionText, opts) {
const msg = censor_1.Censor.censorRawData(questionText.toString(), logger_1.Logger.DEFAULT_CONSOLE_NAME);
if (outer.mStream) {
return new Promise((resolve) => {
// send prompt content
const daemonRequest = (opts === null || opts === void 0 ? void 0 : opts.hideText) ?
DaemonRequest_1.DaemonRequest.create({ securePrompt: msg }) :
DaemonRequest_1.DaemonRequest.create({ prompt: msg });
outer.writeStream(daemonRequest);
// wait for a response here
outer.mStream.once("data", (data) => {
// strip response header and give to content the waiting handler
const response = JSON.parse(data.toString());
resolve(response.stdin.trim());
});
});
}
else {
return utilities_1.CliUtils.readPrompt(msg, opts);
}
}
}();
}
// Return the instance of the console API
return this.mConsoleApi;
}
/**
* Accessor for the data api class - Handlers will use this to construct/influence the response JSON object (data
* is only displayed to the user if JSON mode is requested).
* @readonly
* @type {IHandlerResponseDataApi}
* @memberof CommandResponse
*/
get data() {
// Access to "this" from the inner class.
// eslint-disable-next-line @typescript-eslint/no-this-alias
const outer = this;
// Only create a single instance
if (this.mDataApi == null) {
this.mDataApi = new class {
/**
* Sets the response object "data" field to be the object passed. The data field indicates any structured
* JSON/object data that the command wants to return for programmatic consumption.
* @param {*} obj - The object to set
* @param {boolean} merge - If true will merge with the existing object. If false, the object is
* completely overwritten.
*/
setObj(data, merge = false) {
outer.mData = merge ? DeepMerge(outer.mData, data) : data;
}
/**
* Sets the Message field in the response. The message field indicates a short summary of the command
* to the programmatic caller (JSON response format) of the command.
* @param {string} message - The message (can be a format string).
* @param {...any[]} values - The format string values.
* @returns {string} - The formatted string.
*/
setMessage(message, ...values) {
const formatted = outer.formatMessage(message, values);
outer.mMessage = formatted;
return outer.mMessage;
}
/**
* Sets the response object "data" field to be the object passed. The data field indicates any structured
* JSON/object data that the command wants to return for programmatic consumption.
* @param {*} obj - The object to set
* @param {boolean} merge - If true will merge with the existing object. If false, the object is
* completely overwritten.
*/
setExitCode(code) {
outer.mExitCode = code;
return outer.mExitCode;
}
}();
}
// Return the data API
return this.mDataApi;
}
/**
* Accessor for the progress bar API - Handlers will use this API to create/destroy command progress bars.
* @readonly
* @type {IHandlerProgressApi}
* @memberof CommandResponse
*/
get progress() {
// Remember "this" for the inner classes usage and ensure that progress bar has not been started.
// eslint-disable-next-line @typescript-eslint/no-this-alias
const outer = this;
// Ensure there is only a single instance created of the progress API class
if (this.mProgressApi == null) {
// Create an instance of the class
this.mProgressApi = new class {
constructor() {
this.spinnerIndex = 0;
this.mProgressBarSpinnerIndex = 0;
this.mProgressBarPollFrequency = 65; // eslint-disable-line @typescript-eslint/no-magic-numbers
this.mProgressBarTemplate = " " + utilities_1.TextUtils.chalk[outer.mPrimaryTextColor](":bar|") + " :current% " +
utilities_1.TextUtils.chalk[outer.mPrimaryTextColor](":spin") + " | :statusMessage";
this.mIsDaemon = false;
/**
* TODO: get from config - default value is below
*/
this.mProgressBarSpinnerChars = "-oO0)|(0Oo-";
this.mDaemonProgressBarSpinnerChars = this.mProgressBarSpinnerChars.split("").map((char) => char + DaemonRequest_1.DaemonRequest.EOW_DELIMITER);
}
/**
* Start a spinner
*/
startSpinner(pendingText) {
if (this.spinnerInterval == null) {
this.spinnerInterval = setInterval(() => {
outer.writeStdout(`\r${pendingText} ${this.mProgressBarSpinnerChars[this.spinnerIndex]}`);
this.spinnerIndex = (this.spinnerIndex + 1) % this.mProgressBarSpinnerChars.length;
}, 100); // eslint-disable-line
}
}
/**
* Stop a spinner
*/
endSpinner(stopText) {
if (this.spinnerInterval != null) {
clearInterval(this.spinnerInterval);
this.spinnerInterval = null;
if (stopText)
outer.writeStdout(`\r${stopText}\n`);
outer.writeStdout("\r\x1b[K");
}
}
/**
* Start a progress bar (assuming silent mode is not enabled).
* @param {IProgressBarParms} params - Progress bar control - See interface for details.
*/
startBar(params) {
if (outer.mProgressBar != null) {
throw new error_1.ImperativeError({
msg: `${CommandResponse.RESPONSE_ERR_TAG} A progress bar has already been started. ` +
`Please call progress.endBar() before starting a new one.`
});
}
if (!outer.silent && outer.mResponseFormat !== "json" &&
!(utilities_1.TextUtils.chalk.level === 0 || process.env.CI != null)) {
// Persist the task specifications and determine the stream to use for the progress bar
this.mProgressBarStdoutStartIndex = outer.mStdout.length;
this.mProgressBarStderrStartIndex = outer.mStderr.length;
this.mProgressTask = params.task;
let stream = params.stream == null ? process.stderr : params.stream;
const arbitraryColumnSize = 80;
// if we have an outer stream (e.g. socket connection for daemon mode) use it
if (outer.mStream) {
this.mIsDaemon = true;
stream = outer.mStream;
// NOTE(Kelosky): see https://github.com/visionmedia/node-progress/issues/110
// progress explicitly checks for TTY before writing, so
// we add the keys force this behavior.
if (!stream.isTTY) {
const ttyPrototype = tty.WriteStream.prototype;
Object.keys(ttyPrototype).forEach((key) => {
stream[key] = ttyPrototype[key];
});
stream.columns = arbitraryColumnSize;
}
}
// send header to enable progress bar streaming
// const daemonHeaders = DaemonUtils.buildHeaders({ progress: true });
outer.writeStream(DaemonRequest_1.DaemonRequest.create({ progress: true }));
// Create the progress bar instance
outer.mProgressBar = new ProgressBar(this.mProgressBarTemplate, {
total: 100,
width: 10,
stream,
complete: "█",
clear: true,
incomplete: "_",
});
// Set the interval based on the params of the default
this.mProgressBarInterval = setInterval(this.updateProgressBar.bind(this), params.updateInterval == null ? this.mProgressBarPollFrequency : params.updateInterval);
}
}
/**
* Destroy the outstanding progress bar
*/
endBar() {
if (outer.mProgressBar != null) {
if (this.mProgressBarInterval != null) {
clearInterval(this.mProgressBarInterval);
this.mProgressBarInterval = undefined;
}
const statusMessage = "Complete";
outer.mProgressBar.update(1, {
statusMessage,
spin: " "
});
outer.mProgressBar.terminate();
// NOTE(Kelosky): ansi escape codes for progress bar cursor and line clearing are written on the socket
// so we need to ensure they're emptied out before we write to the stream.
if (this.mIsDaemon)
outer.writeStream(DaemonRequest_1.DaemonRequest.EOW_DELIMITER + DaemonRequest_1.DaemonRequest.create({ progress: false }));
outer.writeStdout(outer.mStdout.subarray(this.mProgressBarStdoutStartIndex));
outer.writeStderr(outer.mStderr.subarray(this.mProgressBarStderrStartIndex));
this.mProgressTask = undefined;
// clear the progress bar field
outer.mProgressBar = undefined;
}
}
/**
* Update the progress bar to the next step, if the stage indicates failed or complete, the
* progress bar is automatically ended.
* @private
*/
updateProgressBar() {
if (this.mProgressTask == null ||
this.mProgressTask.stageName === operations_1.TaskStage.COMPLETE ||
this.mProgressTask.stageName === operations_1.TaskStage.FAILED) {
this.endBar();
}
else {
if (this.mProgressBarInterval != null) {
const percentRatio = this.mProgressTask.percentComplete / operations_1.TaskProgress.ONE_HUNDRED_PERCENT;
this.mProgressBarSpinnerIndex = (this.mProgressBarSpinnerIndex + 1) % this.mProgressBarSpinnerChars.length;
if (this.mIsDaemon) {
outer.mProgressBar.update(percentRatio, {
statusMessage: this.mProgressTask.statusMessage,
spin: this.mDaemonProgressBarSpinnerChars[this.mProgressBarSpinnerIndex]
});
}
else {
outer.mProgressBar.update(percentRatio, {
statusMessage: this.mProgressTask.statusMessage,
spin: this.mProgressBarSpinnerChars[this.mProgressBarSpinnerIndex]
});
}
}
}
}
}();
}
// Return the progress bar API
return this.mProgressApi;
}
/**
* Accessor for the silent flag - silent indicates that the command produces absolutely no output to the console.
* @readonly
* @type {boolean}
* @memberof CommandResponse
*/
get silent() {
return this.mSilent;
}
/**
* Setter for the succeeded flag (sets to false to indicate command failure).
* @memberof CommandResponse
*/
failed() {
this.mSucceeded = false;
}
/**
* Setter for the succeeded flag (sets to true to indicate command success).
* @memberof CommandResponse
*/
succeeded() {
this.mSucceeded = true;
}
/**
* Buffer the message (string or buffer) to the stdout buffer. Used to accumulate messages for different reasons
* (JSON mode is enabled, etc.).
* @param {(Buffer | string)} data - The data/messages to buffer.
* @memberof CommandResponse
*/
bufferStdout(data) {
this.mStdout = Buffer.concat([this.mStdout, data instanceof Buffer ? data : Buffer.from(data)]);
}
/**
* Buffer the message (string or buffer) to the stderr buffer. Used to accumulate messages for different reasons
* (JSON mode is enabled, etc.).
* @param {(Buffer | string)} data - The data/messages to buffer.
* @memberof CommandResponse
*/
bufferStderr(data) {
this.mStderr = Buffer.concat([this.mStderr, data instanceof Buffer ? data : Buffer.from(data)]);
}
/**
* Setter for the error object in the response - automatically populated by the Command Processor if the handler
* rejects the handler promise.
* @param {IImperativeError} error - The error object to place in the response.
* @memberof CommandResponse
*/
setError(error) {
this.mError = error;
}
/**
* Returns the JSON response for the command.
* @returns {ICommandResponse} - The command JSON response - See the interface for details.
* @memberof CommandResponse
*/
buildJsonResponse() {
if (this.mExitCode == null) {
this.mExitCode = this.mSucceeded ? 0 : constants_1.Constants.ERROR_EXIT_CODE;
}
return {
success: this.mSucceeded,
exitCode: this.mExitCode,
message: this.mMessage,
stdout: this.mStdout,
stderr: this.mStderr,
data: this.mData,
error: this.mError
};
}
/**
* Writes the JSON response to the console - Done normally by the command processor dependending on the response
* format specified in the object.
* @returns {ICommandResponse} - Returns the constructed response that is written to the console.
* @memberof CommandResponse
*/
writeJsonResponse() {
let response;
try {
response = this.buildJsonResponse();
response.stderr = response.stderr.toString();
response.stdout = response.stdout.toString();
response.message = censor_1.Censor.censorRawData(response.message, "json");
response.data = response.data ? JSON.parse(censor_1.Censor.censorRawData(JSON.stringify(response.data), "json")) : undefined;
response.error = response.error ? JSON.parse(censor_1.Censor.censorRawData(JSON.stringify(response.error), "json")) : undefined;
if (!this.mSilent) {
this.writeStdout(JSON.stringify(response, null, 2));
}
}
catch (e) {
throw new error_1.ImperativeError({
msg: `${CommandResponse.RESPONSE_ERR_TAG} An error occurred stringifying the JSON response object. ` +
`Error Details: ${e.message}`,
additionalDetails: e
});
}
return response;
}
/**
* Accessor for the response format - see the type for available options - controls how the response will be
* presented to the user (JSON format, default, etc.)
* @readonly
* @type {COMMAND_RESPONSE_FORMAT}
* @memberof CommandResponse
*/
get responseFormat() {
return this.mResponseFormat;
}
/**
* Complete any outstanding progress bars.
* @memberof CommandResponse
*/
endProgressBar() {
if (this.mProgressApi != null) {
this.mProgressApi.endBar();
}
}
/**
* Explicitly end a stream
* @private
* @memberof CommandResponse
*/
endStream() {
if (this.mStream) {
this.setDaemonExitCode();
this.mStream.end();
}
}
/**
* Send headers to daemon client
* @private
* @param {string} headers
* @memberof CommandResponse
*/
setDaemonExitCode() {
this.writeStream(DaemonRequest_1.DaemonRequest.create({ exitCode: this.mExitCode }));
}
/**
* Internal accessor for the full control parameters passed to the command response object.
* @readonly
* @private
* @type {ICommandResponseParms}
* @memberof CommandResponse
*/
get control() {
return this.mControl;
}
/**
* Uses text utils to format the message (format strings).
* @private
* @param {string} msg - The format message
* @param {...any[]} values - The substitution values for the format string
* @returns {string} - Returns the formatted message
* @memberof CommandResponse
*/
formatMessage(msg, ...values) {
return utilities_1.TextUtils.formatMessage(msg, ...values);
}
/**
* Buffers to stdout and optionally prints the msg to the console.
* @private
* @param {(Buffer | string)} data - The data to write/buffer
* @memberof CommandResponse
*/
writeAndBufferStdout(data) {
this.bufferStdout(data);
if (this.write()) {
this.writeStdout(data);
}
}
/**
* Writes the data to stdout
* @private
* @param {(Buffer | string)} data - the data to write
* @memberof CommandResponse
*/
writeStdout(data) {
process.stdout.write(data);
this.writeStream(DaemonRequest_1.DaemonRequest.create({ stdout: data.toString() }));
}
/**
* Writes data to stream if provided (for daemon mode)
* @private
* @param {(Buffer | string)} data
* @memberof CommandResponse
*/
writeStream(data) {
if (this.mStream) {
this.mStream.write(data);
}
}
/**
* Buffers to stderr and optionally prints the msg to the console.
* @private
* @param {(Buffer | string)} data - The data to write/buffer
* @memberof CommandResponse
*/
writeAndBufferStderr(data) {
this.bufferStderr(data);
if (this.write()) {
this.writeStderr(data);
}
}
/**
* Writes the data to stderr
* @private
* @param {(Buffer | string)} data - the data to write to stderr
* @memberof CommandResponse
*/
writeStderr(data) {
process.stderr.write(data);
this.writeStream(DaemonRequest_1.DaemonRequest.create({ stderr: data.toString() }));
}
/**
* Indicates if output should be written immediately to the console/terminal. If silent mode is true or response
* format indicates JSON, then write() will return false.
* @private
* @returns {boolean} - True if the output should be written to the console/terminal.
* @memberof CommandResponse
*/
write() {
return !this.control.silent && this.mResponseFormat !== "json" && this.mProgressBar == null;
}
}
exports.CommandResponse = CommandResponse;
/**
* Imperative Error tag for error messaging
* @private
* @static
* @type {string}
* @memberof CommandResponse
*/
CommandResponse.RESPONSE_ERR_TAG = "Command Response Error:";
/**
* Max column width for formulating tabular output
* @private
* @static
* @memberof CommandProcessor
*/
CommandResponse.MAX_COLUMN_WIDTH = 9999;
//# sourceMappingURL=CommandResponse.js.map