listr2
Version:
Terminal task list reborn! Create beautiful CLI interfaces via easy and logical to implement task lists that feel alive and interactive.
1,462 lines (1,422 loc) • 70.3 kB
JavaScript
import EventEmitter from "eventemitter3";
import { inspect, styleText } from "node:util";
import { format } from "util";
import { EOL } from "os";
import { StringDecoder } from "string_decoder";
import { Writable } from "stream";
import rfdc from "rfdc";
import { randomUUID } from "crypto";
//#region src/constants/ansi-escape-codes.constants.ts
/**
* Indicates an UNICODE characters is coming up.
*/
const ANSI_ESCAPE = "\x1B[";
/**
* Generic ANSI escape characters for terminal based operations.
*/
const ANSI_ESCAPE_CODES = {
CURSOR_HIDE: ANSI_ESCAPE + "?25l",
CURSOR_SHOW: ANSI_ESCAPE + "?25h"
};
//#endregion
//#region src/constants/environment-variables.constants.ts
/**
* Environment variables for Listr.
*/
let ListrEnvironmentVariables = /* @__PURE__ */ function(ListrEnvironmentVariables) {
ListrEnvironmentVariables["FORCE_UNICODE"] = "LISTR_FORCE_UNICODE";
ListrEnvironmentVariables["FORCE_TTY"] = "LISTR_FORCE_TTY";
ListrEnvironmentVariables["DISABLE_COLOR"] = "NO_COLOR";
ListrEnvironmentVariables["FORCE_COLOR"] = "FORCE_COLOR";
return ListrEnvironmentVariables;
}({});
//#endregion
//#region src/constants/listr-error.constants.ts
/**
* The actual error type that is collected and to help identify where the error is triggered from.
*/
let ListrErrorTypes = /* @__PURE__ */ function(ListrErrorTypes) {
/** Task has failed and will try to retry. */
ListrErrorTypes["WILL_RETRY"] = "WILL_RETRY";
/** Task has failed and will try to rollback. */
ListrErrorTypes["WILL_ROLLBACK"] = "WILL_ROLLBACK";
/** Task has failed, ran the rollback action but the rollback action itself has failed. */
ListrErrorTypes["HAS_FAILED_TO_ROLLBACK"] = "HAS_FAILED_TO_ROLLBACK";
/** Task has failed. */
ListrErrorTypes["HAS_FAILED"] = "HAS_FAILED";
/** Task has failed, but exitOnError is set to false, so will ignore this error. */
ListrErrorTypes["HAS_FAILED_WITHOUT_ERROR"] = "HAS_FAILED_WITHOUT_ERROR";
return ListrErrorTypes;
}({});
//#endregion
//#region src/constants/listr-events.constants.ts
/**
* Events that are triggered by Listr.
*
* These are stateful and singleton events by being attached to the main Listr class and propagating to the subtasks.
*
* @see {@link https://listr2.kilic.dev/listr/events.html}
*/
let ListrEventType = /* @__PURE__ */ function(ListrEventType) {
/** Indicates that underlying renderer should refresh the current render. */
ListrEventType["SHOULD_REFRESH_RENDER"] = "SHOUD_REFRESH_RENDER";
return ListrEventType;
}({});
//#endregion
//#region src/constants/listr-renderer.constants.ts
let ListrRendererSelection = /* @__PURE__ */ function(ListrRendererSelection) {
ListrRendererSelection["PRIMARY"] = "PRIMARY";
ListrRendererSelection["SECONDARY"] = "SECONDARY";
ListrRendererSelection["SILENT"] = "SILENT";
return ListrRendererSelection;
}({});
//#endregion
//#region src/constants/listr-task-events.constants.ts
/**
* Internal events that are fired from the Task.
*
* @see {@link https://listr2.kilic.dev/task/events.html}
*/
let ListrTaskEventType = /* @__PURE__ */ function(ListrTaskEventType) {
/** Title has changed for the current Task. */
ListrTaskEventType["TITLE"] = "TITLE";
/**
* State has changed for the current Task.
*
* @see {@link module:listr2.ListrTaskState}
*/
ListrTaskEventType["STATE"] = "STATE";
/** The current Task has been marked as enabled. */
ListrTaskEventType["ENABLED"] = "ENABLED";
/** The current Task is currently processing subtasks. */
ListrTaskEventType["SUBTASK"] = "SUBTASK";
/** The current Task is now processing a prompt. */
ListrTaskEventType["PROMPT"] = "PROMPT";
/** The current Task is now dumping output. */
ListrTaskEventType["OUTPUT"] = "OUTPUT";
/**
* The current Task is now dumping a message.
*
* @see {module:Listr2.ListrTaskMessage}
*/
ListrTaskEventType["MESSAGE"] = "MESSAGE";
/** The current Task is closed and no further action in expected. */
ListrTaskEventType["CLOSED"] = "CLOSED";
return ListrTaskEventType;
}({});
//#endregion
//#region src/constants/listr-task-state.constants.ts
/**
* Tasks can be in various states during the execution.
*
* Whenever a state change occurs, the task will emit a {@link module:listr2.ListrTaskEventType.STATE} with the appropriate state.
*/
let ListrTaskState = /* @__PURE__ */ function(ListrTaskState) {
/** Task has not started yet, waiting for pick-up. */
ListrTaskState["WAITING"] = "WAITING";
/** Task has started. */
ListrTaskState["STARTED"] = "STARTED";
/** Task has been completed. */
ListrTaskState["COMPLETED"] = "COMPLETED";
/** Task has failed. */
ListrTaskState["FAILED"] = "FAILED";
/** Task has been skipped. */
ListrTaskState["SKIPPED"] = "SKIPPED";
/** Task is currently trying to rollback. */
ListrTaskState["ROLLING_BACK"] = "ROLLING_BACK";
/** Task has rolledback successfully after failing. */
ListrTaskState["ROLLED_BACK"] = "ROLLED_BACK";
/** Task is currently retrying. */
ListrTaskState["RETRY"] = "RETRY";
/** Task is currently paused. */
ListrTaskState["PAUSED"] = "PAUSED";
/** Task is currently trying to process a prompt. */
ListrTaskState["PROMPT"] = "PROMPT";
/** Task has successfully processed the prompt. */
ListrTaskState["PROMPT_COMPLETED"] = "PROMPT_COMPLETED";
/** Task has failed to process the prompt. */
ListrTaskState["PROMPT_FAILED"] = "PROMPT_FAILED";
return ListrTaskState;
}({});
//#endregion
//#region src/lib/event-manager.ts
var EventManager = class {
emitter = new EventEmitter();
emit(dispatch, args) {
this.emitter.emit(dispatch, args);
}
on(dispatch, handler) {
this.emitter.addListener(dispatch, handler);
}
once(dispatch, handler) {
this.emitter.once(dispatch, handler);
}
off(dispatch, handler) {
this.emitter.off(dispatch, handler);
}
complete() {
this.emitter.removeAllListeners();
}
};
//#endregion
//#region src/interfaces/event.interface.ts
/**
* Give event map a set of indexes to not make it go crazy when some events are missing from it.
* They are optional after all.
*/
var BaseEventMap = class {};
//#endregion
//#region src/utils/environment/is-observable.ts
/**
* Tests to see if the object is an RxJS {@link Observable}
* @param obj the object to test
*/
function isObservable(obj) {
return !!obj && typeof obj === "object" && typeof obj.subscribe === "function";
}
//#endregion
//#region src/utils/environment/is-readable.ts
/**
* Tests to see if the object is an Readable or NodeJS.ReadableStream {@link Readable, NodeJS.ReadableStream}
* @param obj the object to test
*/
function isReadable(obj) {
return !!obj && typeof obj === "object" && obj.readable === true && typeof obj.read === "function" && typeof obj.on === "function";
}
//#endregion
//#region src/utils/environment/is-unicode-supported.ts
function isUnicodeSupported() {
/* istanbul ignore next */
return !!process.env[ListrEnvironmentVariables.FORCE_UNICODE] || process.platform !== "win32" || !!process.env.CI || !!process.env.WT_SESSION || process.env.TERM_PROGRAM === "vscode" || process.env.TERM === "xterm-256color" || process.env.TERM === "alacritty";
}
//#endregion
//#region src/utils/format/cleanse-ansi.constants.ts
const CLEAR_LINE_REGEX = "(?:\\u001b|\\u009b)\\[[\\=><~/#&.:=?%@~_-]*[0-9]*[\\a-ln-tqyz=><~/#&.:=?%@~_-]+";
const BELL_REGEX = /\u0007/;
//#endregion
//#region src/utils/format/cleanse-ansi.ts
function cleanseAnsi(chunk) {
return String(chunk).replace(new RegExp(CLEAR_LINE_REGEX, "gmi"), "").replace(new RegExp(BELL_REGEX, "gmi"), "").trim();
}
//#endregion
//#region src/utils/format/color.ts
/**
* Color palette.
*/
const color = Object.fromEntries(Object.keys(inspect.colors).map((color) => [color, (text) => styleText(color, String(text))]));
//#endregion
//#region src/utils/format/indent.ts
function indent(string, count) {
return string.replace(/^(?!\s*$)/gm, " ".repeat(count));
}
//#endregion
//#region src/utils/format/figures.ts
const FIGURES_MAIN = {
warning: "⚠",
cross: "✖",
arrowDown: "↓",
tick: "✔",
arrowRight: "→",
pointer: "❯",
checkboxOn: "☒",
arrowLeft: "←",
squareSmallFilled: "◼",
pointerSmall: "›"
};
const FIGURES_FALLBACK = {
...FIGURES_MAIN,
warning: "‼",
cross: "×",
tick: "√",
pointer: ">",
checkboxOn: "[×]",
squareSmallFilled: "■"
};
const figures = isUnicodeSupported() ? FIGURES_MAIN : FIGURES_FALLBACK;
//#endregion
//#region src/utils/format/splat.ts
function splat(message, ...splat) {
return format(String(message), ...splat);
}
//#endregion
//#region src/utils/logger/logger.constants.ts
/** Default ListrLogLevels for the logger */
let ListrLogLevels = /* @__PURE__ */ function(ListrLogLevels) {
ListrLogLevels["STARTED"] = "STARTED";
ListrLogLevels["COMPLETED"] = "COMPLETED";
ListrLogLevels["FAILED"] = "FAILED";
ListrLogLevels["SKIPPED"] = "SKIPPED";
ListrLogLevels["OUTPUT"] = "OUTPUT";
ListrLogLevels["TITLE"] = "TITLE";
ListrLogLevels["ROLLBACK"] = "ROLLBACK";
ListrLogLevels["RETRY"] = "RETRY";
ListrLogLevels["PROMPT"] = "PROMPT";
ListrLogLevels["PAUSED"] = "PAUSED";
return ListrLogLevels;
}({});
const LISTR_LOGGER_STYLE = {
icon: {
[ListrLogLevels.STARTED]: figures.pointer,
[ListrLogLevels.FAILED]: figures.cross,
[ListrLogLevels.SKIPPED]: figures.arrowDown,
[ListrLogLevels.COMPLETED]: figures.tick,
[ListrLogLevels.OUTPUT]: figures.pointerSmall,
[ListrLogLevels.TITLE]: figures.arrowRight,
[ListrLogLevels.RETRY]: figures.warning,
[ListrLogLevels.ROLLBACK]: figures.arrowLeft,
[ListrLogLevels.PAUSED]: figures.squareSmallFilled
},
color: {
[ListrLogLevels.STARTED]: color.yellow,
[ListrLogLevels.FAILED]: color.red,
[ListrLogLevels.SKIPPED]: color.yellow,
[ListrLogLevels.COMPLETED]: color.green,
[ListrLogLevels.RETRY]: color.yellowBright,
[ListrLogLevels.ROLLBACK]: color.redBright,
[ListrLogLevels.PAUSED]: color.yellowBright
}
};
const LISTR_LOGGER_STDERR_LEVELS = [
ListrLogLevels.RETRY,
ListrLogLevels.ROLLBACK,
ListrLogLevels.FAILED
];
//#endregion
//#region src/utils/logger/logger.ts
/**
* Creates a new Listr2 logger.
*
* This logger is used throughout the renderers for consistency.
*
* @see {@link https://listr2.kilic.dev/renderer/logger.html}
*/
var ListrLogger = class {
process;
constructor(options) {
this.options = options;
this.options = {
useIcons: true,
toStderr: [],
...options ?? {}
};
this.options.fields ??= {};
this.options.fields.prefix ??= [];
this.options.fields.suffix ??= [];
this.process = this.options.processOutput ?? new ProcessOutput();
}
log(level, message, options) {
const output = this.format(level, message, options);
if (this.options.toStderr.includes(level)) {
this.process.toStderr(output);
return;
}
this.process.toStdout(output);
}
toStdout(message, options, eol = true) {
this.process.toStdout(this.format(null, message, options), eol);
}
toStderr(message, options, eol = true) {
this.process.toStderr(this.format(null, message, options), eol);
}
wrap(message, options) {
if (!message) return message;
return this.applyFormat(`[${message}]`, options);
}
splat(...args) {
const message = args.shift() ?? "";
return args.length === 0 ? message : splat(message, args);
}
suffix(message, ...suffixes) {
suffixes.filter(Boolean).forEach((suffix) => {
message += this.spacing(message);
if (typeof suffix === "string") message += this.wrap(suffix);
else if (typeof suffix === "object") {
suffix.args ??= [];
if (typeof suffix.condition === "function" ? !suffix.condition(...suffix.args) : !(suffix.condition ?? true)) return message;
message += this.wrap(typeof suffix.field === "function" ? suffix.field(...suffix.args) : suffix.field, { format: suffix?.format(...suffix.args) });
}
});
return message;
}
prefix(message, ...prefixes) {
prefixes.filter(Boolean).forEach((prefix) => {
message = this.spacing(message) + message;
if (typeof prefix === "string") message = this.wrap(prefix) + message;
else if (typeof prefix === "object") {
prefix.args ??= [];
if (typeof prefix.condition === "function" ? !prefix.condition(...prefix.args) : !(prefix.condition ?? true)) return message;
message = this.wrap(typeof prefix.field === "function" ? prefix.field(...prefix.args) : prefix.field, { format: prefix?.format() }) + message;
}
});
return message;
}
fields(message, options) {
if (this.options?.fields?.prefix) message = this.prefix(message, ...this.options.fields.prefix);
if (options?.prefix) message = this.prefix(message, ...options.prefix);
if (options?.suffix) message = this.suffix(message, ...options.suffix);
if (this.options?.fields?.suffix) message = this.suffix(message, ...this.options.fields.suffix);
return message;
}
icon(level, icon) {
if (!level) return null;
if (!icon) {
const i = this.options.icon?.[level];
icon = typeof i === "function" ? i() : i;
}
const coloring = this.options.color?.[level];
if (icon && coloring) icon = coloring(icon);
return icon;
}
format(level, message, options) {
if (!Array.isArray(message)) message = [message];
message = this.splat(message.shift(), ...message).toString().split(EOL).filter((m) => !m || m.trim() !== "").map((m) => {
return this.style(level, this.fields(m, {
prefix: Array.isArray(options?.prefix) ? options.prefix : [options?.prefix],
suffix: Array.isArray(options?.suffix) ? options.suffix : [options?.suffix]
}));
}).join(EOL);
return message;
}
style(level, message) {
if (!level || !message) return message;
const icon = this.icon(level, !this.options.useIcons && this.wrap(level));
if (icon) message = icon + " " + message;
return message;
}
applyFormat(message, options) {
if (options?.format) return options.format(message);
return message;
}
spacing(message) {
return typeof message === "undefined" || message.trim() === "" ? "" : " ";
}
};
//#endregion
//#region src/utils/process-output/process-output-buffer.ts
var ProcessOutputBuffer = class {
buffer = [];
decoder = new StringDecoder();
constructor(options) {
this.options = options;
}
get all() {
return this.buffer;
}
get last() {
return this.buffer.at(-1);
}
get length() {
return this.buffer.length;
}
write(data, ...args) {
const callback = args[args.length - 1];
this.buffer.push({
time: Date.now(),
stream: this.options?.stream,
entry: this.decoder.write(typeof data === "string" ? Buffer.from(data, typeof args[0] === "string" ? args[0] : void 0) : Buffer.from(data))
});
if (this.options?.limit) this.buffer = this.buffer.slice(-this.options.limit);
if (typeof callback === "function") callback();
return true;
}
reset() {
this.buffer = [];
}
};
//#endregion
//#region src/utils/process-output/process-output-stream.ts
var ProcessOutputStream = class {
method;
buffer;
constructor(stream) {
this.stream = stream;
this.method = stream.write;
this.buffer = new ProcessOutputBuffer({ stream });
}
get out() {
const self = this;
return new Proxy(this.stream, { get(target, prop, receiver) {
if (prop === "write") return self.write.bind(self);
return Reflect.get(target, prop, receiver);
} });
}
hijack() {
this.stream.write = this.buffer.write.bind(this.buffer);
}
release() {
this.stream.write = this.method;
const buffer = [...this.buffer.all];
this.buffer.reset();
return buffer;
}
write(...args) {
return this.method.apply(this.stream, args);
}
};
//#endregion
//#region src/utils/process-output/process-output.ts
/**
* Creates a new Listr2 process-output controller.
*
* This is used to control the flow to `process.stdout` and `process.stderr` for all renderers.
*
* @see {@link https://listr2.kilic.dev/renderer/process-output.html}
*/
var ProcessOutput = class {
stream;
active;
constructor(stdout, stderr, options) {
this.options = options;
this.stream = {
stdout: new ProcessOutputStream(stdout ?? process.stdout),
stderr: new ProcessOutputStream(stderr ?? process.stderr)
};
this.options = {
dump: ["stdout", "stderr"],
leaveEmptyLine: true,
...options
};
}
get stdout() {
return this.stream.stdout.out;
}
get stderr() {
return this.stream.stderr.out;
}
hijack() {
if (this.active) throw new Error("ProcessOutput has been already hijacked!");
this.stream.stdout.write(ANSI_ESCAPE_CODES.CURSOR_HIDE);
Object.values(this.stream).forEach((stream) => stream.hijack());
this.active = true;
}
release() {
const output = Object.entries(this.stream).map(([name, stream]) => ({
name,
buffer: stream.release()
})).filter((output) => this.options.dump.includes(output.name)).flatMap((output) => output.buffer).sort((a, b) => a.time - b.time).map((message) => {
return {
...message,
entry: cleanseAnsi(message.entry)
};
}).filter((message) => message.entry);
if (output.length > 0) {
if (this.options.leaveEmptyLine) this.stdout.write(EOL);
output.forEach((message) => {
(message.stream ?? this.stdout).write(message.entry + EOL);
});
}
this.stream.stdout.write(ANSI_ESCAPE_CODES.CURSOR_SHOW);
this.active = false;
}
toStdout(buffer, eol = true) {
if (eol) buffer = buffer + EOL;
return this.stream.stdout.write(buffer);
}
toStderr(buffer, eol = true) {
if (eol) buffer = buffer + EOL;
return this.stream.stderr.write(buffer);
}
};
//#endregion
//#region src/utils/process-output/writable.ts
/* istanbul ignore next */
function createWritable(cb) {
const writable = new Writable();
writable.rows = Infinity;
writable.columns = Infinity;
writable.write = (chunk) => {
cb(chunk.toString());
return true;
};
return writable;
}
//#endregion
//#region src/utils/prompts/adapter.ts
/* istanbul ignore next */
var ListrPromptAdapter = class {
state;
constructor(task, wrapper) {
this.task = task;
this.wrapper = wrapper;
}
reportStarted() {
this.state = this.task.state;
if (this.task.prompt) throw new PromptError("There is already an active prompt attached to this task which may not be cleaned up properly.");
this.task.prompt = this;
this.task.state$ = ListrTaskState.PROMPT;
}
reportFailed() {
this.task.state$ = ListrTaskState.PROMPT_FAILED;
this.restoreState();
}
reportCompleted() {
this.task.state$ = ListrTaskState.PROMPT_COMPLETED;
this.restoreState();
}
restoreState() {
this.task.prompt = void 0;
if (this.state) this.task.state = this.state;
}
};
//#endregion
//#region src/utils/ui/spinner.ts
/* istanbul ignore next */
var Spinner = class {
spinner = !isUnicodeSupported() ? [
"-",
"\\",
"|",
"/"
] : [
"⠋",
"⠙",
"⠹",
"⠸",
"⠼",
"⠴",
"⠦",
"⠧",
"⠇",
"⠏"
];
id;
spinnerPosition = 0;
spin() {
this.spinnerPosition = ++this.spinnerPosition % this.spinner.length;
}
fetch() {
return this.spinner[this.spinnerPosition];
}
isRunning() {
return !!this.id;
}
start(cb, interval = 100) {
this.id = setInterval(() => {
this.spin();
if (cb) cb();
}, interval);
}
stop() {
clearInterval(this.id);
this.id = void 0;
}
};
//#endregion
//#region src/renderer/default/renderer.constants.ts
let ListrDefaultRendererLogLevels = /* @__PURE__ */ function(ListrDefaultRendererLogLevels) {
ListrDefaultRendererLogLevels["SKIPPED_WITH_COLLAPSE"] = "SKIPPED_WITH_COLLAPSE";
ListrDefaultRendererLogLevels["SKIPPED_WITHOUT_COLLAPSE"] = "SKIPPED_WITHOUT_COLLAPSE";
ListrDefaultRendererLogLevels["OUTPUT"] = "OUTPUT";
ListrDefaultRendererLogLevels["OUTPUT_WITH_BOTTOMBAR"] = "OUTPUT_WITH_BOTTOMBAR";
ListrDefaultRendererLogLevels["PENDING"] = "PENDING";
ListrDefaultRendererLogLevels["COMPLETED"] = "COMPLETED";
ListrDefaultRendererLogLevels["COMPLETED_WITH_FAILED_SUBTASKS"] = "COMPLETED_WITH_FAILED_SUBTASKS";
ListrDefaultRendererLogLevels["COMPLETED_WITH_FAILED_SISTER_TASKS"] = "COMPLETED_WITH_SISTER_TASKS_FAILED";
ListrDefaultRendererLogLevels["RETRY"] = "RETRY";
ListrDefaultRendererLogLevels["ROLLING_BACK"] = "ROLLING_BACK";
ListrDefaultRendererLogLevels["ROLLED_BACK"] = "ROLLED_BACK";
ListrDefaultRendererLogLevels["FAILED"] = "FAILED";
ListrDefaultRendererLogLevels["FAILED_WITH_FAILED_SUBTASKS"] = "FAILED_WITH_SUBTASKS";
ListrDefaultRendererLogLevels["WAITING"] = "WAITING";
ListrDefaultRendererLogLevels["PAUSED"] = "PAUSED";
return ListrDefaultRendererLogLevels;
}({});
const LISTR_DEFAULT_RENDERER_STYLE = {
icon: {
[ListrDefaultRendererLogLevels.SKIPPED_WITH_COLLAPSE]: figures.arrowDown,
[ListrDefaultRendererLogLevels.SKIPPED_WITHOUT_COLLAPSE]: figures.warning,
[ListrDefaultRendererLogLevels.OUTPUT]: figures.pointerSmall,
[ListrDefaultRendererLogLevels.OUTPUT_WITH_BOTTOMBAR]: figures.pointerSmall,
[ListrDefaultRendererLogLevels.PENDING]: figures.pointer,
[ListrDefaultRendererLogLevels.COMPLETED]: figures.tick,
[ListrDefaultRendererLogLevels.COMPLETED_WITH_FAILED_SUBTASKS]: figures.warning,
[ListrDefaultRendererLogLevels.COMPLETED_WITH_FAILED_SISTER_TASKS]: figures.squareSmallFilled,
[ListrDefaultRendererLogLevels.RETRY]: figures.warning,
[ListrDefaultRendererLogLevels.ROLLING_BACK]: figures.warning,
[ListrDefaultRendererLogLevels.ROLLED_BACK]: figures.arrowLeft,
[ListrDefaultRendererLogLevels.FAILED]: figures.cross,
[ListrDefaultRendererLogLevels.FAILED_WITH_FAILED_SUBTASKS]: figures.pointer,
[ListrDefaultRendererLogLevels.WAITING]: figures.squareSmallFilled,
[ListrDefaultRendererLogLevels.PAUSED]: figures.squareSmallFilled
},
color: {
[ListrDefaultRendererLogLevels.SKIPPED_WITH_COLLAPSE]: color.yellow,
[ListrDefaultRendererLogLevels.SKIPPED_WITHOUT_COLLAPSE]: color.yellow,
[ListrDefaultRendererLogLevels.PENDING]: color.yellow,
[ListrDefaultRendererLogLevels.COMPLETED]: color.green,
[ListrDefaultRendererLogLevels.COMPLETED_WITH_FAILED_SUBTASKS]: color.yellow,
[ListrDefaultRendererLogLevels.COMPLETED_WITH_FAILED_SISTER_TASKS]: color.red,
[ListrDefaultRendererLogLevels.RETRY]: color.yellowBright,
[ListrDefaultRendererLogLevels.ROLLING_BACK]: color.redBright,
[ListrDefaultRendererLogLevels.ROLLED_BACK]: color.redBright,
[ListrDefaultRendererLogLevels.FAILED]: color.red,
[ListrDefaultRendererLogLevels.FAILED_WITH_FAILED_SUBTASKS]: color.red,
[ListrDefaultRendererLogLevels.WAITING]: color.dim,
[ListrDefaultRendererLogLevels.PAUSED]: color.yellowBright
}
};
//#endregion
//#region src/presets/timer/parser.ts
/**
* A basic function to parse minutes and tasks passed given a duration.
* Useful for renderers to show the task time.
*/
/* istanbul ignore next */
function parseTimer(duration) {
const seconds = Math.floor(duration / 1e3);
const minutes = Math.floor(seconds / 60);
let parsedTime;
if (seconds === 0 && minutes === 0) parsedTime = `0.${Math.floor(duration / 100)}s`;
if (seconds > 0) parsedTime = `${seconds % 60}s`;
if (minutes > 0) parsedTime = `${minutes}m${parsedTime}`;
return parsedTime;
}
//#endregion
//#region src/presets/timer/preset.ts
/* istanbul ignore next */
const PRESET_TIMER = {
condition: true,
field: parseTimer,
format: () => color.dim
};
//#endregion
//#region src/presets/timestamp/parser.ts
/* istanbul ignore next */
function parseTimestamp() {
const now = /* @__PURE__ */ new Date();
return String(now.getHours()).padStart(2, "0") + ":" + String(now.getMinutes()).padStart(2, "0") + ":" + String(now.getSeconds()).padStart(2, "0");
}
//#endregion
//#region src/presets/timestamp/preset.ts
/* istanbul ignore next */
const PRESET_TIMESTAMP = {
condition: true,
field: parseTimestamp,
format: () => color.dim
};
//#endregion
//#region src/renderer/default/renderer.ts
var DefaultRenderer = class DefaultRenderer {
static nonTTY = false;
static rendererOptions = {
indentation: 2,
clearOutput: false,
showSubtasks: true,
collapseSubtasks: true,
collapseSkips: true,
showSkipMessage: true,
suffixSkips: false,
collapseErrors: true,
showErrorMessage: true,
suffixRetries: true,
lazy: false,
removeEmptyLines: true,
formatOutput: "wrap",
pausedTimer: {
...PRESET_TIMER,
format: () => color.yellowBright
}
};
static rendererTaskOptions = { outputBar: true };
prompt;
activePrompt;
spinner;
logger;
updater;
truncate;
wrap;
buffer = {
output: /* @__PURE__ */ new Map(),
bottom: /* @__PURE__ */ new Map()
};
cache = {
render: /* @__PURE__ */ new Map(),
rendererOptions: /* @__PURE__ */ new Map(),
rendererTaskOptions: /* @__PURE__ */ new Map()
};
constructor(tasks, options, events) {
this.tasks = tasks;
this.options = options;
this.events = events;
this.options = {
...DefaultRenderer.rendererOptions,
...this.options,
icon: {
...LISTR_DEFAULT_RENDERER_STYLE.icon,
...options?.icon ?? {}
},
color: {
...LISTR_DEFAULT_RENDERER_STYLE.color,
...options?.color ?? {}
}
};
this.spinner = this.options.spinner ?? new Spinner();
this.logger = this.options.logger ?? new ListrLogger({
useIcons: true,
toStderr: []
});
this.logger.options.icon = this.options.icon;
this.logger.options.color = this.options.color;
}
async render() {
const { createLogUpdate } = await import("log-update");
const { default: truncate } = await import("cli-truncate");
const { default: wrap } = await import("wrap-ansi");
this.updater = createLogUpdate(this.logger.process.stdout);
this.truncate = truncate;
this.wrap = wrap;
this.logger.process.hijack();
/* istanbul ignore if */
if (!this.options?.lazy) this.spinner.start(() => {
this.update();
});
this.events.on(ListrEventType.SHOULD_REFRESH_RENDER, () => {
this.update();
});
}
update() {
this.updater(this.create());
}
end() {
this.spinner.stop();
this.updater.clear();
this.updater.done();
if (!this.options.clearOutput) this.logger.process.toStdout(this.create({ prompt: false }));
this.logger.process.release();
}
create(options) {
options = {
tasks: true,
bottomBar: true,
prompt: true,
...options
};
const render = [];
const renderTasks = this.renderer(this.tasks);
const renderBottomBar = this.renderBottomBar();
const renderPrompt = this.renderPrompt();
if (options.tasks && renderTasks.length > 0) render.push(...renderTasks);
if (options.bottomBar && renderBottomBar.length > 0) {
if (render.length > 0) render.push("");
render.push(...renderBottomBar);
}
if (options.prompt && renderPrompt.length > 0) {
if (render.length > 0) render.push("");
render.push(...renderPrompt);
}
return render.join(EOL);
}
style(task, output = false) {
const rendererOptions = this.cache.rendererOptions.get(task.id);
if (task.isSkipped()) {
if (output || rendererOptions.collapseSkips) return this.logger.icon(ListrDefaultRendererLogLevels.SKIPPED_WITH_COLLAPSE);
else if (rendererOptions.collapseSkips === false) return this.logger.icon(ListrDefaultRendererLogLevels.SKIPPED_WITHOUT_COLLAPSE);
}
if (output) {
if (this.shouldOutputToBottomBar(task)) return this.logger.icon(ListrDefaultRendererLogLevels.OUTPUT_WITH_BOTTOMBAR);
return this.logger.icon(ListrDefaultRendererLogLevels.OUTPUT);
}
if (task.hasSubtasks()) {
if (task.isStarted() || task.isPrompt() && rendererOptions.showSubtasks !== false && !task.subtasks.every((subtask) => !subtask.hasTitle())) return this.logger.icon(ListrDefaultRendererLogLevels.PENDING);
else if (task.isCompleted() && task.subtasks.some((subtask) => subtask.hasFailed())) return this.logger.icon(ListrDefaultRendererLogLevels.COMPLETED_WITH_FAILED_SUBTASKS);
else if (task.hasFailed()) return this.logger.icon(ListrDefaultRendererLogLevels.FAILED_WITH_FAILED_SUBTASKS);
}
if (task.isStarted() || task.isPrompt()) return this.logger.icon(ListrDefaultRendererLogLevels.PENDING, !this.options?.lazy && this.spinner.fetch());
else if (task.isCompleted()) return this.logger.icon(ListrDefaultRendererLogLevels.COMPLETED);
else if (task.isRetrying()) return this.logger.icon(ListrDefaultRendererLogLevels.RETRY, !this.options?.lazy && this.spinner.fetch());
else if (task.isRollingBack()) return this.logger.icon(ListrDefaultRendererLogLevels.ROLLING_BACK, !this.options?.lazy && this.spinner.fetch());
else if (task.hasRolledBack()) return this.logger.icon(ListrDefaultRendererLogLevels.ROLLED_BACK);
else if (task.hasFailed()) return this.logger.icon(ListrDefaultRendererLogLevels.FAILED);
else if (task.isPaused()) return this.logger.icon(ListrDefaultRendererLogLevels.PAUSED);
return this.logger.icon(ListrDefaultRendererLogLevels.WAITING);
}
format(message, icon, level) {
if (message.trim() === "") return [];
if (icon) message = icon + " " + message;
let parsed;
const columns = (process.stdout.columns ?? 80) - level * this.options.indentation - 2;
switch (this.options.formatOutput) {
case "truncate":
parsed = message.split(EOL).map((s, i) => {
return this.truncate(this.indent(s, i), columns);
});
break;
case "wrap":
parsed = this.wrap(message, columns, {
hard: true,
trim: false
}).split(EOL).map((s, i) => this.indent(s, i));
break;
default: throw new ListrRendererError("Format option for the renderer is wrong.");
}
if (this.options.removeEmptyLines) parsed = parsed.filter(Boolean);
return parsed.map((str) => indent(str, level * this.options.indentation));
}
shouldOutputToOutputBar(task) {
const outputBar = this.cache.rendererTaskOptions.get(task.id).outputBar;
return typeof outputBar === "number" && outputBar !== 0 || typeof outputBar === "boolean" && outputBar !== false;
}
shouldOutputToBottomBar(task) {
const bottomBar = this.cache.rendererTaskOptions.get(task.id).bottomBar;
return typeof bottomBar === "number" && bottomBar !== 0 || typeof bottomBar === "boolean" && bottomBar !== false || !task.hasTitle();
}
renderer(tasks, level = 0) {
return tasks.flatMap((task) => {
if (!task.isEnabled()) return [];
if (this.cache.render.has(task.id)) return this.cache.render.get(task.id);
this.calculate(task);
this.setupBuffer(task);
const rendererOptions = this.cache.rendererOptions.get(task.id);
const rendererTaskOptions = this.cache.rendererTaskOptions.get(task.id);
const output = [];
if (task.isPrompt()) {
if (this.activePrompt && this.activePrompt !== task.id) throw new ListrRendererError("Only one prompt can be active at the given time, please re-evaluate your task design.");
else if (!this.activePrompt) {
task.on(ListrTaskEventType.PROMPT, (prompt) => {
const cleansed = cleanseAnsi(prompt);
if (cleansed) this.prompt = cleansed;
});
task.on(ListrTaskEventType.STATE, (state) => {
if (state === ListrTaskState.PROMPT_COMPLETED || task.hasFinalized() || task.hasReset()) {
this.prompt = null;
this.activePrompt = null;
task.off(ListrTaskEventType.PROMPT);
}
});
this.activePrompt = task.id;
}
}
if (task.hasTitle()) if (!(tasks.some((task) => task.hasFailed()) && !task.hasFailed() && task.options.exitOnError !== false && !(task.isCompleted() || task.isSkipped()))) if (task.hasFailed() && rendererOptions.collapseErrors) output.push(...this.format(!task.hasSubtasks() && task.message.error && rendererOptions.showErrorMessage ? task.message.error : task.title, this.style(task), level));
else if (task.isSkipped() && rendererOptions.collapseSkips) output.push(...this.format(this.logger.suffix(task.message.skip && rendererOptions.showSkipMessage ? task.message.skip : task.title, {
field: ListrLogLevels.SKIPPED,
condition: rendererOptions.suffixSkips,
format: () => color.dim
}), this.style(task), level));
else if (task.isRetrying()) output.push(...this.format(this.logger.suffix(task.title, {
field: `${ListrLogLevels.RETRY}:${task.message.retry.count}`,
format: () => color.yellow,
condition: rendererOptions.suffixRetries
}), this.style(task), level));
else if (task.isCompleted() && task.hasTitle() && assertFunctionOrSelf(rendererTaskOptions.timer?.condition, task.message.duration)) output.push(...this.format(this.logger.suffix(task?.title, {
...rendererTaskOptions.timer,
args: [task.message.duration]
}), this.style(task), level));
else if (task.isPaused()) output.push(...this.format(this.logger.suffix(task.title, {
...rendererOptions.pausedTimer,
args: [task.message.paused - Date.now()]
}), this.style(task), level));
else output.push(...this.format(task.title, this.style(task), level));
else output.push(...this.format(task.title, this.logger.icon(ListrDefaultRendererLogLevels.COMPLETED_WITH_FAILED_SISTER_TASKS), level));
if (!task.hasSubtasks() || !rendererOptions.showSubtasks) {
if (task.hasFailed() && rendererOptions.collapseErrors === false && (rendererOptions.showErrorMessage || !rendererOptions.showSubtasks)) output.push(...this.dump(task, level, ListrLogLevels.FAILED));
else if (task.isSkipped() && rendererOptions.collapseSkips === false && (rendererOptions.showSkipMessage || !rendererOptions.showSubtasks)) output.push(...this.dump(task, level, ListrLogLevels.SKIPPED));
}
if (task.isPending() || rendererTaskOptions.persistentOutput) output.push(...this.renderOutputBar(task, level));
if (rendererOptions.showSubtasks !== false && task.hasSubtasks() && (task.isPending() || task.hasFinalized() && !task.hasTitle() || task.isCompleted() && rendererOptions.collapseSubtasks === false && !task.subtasks.some((subtask) => this.cache.rendererOptions.get(subtask.id)?.collapseSubtasks === true) || task.subtasks.some((subtask) => this.cache.rendererOptions.get(subtask.id)?.collapseSubtasks === false) || task.subtasks.some((subtask) => subtask.hasFailed()) || task.subtasks.some((subtask) => subtask.hasRolledBack()))) {
const subtaskLevel = !task.hasTitle() ? level : level + 1;
const subtaskRender = this.renderer(task.subtasks, subtaskLevel);
output.push(...subtaskRender);
}
if (task.hasFinalized()) {
if (!rendererTaskOptions.persistentOutput) {
this.buffer.bottom.delete(task.id);
this.buffer.output.delete(task.id);
}
}
if (task.isClosed()) {
this.cache.render.set(task.id, output);
this.reset(task);
}
return output;
});
}
renderOutputBar(task, level) {
const output = this.buffer.output.get(task.id);
if (!output) return [];
return output.all.flatMap((o) => this.dump(task, level, ListrLogLevels.OUTPUT, o.entry));
}
renderBottomBar() {
if (this.buffer.bottom.size === 0) return [];
return Array.from(this.buffer.bottom.values()).flatMap((output) => output.all).sort((a, b) => a.time - b.time).map((output) => output.entry);
}
renderPrompt() {
if (!this.prompt) return [];
return [this.prompt];
}
calculate(task) {
if (this.cache.rendererOptions.has(task.id) && this.cache.rendererTaskOptions.has(task.id)) return;
const rendererOptions = {
...this.options,
...task.rendererOptions
};
this.cache.rendererOptions.set(task.id, rendererOptions);
this.cache.rendererTaskOptions.set(task.id, {
...DefaultRenderer.rendererTaskOptions,
timer: rendererOptions.timer,
...task.rendererTaskOptions
});
}
setupBuffer(task) {
if (this.buffer.bottom.has(task.id) || this.buffer.output.has(task.id)) return;
const rendererTaskOptions = this.cache.rendererTaskOptions.get(task.id);
if (this.shouldOutputToBottomBar(task) && !this.buffer.bottom.has(task.id)) {
this.buffer.bottom.set(task.id, new ProcessOutputBuffer({ limit: typeof rendererTaskOptions.bottomBar === "number" ? rendererTaskOptions.bottomBar : 1 }));
task.on(ListrTaskEventType.OUTPUT, (output) => {
const data = this.dump(task, -1, ListrLogLevels.OUTPUT, output);
this.buffer.bottom.get(task.id).write(data.join(EOL));
});
task.on(ListrTaskEventType.STATE, (state) => {
switch (state) {
case ListrTaskState.RETRY || ListrTaskState.ROLLING_BACK:
this.buffer.bottom.delete(task.id);
break;
}
});
} else if (this.shouldOutputToOutputBar(task) && !this.buffer.output.has(task.id)) {
this.buffer.output.set(task.id, new ProcessOutputBuffer({ limit: typeof rendererTaskOptions.outputBar === "number" ? rendererTaskOptions.outputBar : 1 }));
task.on(ListrTaskEventType.OUTPUT, (output) => {
this.buffer.output.get(task.id).write(output);
});
task.on(ListrTaskEventType.STATE, (state) => {
switch (state) {
case ListrTaskState.RETRY || ListrTaskState.ROLLING_BACK:
this.buffer.output.delete(task.id);
break;
}
});
}
}
reset(task) {
this.cache.rendererOptions.delete(task.id);
this.cache.rendererTaskOptions.delete(task.id);
this.buffer.output.delete(task.id);
}
dump(task, level, source = ListrLogLevels.OUTPUT, data) {
if (!data) switch (source) {
case ListrLogLevels.OUTPUT:
data = task.output;
break;
case ListrLogLevels.SKIPPED:
data = task.message.skip;
break;
case ListrLogLevels.FAILED:
data = task.message.error;
break;
}
if (task.hasTitle() && source === ListrLogLevels.FAILED && data === task.title || typeof data !== "string") return [];
if (source === ListrLogLevels.OUTPUT) data = cleanseAnsi(data);
return this.format(data, this.style(task, true), level + 1);
}
indent(str, i) {
return i > 0 ? indent(str.trimEnd(), this.options.indentation) : str.trimEnd();
}
};
//#endregion
//#region src/renderer/silent/renderer.ts
var SilentRenderer = class {
static nonTTY = true;
static rendererOptions;
static rendererTaskOptions;
constructor(tasks, options) {
this.tasks = tasks;
this.options = options;
}
render() {}
end() {}
};
//#endregion
//#region src/renderer/simple/renderer.ts
var SimpleRenderer = class SimpleRenderer {
static nonTTY = true;
static rendererOptions = { pausedTimer: {
...PRESET_TIMER,
field: (time) => `${ListrLogLevels.PAUSED}:${time}`,
format: () => color.yellowBright
} };
static rendererTaskOptions = {};
logger;
cache = {
rendererOptions: /* @__PURE__ */ new Map(),
rendererTaskOptions: /* @__PURE__ */ new Map()
};
constructor(tasks, options) {
this.tasks = tasks;
this.options = options;
this.options = {
...SimpleRenderer.rendererOptions,
...options,
icon: {
...LISTR_LOGGER_STYLE.icon,
...options?.icon ?? {}
},
color: {
...LISTR_LOGGER_STYLE.color,
...options?.color ?? {}
}
};
this.logger = this.options.logger ?? new ListrLogger({
useIcons: true,
toStderr: LISTR_LOGGER_STDERR_LEVELS
});
this.logger.options.icon = this.options.icon;
this.logger.options.color = this.options.color;
if (this.options.timestamp) this.logger.options.fields.prefix.unshift(this.options.timestamp);
}
end() {}
render() {
this.renderer(this.tasks);
}
renderer(tasks) {
tasks.forEach((task) => {
this.calculate(task);
task.once(ListrTaskEventType.CLOSED, () => {
this.reset(task);
});
const rendererOptions = this.cache.rendererOptions.get(task.id);
const rendererTaskOptions = this.cache.rendererTaskOptions.get(task.id);
task.on(ListrTaskEventType.SUBTASK, (subtasks) => {
this.renderer(subtasks);
});
task.on(ListrTaskEventType.STATE, (state) => {
if (!task.hasTitle()) return;
if (state === ListrTaskState.STARTED) this.logger.log(ListrLogLevels.STARTED, task.title);
else if (state === ListrTaskState.COMPLETED) {
const timer = rendererTaskOptions?.timer;
this.logger.log(ListrLogLevels.COMPLETED, task.title, timer && { suffix: {
...timer,
condition: !!task.message?.duration && timer.condition,
args: [task.message.duration]
} });
} else if (state === ListrTaskState.PROMPT) {
this.logger.process.hijack();
task.on(ListrTaskEventType.PROMPT, (prompt) => {
this.logger.process.toStderr(prompt, false);
});
} else if (state === ListrTaskState.PROMPT_COMPLETED) {
task.off(ListrTaskEventType.PROMPT);
this.logger.process.release();
}
});
task.on(ListrTaskEventType.OUTPUT, (output) => {
this.logger.log(ListrLogLevels.OUTPUT, output);
});
task.on(ListrTaskEventType.MESSAGE, (message) => {
if (message.error) this.logger.log(ListrLogLevels.FAILED, task.title, { suffix: {
field: `${ListrLogLevels.FAILED}: ${message.error}`,
format: () => color.red
} });
else if (message.skip) this.logger.log(ListrLogLevels.SKIPPED, task.title, { suffix: {
field: `${ListrLogLevels.SKIPPED}: ${message.skip}`,
format: () => color.yellow
} });
else if (message.rollback) this.logger.log(ListrLogLevels.ROLLBACK, task.title, { suffix: {
field: `${ListrLogLevels.ROLLBACK}: ${message.rollback}`,
format: () => color.red
} });
else if (message.retry) this.logger.log(ListrLogLevels.RETRY, task.title, { suffix: {
field: `${ListrLogLevels.RETRY}:${message.retry.count}`,
format: () => color.red
} });
else if (message.paused) {
const timer = rendererOptions?.pausedTimer;
this.logger.log(ListrLogLevels.PAUSED, task.title, timer && { suffix: {
...timer,
condition: !!message?.paused && timer.condition,
args: [message.paused - Date.now()]
} });
}
});
});
}
calculate(task) {
if (this.cache.rendererOptions.has(task.id) && this.cache.rendererTaskOptions.has(task.id)) return;
const rendererOptions = {
...this.options,
...task.rendererOptions
};
this.cache.rendererOptions.set(task.id, rendererOptions);
this.cache.rendererTaskOptions.set(task.id, {
...SimpleRenderer.rendererTaskOptions,
timer: rendererOptions.timer,
...task.rendererTaskOptions
});
}
reset(task) {
this.cache.rendererOptions.delete(task.id);
this.cache.rendererTaskOptions.delete(task.id);
}
};
//#endregion
//#region src/renderer/test/serializer.ts
var TestRendererSerializer = class {
constructor(options) {
this.options = options;
}
serialize(event, data, task) {
return JSON.stringify(this.generate(event, data, task));
}
generate(event, data, task) {
const output = {
event,
data
};
if (typeof this.options?.task !== "boolean") {
const t = Object.fromEntries(this.options.task.map((entity) => {
const property = task[entity];
if (typeof property === "function") return [entity, property.call(task)];
return [entity, property];
}));
if (Object.keys(task).length > 0) output.task = t;
}
return output;
}
};
//#endregion
//#region src/renderer/test/renderer.ts
var TestRenderer = class TestRenderer {
static nonTTY = true;
static rendererOptions = {
subtasks: true,
state: Object.values(ListrTaskState),
output: true,
prompt: true,
title: true,
messages: [
"skip",
"error",
"retry",
"rollback",
"paused"
],
messagesToStderr: [
"error",
"rollback",
"retry"
],
task: [
"hasRolledBack",
"isRollingBack",
"isCompleted",
"isSkipped",
"hasFinalized",
"hasSubtasks",
"title",
"hasReset",
"hasTitle",
"isPrompt",
"isPaused",
"isPending",
"isSkipped",
"isStarted",
"hasFailed",
"isEnabled",
"isRetrying",
"path"
]
};
static rendererTaskOptions;
logger;
serializer;
constructor(tasks, options) {
this.tasks = tasks;
this.options = options;
this.options = {
...TestRenderer.rendererOptions,
...this.options
};
this.logger = this.options.logger ?? new ListrLogger({ useIcons: false });
this.serializer = new TestRendererSerializer(this.options);
}
render() {
this.renderer(this.tasks);
}
end() {}
renderer(tasks) {
tasks.forEach((task) => {
if (this.options.subtasks) task.on(ListrTaskEventType.SUBTASK, (subtasks) => {
this.renderer(subtasks);
});
if (this.options.state) task.on(ListrTaskEventType.STATE, (state) => {
this.logger.toStdout(this.serializer.serialize(ListrTaskEventType.STATE, state, task));
});
if (this.options.output) task.on(ListrTaskEventType.OUTPUT, (data) => {
this.logger.toStdout(this.serializer.serialize(ListrTaskEventType.OUTPUT, data, task));
});
if (this.options.prompt) task.on(ListrTaskEventType.PROMPT, (prompt) => {
this.logger.toStdout(this.serializer.serialize(ListrTaskEventType.PROMPT, prompt, task));
});
if (this.options.title) task.on(ListrTaskEventType.TITLE, (title) => {
this.logger.toStdout(this.serializer.serialize(ListrTaskEventType.TITLE, title, task));
});
task.on(ListrTaskEventType.MESSAGE, (message) => {
const parsed = Object.fromEntries(Object.entries(message).map(([key, value]) => {
if (this.options.messages.includes(key)) return [key, value];
}).filter(Boolean));
if (Object.keys(parsed).length > 0) {
const output = this.serializer.serialize(ListrTaskEventType.MESSAGE, parsed, task);
if (this.options.messagesToStderr.some((state) => Object.keys(parsed).includes(state))) this.logger.toStderr(output);
else this.logger.toStdout(output);
}
});
});
}
};
//#endregion
//#region src/renderer/verbose/renderer.ts
var VerboseRenderer = class VerboseRenderer {
static nonTTY = true;
static rendererOptions = {
logTitleChange: false,
pausedTimer: {
...PRESET_TIMER,
format: () => color.yellowBright
}
};
static rendererTaskOptions;
logger;
cache = {
rendererOptions: /* @__PURE__ */ new Map(),
rendererTaskOptions: /* @__PURE__ */ new Map()
};
constructor(tasks, options) {
this.tasks = tasks;
this.options = options;
this.options = {
...VerboseRenderer.rendererOptions,
...this.options,
icon: {
...LISTR_LOGGER_STYLE.icon,
...options?.icon ?? {}
},
color: {
...LISTR_LOGGER_STYLE.color,
...options?.color ?? {}
}
};
this.logger = this.options.logger ?? new ListrLogger({
useIcons: false,
toStderr: LISTR_LOGGER_STDERR_LEVELS
});
this.logger.options.icon = this.options.icon;
this.logger.options.color = this.options.color;
if (this.options.timestamp) this.logger.options.fields.prefix.unshift(this.options.timestamp);
}
render() {
this.renderer(this.tasks);
}
end() {}
renderer(tasks) {
tasks.forEach((task) => {
this.calculate(task);
task.once(ListrTaskEventType.CLOSED, () => {
this.reset(task);
});
const rendererOptions = this.cache.rendererOptions.get(task.id);
const rendererTaskOptions = this.cache.rendererTaskOptions.get(task.id);
task.on(ListrTaskEventType.SUBTASK, (subtasks) => {
this.renderer(subtasks);
});
task.on(ListrTaskEventType.STATE, (state) => {
if (!task.hasTitle()) return;
if (state === ListrTaskState.STARTED) this.logger.log(ListrLogLevels.STARTED, task.title);
else if (state === ListrTaskState.COMPLETED) {
const timer = rendererTaskOptions.timer;
this.logger.log(ListrLogLevels.COMPLETED, task.title, timer && { suffix: {
...timer,
condition: !!task.message?.duration && timer.condition,
args: [task.message.duration]
} });
}
});
task.on(ListrTaskEventType.OUTPUT, (data) => {
this.logger.log(ListrLogLevels.OUTPUT, data);
});
task.on(ListrTaskEventType.PROMPT, (prompt) => {
const cleansed = cleanseAnsi(prompt);
if (cleansed) this.logger.log(ListrLogLevels.PROMPT, cleansed);
});
if (this.options?.logTitleChange !== false) task.on(ListrTaskEventType.TITLE, (title) => {
this.logger.log(ListrLogLevels.TITLE, title);
});
task.on(ListrTaskEventType.MESSAGE, (message) => {
if (message?.error) this.logger.log(ListrLogLevels.FAILED, message.error);
else if (message?.skip) this.logger.log(ListrLogLevels.SKIPPED, message.skip);
else if (message?.rollback) this.logger.log(ListrLogLevels.ROLLBACK, message.rollback);
else if (message?.retry) this.logger.log(ListrLogLevels.RETRY, task.title, { suffix: message.retry.count.toString() });
else if (message?.paused) {
const timer = rendererOptions?.pausedTimer;
this.logger.log(ListrLogLevels.PAUSED, task.title, timer && { suffix: {
...timer,
condition: !!message?.paused && timer.condition,
args: [message.paused - Date.now()]
} });
}
});
});
}
calculate(task) {
if (this.cache.rendererOptions.has(task.id) && this.cache.rendererTaskOptions.has(task.id)) return;
const rendererOptions = {
...this.options,
...task.rendererOptions
};
this.cache.rendererOptions.set(task.id, rendererOptions);
this.cache.rendererTaskOptions.set(task.id, {
...VerboseRenderer.rendererTaskOptions,
timer: rendererOptions.timer,
...task.rendererTaskOptions
});
}
reset(task) {
this.cache.rendererOptions.delete(task.id);
this.cache.rendererTaskOptions.delete(task.id);
}
};
//#endregion
//#region src/utils/ui/renderer.ts
const RENDERERS = {
default: DefaultRenderer,
simple: SimpleRenderer,
verbose: VerboseRenderer,
test: TestRenderer,
silent: SilentRenderer
};
function isRendererSupported(renderer) {
return process.stdout.isTTY === true || renderer.nonTTY === true;
}
function getRendererClass(renderer) {
if (typeof renderer === "string") return RENDERERS[renderer] ?? RENDERERS.default;
return typeof renderer === "function" ? renderer : RENDERERS.default;
}
function getRenderer(options) {
if (assertFunctionOrSelf(options?.silentRendererCondition)) return {
renderer: getRendererClass("silent"),
selection: ListrRendererSelection.SILENT
};
const r = {
renderer: getRendererClass(options.renderer),
options: options.rendererOptions,
selection: ListrRendererSelection.PRIMARY
};
if (!isRendererSupported(r.renderer) || assertFunctionOrSelf(options?.fallbackRendererCondition)) return {
renderer: getRendererClass(options.fallbackRenderer),
options: options.fallbackRendererOptions,
selection: ListrRendererSelection.SECONDARY
};
return r;
}
//#endregion
//#region src/utils/assert.ts
/**
* This function asserts the given value as a function or itself.
* If the value itself is a function it will evaluate it with the passed in arguments,
* elsewise it will directly return itself.
*/
function assertFunctionOrSelf(functionOrSelf, ...args) {
if (typeof functionOrSelf === "function") return functionOrSelf(...args);
else return functionOrSelf;
}
//#endregion
//#region src/utils/clone.ts
const clone = rfdc({ circles: true });
/*