@poppinss/cliui
Version:
Opinionated UI KIT for Command Line apps
1,893 lines (1,875 loc) • 45.1 kB
JavaScript
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
};