default.renderer.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.DefaultRenderer = void 0;
  4. const cliTruncate = require("cli-truncate");
  5. const logUpdate = require("log-update");
  6. const os_1 = require("os");
  7. const cliWrap = require("wrap-ansi");
  8. const colorette_1 = require("../utils/colorette");
  9. const figures_1 = require("../utils/figures");
  10. const indent_string_1 = require("../utils/indent-string");
  11. const is_unicode_supported_1 = require("../utils/is-unicode-supported");
  12. const parse_time_1 = require("../utils/parse-time");
  13. /** Default updating renderer for Listr2 */
  14. class DefaultRenderer {
  15. constructor(tasks, options, renderHook$) {
  16. this.tasks = tasks;
  17. this.options = options;
  18. this.renderHook$ = renderHook$;
  19. this.bottomBar = {};
  20. this.spinner = !(0, is_unicode_supported_1.isUnicodeSupported)() ? ['-', '\\', '|', '/'] : ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
  21. this.spinnerPosition = 0;
  22. this.options = { ...DefaultRenderer.rendererOptions, ...this.options };
  23. }
  24. getTaskOptions(task) {
  25. return { ...DefaultRenderer.rendererTaskOptions, ...task.rendererTaskOptions };
  26. }
  27. isBottomBar(task) {
  28. const bottomBar = this.getTaskOptions(task).bottomBar;
  29. return typeof bottomBar === 'number' && bottomBar !== 0 || typeof bottomBar === 'boolean' && bottomBar !== false;
  30. }
  31. hasPersistentOutput(task) {
  32. return this.getTaskOptions(task).persistentOutput === true;
  33. }
  34. hasTimer(task) {
  35. return this.getTaskOptions(task).showTimer === true;
  36. }
  37. getSelfOrParentOption(task, key) {
  38. var _a, _b, _c;
  39. 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];
  40. }
  41. /* istanbul ignore next */
  42. getTaskTime(task) {
  43. return colorette_1.default.dim(`[${(0, parse_time_1.parseTaskTime)(task.message.duration)}]`);
  44. }
  45. createRender(options) {
  46. options = {
  47. ...{
  48. tasks: true,
  49. bottomBar: true,
  50. prompt: true
  51. },
  52. ...options
  53. };
  54. const render = [];
  55. const renderTasks = this.multiLineRenderer(this.tasks);
  56. const renderBottomBar = this.renderBottomBar();
  57. const renderPrompt = this.renderPrompt();
  58. if (options.tasks && (renderTasks === null || renderTasks === void 0 ? void 0 : renderTasks.trim().length) > 0) {
  59. render.push(renderTasks);
  60. }
  61. if (options.bottomBar && (renderBottomBar === null || renderBottomBar === void 0 ? void 0 : renderBottomBar.trim().length) > 0) {
  62. render.push((render.length > 0 ? os_1.EOL : '') + renderBottomBar);
  63. }
  64. if (options.prompt && (renderPrompt === null || renderPrompt === void 0 ? void 0 : renderPrompt.trim().length) > 0) {
  65. render.push((render.length > 0 ? os_1.EOL : '') + renderPrompt);
  66. }
  67. return render.length > 0 ? render.join(os_1.EOL) : '';
  68. }
  69. render() {
  70. var _a;
  71. // Do not render if we are already rendering
  72. if (this.id) {
  73. return;
  74. }
  75. const updateRender = () => logUpdate(this.createRender());
  76. /* istanbul ignore if */
  77. if (!((_a = this.options) === null || _a === void 0 ? void 0 : _a.lazy)) {
  78. this.id = setInterval(() => {
  79. this.spinnerPosition = ++this.spinnerPosition % this.spinner.length;
  80. updateRender();
  81. }, 100);
  82. }
  83. this.renderHook$.subscribe(() => {
  84. updateRender();
  85. });
  86. }
  87. end() {
  88. clearInterval(this.id);
  89. if (this.id) {
  90. this.id = undefined;
  91. }
  92. // clear log updater
  93. logUpdate.clear();
  94. logUpdate.done();
  95. // directly write to process.stdout, since logupdate only can update the seen height of terminal
  96. if (!this.options.clearOutput) {
  97. process.stdout.write(this.createRender({ prompt: false }) + os_1.EOL);
  98. }
  99. }
  100. // eslint-disable-next-line
  101. multiLineRenderer(tasks, level = 0) {
  102. var _a, _b;
  103. let output = [];
  104. for (const task of tasks) {
  105. if (task.isEnabled()) {
  106. // Current Task Title
  107. if (task.hasTitle()) {
  108. if (!(tasks.some((task) => task.hasFailed()) && !task.hasFailed() && task.options.exitOnError !== false && !(task.isCompleted() || task.isSkipped()))) {
  109. // if task is skipped
  110. if (task.hasFailed() && this.getSelfOrParentOption(task, 'collapseErrors')) {
  111. // current task title and skip change the title
  112. output = [
  113. ...output,
  114. this.formatString(!task.hasSubtasks() && task.message.error && this.getSelfOrParentOption(task, 'showErrorMessage') ? task.message.error : task.title, this.getSymbol(task), level)
  115. ];
  116. }
  117. else if (task.isSkipped() && this.getSelfOrParentOption(task, 'collapseSkips')) {
  118. // current task title and skip change the title
  119. output = [
  120. ...output,
  121. 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)
  122. ];
  123. }
  124. else if (task.isRetrying() && this.getSelfOrParentOption(task, 'suffixRetries')) {
  125. output = [...output, this.formatString(this.addSuffixToMessage(task.title, `RETRYING-${task.message.retry.count}`), this.getSymbol(task), level)];
  126. }
  127. else if (task.isCompleted() && task.hasTitle() && (this.getSelfOrParentOption(task, 'showTimer') || this.hasTimer(task))) {
  128. // task with timer
  129. output = [...output, this.formatString(`${task === null || task === void 0 ? void 0 : task.title} ${this.getTaskTime(task)}`, this.getSymbol(task), level)];
  130. }
  131. else {
  132. // normal state
  133. output = [...output, this.formatString(task.title, this.getSymbol(task), level)];
  134. }
  135. }
  136. else {
  137. // some sibling task but self has failed and this has stopped
  138. output = [...output, this.formatString(task.title, colorette_1.default.red(figures_1.figures.squareSmallFilled), level)];
  139. }
  140. }
  141. // task should not have subtasks since subtasks will handle the error already
  142. // maybe it is a better idea to show the error or skip messages when show subtasks is disabled.
  143. if (!task.hasSubtasks() || !this.getSelfOrParentOption(task, 'showSubtasks')) {
  144. // without the collapse option for skip and errors
  145. if (task.hasFailed() &&
  146. this.getSelfOrParentOption(task, 'collapseErrors') === false &&
  147. (this.getSelfOrParentOption(task, 'showErrorMessage') || !this.getSelfOrParentOption(task, 'showSubtasks'))) {
  148. // show skip data if collapsing is not defined
  149. output = [...output, this.dumpData(task, level, 'error')];
  150. }
  151. else if (task.isSkipped() &&
  152. this.getSelfOrParentOption(task, 'collapseSkips') === false &&
  153. (this.getSelfOrParentOption(task, 'showSkipMessage') || !this.getSelfOrParentOption(task, 'showSubtasks'))) {
  154. // show skip data if collapsing is not defined
  155. output = [...output, this.dumpData(task, level, 'skip')];
  156. }
  157. }
  158. // Current Task Output
  159. if (task === null || task === void 0 ? void 0 : task.output) {
  160. if ((task.isPending() || task.isRetrying() || task.isRollingBack()) && task.isPrompt()) {
  161. // data output to prompt bar if prompt
  162. this.promptBar = task.output;
  163. }
  164. else if (this.isBottomBar(task) || !task.hasTitle()) {
  165. // data output to bottom bar
  166. const data = [this.dumpData(task, -1)];
  167. // create new if there is no persistent storage created for bottom bar
  168. if (!this.bottomBar[task.id]) {
  169. this.bottomBar[task.id] = {};
  170. this.bottomBar[task.id].data = [];
  171. const bottomBar = this.getTaskOptions(task).bottomBar;
  172. if (typeof bottomBar === 'boolean') {
  173. this.bottomBar[task.id].items = 1;
  174. }
  175. else {
  176. this.bottomBar[task.id].items = bottomBar;
  177. }
  178. }
  179. // persistent bottom bar and limit items in it
  180. 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()) {
  181. this.bottomBar[task.id].data = [...this.bottomBar[task.id].data, ...data];
  182. }
  183. }
  184. else if (task.isPending() || task.isRetrying() || task.isRollingBack() || this.hasPersistentOutput(task)) {
  185. // keep output if persistent output is set
  186. output = [...output, this.dumpData(task, level)];
  187. }
  188. }
  189. // render subtasks, some complicated conditionals going on
  190. if (
  191. // check if renderer option is on first
  192. this.getSelfOrParentOption(task, 'showSubtasks') !== false &&
  193. // if it doesnt have subtasks no need to check
  194. task.hasSubtasks() &&
  195. (task.isPending() ||
  196. task.hasFailed() ||
  197. task.isCompleted() && !task.hasTitle() ||
  198. // have to be completed and have subtasks
  199. task.isCompleted() && this.getSelfOrParentOption(task, 'collapse') === false && !task.subtasks.some((subtask) => subtask.rendererOptions.collapse === true) ||
  200. // if any of the subtasks have the collapse option of
  201. task.subtasks.some((subtask) => subtask.rendererOptions.collapse === false) ||
  202. // if any of the subtasks has failed
  203. task.subtasks.some((subtask) => subtask.hasFailed()) ||
  204. // if any of the subtasks rolled back
  205. task.subtasks.some((subtask) => subtask.hasRolledBack()))) {
  206. // set level
  207. const subtaskLevel = !task.hasTitle() ? level : level + 1;
  208. // render the subtasks as in the same way
  209. const subtaskRender = this.multiLineRenderer(task.subtasks, subtaskLevel);
  210. if ((subtaskRender === null || subtaskRender === void 0 ? void 0 : subtaskRender.trim()) !== '' && !task.subtasks.every((subtask) => !subtask.hasTitle())) {
  211. output = [...output, subtaskRender];
  212. }
  213. }
  214. // after task is finished actions
  215. if (task.isCompleted() || task.hasFailed() || task.isSkipped() || task.hasRolledBack()) {
  216. // clean up prompts
  217. this.promptBar = null;
  218. // clean up bottom bar items if not indicated otherwise
  219. if (!this.hasPersistentOutput(task)) {
  220. delete this.bottomBar[task.id];
  221. }
  222. }
  223. }
  224. }
  225. output = output.filter(Boolean);
  226. if (output.length > 0) {
  227. return output.join(os_1.EOL);
  228. }
  229. else {
  230. return;
  231. }
  232. }
  233. renderBottomBar() {
  234. // parse through all objects return only the last mentioned items
  235. if (Object.keys(this.bottomBar).length > 0) {
  236. this.bottomBar = Object.keys(this.bottomBar).reduce((o, key) => {
  237. if (!(o === null || o === void 0 ? void 0 : o[key])) {
  238. o[key] = {};
  239. }
  240. o[key] = this.bottomBar[key];
  241. this.bottomBar[key].data = this.bottomBar[key].data.slice(-this.bottomBar[key].items);
  242. o[key].data = this.bottomBar[key].data;
  243. return o;
  244. }, {});
  245. return Object.values(this.bottomBar)
  246. .reduce((o, value) => o = [...o, ...value.data], [])
  247. .filter(Boolean)
  248. .join(os_1.EOL);
  249. }
  250. }
  251. renderPrompt() {
  252. if (this.promptBar) {
  253. return this.promptBar;
  254. }
  255. }
  256. dumpData(task, level, source = 'output') {
  257. let data;
  258. switch (source) {
  259. case 'output':
  260. data = task.output;
  261. break;
  262. case 'skip':
  263. data = task.message.skip;
  264. break;
  265. case 'error':
  266. data = task.message.error;
  267. break;
  268. }
  269. // dont return anything on some occasions
  270. if (task.hasTitle() && source === 'error' && data === task.title) {
  271. return;
  272. }
  273. if (typeof data === 'string') {
  274. return this.formatString(data, this.getSymbol(task, true), level + 1);
  275. }
  276. }
  277. formatString(str, icon, level) {
  278. // we dont like empty data around here
  279. if (str.trim() === '') {
  280. return;
  281. }
  282. str = `${icon} ${str}`;
  283. let parsedStr;
  284. let columns = process.stdout.columns ? process.stdout.columns : 80;
  285. columns = columns - level * this.options.indentation - 2;
  286. switch (this.options.formatOutput) {
  287. case 'truncate':
  288. parsedStr = str.split(os_1.EOL).map((s, i) => {
  289. return cliTruncate(this.indentMultilineOutput(s, i), columns);
  290. });
  291. break;
  292. case 'wrap':
  293. parsedStr = cliWrap(str, columns, { hard: true })
  294. .split(os_1.EOL)
  295. .map((s, i) => this.indentMultilineOutput(s, i));
  296. break;
  297. default:
  298. throw new Error('Format option for the renderer is wrong.');
  299. }
  300. // this removes the empty lines
  301. if (this.options.removeEmptyLines) {
  302. parsedStr = parsedStr.filter(Boolean);
  303. }
  304. return (0, indent_string_1.indentString)(parsedStr.join(os_1.EOL), level * this.options.indentation);
  305. }
  306. indentMultilineOutput(str, i) {
  307. return i > 0 ? (0, indent_string_1.indentString)(str.trim(), 2) : str.trim();
  308. }
  309. // eslint-disable-next-line complexity
  310. getSymbol(task, data = false) {
  311. var _a, _b, _c;
  312. if (task.isPending() && !data) {
  313. 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())
  314. ? colorette_1.default.yellow(figures_1.figures.pointer)
  315. : colorette_1.default.yellowBright(this.spinner[this.spinnerPosition]);
  316. }
  317. else if (task.isCompleted() && !data) {
  318. 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);
  319. }
  320. else if (task.isRetrying() && !data) {
  321. 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]);
  322. }
  323. else if (task.isRollingBack() && !data) {
  324. 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]);
  325. }
  326. else if (task.hasRolledBack() && !data) {
  327. return colorette_1.default.red(figures_1.figures.arrowLeft);
  328. }
  329. else if (task.hasFailed() && !data) {
  330. return task.hasSubtasks() ? colorette_1.default.red(figures_1.figures.pointer) : colorette_1.default.red(figures_1.figures.cross);
  331. }
  332. else if (task.isSkipped() && !data && this.getSelfOrParentOption(task, 'collapseSkips') === false) {
  333. return colorette_1.default.yellow(figures_1.figures.warning);
  334. }
  335. else if (task.isSkipped() && (data || this.getSelfOrParentOption(task, 'collapseSkips'))) {
  336. return colorette_1.default.yellow(figures_1.figures.arrowDown);
  337. }
  338. return !data ? colorette_1.default.dim(figures_1.figures.squareSmallFilled) : figures_1.figures.pointerSmall;
  339. }
  340. addSuffixToMessage(message, suffix, condition) {
  341. return (condition !== null && condition !== void 0 ? condition : true) ? message + colorette_1.default.dim(` [${suffix}]`) : message;
  342. }
  343. }
  344. exports.DefaultRenderer = DefaultRenderer;
  345. /** designates whether this renderer can output to a non-tty console */
  346. DefaultRenderer.nonTTY = false;
  347. /** renderer options for the defauult renderer */
  348. DefaultRenderer.rendererOptions = {
  349. indentation: 2,
  350. clearOutput: false,
  351. showSubtasks: true,
  352. collapse: true,
  353. collapseSkips: true,
  354. showSkipMessage: true,
  355. suffixSkips: true,
  356. collapseErrors: true,
  357. showErrorMessage: true,
  358. suffixRetries: true,
  359. lazy: false,
  360. showTimer: false,
  361. removeEmptyLines: true,
  362. formatOutput: 'truncate'
  363. };