364 lines
18 KiB
JavaScript

"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'
};