@boost/core
Version:
Robust pipeline for creating dev tools that separate logic into routines and tasks.
247 lines (246 loc) • 7.82 kB
JavaScript
"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(`/theme-${theme}`);
}
catch (_a) {
try {
palette = require(`boost-theme-${theme}`);
}
catch (_b) {
throw new Error(`Theme could not be loaded. Attempted /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;