Comparing version 1.3.12 to 2.0.0
@@ -0,1 +1,78 @@ | ||
# [2.0.0](https://github.com/cenk1cenk2/listr2/compare/v1.3.12...v2.0.0) (2020-05-06) | ||
### Bug Fixes | ||
* **default-renderer:** added back cli truncate ([22132a5](https://github.com/cenk1cenk2/listr2/commit/22132a5b022ef58a4a463e48af062f54631a3b9d)) | ||
* **error-collection:** fixed error collection on non-failing tasks ([4239094](https://github.com/cenk1cenk2/listr2/commit/4239094a7947cf60d8d030c45aaf75710637a40c)) | ||
* **manager:** added error context ([4f8f387](https://github.com/cenk1cenk2/listr2/commit/4f8f387a576bc83947fd90e83f27de827b9f9d08)) | ||
* **manager:** fixed manager ([57dcd7f](https://github.com/cenk1cenk2/listr2/commit/57dcd7f8362589f2a43b645920cf158c2cb8d591)) | ||
* **types:** fix ([b3ee9be](https://github.com/cenk1cenk2/listr2/commit/b3ee9be0f895e8927c825b3993cc847d360e709d)) | ||
* fixed types for isolated renderer options ([4521832](https://github.com/cenk1cenk2/listr2/commit/452183240c55984db57551082aa049e4799a2425)) | ||
### Features | ||
* **release:** ready to update to new version ([50fb773](https://github.com/cenk1cenk2/listr2/commit/50fb773128073b1ec312fea3121a2f93e9270271)), closes [#19](https://github.com/cenk1cenk2/listr2/issues/19) [#18](https://github.com/cenk1cenk2/listr2/issues/18) | ||
* **renderer-options:** started to isolate the renderer options instead of writing them directly ([95f7f87](https://github.com/cenk1cenk2/listr2/commit/95f7f8749445e45a90d3f4346eb4cd0625e9593e)) | ||
### BREAKING CHANGES | ||
* **release:** - Renderer Options | ||
- Reason: *This was changed because of having all the renderer options that are mangled together and not respecting which renderer has been choosen. It also allows for custom renderers to have their own logic by exposing their options in a single class file rather than expecting that functionality from the project itself.* | ||
- Before <v1.3.12: | ||
```typescript | ||
new Listr<Ctx>([ | ||
{ | ||
task: async (ctx, task): Promise<void> => { | ||
}, | ||
persistentOutput: true | ||
} | ||
], { | ||
concurrent: [secure], | ||
collapse: true | ||
``` | ||
- After <v1.3.12: | ||
```typescript | ||
new Listr<Ctx>([ | ||
{ | ||
task: async (ctx, task): Promise<void> => { | ||
}, | ||
options: { persistentOutput: true } // per task based options are moved to their own key | ||
} | ||
], { | ||
concurrent: [secure], | ||
rendererOptions: { collapse: [secure] } | ||
// global renderer options moved to their own key | ||
}) | ||
``` | ||
- Some of the types has been changed. | ||
- Reason: *Some of the types had to be changed due to compatability reasons with new autocomplete functionality of the dynamic renderer options.* | ||
- Before <v1.3.12: | ||
```typescript | ||
let task: Listr<Ctx> | ||
task = new Listr(..., { renderer: 'verbose' }) | ||
``` | ||
- After <v1.3.12: | ||
```typescript | ||
// this without the indication of verbose will now fail due to default renderer being 'default' for autocompleting goodness of the IDEs. | ||
// So you have to overwrite it manually to 'verbose'. | ||
// If it does not have a default you had to explicitly write { renderer: 'default' } everytime to have the auto complete feature | ||
let task: Listr<Ctx, 'verbose'> | ||
task = new Listr(..., { renderer: 'verbose' }) | ||
``` | ||
- Test renderer removed. | ||
- Reason: *On non-tty environments that the verbose renderer is intended for there is no need to show icons. Since icons are now optional with the default being disabled for the verbose renderer, there is no need for a renderer that does have the same functionality since verbose and test are now basically the same thing. Verbose seemed a better name then test, so I had to remove test from the equation.* | ||
- Before <v1.3.12: | ||
```typescript | ||
const task = new Listr(..., { renderer: 'test' }) | ||
``` | ||
- After <v1.3.12: | ||
```typescript | ||
const task = new Listr(..., { renderer: 'verbose' }) | ||
``` | ||
## [1.3.12](https://github.com/cenk1cenk2/listr2/compare/v1.3.11...v1.3.12) (2020-04-30) | ||
@@ -2,0 +79,0 @@ |
export * from './listr'; | ||
export * from './manager'; | ||
export * from './interfaces/listr.interface'; | ||
export * from './interfaces/manager.interface'; | ||
export * from './utils/logger'; | ||
export * from './utils/logger.constants'; | ||
export * from './utils/prompt.interface'; | ||
export * from './utils/prompt'; |
@@ -9,4 +9,5 @@ "use strict"; | ||
__export(require("./interfaces/listr.interface")); | ||
__export(require("./interfaces/manager.interface")); | ||
__export(require("./utils/logger")); | ||
__export(require("./utils/logger.constants")); | ||
__export(require("./utils/prompt")); | ||
//# sourceMappingURL=index.js.map |
@@ -6,25 +6,31 @@ /// <reference types="node" /> | ||
import { Task } from '../lib/task'; | ||
import { DefaultRenderer } from '../renderer/default.renderer'; | ||
import { SilentRenderer } from '../renderer/silent.renderer'; | ||
import { VerboseRenderer } from '../renderer/verbose.renderer'; | ||
import { Listr } from '../index'; | ||
import { PromptOptionsType, PromptTypes } from '../utils/prompt.interface'; | ||
export declare type ListrContext = any; | ||
export declare class ListrClass<Ctx = ListrContext> { | ||
tasks: Task<Ctx>[]; | ||
constructor(task?: readonly ListrTask<Ctx>[], options?: ListrOptions<Ctx>); | ||
export declare type ListrDefaultRendererValue = 'default'; | ||
export declare type ListrDefaultRenderer = typeof DefaultRenderer; | ||
export declare type ListrFallbackRendererValue = 'verbose'; | ||
export declare type ListrFallbackRenderer = typeof VerboseRenderer; | ||
export declare class ListrClass<Ctx = ListrContext, Renderer extends ListrRendererValue = ListrDefaultRendererValue, FallbackRenderer extends ListrRendererValue = ListrFallbackRendererValue> { | ||
tasks: Task<Ctx, ListrGetRendererClassFromValue<Renderer>>[]; | ||
constructor(task?: readonly ListrTask<Ctx, ListrGetRendererClassFromValue<Renderer>>[], options?: ListrBaseClassOptions<Ctx, Renderer, FallbackRenderer>); | ||
run(ctx?: Ctx): Promise<Ctx>; | ||
add(tasks: ListrTask<Ctx> | readonly ListrTask<Ctx>[]): void; | ||
add(tasks: ListrTask<Ctx, ListrGetRendererClassFromValue<Renderer>> | readonly ListrTask<Ctx, ListrGetRendererClassFromValue<Renderer>>[]): void; | ||
} | ||
export interface ListrTaskObject<Ctx> extends Observable<ListrEvent> { | ||
export interface ListrTaskObject<Ctx, Renderer extends ListrRendererFactory> extends Observable<ListrEvent> { | ||
id: string; | ||
title?: string; | ||
output?: string; | ||
task: (ctx: Ctx, task: ListrTaskWrapper<Ctx>) => void | ListrTaskResult<Ctx>; | ||
skip: (ctx: Ctx) => void | boolean | string | Promise<boolean>; | ||
subtasks: ListrTaskObject<Ctx>[]; | ||
task: (ctx: Ctx, task: ListrTaskWrapper<Ctx, Renderer>) => void | ListrTaskResult<Ctx>; | ||
skip: boolean | string | ((ctx: Ctx) => boolean | string | Promise<boolean> | Promise<string>); | ||
subtasks: ListrTaskObject<Ctx, any>[]; | ||
state: string; | ||
check: (ctx: Ctx) => void; | ||
run: (ctx: Ctx, wrapper: ListrTaskWrapper<Ctx>) => Promise<void>; | ||
options: ListrOptions & { | ||
bottomBar?: ListrTask<Ctx>['bottomBar']; | ||
persistentOutput?: ListrTask<Ctx>['persistentOutput']; | ||
}; | ||
run: (ctx: Ctx, wrapper: ListrTaskWrapper<Ctx, Renderer>) => Promise<void>; | ||
options: ListrOptions; | ||
rendererOptions: ListrGetRendererOptions<Renderer>; | ||
rendererTaskOptions: ListrGetRendererTaskOptions<Renderer>; | ||
spinner?: () => string; | ||
@@ -36,4 +42,2 @@ hasSubtasks(): boolean; | ||
isEnabled(): boolean; | ||
isBottomBar(): boolean; | ||
haspersistentOutput(): boolean; | ||
isPrompt(): boolean; | ||
@@ -43,31 +47,56 @@ hasFailed(): boolean; | ||
} | ||
export interface ListrTask<Ctx = ListrContext> { | ||
export interface ListrTask<Ctx = ListrContext, Renderer extends ListrRendererFactory = any> { | ||
title?: string; | ||
task: (ctx: Ctx, task: ListrTaskWrapper<Ctx>) => void | ListrTaskResult<Ctx>; | ||
skip?: (ctx: Ctx) => void | boolean | string | Promise<boolean>; | ||
task: (ctx: Ctx, task: ListrTaskWrapper<Ctx, Renderer>) => void | ListrTaskResult<Ctx>; | ||
skip?: boolean | string | ((ctx: Ctx) => boolean | string | Promise<boolean> | Promise<string>); | ||
enabled?: boolean | ((ctx: Ctx) => boolean | Promise<boolean>); | ||
bottomBar?: boolean | number; | ||
persistentOutput?: boolean; | ||
options?: ListrGetRendererTaskOptions<Renderer>; | ||
} | ||
export interface ListrTaskWrapper<Ctx = ListrContext> { | ||
export interface ListrTaskWrapper<Ctx, Renderer extends ListrRendererFactory> { | ||
title: string; | ||
output: string; | ||
newListr<Ctx = ListrContext>(task: ListrTask<Ctx>[], options?: ListrOptions<Ctx>): Listr; | ||
newListr(task: ListrTask<Ctx, Renderer> | ListrTask<Ctx, Renderer>[], options?: ListrSubClassOptions<Ctx, Renderer>): Listr<Ctx, any, any>; | ||
report(error: Error): void; | ||
skip(message: string): void; | ||
run(ctx?: Ctx, task?: ListrTaskWrapper<Ctx>): Promise<void>; | ||
run(ctx?: Ctx, task?: ListrTaskWrapper<Ctx, Renderer>): Promise<void>; | ||
prompt<T = any, P extends PromptTypes = PromptTypes>(type: P, options: PromptOptionsType<P>): Promise<T>; | ||
} | ||
export declare type ListrTaskResult<Ctx> = string | Promise<any> | ListrClass<Ctx> | Readable | Observable<any>; | ||
export declare type ListrTaskResult<Ctx> = string | Promise<any> | ListrClass<Ctx, ListrRendererFactory, any> | Readable | Observable<any>; | ||
export declare type ListrBaseClassOptions<Ctx = ListrContext, Renderer extends ListrRendererValue = ListrDefaultRendererValue, FallbackRenderer extends ListrRendererValue = ListrFallbackRendererValue> = ListrOptions<Ctx> & ListrDefaultRendererOptions<Renderer> & ListrDefaultNonTTYRendererOptions<FallbackRenderer>; | ||
export declare type ListrSubClassOptions<Ctx = ListrContext, Renderer extends ListrRendererValue = ListrDefaultRendererValue> = ListrOptions<Ctx> & Omit<ListrDefaultRendererOptions<Renderer>, 'renderer'>; | ||
export interface ListrOptions<Ctx = ListrContext> { | ||
concurrent?: boolean | number; | ||
exitOnError?: boolean; | ||
renderer?: ListrRendererValue<Ctx>; | ||
nonTTYRenderer?: ListrRendererValue<Ctx>; | ||
showSubtasks?: boolean; | ||
collapse?: boolean; | ||
collapseSkips?: boolean; | ||
clearOutput?: boolean; | ||
ctx?: Ctx; | ||
registerSignalListeners?: boolean; | ||
} | ||
export declare type CreateClass<T> = new (...args: any[]) => T; | ||
export declare type ListrGetRendererClassFromValue<T extends ListrRendererValue> = T extends 'default' ? typeof DefaultRenderer : T extends 'verbose' ? typeof VerboseRenderer : T extends 'silent' ? typeof SilentRenderer : T extends ListrRendererFactory ? T : never; | ||
export declare type ListrGetRendererValueFromClass<T extends ListrRendererFactory> = T extends DefaultRenderer ? 'default' : T extends VerboseRenderer ? 'verbose' : T extends SilentRenderer ? 'silent' : T extends ListrRendererFactory ? T : never; | ||
export declare type ListrGetRendererOptions<T extends ListrRendererValue> = T extends 'default' ? typeof DefaultRenderer['rendererOptions'] : T extends 'verbose' ? typeof VerboseRenderer['rendererOptions'] : T extends 'silent' ? typeof SilentRenderer['rendererOptions'] : T extends ListrRendererFactory ? T['rendererOptions'] : never; | ||
export declare type ListrGetRendererTaskOptions<T extends ListrRendererValue> = T extends 'default' ? typeof DefaultRenderer['rendererTaskOptions'] : T extends 'verbose' ? typeof VerboseRenderer['rendererTaskOptions'] : T extends 'silent' ? typeof SilentRenderer['rendererTaskOptions'] : T extends ListrRendererFactory ? T['rendererTaskOptions'] : never; | ||
export interface ListrDefaultRendererOptions<T extends ListrRendererValue> { | ||
renderer?: T; | ||
rendererOptions?: ListrGetRendererOptions<T>; | ||
} | ||
export interface ListrDefaultNonTTYRendererOptions<T extends ListrRendererValue> { | ||
nonTTYRenderer?: T; | ||
nonTTYRendererOptions?: ListrGetRendererOptions<T>; | ||
} | ||
export declare type ListrRendererOptions<Renderer extends ListrRendererValue, FallbackRenderer extends ListrRendererValue> = ListrDefaultRendererOptions<Renderer> & ListrDefaultNonTTYRendererOptions<FallbackRenderer>; | ||
export declare class ListrRenderer { | ||
static rendererOptions: Record<string, any>; | ||
static rendererTaskOptions: Record<string, any>; | ||
static nonTTY: boolean; | ||
constructor(tasks: readonly ListrTaskObject<any, ListrRendererFactory>[], options: typeof ListrRenderer.rendererOptions); | ||
render(): void; | ||
end(err?: Error): void; | ||
} | ||
export interface ListrRendererFactory { | ||
rendererOptions: Record<string, any>; | ||
rendererTaskOptions: Record<string, any>; | ||
nonTTY: boolean; | ||
new (tasks: readonly ListrTaskObject<any, ListrRendererFactory>[], options: typeof ListrRenderer.rendererOptions): ListrRenderer; | ||
} | ||
export declare type ListrRendererValue = 'silent' | 'default' | 'verbose' | ListrRendererFactory; | ||
export interface ListrEvent { | ||
@@ -77,9 +106,7 @@ type: ListrEventTypes; | ||
} | ||
export interface ListrRenderer { | ||
render(): void; | ||
end(err?: Error): void; | ||
} | ||
export declare class ListrError extends Error { | ||
errors?: ListrError[]; | ||
constructor(message: any); | ||
message: string; | ||
errors?: Error[]; | ||
context?: any; | ||
constructor(message: string, errors?: Error[], context?: any); | ||
} | ||
@@ -89,8 +116,3 @@ export declare class PromptError extends Error { | ||
} | ||
export interface ListrRendererClass<Ctx> { | ||
nonTTY: boolean; | ||
new (tasks: readonly ListrTaskObject<Ctx>[], options: ListrOptions<Ctx>): ListrRenderer; | ||
} | ||
export declare type ListrEventTypes = 'TITLE' | 'STATE' | 'ENABLED' | 'SUBTASK' | 'DATA'; | ||
export declare type StateConstants = stateConstants; | ||
export declare type ListrRendererValue<Ctx> = 'silent' | 'default' | 'verbose' | 'test' | ListrRendererClass<Ctx>; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
class ListrError extends Error { | ||
constructor(message) { | ||
constructor(message, errors, context) { | ||
super(message); | ||
this.message = message; | ||
this.errors = errors; | ||
this.context = context; | ||
this.name = 'ListrError'; | ||
@@ -7,0 +10,0 @@ } |
@@ -1,9 +0,9 @@ | ||
import { ListrError, ListrOptions, ListrTask, ListrTaskWrapper, StateConstants } from '../interfaces/listr.interface'; | ||
import { ListrError, ListrRendererFactory, ListrSubClassOptions, ListrTask, ListrTaskWrapper, StateConstants } from '../interfaces/listr.interface'; | ||
import { Task } from './task'; | ||
import { Listr } from '../index'; | ||
import { PromptOptionsType, PromptTypes } from '../utils/prompt.interface'; | ||
export declare class TaskWrapper<Ctx> implements ListrTaskWrapper { | ||
task: Task<Ctx>; | ||
export declare class TaskWrapper<Ctx, Renderer extends ListrRendererFactory> implements ListrTaskWrapper<Ctx, Renderer> { | ||
task: Task<Ctx, ListrRendererFactory>; | ||
errors: ListrError[]; | ||
constructor(task: Task<Ctx>, errors: ListrError[]); | ||
constructor(task: Task<Ctx, ListrRendererFactory>, errors: ListrError[]); | ||
set title(title: string); | ||
@@ -14,3 +14,3 @@ get title(): string; | ||
set state(data: StateConstants); | ||
newListr<Ctx>(task: ListrTask<Ctx>[], options?: ListrOptions): Listr<Ctx>; | ||
newListr(task: ListrTask<Ctx, Renderer> | ListrTask<Ctx, Renderer>[], options?: ListrSubClassOptions<Ctx, Renderer>): Listr<Ctx, any, any>; | ||
report(error: Error | ListrError): void; | ||
@@ -17,0 +17,0 @@ skip(message: string): void; |
import { Subject } from 'rxjs'; | ||
import { ListrContext, ListrEvent, ListrOptions, ListrTask, ListrTaskObject, ListrTaskWrapper, PromptError, StateConstants } from '../interfaces/listr.interface'; | ||
import { ListrRendererFactory, ListrGetRendererTaskOptions, ListrContext, ListrEvent, ListrOptions, ListrTask, ListrTaskObject, ListrTaskWrapper, PromptError, ListrGetRendererOptions, StateConstants } from '../interfaces/listr.interface'; | ||
import { Listr } from '../index'; | ||
export declare class Task<Ctx> extends Subject<ListrEvent> implements ListrTaskObject<ListrContext> { | ||
listr: Listr<Ctx>; | ||
tasks: ListrTask; | ||
injectedOptions: ListrOptions; | ||
id: ListrTaskObject<Ctx>['id']; | ||
title: ListrTaskObject<Ctx>['title']; | ||
task: ListrTaskObject<Ctx>['task']; | ||
skip: ListrTaskObject<Ctx>['skip']; | ||
subtasks: ListrTaskObject<Ctx>['subtasks']; | ||
state: ListrTaskObject<Ctx>['state']; | ||
output: ListrTaskObject<Ctx>['output']; | ||
export declare class Task<Ctx, Renderer extends ListrRendererFactory> extends Subject<ListrEvent> implements ListrTaskObject<ListrContext, Renderer> { | ||
listr: Listr<Ctx, any, any>; | ||
tasks: ListrTask<Ctx, any>; | ||
options: ListrOptions; | ||
rendererOptions: ListrGetRendererOptions<Renderer>; | ||
id: ListrTaskObject<Ctx, Renderer>['id']; | ||
title: ListrTaskObject<Ctx, Renderer>['title']; | ||
task: ListrTaskObject<Ctx, Renderer>['task']; | ||
skip: ListrTaskObject<Ctx, Renderer>['skip']; | ||
subtasks: ListrTaskObject<Ctx, any>['subtasks']; | ||
state: ListrTaskObject<Ctx, Renderer>['state']; | ||
output: ListrTaskObject<Ctx, Renderer>['output']; | ||
prompt: boolean | PromptError; | ||
options: ListrTaskObject<Ctx>['options']; | ||
exitOnError: boolean; | ||
rendererTaskOptions: ListrGetRendererTaskOptions<Renderer>; | ||
private enabled; | ||
private enabledFn; | ||
constructor(listr: Listr<Ctx>, tasks: ListrTask, injectedOptions: ListrOptions); | ||
constructor(listr: Listr<Ctx, any, any>, tasks: ListrTask<Ctx, any>, options: ListrOptions, rendererOptions: ListrGetRendererOptions<Renderer>); | ||
set state$(state: StateConstants); | ||
@@ -29,7 +30,5 @@ check(ctx: Ctx): Promise<void>; | ||
isEnabled(): boolean; | ||
isBottomBar(): boolean; | ||
haspersistentOutput(): boolean; | ||
hasTitle(): boolean; | ||
isPrompt(): boolean; | ||
run(context: Ctx, wrapper: ListrTaskWrapper<Ctx>): Promise<void>; | ||
run(context: Ctx, wrapper: ListrTaskWrapper<Ctx, Renderer>): Promise<void>; | ||
} |
@@ -15,15 +15,15 @@ "use strict"; | ||
class Task extends rxjs_1.Subject { | ||
constructor(listr, tasks, injectedOptions) { | ||
var _a, _b, _c, _d, _e, _f; | ||
constructor(listr, tasks, options, rendererOptions) { | ||
var _a, _b, _c; | ||
super(); | ||
this.listr = listr; | ||
this.tasks = tasks; | ||
this.injectedOptions = injectedOptions; | ||
this.options = options; | ||
this.rendererOptions = rendererOptions; | ||
this.id = uuid_1.v4(); | ||
this.title = (_a = this.tasks) === null || _a === void 0 ? void 0 : _a.title; | ||
this.task = this.tasks.task; | ||
this.options = Object.assign({ persistentOutput: (_b = this.tasks) === null || _b === void 0 ? void 0 : _b.persistentOutput, bottomBar: (_c = this.tasks) === null || _c === void 0 ? void 0 : _c.bottomBar }, this.injectedOptions); | ||
this.skip = ((_d = this.tasks) === null || _d === void 0 ? void 0 : _d.skip) || (() => false); | ||
this.skip = ((_e = this.tasks) === null || _e === void 0 ? void 0 : _e.skip) || (() => false); | ||
this.enabledFn = ((_f = this.tasks) === null || _f === void 0 ? void 0 : _f.enabled) || (() => true); | ||
this.skip = ((_b = this.tasks) === null || _b === void 0 ? void 0 : _b.skip) || (() => false); | ||
this.enabledFn = ((_c = this.tasks) === null || _c === void 0 ? void 0 : _c.enabled) || (() => true); | ||
this.rendererTaskOptions = this.tasks.options; | ||
} | ||
@@ -70,12 +70,2 @@ set state$(state) { | ||
} | ||
isBottomBar() { | ||
if (typeof (this === null || this === void 0 ? void 0 : this.options.bottomBar) === 'number' || typeof this.options.bottomBar === 'boolean') { | ||
return true; | ||
} | ||
} | ||
haspersistentOutput() { | ||
if (this.options.persistentOutput === true) { | ||
return true; | ||
} | ||
} | ||
hasTitle() { | ||
@@ -95,4 +85,5 @@ return typeof (this === null || this === void 0 ? void 0 : this.title) === 'string'; | ||
if (result instanceof index_1.Listr) { | ||
result.options = Object.assign(this.options, result.options); | ||
result.rendererClass = renderer_1.getRenderer('silent'); | ||
result.options = { ...this.options, ...result.options }; | ||
const rendererClass = renderer_1.getRenderer('silent'); | ||
result.rendererClass = rendererClass.renderer; | ||
this.subtasks = result.tasks; | ||
@@ -128,3 +119,6 @@ this.next({ type: 'SUBTASK' }); | ||
this.state$ = state_constants_1.stateConstants.PENDING; | ||
const skipped = this.skip(context); | ||
let skipped; | ||
if (typeof this.skip === 'function') { | ||
skipped = await this.skip(context); | ||
} | ||
if (skipped) { | ||
@@ -131,0 +125,0 @@ if (typeof skipped === 'string') { |
@@ -1,12 +0,14 @@ | ||
import { ListrClass, ListrContext, ListrError, ListrOptions, ListrRendererClass, ListrTask } from './interfaces/listr.interface'; | ||
export declare class Listr<Ctx = ListrContext> implements ListrClass { | ||
task: ListrTask<Ctx> | ListrTask<Ctx>[]; | ||
options?: ListrOptions<Ctx>; | ||
tasks: ListrClass['tasks']; | ||
import { ListrGetRendererOptions, ListrGetRendererClassFromValue, ListrDefaultRendererValue, ListrFallbackRendererValue, ListrBaseClassOptions, ListrClass, ListrContext, ListrError, ListrRendererFactory, ListrRendererValue, ListrTask } from './interfaces/listr.interface'; | ||
import { Task } from './lib/task'; | ||
export declare class Listr<Ctx = ListrContext, Renderer extends ListrRendererValue = ListrDefaultRendererValue, FallbackRenderer extends ListrRendererValue = ListrFallbackRendererValue> implements ListrClass<Ctx, Renderer, FallbackRenderer> { | ||
task: ListrTask<Ctx, ListrGetRendererClassFromValue<Renderer>> | ListrTask<Ctx, ListrGetRendererClassFromValue<Renderer>>[]; | ||
options?: ListrBaseClassOptions<Ctx, Renderer, FallbackRenderer>; | ||
tasks: Task<Ctx, ListrGetRendererClassFromValue<Renderer>>[]; | ||
err: ListrError[]; | ||
rendererClass: ListrRendererClass<Ctx>; | ||
rendererClass: ListrRendererFactory; | ||
rendererClassOptions: ListrGetRendererOptions<ListrRendererFactory>; | ||
private concurrency; | ||
private renderer; | ||
constructor(task: ListrTask<Ctx> | ListrTask<Ctx>[], options?: ListrOptions<Ctx>); | ||
add(task: ListrTask | ListrTask[]): void; | ||
constructor(task: ListrTask<Ctx, ListrGetRendererClassFromValue<Renderer>> | ListrTask<Ctx, ListrGetRendererClassFromValue<Renderer>>[], options?: ListrBaseClassOptions<Ctx, Renderer, FallbackRenderer>); | ||
add(task: ListrTask<Ctx, ListrGetRendererClassFromValue<Renderer>> | ListrTask<Ctx, ListrGetRendererClassFromValue<Renderer>>[]): void; | ||
run(context?: Ctx): Promise<Ctx>; | ||
@@ -13,0 +15,0 @@ private checkAll; |
@@ -19,3 +19,2 @@ "use strict"; | ||
this.options = Object.assign({ | ||
showSubtasks: true, | ||
concurrent: false, | ||
@@ -25,5 +24,3 @@ renderer: 'default', | ||
exitOnError: true, | ||
collapse: true, | ||
collapseSkips: true, | ||
clearOutput: false | ||
registerSignalListeners: true | ||
}, options); | ||
@@ -37,13 +34,22 @@ this.concurrency = 1; | ||
} | ||
this.rendererClass = renderer_1.getRenderer(this.options.renderer, this.options.nonTTYRenderer); | ||
const renderer = renderer_1.getRenderer(this.options.renderer, this.options.nonTTYRenderer); | ||
this.rendererClass = renderer.renderer; | ||
if (!renderer.nonTTY) { | ||
this.rendererClassOptions = this.options.rendererOptions; | ||
} | ||
else { | ||
this.rendererClassOptions = this.options.nonTTYRendererOptions; | ||
} | ||
this.add(task || []); | ||
process.on('SIGINT', async () => { | ||
await Promise.all(this.tasks.map(async (task) => { | ||
if (task.isPending()) { | ||
task.state$ = state_constants_1.stateConstants.FAILED; | ||
} | ||
})); | ||
this.renderer.end(new Error('Interrupted.')); | ||
process.exit(127); | ||
}).setMaxListeners(0); | ||
if (this.options.registerSignalListeners === true) { | ||
process.on('SIGINT', async () => { | ||
await Promise.all(this.tasks.map(async (task) => { | ||
if (task.isPending()) { | ||
task.state$ = state_constants_1.stateConstants.FAILED; | ||
} | ||
})); | ||
this.renderer.end(new Error('Interrupted.')); | ||
process.exit(127); | ||
}).setMaxListeners(0); | ||
} | ||
} | ||
@@ -53,3 +59,3 @@ add(task) { | ||
tasks.forEach((task) => { | ||
this.tasks.push(new task_1.Task(this, task, this.options)); | ||
this.tasks.push(new task_1.Task(this, task, this.options, { ...this.rendererClassOptions, ...task.options })); | ||
}); | ||
@@ -60,3 +66,3 @@ } | ||
if (!this.renderer) { | ||
this.renderer = new this.rendererClass(this.tasks, this.options); | ||
this.renderer = new this.rendererClass(this.tasks, this.rendererClassOptions); | ||
} | ||
@@ -72,16 +78,16 @@ this.renderer.render(); | ||
}, { concurrency: this.concurrency }); | ||
if (errors.length > 0) { | ||
const err = new listr_interface_1.ListrError('Something went wrong'); | ||
err.errors = errors; | ||
throw err; | ||
} | ||
this.renderer.end(); | ||
} | ||
catch (error) { | ||
error.context = context; | ||
this.renderer.end(error); | ||
this.err.push(new listr_interface_1.ListrError(error, [error], context)); | ||
if (this.options.exitOnError !== false) { | ||
this.renderer.end(error); | ||
throw error; | ||
} | ||
} | ||
finally { | ||
if (errors.length > 0) { | ||
this.err.push(new listr_interface_1.ListrError('Task failed without crashing.', errors, context)); | ||
} | ||
} | ||
return context; | ||
@@ -88,0 +94,0 @@ } |
@@ -0,14 +1,16 @@ | ||
import { ListrGetRendererClassFromValue } from './interfaces/listr.interface'; | ||
import { Listr } from './listr'; | ||
import { ListrContext, ListrOptions, ListrTask } from './interfaces/listr.interface'; | ||
export declare class Manager<InjectCtx = ListrContext> { | ||
options: ListrOptions<InjectCtx>; | ||
import { ListrContext, ListrOptions, ListrTask, ListrBaseClassOptions, ListrRendererValue, ListrSubClassOptions, ListrError } from './interfaces/listr.interface'; | ||
export declare class Manager<InjectCtx = ListrContext, Renderer extends ListrRendererValue = 'default', FallbackRenderer extends ListrRendererValue = 'verbose'> { | ||
options?: ListrBaseClassOptions<InjectCtx, Renderer, FallbackRenderer>; | ||
err: ListrError[]; | ||
private tasks; | ||
constructor(options?: ListrOptions<InjectCtx>); | ||
constructor(options?: ListrBaseClassOptions<InjectCtx, Renderer, FallbackRenderer>); | ||
set ctx(ctx: InjectCtx); | ||
add<Ctx = InjectCtx>(tasks: ListrTask<Ctx>[] | ((ctx?: Ctx) => ListrTask<Ctx>[]), options?: ListrOptions<Ctx>): void; | ||
runAll<Ctx = InjectCtx>(options?: ListrOptions<Ctx>): Promise<Ctx>; | ||
newListr<Ctx = InjectCtx>(tasks: ListrTask<Ctx>[], options?: ListrOptions<Ctx>): Listr<Ctx>; | ||
indent<Ctx = InjectCtx>(tasks: ListrTask<Ctx>[] | ((ctx?: Ctx) => ListrTask<Ctx>[]), options?: ListrOptions<Ctx>, taskOptions?: Omit<ListrTask, 'task'>): ListrTask<Ctx>; | ||
run<Ctx = InjectCtx>(tasks: ListrTask<Ctx>[], options?: ListrOptions<Ctx>): Promise<Ctx>; | ||
add<Ctx = InjectCtx>(tasks: ListrTask<Ctx, ListrGetRendererClassFromValue<Renderer>>[] | ((ctx?: Ctx) => ListrTask<Ctx, ListrGetRendererClassFromValue<Renderer>>[]), options?: ListrSubClassOptions<Ctx, Renderer>): void; | ||
runAll(options?: ListrBaseClassOptions<InjectCtx, Renderer, FallbackRenderer>): Promise<InjectCtx>; | ||
newListr<Ctx = InjectCtx>(tasks: ListrTask<Ctx, ListrGetRendererClassFromValue<Renderer>>[], options?: ListrBaseClassOptions<Ctx>): Listr<Ctx, any>; | ||
indent<Ctx = InjectCtx>(tasks: ListrTask<Ctx, ListrGetRendererClassFromValue<Renderer>>[] | ((ctx?: Ctx) => ListrTask<Ctx, ListrGetRendererClassFromValue<Renderer>>[]), options?: ListrOptions<Ctx>, taskOptions?: Omit<ListrTask<Ctx, ListrGetRendererClassFromValue<Renderer>>, 'task'>): ListrTask<Ctx, ListrGetRendererClassFromValue<Renderer>>; | ||
run<Ctx = InjectCtx>(tasks: ListrTask<Ctx, any>[], options?: ListrBaseClassOptions<Ctx, any, any>): Promise<Ctx>; | ||
getRuntime(pipetime: number): string; | ||
} |
@@ -6,4 +6,5 @@ "use strict"; | ||
constructor(options) { | ||
this.options = options; | ||
this.err = []; | ||
this.tasks = []; | ||
this.options = Object.assign({ showSubtasks: true, collapse: false }, options); | ||
} | ||
@@ -43,5 +44,9 @@ set ctx(ctx) { | ||
} | ||
run(tasks, options) { | ||
async run(tasks, options) { | ||
options = { ...this.options, ...options }; | ||
return this.newListr(tasks, options).run(); | ||
const task = this.newListr(tasks, options); | ||
const ctx = await task.run(); | ||
this.err = []; | ||
this.err = [...this.err, ...task.err]; | ||
return ctx; | ||
} | ||
@@ -48,0 +53,0 @@ getRuntime(pipetime) { |
@@ -1,11 +0,24 @@ | ||
import { ListrOptions, ListrRenderer, ListrTaskObject } from '../interfaces/listr.interface'; | ||
export declare class MultiLineRenderer implements ListrRenderer { | ||
tasks: ListrTaskObject<any>[]; | ||
options: ListrOptions; | ||
import { ListrRenderer, ListrTaskObject } from '../interfaces/listr.interface'; | ||
export declare class DefaultRenderer implements ListrRenderer { | ||
tasks: ListrTaskObject<any, typeof DefaultRenderer>[]; | ||
options: typeof DefaultRenderer['rendererOptions']; | ||
static nonTTY: boolean; | ||
static rendererOptions: { | ||
indentation?: number; | ||
clearOutput?: boolean; | ||
showSubtasks?: boolean; | ||
collapse?: boolean; | ||
collapseSkips?: boolean; | ||
}; | ||
static rendererTaskOptions: { | ||
bottomBar?: boolean | number; | ||
persistentOutput?: boolean; | ||
}; | ||
private id?; | ||
private indentation; | ||
private bottomBar; | ||
private promptBar; | ||
constructor(tasks: ListrTaskObject<any>[], options: ListrOptions); | ||
constructor(tasks: ListrTaskObject<any, typeof DefaultRenderer>[], options: typeof DefaultRenderer['rendererOptions']); | ||
getTaskOptions(task: ListrTaskObject<any, typeof DefaultRenderer>): typeof DefaultRenderer['rendererTaskOptions']; | ||
isBottomBar(task: ListrTaskObject<any, typeof DefaultRenderer>): boolean; | ||
hasPersistentOutput(task: ListrTaskObject<any, typeof DefaultRenderer>): boolean; | ||
render(): void; | ||
@@ -12,0 +25,0 @@ end(): void; |
@@ -8,2 +8,3 @@ "use strict"; | ||
const cli_cursor_1 = __importDefault(require("cli-cursor")); | ||
const cli_truncate_1 = __importDefault(require("cli-truncate")); | ||
const elegant_spinner_1 = __importDefault(require("elegant-spinner")); | ||
@@ -13,9 +14,20 @@ const figures_1 = __importDefault(require("figures")); | ||
const log_update_1 = __importDefault(require("log-update")); | ||
class MultiLineRenderer { | ||
class DefaultRenderer { | ||
constructor(tasks, options) { | ||
this.tasks = tasks; | ||
this.options = options; | ||
this.indentation = 2; | ||
this.bottomBar = {}; | ||
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; | ||
} | ||
render() { | ||
@@ -49,3 +61,3 @@ if (this.id) { | ||
if (task.hasTitle()) { | ||
if (task.isSkipped() && (task === null || task === void 0 ? void 0 : task.options.collapseSkips)) { | ||
if (task.isSkipped() && this.options.collapseSkips) { | ||
task.title = !task.isSkipped() ? `${task === null || task === void 0 ? void 0 : task.title}` : `${task === null || task === void 0 ? void 0 : task.output} ${chalk_1.default.dim('[SKIPPED]')}`; | ||
@@ -64,3 +76,3 @@ } | ||
} | ||
else if (task.isBottomBar() || !task.hasTitle()) { | ||
else if (this.isBottomBar(task) || !task.hasTitle()) { | ||
const data = this.dumpData(task, -1); | ||
@@ -70,3 +82,9 @@ if (!this.bottomBar[task.id]) { | ||
this.bottomBar[task.id].data = []; | ||
this.bottomBar[task.id].items = typeof task.options.bottomBar === 'boolean' ? 1 : task.options.bottomBar; | ||
const bottomBar = this.getTaskOptions(task).bottomBar; | ||
if (typeof bottomBar === 'boolean') { | ||
this.bottomBar[task.id].items = 1; | ||
} | ||
else { | ||
this.bottomBar[task.id].items = bottomBar; | ||
} | ||
} | ||
@@ -77,6 +95,6 @@ if (!(data === null || data === void 0 ? void 0 : data.some((element) => this.bottomBar[task.id].data.includes(element)))) { | ||
} | ||
else if (task.isPending() || task.haspersistentOutput()) { | ||
else if (task.isPending() || this.hasPersistentOutput(task)) { | ||
output = [...output, ...this.dumpData(task, level)]; | ||
} | ||
else if (task.isSkipped() && task.options.collapseSkips === false) { | ||
else if (task.isSkipped() && this.options.collapseSkips === false) { | ||
output = [...output, ...this.dumpData(task, level)]; | ||
@@ -87,6 +105,6 @@ } | ||
|| task.isCompleted() && !task.hasTitle() | ||
|| task.isCompleted() && task.options.collapse === false && task.hasSubtasks() && !task.subtasks.some((subtask) => subtask.options.collapse === true) | ||
|| task.isCompleted() && task.hasSubtasks() && task.subtasks.some((subtask) => subtask.options.collapse === false) | ||
|| task.isCompleted() && this.options.collapse === false && task.hasSubtasks() && !task.subtasks.some((subtask) => subtask.rendererOptions.collapse === true) | ||
|| task.isCompleted() && task.hasSubtasks() && task.subtasks.some((subtask) => subtask.rendererOptions.collapse === false) | ||
|| task.isCompleted() && task.hasSubtasks() && task.subtasks.some((subtask) => subtask.hasFailed())) | ||
&& task.options.showSubtasks !== false && task.hasSubtasks()) { | ||
&& this.options.showSubtasks !== false && task.hasSubtasks()) { | ||
const subtaskLevel = !task.hasTitle() ? level : level + 1; | ||
@@ -100,3 +118,3 @@ const subtaskRender = this.multiLineRenderer(task.subtasks, subtaskLevel); | ||
this.promptBar = null; | ||
if (task.hasFailed() || (!task.hasTitle() || task.isBottomBar()) && task.haspersistentOutput() !== true) { | ||
if (task.hasFailed() || (!task.hasTitle() || this.isBottomBar(task)) && this.hasPersistentOutput(task) !== true) { | ||
delete this.bottomBar[task.id]; | ||
@@ -107,3 +125,8 @@ } | ||
} | ||
return output.join('\n'); | ||
if (output.length > 0) { | ||
return output.join('\n'); | ||
} | ||
else { | ||
return; | ||
} | ||
} | ||
@@ -116,3 +139,5 @@ renderBottomBar() { | ||
} | ||
o[key].data = this.bottomBar[key].data.slice(-this.bottomBar[key].items); | ||
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; | ||
@@ -140,3 +165,4 @@ }, {}); | ||
formatString(string, icon, level) { | ||
return `${indent_string_1.default(`${icon} ${string}`, level * this.indentation)}`; | ||
var _a; | ||
return `${cli_truncate_1.default(indent_string_1.default(`${icon} ${string}`, level * this.options.indentation), (_a = process.stdout.columns) !== null && _a !== void 0 ? _a : Infinity)}`; | ||
} | ||
@@ -148,3 +174,3 @@ getSymbol(task, data = false) { | ||
if (task.isPending() && !data) { | ||
return task.options.showSubtasks !== false && task.hasSubtasks() ? chalk_1.default.yellow(figures_1.default.pointer) : chalk_1.default.yellowBright(task.spinner()); | ||
return this.options.showSubtasks !== false && task.hasSubtasks() ? chalk_1.default.yellow(figures_1.default.pointer) : chalk_1.default.yellowBright(task.spinner()); | ||
} | ||
@@ -160,6 +186,6 @@ if (task.isCompleted() && !data) { | ||
} | ||
if (task.isSkipped() && !data && task.options.collapseSkips === false) { | ||
if (task.isSkipped() && !data && this.options.collapseSkips === false) { | ||
return chalk_1.default.yellow(figures_1.default.warning); | ||
} | ||
else if (task.isSkipped() && (data || task.options.collapseSkips)) { | ||
else if (task.isSkipped() && (data || this.options.collapseSkips)) { | ||
return chalk_1.default.yellow(figures_1.default.arrowDown); | ||
@@ -178,4 +204,11 @@ } | ||
} | ||
exports.MultiLineRenderer = MultiLineRenderer; | ||
MultiLineRenderer.nonTTY = false; | ||
exports.DefaultRenderer = DefaultRenderer; | ||
DefaultRenderer.nonTTY = false; | ||
DefaultRenderer.rendererOptions = { | ||
indentation: 2, | ||
clearOutput: false, | ||
showSubtasks: true, | ||
collapse: true, | ||
collapseSkips: true | ||
}; | ||
//# sourceMappingURL=default.renderer.js.map |
@@ -1,9 +0,11 @@ | ||
import { ListrOptions, ListrRenderer, ListrTaskObject } from '../interfaces/listr.interface'; | ||
import { ListrRenderer, ListrTaskObject } from '../interfaces/listr.interface'; | ||
export declare class SilentRenderer implements ListrRenderer { | ||
tasks: ListrTaskObject<any>[]; | ||
options: ListrOptions; | ||
tasks: ListrTaskObject<any, typeof SilentRenderer>[]; | ||
options: typeof SilentRenderer['rendererOptions']; | ||
static nonTTY: boolean; | ||
constructor(tasks: ListrTaskObject<any>[], options: ListrOptions); | ||
static rendererOptions: never; | ||
static rendererTaskOptions: never; | ||
constructor(tasks: ListrTaskObject<any, typeof SilentRenderer>[], options: typeof SilentRenderer['rendererOptions']); | ||
render(): void; | ||
end(): void; | ||
} |
@@ -1,8 +0,14 @@ | ||
import { ListrOptions, ListrRenderer, ListrTaskObject } from '../interfaces/listr.interface'; | ||
import { ListrRenderer, ListrTaskObject } from '../interfaces/listr.interface'; | ||
import { Logger } from '../utils/logger'; | ||
export declare class VerboseRenderer implements ListrRenderer { | ||
tasks: ListrTaskObject<any>[]; | ||
options: ListrOptions; | ||
tasks: ListrTaskObject<any, typeof VerboseRenderer>[]; | ||
options: typeof VerboseRenderer['rendererOptions']; | ||
static nonTTY: boolean; | ||
static rendererOptions: { | ||
useIcons?: boolean; | ||
logger?: new (...args: any) => Logger; | ||
}; | ||
static rendererTaskOptions: never; | ||
private logger; | ||
constructor(tasks: ListrTaskObject<any>[], options: ListrOptions); | ||
constructor(tasks: ListrTaskObject<any, typeof VerboseRenderer>[], options: typeof VerboseRenderer['rendererOptions']); | ||
render(): void; | ||
@@ -9,0 +15,0 @@ end(): void; |
@@ -6,5 +6,11 @@ "use strict"; | ||
constructor(tasks, options) { | ||
var _a, _b; | ||
this.tasks = tasks; | ||
this.options = options; | ||
this.logger = new logger_1.Logger(); | ||
if (!((_a = this.options) === null || _a === void 0 ? void 0 : _a.logger)) { | ||
this.logger = new logger_1.Logger({ useIcons: (_b = this.options) === null || _b === void 0 ? void 0 : _b.useIcons }); | ||
} | ||
else { | ||
this.logger = new this.options.logger(); | ||
} | ||
} | ||
@@ -42,2 +48,5 @@ render() { | ||
} | ||
else if (event.type === 'TITLE') { | ||
this.logger.title(String(event.data)); | ||
} | ||
} | ||
@@ -44,0 +53,0 @@ }, (err) => { |
export interface LoggerOptions { | ||
direct: boolean; | ||
useIcons: boolean; | ||
} | ||
@@ -4,0 +4,0 @@ export declare class Logger { |
@@ -38,3 +38,9 @@ "use strict"; | ||
parseMessage(level, message) { | ||
let multiLineMessage = message.split('\n'); | ||
let multiLineMessage; | ||
try { | ||
multiLineMessage = message.split('\n'); | ||
} | ||
catch { | ||
multiLineMessage = [message]; | ||
} | ||
multiLineMessage = multiLineMessage.map((msg) => { | ||
@@ -56,3 +62,3 @@ return this.logColoring({ | ||
case logger_constants_1.logLevels.fail: | ||
if (!((_a = this.options) === null || _a === void 0 ? void 0 : _a.direct)) { | ||
if ((_a = this.options) === null || _a === void 0 ? void 0 : _a.useIcons) { | ||
coloring = chalk_1.default.red; | ||
@@ -66,3 +72,3 @@ icon = figures_1.default.cross; | ||
case logger_constants_1.logLevels.skip: | ||
if (!((_b = this.options) === null || _b === void 0 ? void 0 : _b.direct)) { | ||
if ((_b = this.options) === null || _b === void 0 ? void 0 : _b.useIcons) { | ||
coloring = chalk_1.default.yellow; | ||
@@ -76,3 +82,3 @@ icon = figures_1.default.arrowDown; | ||
case logger_constants_1.logLevels.success: | ||
if (!((_c = this.options) === null || _c === void 0 ? void 0 : _c.direct)) { | ||
if ((_c = this.options) === null || _c === void 0 ? void 0 : _c.useIcons) { | ||
coloring = chalk_1.default.green; | ||
@@ -86,3 +92,3 @@ icon = figures_1.default.tick; | ||
case logger_constants_1.logLevels.data: | ||
if (!((_d = this.options) === null || _d === void 0 ? void 0 : _d.direct)) { | ||
if ((_d = this.options) === null || _d === void 0 ? void 0 : _d.useIcons) { | ||
icon = figures_1.default.arrowRight; | ||
@@ -95,3 +101,3 @@ } | ||
case logger_constants_1.logLevels.start: | ||
if (!((_e = this.options) === null || _e === void 0 ? void 0 : _e.direct)) { | ||
if ((_e = this.options) === null || _e === void 0 ? void 0 : _e.useIcons) { | ||
icon = figures_1.default.pointer; | ||
@@ -104,3 +110,3 @@ } | ||
case logger_constants_1.logLevels.title: | ||
if (!((_f = this.options) === null || _f === void 0 ? void 0 : _f.direct)) { | ||
if ((_f = this.options) === null || _f === void 0 ? void 0 : _f.useIcons) { | ||
icon = figures_1.default.checkboxOn; | ||
@@ -107,0 +113,0 @@ } |
@@ -1,2 +0,3 @@ | ||
import { ListrContext, ListrRendererClass, ListrRendererValue } from '../interfaces/listr.interface'; | ||
export declare function getRenderer(renderer: ListrRendererValue<ListrContext>, fallbackRenderer?: ListrRendererValue<ListrContext>): ListrRendererClass<ListrContext>; | ||
import { SupportedRenderer } from './renderer.interface'; | ||
import { ListrRendererValue } from '../interfaces/listr.interface'; | ||
export declare function getRenderer(renderer: ListrRendererValue, fallbackRenderer?: ListrRendererValue): SupportedRenderer; |
@@ -5,9 +5,7 @@ "use strict"; | ||
const silent_renderer_1 = require("../renderer/silent.renderer"); | ||
const test_renderer_1 = require("../renderer/test.renderer"); | ||
const verbose_renderer_1 = require("../renderer/verbose.renderer"); | ||
const renderers = { | ||
default: default_renderer_1.MultiLineRenderer, | ||
default: default_renderer_1.DefaultRenderer, | ||
verbose: verbose_renderer_1.VerboseRenderer, | ||
silent: silent_renderer_1.SilentRenderer, | ||
test: test_renderer_1.TestRenderer | ||
silent: silent_renderer_1.SilentRenderer | ||
}; | ||
@@ -24,9 +22,12 @@ function isRendererSupported(renderer) { | ||
function getRenderer(renderer, fallbackRenderer) { | ||
let returnValue; | ||
let ret = getRendererClass(renderer); | ||
returnValue = { renderer: ret, nonTTY: false }; | ||
if (!isRendererSupported(ret)) { | ||
ret = getRendererClass(fallbackRenderer); | ||
returnValue = { renderer: ret, nonTTY: true }; | ||
} | ||
return ret; | ||
return returnValue; | ||
} | ||
exports.getRenderer = getRenderer; | ||
//# sourceMappingURL=renderer.js.map |
{ | ||
"name": "listr2", | ||
"version": "1.3.12", | ||
"version": "2.0.0", | ||
"description": "Terminal task list reborn!", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
1009
README.md
@@ -7,6 +7,10 @@ Listr2 | ||
[![Downloads/week](https://img.shields.io/npm/dw/listr2.svg)](https://npmjs.org/package/listr2) | ||
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) | ||
This is the expanded and re-written in Typescript version of the beautiful plugin by [Sam Verschueren](https://github.com/SamVerschueren) called [Listr](https://github.com/SamVerschueren/listr). Fully backwards compatible with the Listr itself but with more features. | ||
**Create beautiful CLI interfaces via easy and logical to implement task lists that feel alive and interactive.** | ||
This is the expanded and re-written in Typescript version of the beautiful plugin by [Sam Verschueren](https://github.com/SamVerschueren) called [Listr](https://github.com/SamVerschueren/listr). | ||
![Demo](./demo/demo.gif) | ||
> **It breaks backward compatibility with [Listr](https://github.com/SamVerschueren/listr) after v1.3.12, albeit refactoring requires only moving renderer options to their own key, concerning the [conversation on the original repository](https://github.com/SamVerschueren/listr/issues/143#issuecomment-623094930).** You can find the README of compatible version [here](https://github.com/cenk1cenk2/listr2/tree/84ff9c70ba4aab16106d1e7114453ac5e0351ec0). Keep in mind that it will not get further bug fixes. | ||
@@ -17,16 +21,33 @@ * [Changelog](./CHANGELOG.md) | ||
- [Extra Features](#extra-features) | ||
- [Install](#install) | ||
- [Create A New Listr](#create-a-new-listr) | ||
- [Tasks](#tasks) | ||
- [Options](#options) | ||
- [The Concept of Context](#the-concept-of-context) | ||
- [General Usage](#general-usage) | ||
- [Subtasks](#subtasks) | ||
- [Get User Input](#get-user-input) | ||
- [Create A Prompt](#create-a-prompt) | ||
- [Use an Custom Prompt](#use-an-custom-prompt) | ||
- [Use Enquirer in Your Project Without Explicitly Installing It](#use-enquirer-in-your-project-without-explicitly-installing-it) | ||
- [Enable a Task](#enable-a-task) | ||
- [Skip a Task](#skip-a-task) | ||
- [Show Output](#show-output) | ||
- [Utilizing the Task Itself](#utilizing-the-task-itself) | ||
- [Utilizing the Bottom Bar](#utilizing-the-bottom-bar) | ||
- [Utilizing an Observable or Stream](#utilizing-an-observable-or-stream) | ||
- [Throw Errors](#throw-errors) | ||
- [Task Manager](#task-manager) | ||
- [Create A New Task Manager](#create-a-new-task-manager) | ||
- [Add Tasks](#add-tasks) | ||
- [Run Tasks that are in Queue](#run-tasks-that-are-in-queue) | ||
- [Indent A Task](#indent-a-task) | ||
- [Listr Wrapper](#listr-wrapper) | ||
- [Input Module](#input-module) | ||
- [Directly access enquirer without explicitly installing in your project](#directly-access-enquirer-without-explicitly-installing-in-your-project) | ||
- [Inject Context](#inject-context) | ||
- [Bottom Bar For More Information](#bottom-bar-for-more-information) | ||
- [Tasks without Titles](#tasks-without-titles) | ||
- [Multi-Line Renderer](#multi-line-renderer) | ||
- [Fully-Typed](#fully-typed) | ||
- [Basic Use-Case Scenerio](#basic-use-case-scenerio) | ||
- [More Functionality](#more-functionality) | ||
- [Generic Features](#generic-features) | ||
- [Tasks Without Titles](#tasks-without-titles) | ||
- [Signal Interrupt](#signal-interrupt) | ||
- [Testing](#testing) | ||
- [Default Renderers](#default-renderers) | ||
- [Custom Renderers](#custom-renderers) | ||
- [Log To A File](#log-to-a-file) | ||
- [Migration from Version <v1.3.12](#migration-from-version-v1312) | ||
- [Details](#details) | ||
- [Types](#types) | ||
@@ -37,223 +58,919 @@ <!-- tocstop --> | ||
Check out `example.ts` in the root of the repository for the code in demo or follow through with the readme. | ||
Check out `examples/` folder in the root of the repository for the code in demo or follow through with this README. | ||
## Install | ||
```bash | ||
# Install the latest supported version | ||
npm install listr2 | ||
yarn add listr2 | ||
# Install listr compatabile version | ||
npm install listr2@1.3.12 | ||
yarn add listr2@1.3.12 | ||
``` | ||
## Create A New Listr | ||
Create a new task list. It will returns a Listr class. | ||
```typescript | ||
import { Listr } from 'listr2' | ||
interface ListrCtx { | ||
injectedContext: boolean | ||
interface Ctx { | ||
/* some variables for internal use */ | ||
} | ||
const tasks = new Listr<ListrCtx>( | ||
[ | ||
const tasks = new Listr<Ctx>([ | ||
/* tasks */ | ||
], { /* options */ }) | ||
``` | ||
Then you can run this task lists as a async function and it will return the context that is used. | ||
```typescript | ||
try { | ||
await tasks.run() | ||
} catch (e) { | ||
// it will collect all the errors encountered if { exitOnError: false } is set as an option | ||
// elsewise it will throw the first error encountered as expected | ||
console.error(e) | ||
} | ||
``` | ||
### Tasks | ||
```typescript | ||
export interface ListrTask<Ctx, Renderer extends ListrRendererFactory> { | ||
// A title can be given or omitted. For default renderer if the title is omitted, | ||
title?: string | ||
// A task can be a sync or async function that returns a string, readable stream or an observable or plain old void | ||
// if it does actually return string, readable stream or an observable, task output will be refreshed with each data push | ||
task: (ctx: Ctx, task: ListrTaskWrapper<Ctx, Renderer>) => void | ListrTaskResult<Ctx> | ||
// to skip the task programmatically, skip can be a sync or async function that returns a boolean or string | ||
// if string is returned it will be showed as the skip message, else the task title will be used | ||
skip?: boolean | string | ((ctx: Ctx) => boolean | string | Promise<boolean> | Promise<string>) | ||
// to enable the task programmatically, this will show no messages comparing to skip and it will hide the tasks enabled depending on the context | ||
// enabled can be external boolean, a sync or async function that returns a boolean | ||
// pay in mind that context enabled functionallity might depend on other functions to finish first, therefore the list shall be treated as a async function | ||
enabled?: boolean | ((ctx: Ctx) => boolean | Promise<boolean>) | ||
// this will change depending on the available options on the renderer | ||
// these renderer options are per task basis and does not affect global options | ||
options?: ListrGetRendererTaskOptions<Renderer> | ||
} | ||
``` | ||
### Options | ||
```typescript | ||
export interface ListrOptions<Ctx = ListrContext> { | ||
// how many tasks can be run at the same time. | ||
// false or 1 for synhronous task list, true or Infinity for compelete parallel operation, a number for limitting tasks that can run at the same time | ||
// defaults to false | ||
concurrent?: boolean | number | ||
// it will silently fail or throw out an error | ||
// defaults to false | ||
exitOnError?: boolean | ||
// inject a context from another operation | ||
// defaults to any | ||
ctx?: Ctx | ||
// to have graceful exit on signal terminate and to inform the renderer all the tasks awaiting or processing are failed | ||
// defaults to true | ||
registerSignalListeners?: boolean | ||
// select the renderer or inject a class yourself | ||
// defaults to 'default' which is a updating renderer | ||
renderer?: 'default' | 'verbose' | 'silent' | ListrRendererFactory | ||
// renderer options depends on the selected renderer | ||
rendererOptions?: ListrGetRendererOptions<T> | ||
// renderer will fallback to the nonTTYRenderer on non-tty environments as the name suggest | ||
// defaults to verbose | ||
nonTTYRenderer?: 'default' | 'verbose' | 'silent' | ListrRendererFactory | ||
// options for the non-tty renderer | ||
nonTTYrendererOptions?: ListrGetRendererOptions<T> | ||
} | ||
``` | ||
## The Concept of Context | ||
Context is the variables that are shared across the task list. Even though external variables can be used to do the same operation, context gives a self-contained way to process internal tasks. | ||
A successful task will return the context back for further operation. | ||
You can also manually inject a context variable preset depending on the prior operations through the task options. | ||
**If all tasks are in one big Listr list you do not have to inject context manually to the child tasks since it is automatically injected as in the original.** | ||
If an outside variable wants to be injected inside the Listr itself it can be done in two ways. | ||
- Injecting it as an option. | ||
```typescript | ||
const ctx: Ctx = {} | ||
const tasks = new Listr<Ctx>([ | ||
/* tasks */ | ||
], { ctx }) | ||
``` | ||
- Injecting it at runtime. | ||
```typescript | ||
try { | ||
await tasks.run({ctx}) | ||
} catch (e) { | ||
console.error(e) | ||
} | ||
``` | ||
## General Usage | ||
### Subtasks | ||
Any task can return a new Listr. But rather than calling it as `new Listr` to get the full autocompeletion features depending on the parent task's selected renderer, it is a better idea to call it through the `Task` itself by `task.newListr()`. | ||
*Please refer to [examples section](examples/subtasks.example.ts) for more detailed and further examples.* | ||
```typescript | ||
new Listr<Ctx>([ | ||
{ | ||
// a title | ||
title: 'Hello I am a title', | ||
// an sync, async or observable function | ||
task: async (ctx, task): Promise<void> => {} | ||
title: 'This task will execute.', | ||
task: (ctx, task): Listr => task.newListr([ | ||
{ | ||
title: 'This is a subtask.', | ||
task: async (): Promise<void> => { | ||
await delay(3000) | ||
} | ||
} | ||
]) | ||
} | ||
], | ||
], { concurrent: false }) | ||
``` | ||
You can change indivudual settings of the renderer on per-subtask basis. | ||
This includes renderer options as well as Listr options like `exitOnError`, `concurrent` to be set on a per subtask basis independent of the parent task, while it will always use the most adjacent setting. | ||
```typescript | ||
new Listr<Ctx>([ | ||
{ | ||
title: 'This task will execute.', | ||
task: (ctx, task): Listr => task.newListr([ | ||
{ | ||
title: 'This is a subtask.', | ||
task: async (): Promise<void> => { | ||
await delay(3000) | ||
} | ||
}, | ||
{ | ||
title: 'This is an another subtask.', | ||
task: async (): Promise<void> => { | ||
await delay(2000) | ||
} | ||
} | ||
], { concurrent: true, rendererOptions: { collapse: true } }) | ||
}, | ||
{ | ||
title: 'This task will execute.', | ||
task: (ctx, task): Listr => task.newListr([ | ||
{ | ||
title: 'This is a subtask.', | ||
task: async (): Promise<void> => { | ||
await delay(3000) | ||
} | ||
}, | ||
{ | ||
title: 'This is an another subtask.', | ||
task: async (): Promise<void> => { | ||
await delay(2000) | ||
} | ||
} | ||
], { concurrent: true, rendererOptions: { collapse: false } }) | ||
} | ||
], { concurrent: false }) | ||
``` | ||
*Please refer to [Throw Errors Section](#Throw-Errors) for more detailed and further examples on how to handle silently failing errors.* | ||
### Get User Input | ||
The input module uses the beautiful [enquirer](https://www.npmjs.com/package/enquirer). | ||
So with running a `task.prompt` function, you can get access to any [enquirer](https://www.npmjs.com/package/enquirer) default prompts as well as using a custom enquirer prompt. | ||
To get an input you can assign the task a new prompt in an async function and write the response to the context. | ||
**It is not advisable to run prompts in a concurrent task because multiple prompts will clash and overwrite each other's console output and when you do keyboard movements it will apply to them both.** | ||
Prompts, since their rendering is getting passed as a data output will render multiple times in verbose renderer since verbose renderer is not terminal-updating intended to be used in nonTTY environments. It will work anyhow albeit it might not look great. | ||
Prompts can either have a title or not but they will always be rendered at the end of the current console while using the default renderer. | ||
*Please refer to [examples section](examples/get-user-input.example.ts) for more detailed and further examples.* | ||
#### Create A Prompt | ||
To access the prompts just utilize the `task.prompt` jumper function. The first argument takes in one of the default [enquirer](https://www.npmjs.com/package/enquirer) prompts as a string or you can also pass in a custom [enquirer](https://www.npmjs.com/package/enquirer) prompt class as well, while the second argument is the options for the given prompt. | ||
Prompts always rendered at the bottom of the tasks when using the default renderer with one line return in between it and the tasks. | ||
*Please note that I rewrote the types for enquirer, since some of them was failing for me. So it may have a chance of having some mistakes in it since I usually do not use all of them.* | ||
```typescript | ||
new Listr<Ctx>([ | ||
{ | ||
// throw in some options, all have a default | ||
concurrent: false | ||
exitOnError: true | ||
renderer: ListrRendererValue<Ctx> | ||
nonTTYRenderer: ListrRendererValue<Ctx> | ||
showSubtasks: true | ||
collapse: false | ||
clearOutput: false | ||
ctx: Ctx | ||
task: async (ctx, task): Promise<boolean> => ctx.input = await task.prompt<boolean>('Toggle', { message: 'Do you love me?' }) | ||
}, | ||
{ | ||
title: 'This task will get your input.', | ||
task: async (ctx, task): Promise<void> => { | ||
ctx.input = await task.prompt<boolean>('Toggle', { message: 'Do you love me?' }) | ||
// do something | ||
if (ctx.input === false) { | ||
throw new Error(':/') | ||
} | ||
} | ||
} | ||
) | ||
], { concurrent: false }) | ||
``` | ||
// and done! | ||
const ctx = await tasks.run() | ||
#### Use an Custom Prompt | ||
You can either use a custom prompt out of the npm registry or custom-created one as long as it works with [enquirer](https://www.npmjs.com/package/enquirer), it will work expectedly. Instead of passing in the prompt name use the not-generated class. | ||
```typescript | ||
new Listr<Ctx>([ | ||
{ | ||
title: 'Custom prompt', | ||
task: async (ctx, task): Promise<void> => { | ||
ctx.testInput = await task.prompt(EditorPrompt, { | ||
message: 'Write something in this enquirer custom prompt.', | ||
initial: 'Start writing!', | ||
validate: (response): boolean | string => { | ||
// i do declare you valid! | ||
return true | ||
} | ||
}) | ||
} | ||
} | ||
], { concurrent: false }) | ||
``` | ||
# Extra Features | ||
#### Use Enquirer in Your Project Without Explicitly Installing It | ||
**I am planning to move enquirer to peer dependencies as an optional install, so this will likely go away in the near future.** | ||
## Task Manager | ||
I have added a task manager kind of thing that is utilizing the Listr to the mix since I usually use it this kind of way. This task manager consists of three execution steps as described below. | ||
If you want to directly run it, and do not want to create a jumper function you can do as follows. | ||
```typescript | ||
import { Manager } from 'listr2' | ||
import { createPrompt } from 'listr2' | ||
await createPrompt('Input', { message: 'Hey what is that?' }, { cancelCallback: () => { throw new Error('You made me mad now. Just should have answered me!') }}) | ||
``` | ||
This enables user to push the tasks in a queue and execute them when needed. In my opinion this provides a more clean code interface, just to add everything to queue and execute at the end of the script, thats why I included it. | ||
### Enable a Task | ||
Tasks can be enabled depending on the variables programmatically. This enables to skip them depending on the context. Not enabled tasks will never show up in the default renderer, but when or if they get enabled they will magically appear. | ||
### Create A New Task Manager | ||
* You can inject your type through the initial generation. | ||
* The only option for manager is the show run time, which shows the run time off only the middle async part. | ||
*Please pay attention to asynchronous operation while designing a context enabled task list since it does not await for any variable in the context.* | ||
*Please refer to [examples section](examples/task-enable.example.ts) for more detailed and further examples.* | ||
```typescript | ||
private manager: Manager<Ctx> = new Manager<Ctx>() | ||
``` | ||
new Listr<Ctx>([ | ||
{ | ||
title: 'This task will execute.', | ||
task: (ctx): void => { | ||
ctx.skip = true | ||
} | ||
}, | ||
### Add Tasks | ||
```typescript | ||
manager.add([<--TASK-->]) | ||
{ | ||
title: 'This task will never execute.', | ||
enabled: (ctx): boolean => !ctx.skip, | ||
task: (): void => {} | ||
} | ||
], { concurrent: false }) | ||
``` | ||
### Run Tasks that are in Queue | ||
* This will also return the context object back. | ||
### Skip a Task | ||
Skip is more or less the same with enable when used at `Task` level. But the main difference is it will always render the given task. If it is skipped it renders it as skipped. | ||
There are to main ways to skip a task. One is utilizing the `Task` so that instead of enabled it will show a visual output while the other one is inside the task. | ||
*Please pay attention to asynchronous operation while designing a context skipped task list since it does not await for any variable in the context.* | ||
*Please refer to [examples section](examples/task-skip.example.ts) for more detailed and further examples.* | ||
Inside the task itself after some logic is done. | ||
```typescript | ||
const ctx = await manager.runAll<Ctx>({ concurrent: true }) | ||
new Listr<Ctx>([ | ||
{ | ||
title: 'This task will execute.', | ||
task: (ctx, task): void => { | ||
task.skip('I am skipping this tasks for reasons.') | ||
} | ||
} | ||
], { concurrent: false }) | ||
``` | ||
### Indent A Task | ||
To wrap a task around a new listr object with task manager, you can use ```manager.indent([], {...options})```. This way you can change the concurrency in a one big task lists. | ||
Through the task wrapper. | ||
```typescript | ||
const ctx = await manager.runAll<Ctx>({ concurrent: true }) | ||
new Listr<Ctx>([ | ||
{ | ||
title: 'This task will execute.', | ||
task: (ctx): void => { | ||
ctx.skip = true | ||
} | ||
}, | ||
manager.add<Ctx>([ | ||
{ | ||
title: 'Some task.', | ||
task: (): void => {} | ||
}, | ||
// this is equal to wrapping it inside task:(): Listr => new Listr(<-SOMETASK->) | ||
manager.indent<Ctx>([ | ||
{ | ||
title: 'Some task.', | ||
title: 'This task will never execute.', | ||
skip: (ctx): boolean => ctx.skip, | ||
task: (): void => {} | ||
}, | ||
], { concurrent: true }) | ||
], { concurrent: false }) | ||
} | ||
], { concurrent: false }) | ||
``` | ||
### Listr Wrapper | ||
You can also use the method below to directly create a Listr class. This will just return a Listr class, which you need to run after. | ||
There are two rendering methods for the default renderer for skipping the task. The default behavior is to replace the task title with skip message if the skip function returns a string. You can select the other way around with `rendererOptions: { collapseSkips: false }` for the default renderer to show the skip message under the task title. | ||
### Show Output | ||
Showing output from a task can be done in various ways. | ||
To keep the output when the task finishes while using default renderer, you can set `{ persistentOutput: true }` in the `Task`. | ||
```typescript | ||
const myListr = manager.newListr<Ctx>([ | ||
{ | ||
title: 'Some task.', | ||
task: (): void => {} | ||
} | ||
], { concurrent: false }) | ||
new Listr<Ctx>([ | ||
{ | ||
title: 'This task will execute.', | ||
task: async (ctx, task): Promise<void> => { | ||
task.output = 'I will push an output. [0]' | ||
}, | ||
options: { persistentOutput: true } | ||
} | ||
], { concurrent: false }) | ||
``` | ||
// then you can perform any class actions | ||
myListr.run() | ||
*Please refer to [examples section](examples/show-output.example.ts) for more detailed and further examples.* | ||
#### Utilizing the Task Itself | ||
This will show the output in a small bar that can only show the last output from the task. | ||
```typescript | ||
new Listr<Ctx>([ | ||
{ | ||
title: 'This task will execute.', | ||
task: async (ctx, task): Promise<void> => { | ||
task.output = 'I will push an output. [0]' | ||
await delay(500) | ||
task.output = 'I will push an output. [1]' | ||
await delay(500) | ||
task.output = 'I will push an output. [2]' | ||
await delay(500) | ||
} | ||
} | ||
], { concurrent: false }) | ||
``` | ||
#### Utilizing the Bottom Bar | ||
If task output to the bottom bar is selected, it will create a bar at the end of the tasks leaving one line return space in between. The bottom bar can only be used in the default renderer. | ||
## Input Module | ||
Items count that is desired to be showed in the bottom bar can be set through `Task` option `bottomBar`. | ||
- If set to `true` it will only show the last output from the task. | ||
- If it is set to a number it will limit the output to that number. | ||
- If set to `Infinity`, it will keep all the output. | ||
Input module uses the beautiful [enquirer](https://www.npmjs.com/package/enquirer). | ||
```typescript | ||
new Listr<Ctx>([ | ||
{ | ||
title: 'This task will execute.', | ||
task: async (ctx, task): Promise<void> => { | ||
task.output = 'I will push an output. [0]' | ||
await delay(500) | ||
So with running a `task.prompt` function, you first select which kind of prompt that you will use and second one is the enquirer object which you can see more in detail in the designated npm page. | ||
task.output = 'I will push an output. [1]' | ||
await delay(500) | ||
To get a input you can assign the task a new prompt in an async function and write the response to the context. | ||
task.output = 'I will push an output. [2]' | ||
await delay(500) | ||
}, | ||
options: { | ||
bottomBar: Infinity | ||
} | ||
} | ||
], { concurrent: false }) | ||
``` | ||
**It is not advisable to run prompts in a concurrent task because they will class and overwrite each others console output and when you do keyboard movements it will apply to the both.** | ||
#### Utilizing an Observable or Stream | ||
Since observables and streams are supported they can also be used to generate output. | ||
```typescript | ||
new Listr<Ctx>([ | ||
{ | ||
// Task can also handle and observable | ||
title: 'Observable test.', | ||
task: (): Observable<string> => | ||
new Observable((observer) => { | ||
observer.next('test') | ||
It will render multiple times in verbose renderers, because the `enquirer`'s output is passed through the Listr itself as data. It will work anyway, but will not look that nice. | ||
delay(500) | ||
.then(() => { | ||
observer.next('changed') | ||
return delay(500) | ||
}) | ||
.then(() => { | ||
observer.complete() | ||
}) | ||
}) | ||
} | ||
], { concurrent: false }) | ||
``` | ||
Prompts can either have a title or not but they will always be rendered at the end of the current console while using the default renderer. | ||
### Throw Errors | ||
You can throw errors out of the tasks to show they are insuccessful. While this gives a visual output on the terminal, it also handles how to handle tasks that are failed. The default behaviour is any of the tasks have failed, it will deem itself as unsuccessful and exit. This behaviour can be changed with `exitOnError` option. | ||
- Throw out an error in serial execution mode will cause all of the upcoming tasks to be never executed. | ||
```typescript | ||
new Listr<ListrCtx>([ | ||
new Listr<Ctx>([ | ||
{ | ||
task: async (ctx, task): Promise<any> => ctx.testInput = await task.prompt('Input', { message: 'test' }) | ||
title: 'This task will fail.', | ||
task: async (): Promise<void> => { | ||
await delay(2000) | ||
throw new Error('This task failed after 2 seconds.') | ||
} | ||
}, | ||
{ | ||
title: 'Dump prompt.', | ||
task: (ctx,task): void => { | ||
task.output = ctx.testInput | ||
title: 'This task will never execute.', | ||
task: (ctx, task): void => { | ||
task.title = 'I will change my title if this executes.' | ||
} | ||
} | ||
]) | ||
``` | ||
], { concurrent: false }) | ||
``` | ||
### Directly access enquirer without explicitly installing in your project | ||
If you want to use enquirer in your project, outside of the Listr, you can do it as follows. | ||
- Throwing out an error while execution in parallel mode will immediately stop all the actions. | ||
```typescript | ||
new Listr<Ctx>([ | ||
{ | ||
title: 'This task will fail.', | ||
task: async (): Promise<void> => { | ||
await delay(2000) | ||
throw new Error('This task failed after 2 seconds.') | ||
} | ||
}, | ||
{ | ||
title: 'This task will execute.', | ||
task: (ctx, task): void => { | ||
task.title = 'I will change my title since it is concurrent.' | ||
} | ||
} | ||
], { concurrent: true }) | ||
``` | ||
- Default behavior can be changed with `exitOnError` option. | ||
```typescript | ||
import { newPrompt, PromptTypes, PromptOptionsType } from 'listr2' | ||
new Listr<Ctx>([ | ||
{ | ||
title: 'This task will fail.', | ||
task: async (): Promise<void> => { | ||
await delay(2000) | ||
throw new Error('This task failed after 2 seconds.') | ||
} | ||
}, | ||
{ | ||
title: 'This task will execute.', | ||
task: (ctx, task): void => { | ||
task.title = 'I will change my title if this executes.' | ||
} | ||
} | ||
], { concurrent: false, exitOnError: false }) | ||
``` | ||
export async function promptUser <T extends PromptTypes> (type: T, options: PromptOptionsType<T>): Promise<any>{ | ||
try { | ||
return newPrompt(type, options).on('cancel', () => { | ||
console.error('Cancelled prompt. Quitting.') | ||
process.exit(20) | ||
}).run() | ||
} catch (e) { | ||
console.error('There was a problem getting the answer of the last question. Quitting.') | ||
console.debug(e.trace) | ||
process.exit(20) | ||
- `exitOnError` is subtask based so you can change it on the fly for given set of subtasks. | ||
```typescript | ||
new Listr<Ctx>([ | ||
{ | ||
title: 'This task will execute and not quit on errors.', | ||
task: (ctx, task): Listr => task.newListr([ | ||
{ | ||
title: 'This is a subtask.', | ||
task: async (): Promise<void> => { | ||
throw new Error('I have failed [0]') | ||
} | ||
}, | ||
{ | ||
title: 'This is yet an another subtask and it will run.', | ||
task: async (ctx, task): Promise<void> => { | ||
task.title = 'I have succeeded.' | ||
} | ||
} | ||
], { exitOnError: false }) | ||
}, | ||
{ | ||
title: 'This task will execute.', | ||
task: (): void => { | ||
throw new Error('I will exit on error since I am a direct child of parent task.') | ||
} | ||
} | ||
], { concurrent: false, exitOnError: true }) | ||
``` | ||
- The error that makes the application to quit will be thrown out from the async function. | ||
```typescript | ||
try { | ||
const context = await task.run() | ||
} catch(e) { | ||
logger.fail(e) | ||
// which will show the last error | ||
} | ||
``` | ||
- Access all of the errors that makes the application quit or not through `task.err` which is an array of all the errors encountered. | ||
```typescript | ||
const task = new Listr(...) | ||
logger.fail(task.err) | ||
// will show all of the errors that are encountered through execution | ||
``` | ||
- ListrError which is thrown out of `task.err´ in prior example is in the structure of | ||
```typescript | ||
public message: string | ||
public errors?: Error[] | ||
public context?: any | ||
``` | ||
## Task Manager | ||
Task manager is a great way to create a custom-tailored Listr class once and then utilize it more than once. | ||
*Please refer to [examples section](examples/manager.example.ts) for more detailed and further examples.* | ||
### Basic Use-Case Scenerio | ||
- Create something like a manager factory with your own default settings | ||
```typescript | ||
export function TaskManagerFactory<T = any> (override?: ListrBaseClassOptions): Manager<T> { | ||
const myDefaultOptions: ListrBaseClassOptions = { | ||
concurrent: false, | ||
exitOnError: false, | ||
rendererOptions: { | ||
collapse: false, | ||
collapseSkips: false | ||
} | ||
} | ||
return new Manager({ ...myDefaultOptions, ...override }) | ||
} | ||
``` | ||
await promptUser('Input', { message: 'Hey what is that?' }) | ||
``` | ||
- Create your class that benefits from manager | ||
```typescript | ||
export class MyMainClass { | ||
private tasks = TaskManagerFactory<Ctx>() | ||
If you want to directly run it, and do not want to create a jumper function you can do as follows. | ||
constructor () { | ||
this.run() | ||
} | ||
private async run (): Promise<void> { | ||
// CODE WILL GO HERE IN THIS EXAMPLE | ||
} | ||
} | ||
``` | ||
- Add multiple set of subtasks with their own options | ||
```typescript | ||
import { createPrompt } from 'listr2' | ||
this.tasks.add([ | ||
{ | ||
title: 'A task running manager [0]', | ||
task: async (): Promise<void> => { | ||
throw new Error('Do not dare to run the second task.') | ||
} | ||
}, | ||
{ | ||
title: 'This will never run first one failed.', | ||
task: async (): Promise<void> => { | ||
await delay(2000) | ||
} | ||
} | ||
], { exitOnError: true, concurrent: false }) | ||
``` | ||
await createPrompt('Input', { message: 'Hey what is that?' }) | ||
- Run the tasks. Running the tasks will clear the pending queue so you can go ahead and add more new tasks! | ||
```typescript | ||
try { | ||
const ctx = await this.tasks.runAll() | ||
} catch (e) { | ||
this.logger.fail(e) | ||
} | ||
``` | ||
## Inject Context | ||
### More Functionality | ||
- Indenting tasks, to change options like `concurrency`, `exitOnError` and so on. | ||
```typescript | ||
this.tasks.add([ | ||
{ | ||
title: 'Some task that will run in sequential execution mode. [0]', | ||
task: async (): Promise<void> => { | ||
await delay(2000) | ||
} | ||
}, | ||
{ | ||
title: 'Some task that will run in sequential execution mode. [1]', | ||
task: async (): Promise<void> => { | ||
await delay(2000) | ||
} | ||
}, | ||
this.tasks.indent([ | ||
{ | ||
title: 'This will run in parallel. [0]', | ||
task: async (): Promise<void> => { | ||
await delay(2000) | ||
} | ||
}, | ||
{ | ||
title: 'This will run in parallel. [1]', | ||
task: async (): Promise<void> => { | ||
await delay(2000) | ||
} | ||
} | ||
]) | ||
], { concurrent: true }) | ||
``` | ||
Context which is the object that is being used while executing the actions in the Listr can now be enjected to the next Listr through using the custom options. | ||
- Run a Task Directly, which will use the defaults settings you set in the manager. | ||
```typescript | ||
this.tasks.run([ | ||
{ | ||
title: 'I will survive, dont worry', | ||
task: (): void => { | ||
throw new Error('This will not crash since exitOnError is set to false eventhough default setting in Listr is false.') | ||
} | ||
} | ||
]) | ||
``` | ||
**If all tasks are in a one big Listr class you dont have to inject context to the childs, since it is automatically injected as in the original.** | ||
- Access the errors of the last task as in the Listr. | ||
```typescript | ||
this.tasks.run([ | ||
{ | ||
title: 'I will survive, dont worry', | ||
task: (): void => { | ||
throw new Error('This will not crash since exitOnError is set to false eventhough default setting in Listr is false.') | ||
} | ||
} | ||
]) | ||
this.logger.data(this.tasks.err.toString()) | ||
// will yield: ListrError: Task failed without crashing. with the error details in the object | ||
``` | ||
- Access base Listr class directly, this will use the default Listr settings and just a mere jumper function for omiting the need the import the Listr class when using manager. | ||
```typescript | ||
// get the context from other listr object | ||
const ctx = manager.runAll() | ||
// and inject it to next via options | ||
new Listr<ListrCtx>([ | ||
try { | ||
await this.tasks.newListr([ | ||
{ | ||
title: 'Dump prompt.', | ||
task: (ctx,task): void => { | ||
task.output = ctx.testInput | ||
title: 'I will die now, goodbye my freinds.', | ||
task: (): void => { | ||
throw new Error('This will not crash since exitOnError is set to false eventhough default setting in Listr is false.') | ||
} | ||
} | ||
], {ctx}) | ||
]).run() | ||
} catch (e) { | ||
this.logger.fail(e) | ||
} | ||
``` | ||
## Bottom Bar For More Information | ||
- Get Task Runtime, and tailor it as your own | ||
```typescript | ||
this.tasks.run([ | ||
{ | ||
task: async (ctx): Promise<void> => { | ||
// start the clock | ||
ctx.runTime = Date.now() | ||
} | ||
}, | ||
{ | ||
title: 'Running', | ||
task: async (): Promise<void> => { | ||
await delay(1000) | ||
} | ||
}, | ||
{ | ||
task: async (ctx, task): Promise<string> => task.title = this.tasks.getRuntime(ctx.runTime) | ||
} | ||
], { concurrent: false }) | ||
// outputs: "1.001s" in seconds | ||
``` | ||
Default renderer now supports a bottom bar to dump the output if desired. The output lenght can be limited through options of the task. | ||
## Generic Features | ||
**If title has no title, and generates output it will be pushed to the bottom bar instead.** | ||
### Tasks Without Titles | ||
For default renderer, all tasks that do not have titles will be hidden from the visual task list and executed behind. You can still set `task.title` inside the task wrapper programmatically afterward, if you so desire. | ||
Else you have to specicify explicitly to the dump the output to the bottom bar. Bottom bar output from the particular task will be cleared after the task finished, but with ```persistentOutput: true``` option it can be persistent. | ||
Since tasks can have subtasks as in the form of Listr classes again, if a task without a title does have subtasks with the title it will be rendered one less level indented. So you can use this operation to change the individual options of the set of tasks like `exitOnError` or `concurrency` or even render properties, like while you do want collapse parent's subtasks after completed but do not want this for a given set of subtasks. | ||
For verbose renderer, since it is not updating, it will show tasks that do not have a title as `Task without title.` | ||
### Signal Interrupt | ||
When the interrupt signal is caught Listr will render for one last time therefore you will always have clean exits. This registers event listener `process.on('exit')`, therefore it will use a bit more of CPU cycles depending on the Listr task itself. | ||
You can disable this default behavior by passing in the options for the root task `{ registerSignalListeners: false }`. | ||
## Testing | ||
For testing purposes you can use the verbose renderer by passing in the option of `{ renderer: 'verbose' }`. This will generate text-based and linear output which is required for testing. | ||
If you want to change the logger of the verbose renderer you can do that by passing a class implementing `Logger` class which is exported from the index and passing it through as a renderer option with `{ renderer: 'verbose', rendererOptions: { logger: MyLoggerClass } }`. | ||
Verbose renderer will always output predicted output with no fancy features. | ||
On | Output | ||
---------|---------- | ||
Task Started | \[STARTED\] ${TASK TITLE ?? 'Task without title.'} | ||
Task Failure | \[FAILED\] ${TASK TITLE ?? 'Task without title.'} | ||
Task Skipped | \[SKIPPED\] ${TASK TITLE ?? 'Task without title.'} | ||
Task Successful | \[SUCCESS\] ${TASK TITLE ?? 'Task without title.'} | ||
Spit Output | \[DATA\] ${TASK OUTPUT} | ||
Title Change | \[TITLE\] ${NEW TITLE} | ||
## Default Renderers | ||
There are three main renderers which are 'default', 'verbose' and 'silent'. Default renderer is the one that can be seen in the demo, which is an updating renderer. But if the environment advirteses itself as non-tty it will fallback to the verbose renderer automatically. Verbose renderer is a text based renderer. It uses the silent renderer for the subtasks since the parent task already started a renderer. But silent renderer can also be used for processes that wants to have no output but just a task list. | ||
Depending on the selected renderer, `rendererOptions` as well as the `options` in the `Task` will change accordingly. It defaults to default renderer as mentioned with the fallback to verbose renderer on non-tty environments. | ||
- Options for the default renderer. | ||
- Global | ||
```typescript | ||
public static rendererOptions: { | ||
indentation?: number | ||
clearOutput?: boolean | ||
showSubtasks?: boolean | ||
collapse?: boolean | ||
collapseSkips?: boolean | ||
} = { | ||
indentation: 2, | ||
clearOutput: false, | ||
showSubtasks: true, | ||
collapse: true, | ||
collapseSkips: true | ||
} | ||
``` | ||
- Per-Task | ||
```typescript | ||
public static rendererTaskOptions: { | ||
bottomBar?: boolean | number | ||
persistentOutput?: boolean | ||
} | ||
``` | ||
- Options for the verbose renderer. | ||
- Global | ||
```typescript | ||
public static rendererOptions: { useIcons?: boolean, logger?: new (...args: any) => Logger } | ||
``` | ||
- NONE | ||
- Options for the silent renderer. | ||
- NONE | ||
## Custom Renderers | ||
Creating a custom renderer with a beautiful interface can be done in one of two ways. | ||
- First create a Listr renderer class. | ||
```typescript | ||
new Listr<ListrCtx>([ | ||
{ | ||
task: async (ctx, task): Promise<any> => { | ||
task.output = 'Something' | ||
}, | ||
bottomBar: Infinity, // bottom bar items to keep by this particular task, number | boolean. boolean true will set it to 1. | ||
persistentOutput: true // defaults to false, has to be set explicitly if desired | ||
}, | ||
], {ctx}) | ||
/* eslint-disable @typescript-eslint/no-empty-function */ | ||
import { ListrRenderer, ListrTaskObject } from 'listr2' | ||
export class MyAmazingRenderer implements ListrRenderer { | ||
// Designate this renderer as tty or nonTTY | ||
public static nonTTY = true | ||
// designate your renderer options that will be showed inside the `ListrOptions` as rendererOptions | ||
public static rendererOptions: never | ||
// designate your custom internal task-based options that will show as `options` in the task itself | ||
public static rendererTaskOptions: never | ||
// get tasks to be renderered and options of the renderer from the parent | ||
constructor (public tasks: ListrTaskObject<any, typeof MyAmazingRenderer>[], public options: typeof MyAmazingRenderer['rendererOptions']) {} | ||
// implement custom logic for render functionality | ||
render (): void {} | ||
// implement custom logic for end functionality | ||
end (err): void {} | ||
} | ||
``` | ||
## Tasks without Titles | ||
- Then there is a branching here you can either use: | ||
- Utilizing the task functions themselves. Take a look at [default renderer](src/renderer/default.renderer.ts) since it is implemented this way. | ||
```typescript | ||
id: taskUUID | ||
hasSubtasks(): boolean | ||
isPending(): boolean | ||
isSkipped(): boolean | ||
isCompleted(): boolean | ||
isEnabled(): boolean | ||
isPrompt(): boolean | ||
hasFailed(): boolean | ||
hasTitle(): boolean | ||
``` | ||
- Observables, where `event` has `event.type` which can either be `SUBTASK`, `STATE`, `DATA` or `TITLE` and `event.data` depending on the `event.type`. Take a look at [verbose renderer](src/renderer/verbose.renderer.ts) since it is implemented this way. | ||
```typescript | ||
tasks?.forEach((task) => { | ||
task.subscribe((event: ListrEvent) => { | ||
... | ||
``` | ||
- Or if you so desire both! | ||
Tasks can be created without titles, and if any output is dumped it will be dumped to the bottom bar instead. If a task with no title is returns a new Listr task, it can be used to change the parallel task count and execute those particular tasks in order or in parallel. The subtasks of the untitled tasks will drop down one indentation level to be consistent. In the verbose renderer tasks with no-title will render as 'Task with no title.' | ||
## Log To A File | ||
Logging to a file can be done utilizing a module like [winston](https://www.npmjs.com/package/winston). This can be obtained through using the verbose renderer and creating a custom logger class that implements `Logger` which is exported from the index. | ||
While calling a new Listr you can call it with `{ renderer: 'verbose', rendererOptions: { logger: MyLoggerClass } }`. | ||
```typescript | ||
new Listr<ListrCtx>([ | ||
{ | ||
task: async (ctx, task): Promise<any> => { | ||
task.output = 'Something' | ||
} | ||
}, | ||
], {}) | ||
import { logLevels, Logger } from 'listr2' | ||
export class MyLoggerClass implements Logger { | ||
constructor (private options?: LoggerOptions) {} | ||
/* CUSTOM LOGIC */ | ||
/* CUSTOM LOGIC */ | ||
/* CUSTOM LOGIC */ | ||
public fail (message: string): void { | ||
message = this.parseMessage(logLevels.fail, message) | ||
console.error(message) | ||
} | ||
public skip (message: string): void { | ||
message = this.parseMessage(logLevels.skip, message) | ||
console.warn(message) | ||
} | ||
public success (message: string): void { | ||
message = this.parseMessage(logLevels.success, message) | ||
console.log(message) | ||
} | ||
public data (message: string): void { | ||
message = this.parseMessage(logLevels.data, message) | ||
console.info(message) | ||
} | ||
public start (message: string): void { | ||
message = this.parseMessage(logLevels.start, message) | ||
console.log(message) | ||
} | ||
public title (message: string): void { | ||
message = this.parseMessage(logLevels.title, message) | ||
console.info(message) | ||
} | ||
} | ||
``` | ||
## Multi-Line Renderer | ||
## Migration from Version <v1.3.12 | ||
To migrate from prior versions that are older than v1.3.12, which is advisable due to upcoming potential bug fixes: | ||
- rendererOptions has to be moved to their own key | ||
- some of the types if initiated before assigning a Listr has to be fixed accordingly | ||
- test renderer also combined with verbose renderer and icons of the verbose renderer is disabled by default which makes them basically same thing, so I think verbose is a better name for it | ||
The default update renderer now supports multi-line rendering. Therefore implementations like pushing through multi-line data now works properly. | ||
### Details | ||
- Renderer Options | ||
- Reason: *This was changed because of having all the renderer options that are mangled together and not respecting which renderer has been choosen. It also allows for custom renderers to have their own logic by exposing their options in a single class file rather than expecting that functionality from the project itself.* | ||
- Before <v1.3.12: | ||
```typescript | ||
new Listr<Ctx>([ | ||
{ | ||
task: async (ctx, task): Promise<void> => { | ||
}, | ||
persistentOutput: true | ||
} | ||
], { | ||
concurrent: false, | ||
collapse: true | ||
``` | ||
- After <v1.3.12: | ||
```typescript | ||
new Listr<Ctx>([ | ||
{ | ||
task: async (ctx, task): Promise<void> => { | ||
}, | ||
options: { persistentOutput: true } // per task based options are moved to their own key | ||
} | ||
], { | ||
concurrent: false, | ||
rendererOptions: { collapse: false } | ||
// global renderer options moved to their own key | ||
}) | ||
``` | ||
- Some of the types has been changed. | ||
- Reason: *Some of the types had to be changed due to compatability reasons with new autocomplete functionality of the dynamic renderer options.* | ||
- Before <v1.3.12: | ||
```typescript | ||
let task: Listr<Ctx> | ||
## Fully-Typed | ||
task = new Listr(..., { renderer: 'verbose' }) | ||
``` | ||
- After <v1.3.12: | ||
```typescript | ||
// this without the indication of verbose will now fail due to default renderer being 'default' for autocompleting goodness of the IDEs. | ||
// So you have to overwrite it manually to 'verbose'. | ||
// If it does not have a default you had to explicitly write { renderer: 'default' } everytime to have the auto complete feature | ||
let task: Listr<Ctx, 'verbose'> | ||
Types are exported from the root. | ||
task = new Listr(..., { renderer: 'verbose' }) | ||
``` | ||
- Test renderer removed. | ||
- Reason: *On non-tty environments that the verbose renderer is intended for there is no need to show icons. Since icons are now optional with the default being disabled for the verbose renderer, there is no need for a renderer that does have the same functionality since verbose and test are now basically the same thing. Verbose seemed a better name then test, so I had to remove test from the equation.* | ||
- Before <v1.3.12: | ||
```typescript | ||
const task = new Listr(..., { renderer: 'test' }) | ||
``` | ||
- After <v1.3.12: | ||
```typescript | ||
const task = new Listr(..., { renderer: 'verbose' }) | ||
``` | ||
## Types | ||
Useful types are exported from the root. It is written with Typescript, so it will work great with any modern IDE/Editor. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
133766
1380
974
55