"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DefaultRenderer = void 0; const cliTruncate = require("cli-truncate"); const logUpdate = require("log-update"); const os_1 = require("os"); const cliWrap = require("wrap-ansi"); const colorette_1 = require("../utils/colorette"); const figures_1 = require("../utils/figures"); const indent_string_1 = require("../utils/indent-string"); const is_unicode_supported_1 = require("../utils/is-unicode-supported"); const parse_time_1 = require("../utils/parse-time"); /** Default updating renderer for Listr2 */ class DefaultRenderer { constructor(tasks, options, renderHook$) { this.tasks = tasks; this.options = options; this.renderHook$ = renderHook$; this.bottomBar = {}; this.spinner = !(0, is_unicode_supported_1.isUnicodeSupported)() ? ['-', '\\', '|', '/'] : ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; this.spinnerPosition = 0; this.options = { ...DefaultRenderer.rendererOptions, ...this.options }; } getTaskOptions(task) { return { ...DefaultRenderer.rendererTaskOptions, ...task.rendererTaskOptions }; } isBottomBar(task) { const bottomBar = this.getTaskOptions(task).bottomBar; return typeof bottomBar === 'number' && bottomBar !== 0 || typeof bottomBar === 'boolean' && bottomBar !== false; } hasPersistentOutput(task) { return this.getTaskOptions(task).persistentOutput === true; } hasTimer(task) { return this.getTaskOptions(task).showTimer === true; } getSelfOrParentOption(task, key) { var _a, _b, _c; return (_b = (_a = task === null || task === void 0 ? void 0 : task.rendererOptions) === null || _a === void 0 ? void 0 : _a[key]) !== null && _b !== void 0 ? _b : (_c = this.options) === null || _c === void 0 ? void 0 : _c[key]; } /* istanbul ignore next */ getTaskTime(task) { return colorette_1.default.dim(`[${(0, parse_time_1.parseTaskTime)(task.message.duration)}]`); } createRender(options) { options = { ...{ tasks: true, bottomBar: true, prompt: true }, ...options }; const render = []; const renderTasks = this.multiLineRenderer(this.tasks); const renderBottomBar = this.renderBottomBar(); const renderPrompt = this.renderPrompt(); if (options.tasks && (renderTasks === null || renderTasks === void 0 ? void 0 : renderTasks.trim().length) > 0) { render.push(renderTasks); } if (options.bottomBar && (renderBottomBar === null || renderBottomBar === void 0 ? void 0 : renderBottomBar.trim().length) > 0) { render.push((render.length > 0 ? os_1.EOL : '') + renderBottomBar); } if (options.prompt && (renderPrompt === null || renderPrompt === void 0 ? void 0 : renderPrompt.trim().length) > 0) { render.push((render.length > 0 ? os_1.EOL : '') + renderPrompt); } return render.length > 0 ? render.join(os_1.EOL) : ''; } render() { var _a; // Do not render if we are already rendering if (this.id) { return; } const updateRender = () => logUpdate(this.createRender()); /* istanbul ignore if */ if (!((_a = this.options) === null || _a === void 0 ? void 0 : _a.lazy)) { this.id = setInterval(() => { this.spinnerPosition = ++this.spinnerPosition % this.spinner.length; updateRender(); }, 100); } this.renderHook$.subscribe(() => { updateRender(); }); } end() { clearInterval(this.id); if (this.id) { this.id = undefined; } // clear log updater logUpdate.clear(); logUpdate.done(); // directly write to process.stdout, since logupdate only can update the seen height of terminal if (!this.options.clearOutput) { process.stdout.write(this.createRender({ prompt: false }) + os_1.EOL); } } // eslint-disable-next-line multiLineRenderer(tasks, level = 0) { var _a, _b; let output = []; for (const task of tasks) { if (task.isEnabled()) { // Current Task Title if (task.hasTitle()) { if (!(tasks.some((task) => task.hasFailed()) && !task.hasFailed() && task.options.exitOnError !== false && !(task.isCompleted() || task.isSkipped()))) { // if task is skipped if (task.hasFailed() && this.getSelfOrParentOption(task, 'collapseErrors')) { // current task title and skip change the title output = [ ...output, this.formatString(!task.hasSubtasks() && task.message.error && this.getSelfOrParentOption(task, 'showErrorMessage') ? task.message.error : task.title, this.getSymbol(task), level) ]; } else if (task.isSkipped() && this.getSelfOrParentOption(task, 'collapseSkips')) { // current task title and skip change the title output = [ ...output, this.formatString(this.addSuffixToMessage(task.message.skip && this.getSelfOrParentOption(task, 'showSkipMessage') ? task.message.skip : task.title, 'SKIPPED', this.getSelfOrParentOption(task, 'suffixSkips')), this.getSymbol(task), level) ]; } else if (task.isRetrying() && this.getSelfOrParentOption(task, 'suffixRetries')) { output = [...output, this.formatString(this.addSuffixToMessage(task.title, `RETRYING-${task.message.retry.count}`), this.getSymbol(task), level)]; } else if (task.isCompleted() && task.hasTitle() && (this.getSelfOrParentOption(task, 'showTimer') || this.hasTimer(task))) { // task with timer output = [...output, this.formatString(`${task === null || task === void 0 ? void 0 : task.title} ${this.getTaskTime(task)}`, this.getSymbol(task), level)]; } else { // normal state output = [...output, this.formatString(task.title, this.getSymbol(task), level)]; } } else { // some sibling task but self has failed and this has stopped output = [...output, this.formatString(task.title, colorette_1.default.red(figures_1.figures.squareSmallFilled), level)]; } } // task should not have subtasks since subtasks will handle the error already // maybe it is a better idea to show the error or skip messages when show subtasks is disabled. if (!task.hasSubtasks() || !this.getSelfOrParentOption(task, 'showSubtasks')) { // without the collapse option for skip and errors if (task.hasFailed() && this.getSelfOrParentOption(task, 'collapseErrors') === false && (this.getSelfOrParentOption(task, 'showErrorMessage') || !this.getSelfOrParentOption(task, 'showSubtasks'))) { // show skip data if collapsing is not defined output = [...output, this.dumpData(task, level, 'error')]; } else if (task.isSkipped() && this.getSelfOrParentOption(task, 'collapseSkips') === false && (this.getSelfOrParentOption(task, 'showSkipMessage') || !this.getSelfOrParentOption(task, 'showSubtasks'))) { // show skip data if collapsing is not defined output = [...output, this.dumpData(task, level, 'skip')]; } } // Current Task Output if (task === null || task === void 0 ? void 0 : task.output) { if ((task.isPending() || task.isRetrying() || task.isRollingBack()) && task.isPrompt()) { // data output to prompt bar if prompt this.promptBar = task.output; } else if (this.isBottomBar(task) || !task.hasTitle()) { // data output to bottom bar const data = [this.dumpData(task, -1)]; // create new if there is no persistent storage created for bottom bar if (!this.bottomBar[task.id]) { this.bottomBar[task.id] = {}; this.bottomBar[task.id].data = []; const bottomBar = this.getTaskOptions(task).bottomBar; if (typeof bottomBar === 'boolean') { this.bottomBar[task.id].items = 1; } else { this.bottomBar[task.id].items = bottomBar; } } // persistent bottom bar and limit items in it if (!((_b = (_a = this.bottomBar[task.id]) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.some((element) => data.includes(element))) && !task.isSkipped()) { this.bottomBar[task.id].data = [...this.bottomBar[task.id].data, ...data]; } } else if (task.isPending() || task.isRetrying() || task.isRollingBack() || this.hasPersistentOutput(task)) { // keep output if persistent output is set output = [...output, this.dumpData(task, level)]; } } // render subtasks, some complicated conditionals going on if ( // check if renderer option is on first this.getSelfOrParentOption(task, 'showSubtasks') !== false && // if it doesnt have subtasks no need to check task.hasSubtasks() && (task.isPending() || task.hasFailed() || task.isCompleted() && !task.hasTitle() || // have to be completed and have subtasks task.isCompleted() && this.getSelfOrParentOption(task, 'collapse') === false && !task.subtasks.some((subtask) => subtask.rendererOptions.collapse === true) || // if any of the subtasks have the collapse option of task.subtasks.some((subtask) => subtask.rendererOptions.collapse === false) || // if any of the subtasks has failed task.subtasks.some((subtask) => subtask.hasFailed()) || // if any of the subtasks rolled back task.subtasks.some((subtask) => subtask.hasRolledBack()))) { // set level const subtaskLevel = !task.hasTitle() ? level : level + 1; // render the subtasks as in the same way const subtaskRender = this.multiLineRenderer(task.subtasks, subtaskLevel); if ((subtaskRender === null || subtaskRender === void 0 ? void 0 : subtaskRender.trim()) !== '' && !task.subtasks.every((subtask) => !subtask.hasTitle())) { output = [...output, subtaskRender]; } } // after task is finished actions if (task.isCompleted() || task.hasFailed() || task.isSkipped() || task.hasRolledBack()) { // clean up prompts this.promptBar = null; // clean up bottom bar items if not indicated otherwise if (!this.hasPersistentOutput(task)) { delete this.bottomBar[task.id]; } } } } output = output.filter(Boolean); if (output.length > 0) { return output.join(os_1.EOL); } else { return; } } renderBottomBar() { // parse through all objects return only the last mentioned items if (Object.keys(this.bottomBar).length > 0) { this.bottomBar = Object.keys(this.bottomBar).reduce((o, key) => { if (!(o === null || o === void 0 ? void 0 : o[key])) { o[key] = {}; } o[key] = this.bottomBar[key]; this.bottomBar[key].data = this.bottomBar[key].data.slice(-this.bottomBar[key].items); o[key].data = this.bottomBar[key].data; return o; }, {}); return Object.values(this.bottomBar) .reduce((o, value) => o = [...o, ...value.data], []) .filter(Boolean) .join(os_1.EOL); } } renderPrompt() { if (this.promptBar) { return this.promptBar; } } dumpData(task, level, source = 'output') { let data; switch (source) { case 'output': data = task.output; break; case 'skip': data = task.message.skip; break; case 'error': data = task.message.error; break; } // dont return anything on some occasions if (task.hasTitle() && source === 'error' && data === task.title) { return; } if (typeof data === 'string') { return this.formatString(data, this.getSymbol(task, true), level + 1); } } formatString(str, icon, level) { // we dont like empty data around here if (str.trim() === '') { return; } str = `${icon} ${str}`; let parsedStr; let columns = process.stdout.columns ? process.stdout.columns : 80; columns = columns - level * this.options.indentation - 2; switch (this.options.formatOutput) { case 'truncate': parsedStr = str.split(os_1.EOL).map((s, i) => { return cliTruncate(this.indentMultilineOutput(s, i), columns); }); break; case 'wrap': parsedStr = cliWrap(str, columns, { hard: true }) .split(os_1.EOL) .map((s, i) => this.indentMultilineOutput(s, i)); break; default: throw new Error('Format option for the renderer is wrong.'); } // this removes the empty lines if (this.options.removeEmptyLines) { parsedStr = parsedStr.filter(Boolean); } return (0, indent_string_1.indentString)(parsedStr.join(os_1.EOL), level * this.options.indentation); } indentMultilineOutput(str, i) { return i > 0 ? (0, indent_string_1.indentString)(str.trim(), 2) : str.trim(); } // eslint-disable-next-line complexity getSymbol(task, data = false) { var _a, _b, _c; if (task.isPending() && !data) { return ((_a = this.options) === null || _a === void 0 ? void 0 : _a.lazy) || this.getSelfOrParentOption(task, 'showSubtasks') !== false && task.hasSubtasks() && !task.subtasks.every((subtask) => !subtask.hasTitle()) ? colorette_1.default.yellow(figures_1.figures.pointer) : colorette_1.default.yellowBright(this.spinner[this.spinnerPosition]); } else if (task.isCompleted() && !data) { return task.hasSubtasks() && task.subtasks.some((subtask) => subtask.hasFailed()) ? colorette_1.default.yellow(figures_1.figures.warning) : colorette_1.default.green(figures_1.figures.tick); } else if (task.isRetrying() && !data) { return ((_b = this.options) === null || _b === void 0 ? void 0 : _b.lazy) ? colorette_1.default.yellow(figures_1.figures.warning) : colorette_1.default.yellow(this.spinner[this.spinnerPosition]); } else if (task.isRollingBack() && !data) { return ((_c = this.options) === null || _c === void 0 ? void 0 : _c.lazy) ? colorette_1.default.red(figures_1.figures.warning) : colorette_1.default.red(this.spinner[this.spinnerPosition]); } else if (task.hasRolledBack() && !data) { return colorette_1.default.red(figures_1.figures.arrowLeft); } else if (task.hasFailed() && !data) { return task.hasSubtasks() ? colorette_1.default.red(figures_1.figures.pointer) : colorette_1.default.red(figures_1.figures.cross); } else if (task.isSkipped() && !data && this.getSelfOrParentOption(task, 'collapseSkips') === false) { return colorette_1.default.yellow(figures_1.figures.warning); } else if (task.isSkipped() && (data || this.getSelfOrParentOption(task, 'collapseSkips'))) { return colorette_1.default.yellow(figures_1.figures.arrowDown); } return !data ? colorette_1.default.dim(figures_1.figures.squareSmallFilled) : figures_1.figures.pointerSmall; } addSuffixToMessage(message, suffix, condition) { return (condition !== null && condition !== void 0 ? condition : true) ? message + colorette_1.default.dim(` [${suffix}]`) : message; } } exports.DefaultRenderer = DefaultRenderer; /** designates whether this renderer can output to a non-tty console */ DefaultRenderer.nonTTY = false; /** renderer options for the defauult renderer */ DefaultRenderer.rendererOptions = { indentation: 2, clearOutput: false, showSubtasks: true, collapse: true, collapseSkips: true, showSkipMessage: true, suffixSkips: true, collapseErrors: true, showErrorMessage: true, suffixRetries: true, lazy: false, showTimer: false, removeEmptyLines: true, formatOutput: 'truncate' };