UNPKG

@zowe/imperative

Version:
926 lines 46.1 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. * */ 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