UNPKG

@boost/core

Version:

Robust pipeline for creating dev tools that separate logic into routines and tasks.

247 lines (246 loc) 7.82 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const common_1 = require("@boost/common"); const terminal_1 = require("@boost/terminal"); const Output_1 = __importDefault(require("./Output")); const ProgressOutput_1 = __importDefault(require("./outputs/ProgressOutput")); const Plugin_1 = __importDefault(require("./Plugin")); let restoreCursorOnExit = false; exports.SLOW_THRESHOLD = 10000; // ms class Reporter extends Plugin_1.default { constructor() { super(...arguments); this.startTime = 0; this.stopTime = 0; /** * Set start time. */ this.handleBaseStart = () => { this.startTime = Date.now(); }; /** * Set stop time and render. */ this.handleBaseStop = () => { this.stopTime = Date.now(); }; } /** * Register console listeners. */ bootstrap() { this.console.onStart.listen(this.handleBaseStart); this.console.onStop.listen(this.handleBaseStop); } /** * Calculate the current number of tasks that have completed. */ calculateTaskCompletion(tasks) { return tasks.reduce((sum, task) => (task.hasPassed() || task.isSkipped() ? sum + 1 : sum), 0); } /** * Create a new output to be rendered with the defined renderer function. * Concurrent outputs will be rendered in parallel with other concurrent * outputs and the top of the queue output. */ createConcurrentOutput(renderer) { const output = this.createOutput(renderer); output.concurrent(); return output; } /** * Create a new output to be rendered with the defined renderer function. */ createOutput(renderer) { return new Output_1.default(this.console, renderer); } /** * Create a new output to continuously render a progress bar. */ createProgressOutput(renderer) { return new ProgressOutput_1.default(this.console, renderer); } /** * Display an error and it's stack. */ displayError(error) { this.console.err(`\n${this.style(error.message, 'failure', ['bold'])}\n`); // Remove message line from stack if (error.stack) { this.console.err(this.style(error.stack.replace(`Error: ${error.message}\n`, ''), 'pending'), 1); } this.console.err('\n'); } /** * Return specific colors based on chosen theme. */ getColorPalette() { const { theme = 'default' } = this.tool.config; let palette = {}; /* eslint-disable global-require, import/no-dynamic-require */ if (terminal_1.style.level >= 2 && theme !== 'default') { try { palette = require(`@boost/theme-${theme}`); } catch (_a) { try { palette = require(`boost-theme-${theme}`); } catch (_b) { throw new Error(`Theme could not be loaded. Attempted @boost/theme-${theme} and boost-theme-${theme}.`); } } } return Object.assign({ default: 'white', failure: 'red', pending: 'gray', success: 'green', warning: 'yellow' }, palette); } /** * Return a specific color for each task status. */ getColorType(task) { if (task.isSkipped()) { return 'warning'; } else if (task.hasPassed()) { return 'success'; } else if (task.hasFailed()) { return 'failure'; } return 'pending'; } /** * Calculate the elapsed time and highlight as red if over the threshold. */ getElapsedTime(start, stop = 0, highlight = true) { const time = (stop || Date.now()) - start; const isSlow = time > exports.SLOW_THRESHOLD; const elapsed = common_1.formatMs(time); return isSlow && highlight ? this.style(elapsed, 'failure') : elapsed; } /** * Return the output level: 1 (compact), 2 (normal), 3 (verbose). */ getOutputLevel() { return this.tool.config.output; } /** * Return the root parent (depth of 0) in the current routine tree. */ getRootParent(routine) { let current = routine; while (current.metadata.depth > 0 && current.parent) { current = current.parent; } return current; } /** * Return true if the user's terminal supports color. */ hasColorSupport(level = 1) { return terminal_1.style.supportsColor && terminal_1.style.supportsColor.level >= level; } /** * Hide the console cursor. */ hideCursor() { this.console.out(terminal_1.cursor.hide); if (!restoreCursorOnExit) { restoreCursorOnExit = true; process.on('exit', () => { process.stdout.write(terminal_1.cursor.show); }); } return this; } /** * Create an indentation based on the defined length. */ indent(length = 0) { return length <= 0 ? '' : ' '.repeat(length); } /** * Return true if the final render. */ isFinalRender() { return this.console.isFinalRender(); } /** * Return true if the there should be no output. */ isSilent() { return this.tool.config.silent; } /** * Reset the cursor back to the bottom of the console. */ resetCursor() { this.console.out(terminal_1.cursor.to(0, this.size().rows)); return this; } /** * Show the console cursor. */ showCursor() { this.console.out(terminal_1.cursor.show); return this; } /** * Return size information about the terminal window. */ size() { return terminal_1.screen.size(); } /** * Sort all tasks by start time and filter to remove pending tasks. */ sortTasksByStartTime(tasks) { return [...tasks].sort((a, b) => a.metadata.startTime - b.metadata.startTime); } /** * Strip ANSI escape characters from a string. */ strip(message) { return terminal_1.stripAnsi(message); } /** * Create a chalk formatted string with accessible colors and modifiers applied. */ style(message, type = 'default', modifiers = []) { if (!this.hasColorSupport()) { return message; } const color = this.getColorPalette()[type]; let out = color.charAt(0) === '#' ? terminal_1.style.hex(color) : terminal_1.style[color]; modifiers.forEach(modifier => { out = out[modifier]; }); return out(message); } /** * Truncate a string that contains ANSI escape characters to a specific column width. */ truncate(message, columns, options) { return terminal_1.truncate(message, columns || this.size().columns, options); } /** * Wrap a string that contains ANSI escape characters to a specific column width. */ wrap(message, columns, options) { return terminal_1.wrapAnsi(message, columns || this.size().columns, Object.assign({ hard: true, trim: false }, options)); } } exports.default = Reporter; // eslint-disable-next-line no-magic-numbers Reporter.BUFFER = 10; Reporter.OUTPUT_COMPACT = 1; Reporter.OUTPUT_NORMAL = 2; Reporter.OUTPUT_VERBOSE = 3; function testOnlyResetRestoreCursor() { if (process.env.NODE_ENV === 'test') { restoreCursorOnExit = false; } } exports.testOnlyResetRestoreCursor = testOnlyResetRestoreCursor;