This commit is contained in:
Tutur33
2023-11-24 22:35:41 +01:00
parent 3c0b507a93
commit 7644b2a0f7
45165 changed files with 4803356 additions and 3 deletions
+5
View File
@@ -0,0 +1,5 @@
import { Colors as BaseColors } from '@poppinss/colors/build/src/Base';
/**
* Returns the colors instance based upon the environment
*/
export declare function getBest(testing: boolean, enabled: boolean): BaseColors;
+25
View File
@@ -0,0 +1,25 @@
"use strict";
/*
* @poppinss/utils
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getBest = void 0;
const colors_1 = require("@poppinss/colors");
/**
* Returns the colors instance based upon the environment
*/
function getBest(testing, enabled) {
if (!enabled) {
return new colors_1.Raw();
}
if (testing) {
return new colors_1.FakeColors();
}
return new colors_1.Colors();
}
exports.getBest = getBest;
+103
View File
@@ -0,0 +1,103 @@
import { Logger } from '../Logger';
/**
* Shape of the renderer contract. Except the spinner, every
* interface accepts a renderer
*/
export interface RendererContract {
log(message: string): void;
logError(message: string): void;
logUpdate(message: string): void;
logUpdateDone(): void;
}
/**
* Task update listener. Mainly used by the task renderers
*/
export declare type UpdateListener = (task: TaskContract) => void;
/**
* Shape of a task
*/
export interface TaskContract {
title: string;
state: 'idle' | 'running' | 'failed' | 'succeeded';
duration?: string;
completionMessage?: string | {
message: string;
stack?: string;
};
start(): this;
onUpdate(callback: UpdateListener): this;
complete(message?: string): this;
fail(error: string | {
message: string;
stack?: string;
}): this;
}
/**
* Callback passed while registering task with the task
* manager
*/
export declare type TaskCallback = (logger: Logger, task: {
fail: (error: string | {
message: string;
stack?: string;
}) => Promise<void>;
complete: (message?: string) => Promise<void>;
}) => void | Promise<void>;
/**
* Options accepted by the tasks renderers
*/
export declare type TaskRendererOptions = {
colors: boolean;
interactive: boolean;
};
/**
* Options accepted by the tasks manager
*/
export declare type TaskManagerOptions = TaskRendererOptions & {
verbose: boolean;
};
/**
* Logging types
*/
export declare type LoggingTypes = 'success' | 'error' | 'fatal' | 'warning' | 'info' | 'debug' | 'await';
/**
* Options accepted by the logger
*/
export declare type LoggerOptions = {
colors: boolean;
labelColors: boolean;
dim: boolean;
dimLabels: boolean;
interactive: boolean;
};
/**
* Options accepted by table
*/
export declare type TableOptions = {
colors: boolean;
};
export declare type TableRow = (string | {
colSpan?: number;
hAlign?: 'left' | 'center' | 'right';
content: string;
} | {
rowSpan?: number;
vAlign?: 'top' | 'center' | 'bottom';
content: string;
})[] | {
[key: string]: string[];
};
/**
* Options accepted by instructions
*/
export declare type InstructionsOptions = {
icons: boolean;
colors: boolean;
};
/**
* Shape of the instructions line
*/
export declare type InstructionsLine = {
text: string;
width: number;
};
+10
View File
@@ -0,0 +1,10 @@
"use strict";
/*
* @poppinss/utils
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
+10
View File
@@ -0,0 +1,10 @@
export declare const icons: {
tick: string;
cross: string;
bullet: string;
nodejs: string;
pointer: string;
info: string;
warning: string;
squareSmallFilled: string;
};
+33
View File
@@ -0,0 +1,33 @@
"use strict";
/*
* @poppinss/utils
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.icons = void 0;
const { platform } = process;
exports.icons = platform === 'win32' && !process.env.WT_SESSION
? {
tick: '√',
cross: '×',
bullet: '*',
nodejs: '♦',
pointer: '>',
info: 'i',
warning: '‼',
squareSmallFilled: '[█]',
}
: {
tick: '✔',
cross: '✖',
bullet: '●',
nodejs: '⬢',
pointer: '',
info: '',
warning: '⚠',
squareSmallFilled: '◼',
};
+100
View File
@@ -0,0 +1,100 @@
import { InstructionsOptions, RendererContract } from '../Contracts';
/**
* The API to render instructions wrapped inside a box
*/
export declare class Instructions {
private testing;
private state;
/**
* Renderer to use for rendering instructions
*/
private renderer?;
/**
* Line of the widest line inside instructions content
*/
private widestLineLength;
/**
* Number of white spaces on the left of the box
*/
private leftPadding;
/**
* Number of white spaces on the right of the box
*/
private rightPadding;
/**
* Number of empty lines at the top
*/
private paddingTop;
/**
* Number of empty lines at the bottom
*/
private paddingBottom;
/**
* Reference to the colors
*/
private colors;
/**
* Options
*/
options: InstructionsOptions;
constructor(options?: Partial<InstructionsOptions>, testing?: boolean);
/**
* Returns the renderer for rendering the messages
*/
private getRenderer;
/**
* Repeats text for given number of times
*/
private repeat;
/**
* Adds dim transformation
*/
private dim;
/**
* Wraps content inside the left and right vertical lines
*/
private wrapInVerticalLines;
/**
* Returns the top line for the box
*/
private getTopLine;
/**
* Returns the bottom line for the box
*/
private getBottomLine;
/**
* Decorates the instruction line by wrapping it inside the box
* lines
*/
private getContentLine;
/**
* Returns the heading line with the border bottom
*/
private getHeading;
/**
* Returns node for a empty line
*/
private getEmptyLineNode;
/**
* Returns instructions lines with the padding
*/
private getLinesWithPadding;
/**
* Define a custom renderer. Logs to "stdout" and "stderr"
* by default
*/
useRenderer(renderer: RendererContract): this;
/**
* Define heading for instructions
*/
heading(text: string): this;
/**
* Add new instruction. Each instruction is rendered
* in a new line inside a box
*/
add(text: string): this;
/**
* Render instructions
*/
render(): void;
}
+208
View File
@@ -0,0 +1,208 @@
"use strict";
/*
* @poppinss/cliui
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Instructions = void 0;
const cli_boxes_1 = __importDefault(require("cli-boxes"));
const string_width_1 = __importDefault(require("string-width"));
const Icons_1 = require("../Icons");
const Colors_1 = require("../Colors");
const Console_1 = require("../Renderer/Console");
/**
* The box styling used by the instructions
*/
const BOX = cli_boxes_1.default.round;
/**
* Default config options
*/
const DEFAULTS = {
icons: true,
colors: true,
};
/**
* The API to render instructions wrapped inside a box
*/
class Instructions {
constructor(options, testing = false) {
this.testing = testing;
this.state = {
content: [],
};
/**
* Line of the widest line inside instructions content
*/
this.widestLineLength = 0;
/**
* Number of white spaces on the left of the box
*/
this.leftPadding = 4;
/**
* Number of white spaces on the right of the box
*/
this.rightPadding = 8;
/**
* Number of empty lines at the top
*/
this.paddingTop = 1;
/**
* Number of empty lines at the bottom
*/
this.paddingBottom = 1;
this.options = { ...DEFAULTS, ...options };
this.colors = (0, Colors_1.getBest)(this.testing, this.options.colors);
}
/**
* Returns the renderer for rendering the messages
*/
getRenderer() {
if (!this.renderer) {
this.renderer = new Console_1.ConsoleRenderer();
}
return this.renderer;
}
/**
* Repeats text for given number of times
*/
repeat(text, times) {
return new Array(times + 1).join(text);
}
/**
* Adds dim transformation
*/
dim(text) {
return this.colors.dim(text);
}
/**
* Wraps content inside the left and right vertical lines
*/
wrapInVerticalLines(content, leftWhitespace, rightWhitespace) {
return `${this.dim(BOX.left)}${leftWhitespace}${content}${rightWhitespace}${this.dim(BOX.right)}`;
}
/**
* Returns the top line for the box
*/
getTopLine() {
const horizontalLength = this.widestLineLength + this.leftPadding + this.rightPadding;
const horizontalLine = this.repeat(this.dim(BOX.top), horizontalLength);
return `${this.dim(BOX.topLeft)}${horizontalLine}${this.dim(BOX.topRight)}`;
}
/**
* Returns the bottom line for the box
*/
getBottomLine() {
const horizontalLength = this.widestLineLength + this.leftPadding + this.rightPadding;
const horizontalLine = this.repeat(this.dim(BOX.bottom), horizontalLength);
return `${this.dim(BOX.bottomLeft)}${horizontalLine}${this.dim(BOX.bottomRight)}`;
}
/**
* Decorates the instruction line by wrapping it inside the box
* lines
*/
getContentLine(line) {
const rightWhitespace = this.repeat(' ', this.widestLineLength - line.width + this.rightPadding);
const leftWhitespace = this.repeat(' ', this.leftPadding);
return this.wrapInVerticalLines(line.text, leftWhitespace, rightWhitespace);
}
/**
* Returns the heading line with the border bottom
*/
getHeading() {
if (!this.state.heading) {
return;
}
/**
* Creating the header text
*/
const leftWhitespace = this.repeat(' ', this.leftPadding);
const rightWhitespace = this.repeat(' ', this.widestLineLength - this.state.heading.width + this.rightPadding);
const headingContent = this.wrapInVerticalLines(this.state.heading.text, leftWhitespace, rightWhitespace);
/**
* Creating the heading border bottom
*/
const horizontalLength = this.widestLineLength + this.leftPadding + this.rightPadding;
const borderLine = this.repeat(this.dim(cli_boxes_1.default.single.top), horizontalLength);
const border = this.wrapInVerticalLines(borderLine, '', '');
return `${headingContent}\n${border}`;
}
/**
* Returns node for a empty line
*/
getEmptyLineNode() {
return { text: '', width: 0 };
}
/**
* Returns instructions lines with the padding
*/
getLinesWithPadding() {
const top = new Array(this.paddingTop).fill('').map(this.getEmptyLineNode);
const bottom = new Array(this.paddingBottom).fill('').map(this.getEmptyLineNode);
return top.concat(this.state.content).concat(bottom);
}
/**
* Define a custom renderer. Logs to "stdout" and "stderr"
* by default
*/
useRenderer(renderer) {
this.renderer = renderer;
return this;
}
/**
* Define heading for instructions
*/
heading(text) {
const width = (0, string_width_1.default)(text);
if (width > this.widestLineLength) {
this.widestLineLength = width;
}
this.state.heading = { text, width };
return this;
}
/**
* Add new instruction. Each instruction is rendered
* in a new line inside a box
*/
add(text) {
text = this.options.icons ? `${this.dim(Icons_1.icons.pointer)} ${text}` : `${text}`;
const width = (0, string_width_1.default)(text);
if (width > this.widestLineLength) {
this.widestLineLength = width;
}
this.state.content.push({ text, width });
return this;
}
/**
* Render instructions
*/
render() {
const renderer = this.getRenderer();
/**
* Render content as it is in testing mode
*/
if (this.testing) {
this.state.heading && renderer.log(this.state.heading.text);
this.state.content.forEach(({ text }) => renderer.log(text));
return;
}
const top = this.getTopLine();
const heading = this.getHeading();
const body = this.getLinesWithPadding()
.map((line) => this.getContentLine(line))
.join('\n');
const bottom = this.getBottomLine();
let output = `${top}\n`;
if (heading) {
output = `${output}${heading}\n`;
}
renderer.log(`${output}${body}\n${bottom}`);
}
}
exports.Instructions = Instructions;
+36
View File
@@ -0,0 +1,36 @@
import { Logger } from '../index';
import { RendererContract } from '../../Contracts';
/**
* Exposes the API to print actions in one of the following three states
*
* - failed
* - succeeded
* - skipped
*/
export declare class Action {
private label;
private logger;
constructor(label: string, logger: Logger);
/**
* Returns the label
*/
private getLabel;
private formatMessage;
/**
* Define a custom renderer. Logs to "stdout" and "stderr"
* by default
*/
useRenderer(renderer: RendererContract): this;
/**
* Mark action as successful
*/
succeeded(message: string): void;
/**
* Mark action as skipped
*/
skipped(message: string, skipReason?: string): void;
/**
* Mark action as failed
*/
failed(message: string, errorMessage: string): void;
}
+72
View File
@@ -0,0 +1,72 @@
"use strict";
/*
* @poppinss/utils
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Action = void 0;
/**
* Exposes the API to print actions in one of the following three states
*
* - failed
* - succeeded
* - skipped
*/
class Action {
constructor(label, logger) {
this.label = label;
this.logger = logger;
}
/**
* Returns the label
*/
getLabel(label, color) {
if (!this.logger.options.colors) {
return `${label.toUpperCase()}:`;
}
return `${this.logger.colors[color](`${label.toUpperCase()}:`)}`;
}
formatMessage(message) {
if (this.logger.options.dim) {
return this.logger.colors.dim(message);
}
return message;
}
/**
* Define a custom renderer. Logs to "stdout" and "stderr"
* by default
*/
useRenderer(renderer) {
this.logger.useRenderer(renderer);
return this;
}
/**
* Mark action as successful
*/
succeeded(message) {
const label = this.getLabel(this.label, 'green');
this.logger.log(this.formatMessage(`${label} ${message}`));
}
/**
* Mark action as skipped
*/
skipped(message, skipReason) {
let logMessage = this.formatMessage(`${this.getLabel('skip', 'cyan')} ${message}`);
if (skipReason) {
logMessage = `${logMessage} ${this.logger.colors.dim(`(${skipReason})`)}`;
}
this.logger.log(logMessage);
}
/**
* Mark action as failed
*/
failed(message, errorMessage) {
let logMessage = this.formatMessage(`${this.getLabel('error', 'red')} ${message}`);
this.logger.logError(`${logMessage} ${this.logger.colors.dim(`(${errorMessage})`)}`);
}
}
exports.Action = Action;
+56
View File
@@ -0,0 +1,56 @@
import { Logger } from '../index';
/**
* The most simplest spinner to log message with a progress indicator
*/
export declare class Spinner {
private text;
private logger;
private testing;
/**
* Frames to loop over
*/
private frames;
/**
* Animation duration
*/
private interval;
/**
* The state of the spinner
*/
private state;
/**
* The current index for the frames
*/
private currentIndex;
/**
* Builds the message to the print from the text
*/
private messageBuilder;
constructor(text: string, logger: Logger, testing?: boolean);
/**
* Increment index. Also, handles the index overflow
*/
private incrementIndex;
/**
* Loop over the message and animate the spinner
*/
private loop;
/**
* Star the spinner
*/
start(): this;
/**
* Define a custom message builder
*/
useMessageBuilder(messageBuilder: {
render: (text: string) => string;
}): this;
/**
* Update spinner
*/
update(text: string, prefix?: string, suffix?: string): this;
/**
* Stop spinner
*/
stop(): void;
}
+131
View File
@@ -0,0 +1,131 @@
"use strict";
/*
* @poppinss/utils
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Spinner = void 0;
/**
* The most simplest spinner to log message with a progress indicator
*/
class Spinner {
constructor(text, logger, testing = false) {
this.text = text;
this.logger = logger;
this.testing = testing;
/**
* Frames to loop over
*/
this.frames = ['. ', '.. ', '...', ' ..', ' .', ' '];
/**
* Animation duration
*/
this.interval = 200;
/**
* The state of the spinner
*/
this.state = 'idle';
/**
* The current index for the frames
*/
this.currentIndex = 0;
/**
* Builds the message to the print from the text
*/
this.messageBuilder = {
render(text) {
if (this.prefix) {
text = `[${this.prefix}] ${text}`;
}
if (this.suffix) {
text = `${text} ${this.suffix}`;
}
return text;
},
};
}
/**
* Increment index. Also, handles the index overflow
*/
incrementIndex() {
this.currentIndex = this.frames.length === this.currentIndex + 1 ? 0 : this.currentIndex + 1;
}
/**
* Loop over the message and animate the spinner
*/
loop() {
if (this.state !== 'running') {
return;
}
/**
* Print the message as it is in testing mode or when the TTY is
* not interactive
*/
if (this.testing || !this.logger.options.interactive) {
this.logger.logUpdate(`${this.messageBuilder.render(this.text)} ${this.frames[2]}`);
return;
}
/**
* Otherwise log the current frame and re-run the function
* with some delay
*/
const frame = this.frames[this.currentIndex];
this.logger.logUpdate(`${this.messageBuilder.render(this.text)} ${frame}`);
setTimeout(() => {
this.incrementIndex();
this.loop();
}, this.interval);
}
/**
* Star the spinner
*/
start() {
this.state = 'running';
this.loop();
return this;
}
/**
* Define a custom message builder
*/
useMessageBuilder(messageBuilder) {
this.messageBuilder = messageBuilder;
return this;
}
/**
* Update spinner
*/
update(text, prefix, suffix) {
if (this.state !== 'running') {
return this;
}
this.text = text;
if (prefix !== undefined) {
this.messageBuilder.prefix = prefix;
}
if (suffix !== undefined) {
this.messageBuilder.suffix = suffix;
}
/**
* Print the message as it is in testing mode or when the TTY is
* not interactive
*/
if (this.testing || !this.logger.options.interactive) {
this.logger.logUpdate(`${this.messageBuilder.render(this.text)} ${this.frames[2]}`);
return this;
}
return this;
}
/**
* Stop spinner
*/
stop() {
this.state = 'stopped';
this.currentIndex = 0;
this.logger.logUpdatePersist();
}
}
exports.Spinner = Spinner;
+121
View File
@@ -0,0 +1,121 @@
import { Action } from './Action';
import { getBest } from '../Colors';
import { Spinner } from './Spinner';
import { LoggerOptions, RendererContract } from '../Contracts';
/**
* Logger exposes the API to log messages with consistent styles
* and colors
*/
export declare class Logger {
private testing;
/**
* Logger configuration options
*/
options: LoggerOptions;
/**
* The colors reference
*/
colors: ReturnType<typeof getBest>;
/**
* The label colors reference
*/
private labelColors;
/**
* The renderer to use to output logs
*/
private renderer?;
constructor(options?: Partial<LoggerOptions>, testing?: boolean);
/**
* Colors the logger label
*/
private colorizeLabel;
/**
* Returns the label for a given logging type
*/
private getLabel;
/**
* Appends the suffix to the message
*/
private addSuffix;
/**
* Prepends the prefix to the message. We do not DIM the prefix, since
* gray doesn't have much brightness already
*/
private addPrefix;
/**
* Prepends the prefix to the message
*/
private prefixLabel;
/**
* Decorate message string
*/
private decorateMessage;
/**
* Decorate message string
*/
private formatStack;
/**
* Returns the renderer for rendering the messages
*/
private getRenderer;
/**
* Define a custom renderer. Logs to "stdout" and "stderr"
* by default
*/
useRenderer(renderer: RendererContract): this;
/**
* Log message using the renderer. It is similar to `console.log`
* but uses the underlying renderer instead
*/
log(message: string): void;
/**
* Log message by overwriting the existing one
*/
logUpdate(message: string): void;
/**
* Persist the message logged using [[this.logUpdate]]
*/
logUpdatePersist(): void;
/**
* Log error message using the renderer. It is similar to `console.error`
* but uses the underlying renderer instead
*/
logError(message: string): void;
/**
* Log success message
*/
success(message: string, prefix?: string, suffix?: string): void;
/**
* Log error message
*/
error(message: string | {
message: string;
}, prefix?: string, suffix?: string): void;
/**
* Log fatal message
*/
fatal(message: string | {
message: string;
stack?: string;
}, prefix?: string, suffix?: string): void;
/**
* Log warning message
*/
warning(message: string, prefix?: string, suffix?: string): void;
/**
* Log info message
*/
info(message: string, prefix?: string, suffix?: string): void;
/**
* Log debug message
*/
debug(message: string, prefix?: string, suffix?: string): void;
/**
* Log a message with a spinner
*/
await(message: string, prefix?: string, suffix?: string): Spinner;
/**
* Initiates a new action
*/
action(title: string): Action;
}
+247
View File
@@ -0,0 +1,247 @@
"use strict";
/*
* @poppinss/clui
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Logger = void 0;
const Action_1 = require("./Action");
const Colors_1 = require("../Colors");
const Spinner_1 = require("./Spinner");
const Console_1 = require("../Renderer/Console");
/**
* Default config options
*/
const DEFAULTS = {
dim: false,
dimLabels: false,
colors: true,
labelColors: true,
interactive: true,
};
/**
* Logger exposes the API to log messages with consistent styles
* and colors
*/
class Logger {
constructor(options, testing = false) {
this.testing = testing;
this.options = { ...DEFAULTS, ...options };
this.colors = (0, Colors_1.getBest)(this.testing, this.options.colors);
this.labelColors = (0, Colors_1.getBest)(this.testing, this.options.labelColors && this.options.colors);
}
/**
* Colors the logger label
*/
colorizeLabel(color, text) {
if (this.options.dim || this.options.dimLabels) {
return `[ ${this.labelColors.dim()[color](text)} ]`;
}
return `[ ${this.labelColors[color](text)} ]`;
}
/**
* Returns the label for a given logging type
*/
getLabel(type) {
switch (type) {
case 'success':
return this.colorizeLabel('green', 'success');
case 'error':
case 'fatal':
return this.colorizeLabel('red', type);
case 'warning':
return this.colorizeLabel('yellow', 'warn');
case 'info':
return this.colorizeLabel('blue', 'info');
case 'debug':
return this.colorizeLabel('cyan', 'debug');
case 'await':
return this.colorizeLabel('cyan', 'wait');
}
}
/**
* Appends the suffix to the message
*/
addSuffix(message, suffix) {
if (!suffix) {
return message;
}
return `${message} ${this.colors.dim().yellow(`(${suffix})`)}`;
}
/**
* Prepends the prefix to the message. We do not DIM the prefix, since
* gray doesn't have much brightness already
*/
addPrefix(message, prefix) {
if (!prefix) {
return message;
}
prefix = prefix.replace(/%time%/, new Date().toISOString());
return `${this.colors.dim(`[${prefix}]`)} ${message}`;
}
/**
* Prepends the prefix to the message
*/
prefixLabel(message, label) {
return `${label} ${message}`;
}
/**
* Decorate message string
*/
decorateMessage(message) {
if (this.options.dim) {
return this.colors.dim(message);
}
return message;
}
/**
* Decorate message string
*/
formatStack(stack) {
if (!stack) {
return '';
}
return `\n${stack
.split('\n')
.splice(1)
.map((line) => {
return `${this.colors.dim(line)}`;
})
.join('\n')}`;
}
/**
* Returns the renderer for rendering the messages
*/
getRenderer() {
if (!this.renderer) {
this.renderer = new Console_1.ConsoleRenderer();
}
return this.renderer;
}
/**
* Define a custom renderer. Logs to "stdout" and "stderr"
* by default
*/
useRenderer(renderer) {
this.renderer = renderer;
return this;
}
/**
* Log message using the renderer. It is similar to `console.log`
* but uses the underlying renderer instead
*/
log(message) {
this.getRenderer().log(message);
}
/**
* Log message by overwriting the existing one
*/
logUpdate(message) {
this.getRenderer().logUpdate(message);
}
/**
* Persist the message logged using [[this.logUpdate]]
*/
logUpdatePersist() {
this.getRenderer().logUpdateDone();
}
/**
* Log error message using the renderer. It is similar to `console.error`
* but uses the underlying renderer instead
*/
logError(message) {
this.getRenderer().logError(message);
}
/**
* Log success message
*/
success(message, prefix, suffix) {
message = this.decorateMessage(message);
message = this.prefixLabel(message, this.getLabel('success'));
message = this.addPrefix(message, prefix);
message = this.addSuffix(message, suffix);
this.log(message);
}
/**
* Log error message
*/
error(message, prefix, suffix) {
message = typeof message === 'string' ? message : message.message;
message = this.decorateMessage(message);
message = this.prefixLabel(message, this.getLabel('error'));
message = this.addPrefix(message, prefix);
message = this.addSuffix(message, suffix);
this.logError(message);
}
/**
* Log fatal message
*/
fatal(message, prefix, suffix) {
const stack = this.formatStack(typeof message === 'string' ? undefined : message.stack);
message = typeof message === 'string' ? message : message.message;
message = this.decorateMessage(message);
message = this.prefixLabel(message, this.getLabel('error'));
message = this.addPrefix(message, prefix);
message = this.addSuffix(message, suffix);
this.logError(`${message}${stack}`);
}
/**
* Log warning message
*/
warning(message, prefix, suffix) {
message = this.decorateMessage(message);
message = this.prefixLabel(message, this.getLabel('warning'));
message = this.addPrefix(message, prefix);
message = this.addSuffix(message, suffix);
this.log(message);
}
/**
* Log info message
*/
info(message, prefix, suffix) {
message = this.decorateMessage(message);
message = this.prefixLabel(message, this.getLabel('info'));
message = this.addPrefix(message, prefix);
message = this.addSuffix(message, suffix);
this.log(message);
}
/**
* Log debug message
*/
debug(message, prefix, suffix) {
message = this.decorateMessage(message);
message = this.prefixLabel(message, this.getLabel('debug'));
message = this.addPrefix(message, prefix);
message = this.addSuffix(message, suffix);
this.log(message);
}
/**
* Log a message with a spinner
*/
await(message, prefix, suffix) {
const messageBuilder = {
prefix: prefix,
suffix: suffix,
logger: this,
render(text) {
text = this.logger.decorateMessage(text);
text = this.logger.prefixLabel(text, this.logger.getLabel('await'));
text = this.logger.addPrefix(text, this.prefix);
text = this.logger.addSuffix(text, this.suffix);
return text;
},
};
return new Spinner_1.Spinner(message, this, this.testing).useMessageBuilder(messageBuilder).start();
}
/**
* Initiates a new action
*/
action(title) {
return new Action_1.Action(title, this);
}
}
exports.Logger = Logger;
+19
View File
@@ -0,0 +1,19 @@
import { RendererContract } from '../Contracts';
/**
* Renders messages to the "stdout" and "stderr"
*/
export declare class ConsoleRenderer implements RendererContract {
log(message: string): void;
/**
* Log message by overwriting the existing one
*/
logUpdate(message: string): void;
/**
* Persist the last logged message
*/
logUpdateDone(): void;
/**
* Log error
*/
logError(message: string): void;
}
+42
View File
@@ -0,0 +1,42 @@
"use strict";
/*
* @poppinss/utils
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConsoleRenderer = void 0;
const log_update_1 = __importDefault(require("log-update"));
/**
* Renders messages to the "stdout" and "stderr"
*/
class ConsoleRenderer {
log(message) {
console.log(message);
}
/**
* Log message by overwriting the existing one
*/
logUpdate(message) {
(0, log_update_1.default)(message);
}
/**
* Persist the last logged message
*/
logUpdateDone() {
log_update_1.default.done();
}
/**
* Log error
*/
logError(message) {
console.error(message);
}
}
exports.ConsoleRenderer = ConsoleRenderer;
+26
View File
@@ -0,0 +1,26 @@
import { RendererContract } from '../Contracts';
/**
* Keeps log messages within memory. Useful for testing
*/
export declare class MemoryRenderer implements RendererContract {
logs: {
message: string;
stream: 'stdout' | 'stderr';
}[];
/**
* Log message
*/
log(message: string): void;
/**
* For memory renderer the logUpdate is similar to log
*/
logUpdate(message: string): void;
/**
* Its a noop
*/
logUpdateDone(): void;
/**
* Log message as error
*/
logError(message: string): void;
}
+42
View File
@@ -0,0 +1,42 @@
"use strict";
/*
* @poppinss/utils
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MemoryRenderer = void 0;
/**
* Keeps log messages within memory. Useful for testing
*/
class MemoryRenderer {
constructor() {
this.logs = [];
}
/**
* Log message
*/
log(message) {
this.logs.push({ message, stream: 'stdout' });
}
/**
* For memory renderer the logUpdate is similar to log
*/
logUpdate(message) {
this.log(message);
}
/**
* Its a noop
*/
logUpdateDone() { }
/**
* Log message as error
*/
logError(message) {
this.logs.push({ message, stream: 'stderr' });
}
}
exports.MemoryRenderer = MemoryRenderer;
+47
View File
@@ -0,0 +1,47 @@
import { getBest } from '../Colors';
import { RendererContract, TableOptions, TableRow } from '../Contracts';
/**
* Exposes the API to represent a table
*/
export declare class Table {
private testing;
private state;
/**
* The renderer to use to output logs
*/
private renderer?;
/**
* Logger configuration options
*/
options: TableOptions;
/**
* The colors reference
*/
colors: ReturnType<typeof getBest>;
constructor(options?: Partial<TableOptions>, testing?: boolean);
/**
* Returns the renderer for rendering the messages
*/
private getRenderer;
/**
* Define a custom renderer. Logs to "stdout" and "stderr"
* by default
*/
useRenderer(renderer: RendererContract): this;
/**
* Define table head
*/
head(headColumns: string[]): this;
/**
* Add a new table row
*/
row(row: TableRow): this;
/**
* Define custom column widths
*/
columnWidths(widths: number[]): this;
/**
* Render table
*/
render(): void;
}
+103
View File
@@ -0,0 +1,103 @@
"use strict";
/*
* @poppinss/cliui
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Table = void 0;
const cli_table3_1 = __importDefault(require("cli-table3"));
const Colors_1 = require("../Colors");
const Console_1 = require("../Renderer/Console");
/**
* Default config options
*/
const DEFAULTS = {
colors: true,
};
/**
* Exposes the API to represent a table
*/
class Table {
constructor(options, testing = false) {
this.testing = testing;
this.state = {
head: [],
rows: [],
};
this.options = { ...DEFAULTS, ...options };
this.colors = (0, Colors_1.getBest)(this.testing, this.options.colors);
}
/**
* Returns the renderer for rendering the messages
*/
getRenderer() {
if (!this.renderer) {
this.renderer = new Console_1.ConsoleRenderer();
}
return this.renderer;
}
/**
* Define a custom renderer. Logs to "stdout" and "stderr"
* by default
*/
useRenderer(renderer) {
this.renderer = renderer;
return this;
}
/**
* Define table head
*/
head(headColumns) {
this.state.head = headColumns.map((col) => this.colors.cyan(col));
return this;
}
/**
* Add a new table row
*/
row(row) {
this.state.rows.push(row);
return this;
}
/**
* Define custom column widths
*/
columnWidths(widths) {
this.state.colWidths = widths;
return this;
}
/**
* Render table
*/
render() {
if (this.testing) {
this.getRenderer().log(this.state.head.join('|'));
this.state.rows.forEach((row) => {
const content = Array.isArray(row)
? row.map((cell) => {
if (typeof cell === 'string') {
return cell;
}
return cell.content;
})
: Object.keys(row);
this.getRenderer().log(content.join('|'));
});
return;
}
const cliTable = new cli_table3_1.default({
head: this.state.head,
style: { head: [], border: ['dim'] },
...(this.state.colWidths ? { colWidths: this.state.colWidths } : {}),
});
this.state.rows.forEach((row) => cliTable.push(row));
this.getRenderer().log(cliTable.toString());
}
}
exports.Table = Table;
+50
View File
@@ -0,0 +1,50 @@
import { TaskManagerOptions, TaskCallback, RendererContract } from '../Contracts';
/**
* Exposes the API to create a group of tasks and run them in sequence
*/
export declare class TaskManager {
private testing;
/**
* Options
*/
private options;
/**
* The renderer to use for rendering tasks. Automatically decided
*/
private renderer;
/**
* A set of created tasks
*/
private tasks;
/**
* State of the tasks manager
*/
state: 'idle' | 'running' | 'succeeded' | 'failed';
/**
* Reference to the error raised by the task callback (if any)
*/
error?: any;
constructor(options?: Partial<TaskManagerOptions>, testing?: boolean);
/**
* Instantiates the tasks renderer
*/
private instantiateRenderer;
/**
* Run a given task. The underlying code assumes that tasks are
* executed in sequence.
*/
private runTask;
/**
* Register a new task
*/
add(title: string, callback: TaskCallback): this;
/**
* Define a custom logging renderer. Logs to "stdout" and "stderr"
* by default
*/
useRenderer(renderer: RendererContract): this;
/**
* Run tasks
*/
run(): Promise<void>;
}
+135
View File
@@ -0,0 +1,135 @@
"use strict";
/*
* @poppinss/cliui
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.TaskManager = void 0;
const index_1 = require("./index");
const Verbose_1 = require("./Renderers/Verbose");
const Minimal_1 = require("./Renderers/Minimal");
/**
* Default set of options
*/
const DEFAULTS = {
colors: true,
interactive: true,
verbose: false,
};
/**
* Exposes the API to create a group of tasks and run them in sequence
*/
class TaskManager {
constructor(options, testing = false) {
this.testing = testing;
/**
* A set of created tasks
*/
this.tasks = [];
/**
* State of the tasks manager
*/
this.state = 'idle';
this.options = { ...DEFAULTS, ...options };
this.instantiateRenderer();
}
/**
* Instantiates the tasks renderer
*/
instantiateRenderer() {
const rendererOptions = {
colors: this.options.colors,
interactive: this.options.interactive,
};
/**
* Using verbose render when verbose option is true or terminal is not
* interactive
*/
if (this.options.verbose || this.testing || !this.options.interactive) {
this.renderer = new Verbose_1.VerboseRenderer(rendererOptions, this.testing);
return;
}
/**
* Otheriwse using the minimal renderer
*/
this.renderer = new Minimal_1.MinimalRenderer(rendererOptions, this.testing);
}
/**
* Run a given task. The underlying code assumes that tasks are
* executed in sequence.
*/
async runTask(index) {
const task = this.tasks[index];
if (!task) {
return;
}
/**
* Start the underlying task
*/
task.task.start();
/**
* Method to invoke when callback has been completed
*/
const complete = async (message) => {
if (task.task.state !== 'running') {
return;
}
task.task.complete(message);
await this.runTask(index + 1);
};
/**
* Method to invoke when callback has been failed
*/
const fail = async (message) => {
if (task.task.state !== 'running') {
return;
}
this.error = message;
this.state = 'failed';
task.task.fail(message);
};
/**
* Invoke callback
*/
try {
await task.callback(this.renderer.logger, { complete, fail });
}
catch (error) {
await fail(error);
}
}
/**
* Register a new task
*/
add(title, callback) {
this.tasks.push({ task: new index_1.Task(title), callback });
return this;
}
/**
* Define a custom logging renderer. Logs to "stdout" and "stderr"
* by default
*/
useRenderer(renderer) {
this.renderer.useRenderer(renderer);
return this;
}
/**
* Run tasks
*/
async run() {
if (this.state !== 'idle') {
return;
}
this.state = 'running';
this.renderer.tasks(this.tasks.map(({ task }) => task)).render();
await this.runTask(0);
if (this.state === 'running') {
this.state = 'succeeded';
}
}
}
exports.TaskManager = TaskManager;
+72
View File
@@ -0,0 +1,72 @@
import { Logger } from '../../Logger';
import { TaskContract, TaskRendererOptions, RendererContract } from '../../Contracts';
/**
* As the name suggests, render tasks in minimal UI for better viewing
* experience.
*/
export declare class MinimalRenderer {
private options;
private testing;
/**
* The renderer to use to output logs
*/
private renderer?;
/**
* List of registered tasks
*/
private registeredTasks;
/**
* Reference to the logger. We will capture logger messages
* and show them next to the task
*/
logger: Logger;
constructor(options: TaskRendererOptions, testing?: boolean);
/**
* Returns the renderer for rendering the messages
*/
private getRenderer;
/**
* Instantiates the logger and defines a custom renderer
* to log messages in context with the currently running
* task
*/
private instantiateLogger;
/**
* Returns the presentation string for an idle task
*/
private presentIdleTask;
/**
* Returns the presentation string for a running task. The log line is
* updated when logger recieves the message.
*/
private presentRunningTask;
/**
* Returns the presentation string for a failed task
*/
private presentFailedTask;
/**
* Returns the presentation string for a succeeded task
*/
private presentSucceededTask;
/**
* Renders a given task
*/
private renderTask;
/**
* Re-renders all tasks by inspecting their current state
*/
private renderTasks;
/**
* Define a custom renderer. Logs to "stdout" and "stderr"
* by default
*/
useRenderer(renderer: RendererContract): this;
/**
* Register tasks to render
*/
tasks(tasks: TaskContract[]): this;
/**
* Render all tasks
*/
render(): void;
}
+143
View File
@@ -0,0 +1,143 @@
"use strict";
/*
* @poppinss/cliui
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MinimalRenderer = void 0;
const Icons_1 = require("../../Icons");
const Logger_1 = require("../../Logger");
const Console_1 = require("../../Renderer/Console");
/**
* As the name suggests, render tasks in minimal UI for better viewing
* experience.
*/
class MinimalRenderer {
constructor(options, testing = false) {
this.options = options;
this.testing = testing;
}
/**
* Returns the renderer for rendering the messages
*/
getRenderer() {
if (!this.renderer) {
this.renderer = new Console_1.ConsoleRenderer();
}
return this.renderer;
}
/**
* Instantiates the logger and defines a custom renderer
* to log messages in context with the currently running
* task
*/
instantiateLogger() {
/**
* The minimal renderer must always be used when term
* has support for colors and is tty
*/
this.logger = new Logger_1.Logger({ ...this.options, dim: true }, this.testing);
this.logger.useRenderer({
log: (message) => this.renderTasks(message),
logError: (message) => this.renderTasks(message),
logUpdate: (message) => this.renderTasks(message),
logUpdateDone: () => { },
});
}
/**
* Returns the presentation string for an idle task
*/
presentIdleTask(task) {
return `${this.logger.colors.dim(Icons_1.icons.pointer)} ${this.logger.colors.dim(task.title)}`;
}
/**
* Returns the presentation string for a running task. The log line is
* updated when logger recieves the message.
*/
presentRunningTask(task, logLine) {
let message = `${Icons_1.icons.pointer} ${task.title}`;
if (!logLine) {
return message;
}
const lines = logLine.trim().split('\n');
return `${message}\n ${lines[0]}`;
}
/**
* Returns the presentation string for a failed task
*/
presentFailedTask(task) {
const pointer = this.logger.colors.red(Icons_1.icons.pointer);
const duration = this.logger.colors.dim(task.duration);
let message = `${pointer} ${task.title} ${duration}`;
if (!task.completionMessage) {
return message;
}
const errorMessage = typeof task.completionMessage === 'string'
? task.completionMessage
: task.completionMessage.message;
message = `${message}\n ${this.logger.colors.red(errorMessage)}`;
return message;
}
/**
* Returns the presentation string for a succeeded task
*/
presentSucceededTask(task) {
const pointer = this.logger.colors.green(Icons_1.icons.pointer);
const duration = this.logger.colors.dim(task.duration);
let message = `${pointer} ${task.title} ${duration}`;
if (!task.completionMessage) {
return message;
}
message = `${message}\n ${this.logger.colors.dim(task.completionMessage)}`;
return message;
}
/**
* Renders a given task
*/
renderTask(task, logLine) {
switch (task.state) {
case 'idle':
return this.presentIdleTask(task);
case 'running':
return this.presentRunningTask(task, logLine);
case 'succeeded':
return this.presentSucceededTask(task);
case 'failed':
return this.presentFailedTask(task);
}
}
/**
* Re-renders all tasks by inspecting their current state
*/
renderTasks(logLine) {
this.getRenderer().logUpdate(this.registeredTasks.map((task) => this.renderTask(task, logLine)).join('\n'));
}
/**
* Define a custom renderer. Logs to "stdout" and "stderr"
* by default
*/
useRenderer(renderer) {
this.renderer = renderer;
return this;
}
/**
* Register tasks to render
*/
tasks(tasks) {
this.registeredTasks = tasks;
return this;
}
/**
* Render all tasks
*/
render() {
this.instantiateLogger();
this.registeredTasks.forEach((task) => task.onUpdate(() => this.renderTasks()));
this.renderTasks();
}
}
exports.MinimalRenderer = MinimalRenderer;
+55
View File
@@ -0,0 +1,55 @@
import { Logger } from '../../Logger';
import { TaskContract, TaskRendererOptions, RendererContract } from '../../Contracts';
/**
* Verbose renderer shows a detailed output of the tasks and the
* messages logged by a given task
*/
export declare class VerboseRenderer {
private options;
private testing;
/**
* The renderer to use to output logs
*/
private renderer?;
/**
* List of registered tasks
*/
private registeredTasks;
/**
* Reference to the logger. We will capture logger messages
* and show them next to the task
*/
logger: Logger;
constructor(options: TaskRendererOptions, testing?: boolean);
/**
* Returns the renderer for rendering the messages
*/
private getRenderer;
/**
* Prefixes pipe to a line of text
*/
private prefixPipe;
/**
* Instantiates the logger and defines a custom renderer
* to log messages in context with the currently running
* task
*/
private instantiateLogger;
/**
* Logs message based upon the state of the task
*/
private updateTask;
/**
* Define a custom renderer. Logs to "stdout" and "stderr"
* by default
*/
useRenderer(renderer: RendererContract): this;
/**
* Register tasks to render
*/
tasks(tasks: TaskContract[]): this;
/**
* Render all tasks
*/
render(): void;
}
+108
View File
@@ -0,0 +1,108 @@
"use strict";
/*
* @poppinss/cliui
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.VerboseRenderer = void 0;
const Logger_1 = require("../../Logger");
const Console_1 = require("../../Renderer/Console");
/**
* Verbose renderer shows a detailed output of the tasks and the
* messages logged by a given task
*/
class VerboseRenderer {
constructor(options, testing = false) {
this.options = options;
this.testing = testing;
}
/**
* Returns the renderer for rendering the messages
*/
getRenderer() {
if (!this.renderer) {
this.renderer = new Console_1.ConsoleRenderer();
}
return this.renderer;
}
/**
* Prefixes pipe to a line of text
*/
prefixPipe(text) {
return text
.split('\n')
.map((line) => `${this.logger.colors.dim('│')} ${line}`)
.join('\n');
}
/**
* Instantiates the logger and defines a custom renderer
* to log messages in context with the currently running
* task
*/
instantiateLogger() {
this.logger = new Logger_1.Logger({ ...this.options, dim: true }, this.testing);
this.logger.useRenderer({
log: (message) => this.getRenderer().log(this.prefixPipe(message)),
logError: (message) => this.getRenderer().logError(this.prefixPipe(message)),
logUpdate: (message) => this.getRenderer().logUpdate(this.prefixPipe(message)),
logUpdateDone: () => this.getRenderer().logUpdateDone(),
});
}
/**
* Logs message based upon the state of the task
*/
updateTask(task) {
/**
* Task started running
*/
if (task.state === 'running') {
this.getRenderer().log(`${this.logger.colors.dim('┌')} ${task.title}`);
return;
}
const pipe = this.logger.colors.dim('└');
const duration = this.logger.colors.dim(`(${task.duration})`);
/**
* Task failed
*/
if (task.state === 'failed') {
task.completionMessage && this.logger.fatal(task.completionMessage);
this.getRenderer().logError(`${pipe} ${this.logger.colors.red('failed')} ${duration}`);
return;
}
/**
* Task succeeded
*/
if (task.state === 'succeeded') {
task.completionMessage && this.logger.colors.green(task.completionMessage);
this.getRenderer().log(`${pipe} ${this.logger.colors.green('completed')} ${duration}`);
return;
}
}
/**
* Define a custom renderer. Logs to "stdout" and "stderr"
* by default
*/
useRenderer(renderer) {
this.renderer = renderer;
return this;
}
/**
* Register tasks to render
*/
tasks(tasks) {
this.registeredTasks = tasks;
return this;
}
/**
* Render all tasks
*/
render() {
this.instantiateLogger();
this.registeredTasks.forEach((task) => task.onUpdate(($task) => this.updateTask($task)));
}
}
exports.VerboseRenderer = VerboseRenderer;
+43
View File
@@ -0,0 +1,43 @@
import { TaskContract, UpdateListener } from '../Contracts';
/**
* Task exposes a very simple API to create tasks with states, along with a
* listener to listen for the task state updates.
*
* The task itself has does not render anything to the console. The task
* renderers does that.
*/
export declare class Task implements TaskContract {
title: string;
private startTime;
private onUpdateListener;
/**
* Duration of the task. Updated after the task is over
*/
duration?: string;
/**
* Message set after completing the task. Can be an error or the
* a success message
*/
completionMessage?: TaskContract['completionMessage'];
/**
* Task current state
*/
state: TaskContract['state'];
constructor(title: string);
/**
* Bind a listener to listen to the state updates of the task
*/
onUpdate(listener: UpdateListener): this;
/**
* Start the task
*/
start(): this;
/**
* Mark task as completed
*/
complete(message?: string): this;
/**
* Mark task as failed
*/
fail(error: TaskContract['completionMessage']): this;
}
+69
View File
@@ -0,0 +1,69 @@
"use strict";
/*
* @poppinss/cliui
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Task = void 0;
const pretty_hrtime_1 = __importDefault(require("pretty-hrtime"));
/**
* Task exposes a very simple API to create tasks with states, along with a
* listener to listen for the task state updates.
*
* The task itself has does not render anything to the console. The task
* renderers does that.
*/
class Task {
constructor(title) {
this.title = title;
this.onUpdateListener = () => { };
/**
* Task current state
*/
this.state = 'idle';
}
/**
* Bind a listener to listen to the state updates of the task
*/
onUpdate(listener) {
this.onUpdateListener = listener;
return this;
}
/**
* Start the task
*/
start() {
this.state = 'running';
this.startTime = process.hrtime();
this.onUpdateListener && this.onUpdateListener(this);
return this;
}
/**
* Mark task as completed
*/
complete(message) {
this.state = 'succeeded';
this.duration = (0, pretty_hrtime_1.default)(process.hrtime(this.startTime));
this.completionMessage = message;
this.onUpdateListener && this.onUpdateListener(this);
return this;
}
/**
* Mark task as failed
*/
fail(error) {
this.state = 'failed';
this.duration = (0, pretty_hrtime_1.default)(process.hrtime(this.startTime));
this.completionMessage = error;
this.onUpdateListener && this.onUpdateListener(this);
return this;
}
}
exports.Task = Task;