UNPKG

@poppinss/cliui

Version:

Opinionated UI KIT for Command Line apps

1,893 lines (1,875 loc) 45.1 kB
import { TERMINAL_SIZE } from "./chunk-BCXMHMWM.js"; // index.ts import supportsColor from "supports-color"; import { default as poppinssColors } from "@poppinss/colors"; // src/icons.ts var { platform } = process; var icons = platform === "win32" && !process.env.WT_SESSION ? { tick: "\u221A", cross: "\xD7", bullet: "*", nodejs: "\u2666", pointer: ">", info: "i", warning: "\u203C", squareSmallFilled: "[\u2588]" } : { tick: "\u2714", cross: "\u2716", bullet: "\u25CF", nodejs: "\u2B22", pointer: "\u276F", info: "\u2139", warning: "\u26A0", squareSmallFilled: "\u25FC" }; // src/table.ts import CliTable from "cli-table3"; import stringWidth from "string-width"; // src/colors.ts import colors from "@poppinss/colors"; function useColors(options = {}) { if (options.raw) { return colors.raw(); } if (options.silent) { return colors.silent(); } return colors.ansi(); } // src/renderers/console.ts import logUpdate from "log-update"; var ConsoleRenderer = class { getLogs() { return []; } flushLogs() { } log(message) { console.log(message); } /** * Log message by overwriting the existing one */ logUpdate(message) { logUpdate(message); } /** * Persist the last logged message */ logUpdatePersist() { logUpdate.done(); } /** * Log error */ logError(message) { console.error(message); } }; // src/table.ts var Table = class { #state = { head: [], rows: [] }; /** * Size of the largest row for a given * column */ #columnSizes = []; /** * The renderer to use to output logs */ #renderer; /** * Logger configuration options */ #options; /** * The colors reference */ #colors; /** * Whether or not to render full width */ #renderFullWidth = false; /** * The column index that should take remaining * width. */ #fluidColumnIndex = 0; /** * Padding for columns */ #padding = 2; constructor(options = {}) { this.#options = { raw: options.raw === void 0 ? false : options.raw, chars: options.chars }; } /** * Tracking the column size and keeping on the largest * one by tracking the content size */ #storeColumnSize(columns) { columns.forEach((column, index) => { const size = stringWidth(column); const existingSize = this.#columnSizes[index]; if (!existingSize || existingSize < size) { this.#columnSizes[index] = size; } }); } /** * Computes the col widths based when in fullwidth mode */ #computeColumnsWidth() { if (!this.#renderFullWidth) { return; } let columns = TERMINAL_SIZE - (this.#columnSizes.length + 1); this.#state.colWidths = this.#state.colWidths || []; this.#columnSizes.forEach((column, index) => { this.#state.colWidths[index] = this.#state.colWidths[index] || column + this.#padding * 2; columns = columns - this.#state.colWidths[index]; }); if (columns) { const index = this.#fluidColumnIndex > this.#columnSizes.length - 1 ? 0 : this.#fluidColumnIndex; this.#state.colWidths[index] = this.#state.colWidths[index] + columns; } } /** * Returns the renderer for rendering the messages */ getRenderer() { if (!this.#renderer) { this.#renderer = new ConsoleRenderer(); } return this.#renderer; } /** * Define a custom renderer. Logs to "stdout" and "stderr" * by default */ useRenderer(renderer) { this.#renderer = renderer; return this; } /** * Returns the colors implementation in use */ getColors() { if (!this.#colors) { this.#colors = useColors(); } return this.#colors; } /** * Define a custom colors implementation */ useColors(color) { this.#colors = color; return this; } /** * Define table head */ head(headColumns) { this.#state.head = headColumns; this.#storeColumnSize( headColumns.map((column) => typeof column === "string" ? column : column.content) ); return this; } /** * Add a new table row */ row(row) { this.#state.rows.push(row); if (Array.isArray(row)) { this.#storeColumnSize(row.map((cell) => typeof cell === "string" ? cell : cell.content)); } return this; } /** * Define custom column widths */ columnWidths(widths) { this.#state.colWidths = widths; return this; } /** * Toggle whether or render in full width or not */ fullWidth(renderFullWidth = true) { this.#renderFullWidth = renderFullWidth; return this; } /** * Define the column index that should take * will remaining width when rendering in * full-width */ fluidColumnIndex(index) { this.#fluidColumnIndex = index; return this; } /** * Render table */ render() { if (this.#options.raw) { this.getRenderer().log( this.#state.head.map((col) => typeof col === "string" ? col : col.content).join("|") ); this.#state.rows.forEach((row) => { const content = Array.isArray(row) ? row.map((cell) => typeof cell === "string" ? cell : cell.content) : Object.keys(row); this.getRenderer().log(content.join("|")); }); return; } this.#computeColumnsWidth(); const cliTable = new CliTable({ head: this.#state.head, style: { "head": [], "border": ["dim"], "padding-left": 2, "padding-right": 2 }, wordWrap: true, ...this.#state.colWidths ? { colWidths: this.#state.colWidths } : {}, chars: this.#options.chars }); this.#state.rows.forEach((row) => cliTable.push(row)); this.getRenderer().log(cliTable.toString()); } }; // src/logger/action.ts import prettyHrtime from "pretty-hrtime"; var Action = class { #startTime; /** * Action options */ #options; /** * Action message */ #message; /** * Reference to the colors implementation */ #colors; /** * The renderer to use for writing to the console */ #renderer; /** * Whether or not to display duration of the action */ #displayDuration = false; constructor(message, options = {}) { this.#message = message; this.#startTime = process.hrtime(); this.#options = { dim: options.dim === void 0 ? false : options.dim }; } /** * Format label */ #formatLabel(label, color) { label = this.getColors()[color](`${label.toUpperCase()}:`); if (this.#options.dim) { return this.getColors().dim(label); } return label; } /** * Format message */ #formatMessage(message) { if (this.#options.dim) { return this.getColors().dim(message); } return message; } /** * Format the suffix */ #formatSuffix(message) { message = `(${message})`; return this.getColors().dim(message); } /** * Format error */ #formatError(error) { let message = typeof error === "string" ? error : error.stack || error.message; return ` ${message.split("\n").map((line) => { if (this.#options.dim) { line = this.getColors().dim(line); } return ` ${this.getColors().red(line)}`; }).join("\n")}`; } /** * Returns the renderer for rendering the messages */ getRenderer() { if (!this.#renderer) { this.#renderer = new ConsoleRenderer(); } return this.#renderer; } /** * Define a custom renderer. */ useRenderer(renderer) { this.#renderer = renderer; return this; } /** * Returns the colors implementation in use */ getColors() { if (!this.#colors) { this.#colors = useColors(); } return this.#colors; } /** * Define a custom colors implementation */ useColors(color) { this.#colors = color; return this; } /** * Toggle whether to display duration for completed * tasks or not. */ displayDuration(displayDuration = true) { this.#displayDuration = displayDuration; return this; } /** * Prepares the message to mark action as successful */ prepareSucceeded() { const formattedLabel = this.#formatLabel("done", "green"); const formattedMessage = this.#formatMessage(this.#message); let logMessage = `${formattedLabel} ${formattedMessage}`; if (this.#displayDuration) { logMessage = `${logMessage} ${this.#formatSuffix( prettyHrtime(process.hrtime(this.#startTime)) )}`; } return logMessage; } /** * Mark action as successful */ succeeded() { this.getRenderer().log(this.prepareSucceeded()); } /** * Prepares the message to mark action as skipped */ prepareSkipped(skipReason) { const formattedLabel = this.#formatLabel("skipped", "cyan"); const formattedMessage = this.#formatMessage(this.#message); let logMessage = `${formattedLabel} ${formattedMessage}`; if (skipReason) { logMessage = `${logMessage} ${this.#formatSuffix(skipReason)}`; } return logMessage; } /** * Mark action as skipped. An optional skip reason can be * supplied */ skipped(skipReason) { this.getRenderer().log(this.prepareSkipped(skipReason)); } /** * Prepares the message to mark action as failed */ prepareFailed(error) { const formattedLabel = this.#formatLabel("failed", "red"); const formattedMessage = this.#formatMessage(this.#message); const formattedError = this.#formatError(error); const logMessage = `${formattedLabel} ${formattedMessage} ${formattedError}`; return logMessage; } /** * Mark action as failed. An error message is required */ failed(error) { this.getRenderer().logError(this.prepareFailed(error)); } }; // src/logger/spinner.ts var Spinner = class { #animator = { frames: [". ", ".. ", "...", " ..", " .", " "], interval: 200, index: 0, getFrame() { return this.frames[this.index]; }, advance() { this.index = this.index + 1 === this.frames.length ? 0 : this.index + 1; return this.index; } }; /** * The state of the spinner */ #state = "idle"; /** * Spinner message */ #message; /** * The renderer to use for writing to the console */ #renderer; /** * Custom method to handle animation result */ #spinnerWriter; constructor(message) { this.#message = message; } /** * Loop over the message and animate the spinner */ #animate() { if (this.#state !== "running") { return; } if (this.#message.silent) { return; } const frame = this.#animator.getFrame(); if (this.#spinnerWriter) { this.#spinnerWriter(`${this.#message.render()} ${frame}`); } else { this.getRenderer().logUpdate(`${this.#message.render()} ${frame}`); } setTimeout(() => { this.#animator.advance(); this.#animate(); }, this.#animator.interval); } /** * Returns the renderer for rendering the messages */ getRenderer() { if (!this.#renderer) { this.#renderer = new ConsoleRenderer(); } return this.#renderer; } /** * Define the custom renderer */ useRenderer(renderer) { this.#renderer = renderer; return this; } /** * Star the spinner */ start() { this.#state = "running"; this.#animate(); return this; } /** * Update spinner */ update(text, options) { if (this.#state !== "running") { return this; } Object.assign(this.#message, { text, ...options }); return this; } /** * Stop spinner */ stop() { this.#state = "stopped"; this.#animator.index = 0; if (!this.#spinnerWriter && !this.#message.silent) { this.getRenderer().logUpdate(`${this.#message.render()} ${this.#animator.frames[2]}`); this.getRenderer().logUpdatePersist(); } } /** * Tap into spinner to manually write the * output. */ tap(callback) { this.#spinnerWriter = callback; return this; } }; // src/logger/main.ts var Logger = class { /** * Logger configuration options */ #options; /** * Reference to the colors implementation */ #colors; /** * The renderer to use to output logs */ #renderer; getLogs() { return this.getRenderer().getLogs(); } flushLogs() { this.getRenderer().flushLogs(); } constructor(options = {}) { const dimOutput = options.dim === void 0 ? false : options.dim; this.#options = { dim: dimOutput, dimLabels: options.dimLabels === void 0 ? dimOutput : options.dimLabels }; } /** * Color the logger label */ #colorizeLabel(color, text) { text = this.getColors()[color](text); if (this.#options.dimLabels) { return `[ ${this.getColors().dim(text)} ]`; } return `[ ${text} ]`; } /** * Returns the label for a given logging type */ #getLabel(type) { switch (type) { case "success": return this.#colorizeLabel("green", type); case "error": case "fatal": return this.#colorizeLabel("red", type); case "warning": return this.#colorizeLabel("yellow", "warn"); case "info": return this.#colorizeLabel("blue", type); case "debug": return this.#colorizeLabel("cyan", type); case "await": return this.#colorizeLabel("cyan", "wait"); } } /** * Appends the suffix to the message */ #addSuffix(message, suffix) { if (!suffix) { return message; } return `${message} ${this.getColors().dim().yellow(`(${suffix})`)}`; } /** * Prepends the prefix to the message. We do not DIM the prefix, since * gray doesn't have much brightness already */ #addPrefix(message, prefix) { if (!prefix) { return message; } prefix = prefix.replace(/%time%/, (/* @__PURE__ */ new Date()).toISOString()); return `${this.getColors().dim(`[${prefix}]`)} ${message}`; } /** * Prepends the prefix to the message */ #prefixLabel(message, label) { return `${label} ${message}`; } /** * Decorate message string */ #decorateMessage(message) { if (this.#options.dim) { return this.getColors().dim(message); } return message; } /** * Decorate error stack */ #formatStack(stack) { if (!stack) { return ""; } return ` ${stack.split("\n").splice(1).map((line) => { if (this.#options.dim) { line = this.getColors().dim(line); } return ` ${this.getColors().red(line)}`; }).join("\n")}`; } /** * Returns the renderer for rendering the messages */ getRenderer() { if (!this.#renderer) { this.#renderer = new ConsoleRenderer(); } return this.#renderer; } /** * Define a custom renderer to output logos */ useRenderer(renderer) { this.#renderer = renderer; return this; } /** * Returns the colors implementation in use */ getColors() { if (!this.#colors) { this.#colors = useColors(); } return this.#colors; } /** * Define a custom colors implementation */ useColors(color) { this.#colors = color; return this; } /** * Log message */ log(message) { this.getRenderer().log(message); } /** * Log message by updating the existing line */ logUpdate(message) { this.getRenderer().logUpdate(message); } /** * Persist log line written using the `logUpdate` * method. */ logUpdatePersist() { this.getRenderer().logUpdatePersist(); } /** * Log error message using the renderer. It is similar to `console.error` * but uses the underlying renderer instead */ logError(message) { this.getRenderer().logError(message); } /** * Prepares the success message */ prepareSuccess(message, options) { message = this.#decorateMessage(message); message = this.#prefixLabel(message, this.#getLabel("success")); message = this.#addPrefix(message, options?.prefix); message = this.#addSuffix(message, options?.suffix); return message; } /** * Log success message */ success(message, options) { this.log(this.prepareSuccess(message, options)); } /** * Prepares the error message */ prepareError(message, options) { message = typeof message === "string" ? message : message.message; message = this.#decorateMessage(message); message = this.#prefixLabel(message, this.#getLabel("error")); message = this.#addPrefix(message, options?.prefix); message = this.#addSuffix(message, options?.suffix); return message; } /** * Log error message */ error(message, options) { this.logError(this.prepareError(message, options)); } /** * Prepares the fatal message */ prepareFatal(message, options) { const stack = this.#formatStack(typeof message === "string" ? void 0 : message.stack); message = typeof message === "string" ? message : message.message; message = this.#decorateMessage(message); message = this.#prefixLabel(message, this.#getLabel("error")); message = this.#addPrefix(message, options?.prefix); message = this.#addSuffix(message, options?.suffix); return `${message}${stack}`; } /** * Log fatal message */ fatal(message, options) { this.logError(this.prepareFatal(message, options)); } /** * Prepares the warning message */ prepareWarning(message, options) { message = this.#decorateMessage(message); message = this.#prefixLabel(message, this.#getLabel("warning")); message = this.#addPrefix(message, options?.prefix); message = this.#addSuffix(message, options?.suffix); return message; } /** * Log warning message */ warning(message, options) { this.log(this.prepareWarning(message, options)); } /** * Prepares the info message */ prepareInfo(message, options) { message = this.#decorateMessage(message); message = this.#prefixLabel(message, this.#getLabel("info")); message = this.#addPrefix(message, options?.prefix); message = this.#addSuffix(message, options?.suffix); return message; } /** * Log info message */ info(message, options) { this.log(this.prepareInfo(message, options)); } /** * Prepares the debug message */ prepareDebug(message, options) { message = this.#decorateMessage(message); message = this.#prefixLabel(message, this.#getLabel("debug")); message = this.#addPrefix(message, options?.prefix); message = this.#addSuffix(message, options?.suffix); return message; } /** * Log debug message */ debug(message, options) { this.log(this.prepareDebug(message, options)); } /** * Log a message with a spinner */ await(text, options) { const message = { logger: this, text, ...options, render() { let decorated = this.logger.#decorateMessage(this.text); decorated = this.logger.#prefixLabel(decorated, this.logger.#getLabel("await")); decorated = this.logger.#addPrefix(decorated, this.prefix); decorated = this.logger.#addSuffix(decorated, this.suffix); return decorated; } }; return new Spinner(message).useRenderer(this.getRenderer()); } /** * Initiates a new action */ action(title) { return new Action(title, { dim: this.#options.dim }).useColors(this.getColors()).useRenderer(this.getRenderer()); } /** * Create a new child instance of self */ child(options) { return new this.constructor(options).useColors(this.getColors()).useRenderer(this.getRenderer()); } }; // src/instructions.ts import boxes from "cli-boxes"; import stringWidth2 from "string-width"; var BOX = boxes.round; var Instructions = class { #state = { content: [] }; /** * Renderer to use for rendering instructions */ #renderer; /** * Length of the widest line inside instructions content */ #widestLineLength = 0; /** * Number of white spaces on the left of the box */ #leftPadding = 4; /** * Number of white spaces on the right of the box */ #rightPadding = 8; /** * Number of empty lines at the top */ #paddingTop = 1; /** * Number of empty lines at the bottom */ #paddingBottom = 1; /** * Reference to the colors */ #colors; /** * Options */ #options; /** * Draws the border */ #drawBorder = (border, colors2) => { return colors2.dim(border); }; constructor(options = {}) { this.#options = { icons: options.icons === void 0 ? true : options.icons, raw: options.raw === void 0 ? false : options.raw }; } /** * Returns the length of the horizontal line */ #getHorizontalLength() { return this.#widestLineLength + this.#leftPadding + this.#rightPadding; } /** * Repeats text for given number of times */ #repeat(text, times) { return new Array(times + 1).join(text); } /** * Wraps content inside the left and right vertical lines */ #wrapInVerticalLines(content, leftWhitespace, rightWhitespace) { return `${this.#drawBorder( BOX.left, this.getColors() )}${leftWhitespace}${content}${rightWhitespace}${this.#drawBorder(BOX.right, this.getColors())}`; } /** * Returns the top line for the box */ #getTopLine() { const horizontalLength = this.#getHorizontalLength(); const horizontalLine = this.#repeat( this.#drawBorder(BOX.top, this.getColors()), horizontalLength ); return `${this.#drawBorder(BOX.topLeft, this.getColors())}${horizontalLine}${this.#drawBorder( BOX.topRight, this.getColors() )}`; } /** * Returns the bottom line for the box */ #getBottomLine() { const horizontalLength = this.#getHorizontalLength(); const horizontalLine = this.#repeat( this.#drawBorder(BOX.bottom, this.getColors()), horizontalLength ); return `${this.#drawBorder( BOX.bottomLeft, this.getColors() )}${horizontalLine}${this.#drawBorder(BOX.bottomRight, this.getColors())}`; } /** * Returns the heading border bottom */ #getHeadingBorderBottom() { const horizontalLength = this.#getHorizontalLength(); const horizontalLine = this.#repeat( this.#drawBorder(boxes.single.top, this.getColors()), horizontalLength ); return this.#wrapInVerticalLines(horizontalLine, "", ""); } /** * Decorates the instruction line by wrapping it inside the box * lines */ #getContentLine(line) { const leftWhitespace = this.#repeat(" ", this.#leftPadding); const rightWhitespace = this.#repeat( " ", this.#widestLineLength - line.width + this.#rightPadding ); return this.#wrapInVerticalLines(line.text, leftWhitespace, rightWhitespace); } /** * Returns the heading line by applying padding */ #getHeading() { if (!this.#state.heading) { return; } return this.#getContentLine(this.#state.heading); } /** * Returns the formatted body */ #getBody() { if (!this.#state.content || !this.#state.content.length) { return; } const top = new Array(this.#paddingTop).fill("").map(this.#getEmptyLineNode); const bottom = new Array(this.#paddingBottom).fill("").map(this.#getEmptyLineNode); return top.concat(this.#state.content).concat(bottom).map((line) => this.#getContentLine(line)).join("\n"); } /** * Returns node for a empty line */ #getEmptyLineNode() { return { text: "", width: 0 }; } /** * Returns the renderer for rendering the messages */ getRenderer() { if (!this.#renderer) { this.#renderer = new ConsoleRenderer(); } return this.#renderer; } /** * Define a custom renderer. Logs to "stdout" and "stderr" * by default */ useRenderer(renderer) { this.#renderer = renderer; return this; } /** * Returns the colors implementation in use */ getColors() { if (!this.#colors) { this.#colors = useColors(); } return this.#colors; } /** * Define a custom colors implementation */ useColors(color) { this.#colors = color; return this; } /** * Draw the instructions box in fullscreen */ fullScreen() { const borderWidth = 2; this.#widestLineLength = TERMINAL_SIZE - (this.#leftPadding + this.#rightPadding) - borderWidth; return this; } /** * Attach a callback to self draw the borders */ drawBorder(callback) { this.#drawBorder = callback; return this; } /** * Define heading for instructions */ heading(text) { const width = stringWidth2(text); if (width > this.#widestLineLength) { this.#widestLineLength = width; } this.#state.heading = { text, width }; return this; } /** * Add new instruction. Each instruction is rendered * in a new line inside a box */ add(text) { text = this.#options.icons ? `${this.getColors().dim(icons.pointer)} ${text}` : `${text}`; const width = stringWidth2(text); if (width > this.#widestLineLength) { this.#widestLineLength = width; } this.#state.content.push({ text, width }); return this; } prepare() { if (this.#options.raw) { let output2 = []; if (this.#state.heading) { output2.push(this.#state.heading.text); } output2 = output2.concat(this.#state.content.map(({ text }) => text)); return output2.join("\n"); } const top = this.#getTopLine(); const heading = this.#getHeading(); const headingBorderBottom = this.#getHeadingBorderBottom(); const body = this.#getBody(); const bottom = this.#getBottomLine(); let output = `${top} `; if (heading) { output = `${output}${heading}`; } if (heading && body) { output = `${output} ${headingBorderBottom} `; } if (body) { output = `${output}${body}`; } return `${output} ${bottom}`; } /** * Render instructions */ render() { this.getRenderer().log(this.prepare()); } }; // src/tasks/task.ts import prettyHrtime2 from "pretty-hrtime"; var Task = class { constructor(title) { this.title = title; } #startTime; /** * Last logged line for the task */ #lastLogLine; /** * Define one or more update listeners */ #updateListeners = []; /** * Duration of the task. Updated after the task is over */ #duration; /** * Message set after completing the task. Can be an error or the * a success message */ #completionMessage; /** * Task current state */ #state = "idle"; #notifyListeners() { for (let listener of this.#updateListeners) { listener(this); } } /** * Access the task state */ getState() { return this.#state; } /** * Get the time spent in running the task */ getDuration() { return this.#duration || null; } /** * Get error occurred while running the task */ getError() { return this.#completionMessage || null; } /** * Get task completion success message */ getSuccessMessage() { return typeof this.#completionMessage === "string" ? this.#completionMessage : null; } /** * Last logged line for the task */ getLastLoggedLine() { return this.#lastLogLine || null; } /** * Bind a listener to listen to the state updates of the task */ onUpdate(listener) { this.#updateListeners.push(listener); return this; } /** * Start the task */ start() { this.#state = "running"; this.#startTime = process.hrtime(); this.#notifyListeners(); return this; } /** * Update task with log messages. Based upon the renderer * in use, it may only display one line at a time. */ update(message) { this.#lastLogLine = message; this.#notifyListeners(); return this; } /** * Mark task as completed */ markAsSucceeded(message) { this.#state = "succeeded"; this.#duration = prettyHrtime2(process.hrtime(this.#startTime)); this.#completionMessage = message; this.#notifyListeners(); return this; } /** * Mark task as failed */ markAsFailed(error) { this.#state = "failed"; this.#duration = prettyHrtime2(process.hrtime(this.#startTime)); this.#completionMessage = error; this.#notifyListeners(); return this; } }; // src/tasks/renderers/verbose.ts var VerboseRenderer = class { /** * Reference to the colors implementation */ #colors; /** * The renderer to use to output logs */ #renderer; /** * List of registered tasks */ #registeredTasks = []; #notifiedTasks = /* @__PURE__ */ new Set(); constructor() { } /** * Format error */ #formatError(error) { if (typeof error === "string") { return `${this.#getAnsiIcon("\u2502", "dim")}${this.getColors().red(error)}`; } if (!error.stack) { return `${this.#getAnsiIcon("\u2502", "dim")}${this.getColors().red(error.message)}`; } return `${error.stack.split("\n").map((line) => `${this.#getAnsiIcon("\u2502", "dim")} ${this.getColors().red(line)}`).join("\n")}`; } /** * Returns the ansi icon back when icons are enabled * or an empty string */ #getAnsiIcon(icon, color) { return this.getColors()[color](`${icon} `); } /** * Renders message for a running task */ #renderRunningTask(task) { if (this.#notifiedTasks.has(task.title)) { const lastLoggedLine = task.getLastLoggedLine(); if (lastLoggedLine) { this.getRenderer().log(`${this.#getAnsiIcon("\u2502", "dim")}${lastLoggedLine}`); } return; } this.getRenderer().log(`${this.#getAnsiIcon("\u250C", "dim")}${task.title}`); this.#notifiedTasks.add(task.title); } /** * Renders message for a succeeded task */ #renderSucceededTask(task) { const successMessage = task.getSuccessMessage(); const icon = this.#getAnsiIcon("\u2514", "dim"); const status = this.getColors().green(successMessage || "Completed"); const duration = this.getColors().dim(`(${task.getDuration()})`); this.getRenderer().log(`${icon}${status} ${duration}`); } /** * Renders message for a failed task */ #renderFailedTask(task) { const error = task.getError(); if (error) { this.getRenderer().logError(this.#formatError(error)); } const icon = this.#getAnsiIcon("\u2514", "dim"); const status = this.getColors().red("Failed"); const duration = this.getColors().dim(`(${task.getDuration()})`); this.getRenderer().logError(`${icon}${status} ${duration}`); } /** * Renders a given task */ #renderTask(task) { switch (task.getState()) { case "running": return this.#renderRunningTask(task); case "succeeded": return this.#renderSucceededTask(task); case "failed": return this.#renderFailedTask(task); } } /** * Renders all tasks */ #renderTasks() { this.#registeredTasks.forEach((task) => this.#renderTask(task)); } /** * Returns the renderer for rendering the messages */ getRenderer() { if (!this.#renderer) { this.#renderer = new ConsoleRenderer(); } return this.#renderer; } /** * Define a custom renderer. Logs to "stdout" and "stderr" * by default */ useRenderer(renderer) { this.#renderer = renderer; return this; } /** * Returns the colors implementation in use */ getColors() { if (!this.#colors) { this.#colors = useColors(); } return this.#colors; } /** * Define a custom colors implementation */ useColors(color) { this.#colors = color; return this; } /** * Register tasks to render */ tasks(tasks) { this.#registeredTasks = tasks; return this; } /** * Render all tasks */ render() { this.#registeredTasks.forEach((task) => task.onUpdate(($task) => this.#renderTask($task))); this.#renderTasks(); } }; // src/tasks/renderers/minimal.ts var MinimalRenderer = class { /** * Renderer options */ #options; /** * Reference to the colors implementation */ #colors; /** * The renderer to use to output logs */ #renderer; /** * List of registered tasks */ #registeredTasks = []; constructor(options) { this.#options = { icons: options.icons === void 0 ? true : options.icons }; } /** * Format error */ #formatError(error) { let message = typeof error === "string" ? error : error.message; message = this.getColors().red(message); return ` ${message.split("\n").map((line) => `${line}`).join("\n")}`; } /** * Returns the pointer icon, if icons are not disabled. */ #getPointerIcon(color) { const icon = this.#options.icons ? `${icons.pointer} ` : ""; if (!icon) { return icon; } return this.getColors()[color](icon); } /** * Returns the display string for an idle task */ #renderIdleTask(task) { return `${this.#getPointerIcon("dim")}${this.getColors().dim(task.title)}`; } /** * Returns the display string for a running task */ #renderRunningTask(task) { const lastLogLine = task.getLastLoggedLine(); const title = this.#options.icons ? `${icons.pointer} ${task.title}` : task.title; return `${title} ${lastLogLine || ""}`; } /** * Returns the display string for a failed task */ #renderFailedTask(task) { const pointer = this.#getPointerIcon("red"); const duration = this.getColors().dim(`(${task.getDuration()})`); let message = `${pointer}${task.title} ${duration}`; const error = task.getError(); if (!error) { return `${message} `; } message = `${message}${this.#formatError(error)}`; return message; } /** * Returns the display string for a succeeded task */ #renderSucceededTask(task) { const pointer = this.#getPointerIcon("green"); const duration = this.getColors().dim(`(${task.getDuration()})`); let message = `${pointer}${task.title} ${duration}`; const successMessage = task.getSuccessMessage(); if (!successMessage) { return `${message} `; } message = `${message} ${this.getColors().green(successMessage)}`; return message; } /** * Renders a given task */ #renderTask(task) { switch (task.getState()) { case "idle": return this.#renderIdleTask(task); case "running": return this.#renderRunningTask(task); case "succeeded": return this.#renderSucceededTask(task); case "failed": return this.#renderFailedTask(task); } } /** * Renders all tasks */ #renderTasks() { const lastTaskState = this.#registeredTasks[this.#registeredTasks.length - 1].getState(); this.getRenderer().logUpdate( this.#registeredTasks.map((task) => this.#renderTask(task)).join("\n") ); if (lastTaskState === "succeeded" || lastTaskState === "failed") { this.getRenderer().logUpdatePersist(); } } /** * Returns the renderer for rendering the messages */ getRenderer() { if (!this.#renderer) { this.#renderer = new ConsoleRenderer(); } return this.#renderer; } /** * Define a custom renderer. Logs to "stdout" and "stderr" * by default */ useRenderer(renderer) { this.#renderer = renderer; return this; } /** * Returns the colors implementation in use */ getColors() { if (!this.#colors) { this.#colors = useColors(); } return this.#colors; } /** * Define a custom colors implementation */ useColors(color) { this.#colors = color; return this; } /** * Register tasks to render */ tasks(tasks) { this.#registeredTasks = tasks; return this; } /** * Render all tasks */ render() { this.#registeredTasks.forEach((task) => { task.onUpdate(() => this.#renderTasks()); }); this.#renderTasks(); } }; // src/tasks/renderers/raw.ts var RawRenderer = class { /** * Reference to the colors implementation */ #colors; /** * The renderer to use to output logs */ #renderer; /** * List of registered tasks */ #registeredTasks = []; #notifiedTasks = /* @__PURE__ */ new Set(); constructor() { } /** * Format error */ #formatError(error) { if (typeof error === "string") { return `${this.getColors().red(error)}`; } if (!error.stack) { return `${this.getColors().red(error.message)}`; } return `${error.stack.split("\n").map((line) => ` ${this.getColors().red(line)}`).join("\n")}`; } /** * Renders message for a running task */ #renderRunningTask(task) { if (this.#notifiedTasks.has(task.title)) { const lastLoggedLine = task.getLastLoggedLine(); if (lastLoggedLine) { this.getRenderer().log(lastLoggedLine); } return; } this.getRenderer().log(`${task.title} ${new Array(task.title.length + 1).join("-")}`); this.#notifiedTasks.add(task.title); } /** * Renders message for a succeeded task */ #renderSucceededTask(task) { const successMessage = task.getSuccessMessage(); const status = this.getColors().green(successMessage || "completed"); const duration = this.getColors().dim(`(${task.getDuration()})`); this.getRenderer().log(`${status} ${duration} `); } /** * Renders message for a failed task */ #renderFailedTask(task) { const error = task.getError(); if (error) { this.getRenderer().logError(this.#formatError(error)); } const status = this.getColors().red("failed"); const duration = this.getColors().dim(`(${task.getDuration()})`); this.getRenderer().logError(`${status} ${duration} `); } /** * Renders a given task */ #renderTask(task) { switch (task.getState()) { case "running": return this.#renderRunningTask(task); case "succeeded": return this.#renderSucceededTask(task); case "failed": return this.#renderFailedTask(task); } } /** * Renders all tasks */ #renderTasks() { this.#registeredTasks.forEach((task) => this.#renderTask(task)); } /** * Returns the renderer for rendering the messages */ getRenderer() { if (!this.#renderer) { this.#renderer = new ConsoleRenderer(); } return this.#renderer; } /** * Define a custom renderer. Logs to "stdout" and "stderr" * by default */ useRenderer(renderer) { this.#renderer = renderer; return this; } /** * Returns the colors implementation in use */ getColors() { if (!this.#colors) { this.#colors = useColors(); } return this.#colors; } /** * Define a custom colors implementation */ useColors(color) { this.#colors = color; return this; } /** * Register tasks to render */ tasks(tasks) { this.#registeredTasks = tasks; return this; } /** * Render all tasks */ render() { this.#registeredTasks.forEach((task) => task.onUpdate(($task) => this.#renderTask($task))); this.#renderTasks(); } }; // src/tasks/manager.ts function TRANSFORM_ERROR(error) { if (typeof error === "string") { return { isError: true, message: error }; } return error; } var TaskManager = class { /** * Last handled error */ error; /** * Options */ #options; /** * The renderer to use for rendering tasks. The verbose renderer is * used When "raw" is set to true. */ #tasksRenderer; /** * A set of created tasks */ #tasks = []; /** * State of the tasks manager */ #state = "idle"; constructor(options = {}) { this.#options = { icons: options.icons === void 0 ? true : options.icons, raw: options.raw === void 0 ? false : options.raw, verbose: options.verbose === void 0 ? false : options.verbose }; if (this.#options.raw) { this.#tasksRenderer = new RawRenderer(); } else if (this.#options.verbose) { this.#tasksRenderer = new VerboseRenderer(); } else { this.#tasksRenderer = new MinimalRenderer({ icons: this.#options.icons }); } } /** * Run a given task. The underlying code assumes that tasks are * executed in sequence. */ async #runTask(index) { const task = this.#tasks[index]; if (!task) { return; } task.task.start(); const update = (logMessage) => { task.task.update(logMessage); }; try { const response = await task.callback({ error: TRANSFORM_ERROR, update }); if (typeof response === "string") { task.task.markAsSucceeded(response); await this.#runTask(index + 1); } else { this.#state = "failed"; task.task.markAsFailed(response); } } catch (error) { this.#state = "failed"; this.error = error; task.task.markAsFailed(error); } } /** * Access the task state */ getState() { return this.#state; } /** * Register a new task */ add(title, callback) { this.#tasks.push({ task: new Task(title), callback }); return this; } /** * Register a new task, when the "conditional = true" */ addIf(conditional, title, callback) { if (conditional) { this.add(title, callback); } return this; } /** * Register a new task, when the "conditional = false" */ addUnless(conditional, title, callback) { if (!conditional) { this.add(title, callback); } return this; } /** * Get access to registered tasks */ tasks() { return this.#tasks.map(({ task }) => task); } /** * Returns the renderer for rendering the messages */ getRenderer() { return this.#tasksRenderer.getRenderer(); } /** * Define a custom renderer. Logs to "stdout" and "stderr" * by default */ useRenderer(renderer) { this.#tasksRenderer.useRenderer(renderer); return this; } /** * Define a custom colors implementation */ useColors(color) { this.#tasksRenderer.useColors(color); return this; } /** * Run tasks */ async run() { if (this.#state !== "idle") { return; } this.#state = "running"; this.#tasksRenderer.tasks(this.tasks()).render(); await this.#runTask(0); if (this.#state === "running") { this.#state = "succeeded"; } } }; // src/renderers/memory.ts var MemoryRenderer = class { #logs = []; getLogs() { return this.#logs; } flushLogs() { this.#logs = []; } /** * Log message */ log(message) { this.#logs.push({ message, stream: "stdout" }); } /** * For memory renderer the logUpdate is similar to log */ logUpdate(message) { this.log(message); } /** * Its a noop */ logUpdatePersist() { } /** * Log message as error */ logError(message) { this.#logs.push({ message, stream: "stderr" }); } }; // index.ts function cliui(options = {}) { let mode = options.mode; if (!mode && !supportsColor.stdout) { mode = "silent"; } let renderer = mode === "raw" ? new MemoryRenderer() : new ConsoleRenderer(); let colors2 = useColors({ silent: mode === "silent", raw: mode === "raw" }); const logger = new Logger(); logger.useRenderer(renderer); logger.useColors(colors2); const instructions = () => { const instructionsInstance = new Instructions({ icons: true, raw: mode === "raw" }); instructionsInstance.useRenderer(renderer); instructionsInstance.useColors(colors2); return instructionsInstance; }; const sticker = () => { const instructionsInstance = new Instructions({ icons: false, raw: mode === "raw" }); instructionsInstance.useRenderer(renderer); instructionsInstance.useColors(colors2); return instructionsInstance; }; const tasks = (tasksOptions) => { const manager = new TaskManager({ raw: mode === "raw", ...tasksOptions }); manager.useRenderer(renderer); manager.useColors(colors2); return manager; }; const table = (tableOptions) => { const tableInstance = new Table({ raw: mode === "raw", ...tableOptions }); tableInstance.useRenderer(renderer); tableInstance.useColors(colors2); return tableInstance; }; return { colors: colors2, logger, table, tasks, icons, sticker, instructions, switchMode(modeToUse) { mode = modeToUse; if (mode === "raw") { this.useRenderer(new MemoryRenderer()); } else { this.useRenderer(new ConsoleRenderer()); } this.useColors(useColors({ silent: mode === "silent", raw: mode === "raw" })); }, useRenderer(rendererToUse) { renderer = rendererToUse; logger.useRenderer(renderer); }, useColors(colorsToUse) { colors2 = colorsToUse; logger.useColors(colors2); this.colors = colors2; } }; } export { ConsoleRenderer, Instructions, Logger, MemoryRenderer, Table, TaskManager, cliui, poppinssColors as colors, icons };