Terminal task list library for creating beautiful, interactive CLI interfaces with task management, rendering options, and error handling.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Individual task definition, execution control, and lifecycle management including skip conditions, retry logic, rollback functionality, and dynamic task behavior.
Core interface for defining individual tasks with all available configuration options.
/**
* Configuration interface for individual tasks
* @template Ctx - Context type shared between tasks
* @template Renderer - Renderer type for this task
* @template FallbackRenderer - Fallback renderer type
*/
interface ListrTask<Ctx, Renderer, FallbackRenderer> {
/** Task display title (can be dynamic) */
title?: string | any[];
/** Main task execution function */
task: ListrTaskFn<Ctx, Renderer, FallbackRenderer>;
/** Condition to determine if task should be enabled */
enabled?: boolean | ((ctx: Ctx) => boolean | Promise<boolean>);
/** Condition or message for skipping the task */
skip?: boolean | string | ((ctx: Ctx) => boolean | string | Promise<boolean | string>);
/** Retry configuration for failed tasks */
retry?: number | ListrTaskRetry;
/** Rollback function to execute if task fails */
rollback?: ListrTaskFn<Ctx, Renderer, FallbackRenderer>;
/** Whether to exit immediately if this task fails */
exitOnError?: boolean | ((ctx: Ctx) => boolean | Promise<boolean>);
/** Renderer-specific options for primary renderer */
rendererOptions?: ListrGetRendererTaskOptions<ListrGetRendererClassFromValue<Renderer>>;
/** Renderer-specific options for fallback renderer */
fallbackRendererOptions?: ListrGetRendererTaskOptions<ListrGetRendererClassFromValue<FallbackRenderer>>;
}The main function that defines what a task does during execution.
/**
* Task execution function signature
* @template Ctx - Context type
* @template Renderer - Renderer type
* @template FallbackRenderer - Fallback renderer type
* @param ctx - Shared context object
* @param task - Task wrapper for manipulation and output
* @returns Void, promise, string output, observable, or subtask list
*/
type ListrTaskFn<Ctx, Renderer, FallbackRenderer> = (
ctx: Ctx,
task: TaskWrapper<Ctx, Renderer, FallbackRenderer>
) => void | ListrTaskResult<Ctx>;
/**
* Valid return types from task execution functions
*/
type ListrTaskResult<Ctx> =
| string
| Promise<any>
| Listr<Ctx, any, any>
| ReadableLike
| ObservableLike<any>;Advanced retry configuration for handling task failures with customizable delays and attempt limits.
/**
* Retry configuration for failed tasks
*/
interface ListrTaskRetry {
/** Number of retry attempts */
tries: number;
/** Delay between retry attempts in milliseconds */
delay?: number;
}Usage Examples:
import { Listr } from "listr2";
const tasks = new Listr([
{
title: "Simple task",
task: () => {
console.log("Executing task...");
}
},
{
title: "Async task with output",
task: async (ctx, task) => {
task.output = "Starting async operation...";
await new Promise(resolve => setTimeout(resolve, 2000));
task.output = "Async operation completed";
return "Task completed successfully";
}
}
]);import { Listr } from "listr2";
interface BuildContext {
environment: 'development' | 'production';
skipTests: boolean;
}
const tasks = new Listr<BuildContext>([
{
title: "Install dependencies",
task: () => {
// Always runs
return Promise.resolve();
}
},
{
title: "Run tests",
enabled: (ctx) => !ctx.skipTests,
task: () => {
return Promise.resolve();
}
},
{
title: "Production build",
enabled: (ctx) => ctx.environment === 'production',
task: () => {
return Promise.resolve();
}
},
{
title: "Development build",
enabled: (ctx) => ctx.environment === 'development',
task: () => {
return Promise.resolve();
}
}
]);
await tasks.run({ environment: 'production', skipTests: false });import { Listr } from "listr2";
const tasks = new Listr([
{
title: "Check prerequisites",
task: (ctx) => {
ctx.hasPrereqs = Math.random() > 0.5;
}
},
{
title: "Main task",
skip: (ctx) => !ctx.hasPrereqs ? "Prerequisites not met" : false,
task: () => {
return Promise.resolve();
}
},
{
title: "Always skipped task",
skip: "This task is always skipped",
task: () => {
return Promise.resolve();
}
}
]);import { Listr } from "listr2";
const tasks = new Listr([
{
title: "Flaky network request",
retry: 3, // Simple retry count
task: async () => {
if (Math.random() < 0.7) {
throw new Error("Network request failed");
}
return "Request successful";
}
},
{
title: "API call with delay",
retry: {
tries: 5,
delay: 1000 // 1 second delay between retries
},
task: async () => {
// Simulate API call that might fail
const response = await fetch("https://api.example.com/data");
if (!response.ok) {
throw new Error(`API request failed: ${response.status}`);
}
return response.json();
}
}
]);import { Listr } from "listr2";
interface DeployContext {
backupCreated: boolean;
deploymentId?: string;
}
const tasks = new Listr<DeployContext>([
{
title: "Create backup",
task: (ctx) => {
// Create backup logic
ctx.backupCreated = true;
return Promise.resolve();
}
},
{
title: "Deploy application",
task: (ctx) => {
ctx.deploymentId = "deploy-" + Date.now();
// Simulate deployment that might fail
if (Math.random() < 0.3) {
throw new Error("Deployment failed");
}
return Promise.resolve();
},
rollback: (ctx, task) => {
if (ctx.backupCreated && ctx.deploymentId) {
task.output = `Rolling back deployment ${ctx.deploymentId}...`;
// Rollback logic here
return Promise.resolve();
}
}
}
]);import { Listr } from "listr2";
const files = ["file1.txt", "file2.txt", "file3.txt"];
const tasks = new Listr(
files.map((file, index) => ({
title: `Processing ${file}`,
task: (ctx, task) => {
return new Promise(resolve => {
setTimeout(() => {
task.title = `✅ Processed ${file}`;
resolve(undefined);
}, 1000);
});
}
}))
);import { Listr } from "listr2";
import { Observable } from "rxjs";
import { spawn } from "child_process";
const tasks = new Listr([
{
title: "Observable task",
task: () => {
return new Observable(observer => {
let count = 0;
const interval = setInterval(() => {
observer.next(`Progress: ${++count}/10`);
if (count >= 10) {
observer.complete();
clearInterval(interval);
}
}, 100);
});
}
},
{
title: "Stream from child process",
task: (ctx, task) => {
const child = spawn('npm', ['install'], { cwd: process.cwd() });
// Pipe stdout to task output
child.stdout.on('data', (data) => {
task.output = data.toString();
});
return new Promise((resolve, reject) => {
child.on('close', (code) => {
if (code === 0) {
resolve(undefined);
} else {
reject(new Error(`Process exited with code ${code}`));
}
});
});
}
}
]);import { Listr } from "listr2";
const tasks = new Listr([
{
title: "Critical task",
exitOnError: true, // Stop entire task list if this fails
task: () => {
if (Math.random() < 0.5) {
throw new Error("Critical failure");
}
return Promise.resolve();
}
},
{
title: "Non-critical task",
exitOnError: false, // Continue even if this fails
task: () => {
throw new Error("Non-critical failure");
}
},
{
title: "Context-dependent error handling",
exitOnError: (ctx) => ctx.strictMode === true,
task: () => {
throw new Error("Conditional failure");
}
}
]);
await tasks.run({ strictMode: false });interface ListrTaskMessage {
/** Error message if task failed */
error?: string;
/** Skip message if task was skipped */
skip?: string;
/** Rollback message during rollback operations */
rollback?: string;
/** Retry message during retry attempts */
retry?: { count: number; message?: string };
}
interface ListrTaskPrompt {
/** Current prompt instance */
prompt?: any;
/** Prompt error if prompt failed */
error?: Error;
}
/**
* Stream-like interface for task output
*/
interface ReadableLike {
readable: boolean;
read: (size?: number) => string | Buffer;
on: (eventName: 'data' | 'error' | 'end', listener: (data: Buffer | string) => void) => unknown;
}
/**
* Observable-like interface for task progress
*/
interface ObservableLike<T> {
subscribe: (observer: ObserverLike<T>) => unknown;
}
interface ObserverLike<T> {
next?: (value: T) => void;
error?: (error: any) => void;
complete?: () => void;
}