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
Comprehensive error handling system with task state management, error collection modes, rollback capabilities, and detailed error reporting for robust task execution.
Main error class that wraps errors with context information and execution path details.
/**
* Main error class for Listr task failures
* Provides context and path information for debugging
* @template Ctx - Context type
*/
class ListrError<Ctx> extends Error {
/** Execution path where error occurred */
public path: string[];
/** Task context when error occurred */
public ctx: Ctx;
/** Original error that was wrapped */
public error: Error;
/** Error type classification */
public type: ListrErrorTypes;
/** Task that generated the error */
public task: Task<Ctx, any, any>;
/**
* Create a new ListrError
* @param error - Original error that occurred
* @param type - Classification of the error type
* @param task - Task instance where error occurred
*/
constructor(error: Error, type: ListrErrorTypes, task: Task<Ctx, any, any>);
/**
* Get formatted error message with context
* @returns Formatted error string
*/
toString(): string;
/**
* Get error details as JSON
* @returns Serializable error information
*/
toJSON(): {
message: string;
path: string[];
type: ListrErrorTypes;
error: {
name: string;
message: string;
stack?: string;
};
};
}Specialized error class for prompt-related failures and user interaction errors.
/**
* Error class for prompt-related failures
* Used when user prompts fail or are cancelled
*/
class PromptError extends Error {
/** Original prompt error if available */
public originalError?: Error;
/**
* Create a new PromptError
* @param message - Error message
* @param originalError - Optional original error that caused prompt failure
*/
constructor(message: string, originalError?: Error);
}Enumeration of different error types for categorizing failures and determining handling strategies.
/**
* Error type classifications for different failure scenarios
*/
enum ListrErrorTypes {
/** Task will be retried after this error */
WILL_RETRY = 'WILL_RETRY',
/** Task will be rolled back after this error */
WILL_ROLLBACK = 'WILL_ROLLBACK',
/** Task failed during rollback operation */
HAS_FAILED_TO_ROLLBACK = 'HAS_FAILED_TO_ROLLBACK',
/** Task has failed permanently */
HAS_FAILED = 'HAS_FAILED',
/** Task failed without throwing an error */
HAS_FAILED_WITHOUT_ERROR = 'HAS_FAILED_WITHOUT_ERROR'
}Task state enumeration covering all possible states including error and recovery states.
/**
* Complete enumeration of task execution states
*/
enum ListrTaskState {
/** Task is waiting to be executed */
WAITING = 'WAITING',
/** Task has started execution */
STARTED = 'STARTED',
/** Task completed successfully */
COMPLETED = 'COMPLETED',
/** Task failed with an error */
FAILED = 'FAILED',
/** Task was skipped */
SKIPPED = 'SKIPPED',
/** Task is performing rollback operations */
ROLLING_BACK = 'ROLLING_BACK',
/** Task has been rolled back */
ROLLED_BACK = 'ROLLED_BACK',
/** Task is retrying after a failure */
RETRY = 'RETRY',
/** Task execution is paused */
PAUSED = 'PAUSED',
/** Task is waiting for user prompt input */
PROMPT = 'PROMPT',
/** Task prompt completed successfully */
PROMPT_COMPLETED = 'PROMPT_COMPLETED',
/** Task prompt failed */
PROMPT_FAILED = 'PROMPT_FAILED'
}Configuration options for how errors are collected and reported during task execution.
/**
* Error collection configuration in Listr options
*/
interface ListrBaseClassOptions<Ctx, Renderer, FallbackRenderer> {
/**
* Error collection mode
* - false: Don't collect errors (exit immediately)
* - 'minimal': Collect basic error information
* - 'full': Collect detailed error information including stack traces
*/
collectErrors?: false | 'minimal' | 'full';
/** Exit immediately when any task fails */
exitOnError?: boolean;
/** Exit after rollback operations complete */
exitAfterRollback?: boolean;
}Usage Examples:
import { Listr, ListrError } from "listr2";
const tasks = new Listr([
{
title: "Task that might fail",
task: () => {
if (Math.random() < 0.5) {
throw new Error("Random failure occurred");
}
return "Success";
}
},
{
title: "Task that always runs",
task: () => {
console.log("This task always executes");
}
}
], {
exitOnError: false, // Continue execution even if tasks fail
collectErrors: 'full' // Collect detailed error information
});
try {
await tasks.run();
console.log("All tasks completed");
} catch (error) {
if (error instanceof ListrError) {
console.log("Listr execution failed:");
console.log("Path:", error.path);
console.log("Type:", error.type);
console.log("Original error:", error.error.message);
}
}import { Listr, ListrErrorTypes } from "listr2";
const tasks = new Listr([
{
title: "Flaky network request",
retry: {
tries: 3,
delay: 1000
},
task: async (ctx, task) => {
const attempt = (ctx.attempt || 0) + 1;
ctx.attempt = attempt;
task.output = `Attempt ${attempt}`;
// Simulate network request that fails 70% of the time
if (Math.random() < 0.7) {
throw new Error(`Network request failed (attempt ${attempt})`);
}
task.output = `Success on attempt ${attempt}`;
return "Request completed";
}
}
]);
// Monitor retry attempts
tasks.events.on('TASK_ERROR', (event) => {
console.log(`Task failed: ${event.error.message}`);
if (event.willRetry) {
console.log(`Will retry (${event.retryCount + 1} attempts so far)`);
} else {
console.log("No more retries, task has failed permanently");
}
});
try {
await tasks.run();
} catch (error) {
console.log("Final error:", error.message);
}import { Listr } from "listr2";
interface DeployContext {
backupId?: string;
deploymentId?: string;
serverStarted?: boolean;
}
const deployTasks = new Listr<DeployContext>([
{
title: "Create backup",
task: (ctx) => {
ctx.backupId = `backup-${Date.now()}`;
console.log(`Created backup: ${ctx.backupId}`);
}
},
{
title: "Deploy application",
task: (ctx) => {
ctx.deploymentId = `deploy-${Date.now()}`;
// Simulate deployment failure
if (Math.random() < 0.4) {
throw new Error("Deployment failed - server error");
}
console.log(`Deployed: ${ctx.deploymentId}`);
},
rollback: (ctx, task) => {
if (ctx.backupId && ctx.deploymentId) {
task.output = `Rolling back deployment ${ctx.deploymentId}...`;
console.log(`Restoring from backup: ${ctx.backupId}`);
// Simulate rollback operations
return new Promise(resolve => setTimeout(resolve, 1000));
}
}
},
{
title: "Start services",
task: (ctx) => {
ctx.serverStarted = true;
console.log("Services started");
},
rollback: (ctx, task) => {
if (ctx.serverStarted) {
task.output = "Stopping services...";
console.log("Services stopped");
ctx.serverStarted = false;
}
}
}
], {
exitAfterRollback: true // Exit after rollback operations complete
});
try {
await deployTasks.run();
console.log("Deployment successful");
} catch (error) {
console.log("Deployment failed and rolled back:", error.message);
}import { Listr, ListrError } from "listr2";
const tasks = new Listr([
{
title: "Task 1",
task: () => {
throw new Error("First task error");
}
},
{
title: "Task 2",
task: () => {
throw new Error("Second task error");
}
},
{
title: "Task 3",
task: () => {
console.log("Task 3 executes successfully");
}
}
], {
exitOnError: false,
collectErrors: 'full'
});
try {
await tasks.run();
} catch (error) {
if (error instanceof ListrError) {
console.log("\n=== Error Summary ===");
console.log(`Main error: ${error.message}`);
console.log(`Error type: ${error.type}`);
console.log(`Execution path: ${error.path.join(' → ')}`);
// Access all collected errors
if (tasks.errors.length > 0) {
console.log("\n=== All Errors ===");
tasks.errors.forEach((err, index) => {
console.log(`${index + 1}. ${err.message}`);
console.log(` Path: ${err.path.join(' → ')}`);
console.log(` Type: ${err.type}`);
console.log(` Original: ${err.error.message}`);
});
}
}
}import { Listr } from "listr2";
interface ProcessingContext {
strictMode: boolean;
errors: string[];
}
const tasks = new Listr<ProcessingContext>([
{
title: "Critical system task",
exitOnError: true, // Always exit if this task fails
task: () => {
if (Math.random() < 0.2) {
throw new Error("Critical system failure");
}
}
},
{
title: "Optional enhancement",
exitOnError: false, // Never exit for this task
task: () => {
throw new Error("Enhancement failed, but continuing");
}
},
{
title: "Context-dependent task",
exitOnError: (ctx) => ctx.strictMode, // Exit based on context
task: (ctx) => {
if (Math.random() < 0.3) {
const error = "Context-dependent failure";
ctx.errors.push(error);
throw new Error(error);
}
}
}
]);
await tasks.run({
strictMode: false,
errors: []
});import { Listr, PromptError } from "listr2";
const tasks = new Listr([
{
title: "Interactive configuration",
task: async (ctx, task) => {
try {
// Simulate prompt interaction
const userInput = await new Promise((resolve, reject) => {
setTimeout(() => {
// Simulate user cancellation or timeout
if (Math.random() < 0.3) {
reject(new PromptError("User cancelled the prompt"));
} else {
resolve("user-input-value");
}
}, 1000);
});
ctx.userConfig = userInput;
task.output = "Configuration completed";
} catch (error) {
if (error instanceof PromptError) {
task.output = "Prompt was cancelled, using defaults";
ctx.userConfig = "default-value";
} else {
throw error; // Re-throw non-prompt errors
}
}
}
}
]);import { Listr, ListrError, ListrErrorTypes, ListrTaskState } from "listr2";
class ErrorAnalyzer {
private errors: ListrError<any>[] = [];
private retryCount = 0;
private rollbackCount = 0;
analyze(tasks: Listr<any>) {
// Listen to various error events
tasks.events.on('TASK_ERROR', (event) => {
this.retryCount++;
console.log(`Retry ${this.retryCount}: ${event.error.message}`);
});
tasks.events.on('TASK_STATE_CHANGED', (event) => {
if (event.newState === ListrTaskState.ROLLING_BACK) {
this.rollbackCount++;
console.log(`Rollback ${this.rollbackCount} initiated for task ${event.taskId}`);
}
});
}
generateReport(tasksErrors: ListrError<any>[]) {
const report = {
totalErrors: tasksErrors.length,
retryAttempts: this.retryCount,
rollbackOperations: this.rollbackCount,
errorsByType: {} as Record<ListrErrorTypes, number>,
errorsByPath: {} as Record<string, number>
};
tasksErrors.forEach(error => {
// Count by error type
report.errorsByType[error.type] = (report.errorsByType[error.type] || 0) + 1;
// Count by execution path
const pathKey = error.path.join(' → ');
report.errorsByPath[pathKey] = (report.errorsByPath[pathKey] || 0) + 1;
});
return report;
}
}
// Usage
const analyzer = new ErrorAnalyzer();
const tasks = new Listr([
// ... tasks with potential failures
], {
collectErrors: 'full',
exitOnError: false
});
analyzer.analyze(tasks);
try {
await tasks.run();
} catch (error) {
const report = analyzer.generateReport(tasks.errors);
console.log("Error Analysis Report:", JSON.stringify(report, null, 2));
}/**
* Error context information
*/
interface ListrErrorContext {
/** Task execution path */
path: string[];
/** Task context at time of error */
context: any;
/** Timestamp when error occurred */
timestamp: Date;
/** Whether task will be retried */
willRetry: boolean;
/** Current retry attempt number */
retryAttempt: number;
/** Maximum retry attempts allowed */
maxRetries: number;
}
/**
* Rollback operation result
*/
interface RollbackResult {
/** Whether rollback was successful */
success: boolean;
/** Error that occurred during rollback, if any */
error?: Error;
/** Duration of rollback operation */
duration: number;
/** Additional rollback metadata */
metadata?: Record<string, any>;
}
/**
* Error collection summary
*/
interface ErrorSummary {
/** Total number of errors */
totalErrors: number;
/** Errors by type */
byType: Record<ListrErrorTypes, ListrError<any>[]>;
/** Errors by execution path */
byPath: Record<string, ListrError<any>[]>;
/** Tasks that were retried */
retriedTasks: string[];
/** Tasks that were rolled back */
rolledBackTasks: string[];
}