CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-listr2

Terminal task list library for creating beautiful, interactive CLI interfaces with task management, rendering options, and error handling.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

error-handling.mddocs/

Error Handling

Comprehensive error handling system with task state management, error collection modes, rollback capabilities, and detailed error reporting for robust task execution.

Capabilities

ListrError Class

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;
    };
  };
}

PromptError Class

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);
}

Error Types Classification

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 Management

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'
}

Error Collection Options

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:

Basic Error Handling

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);
  }
}

Retry with Error Handling

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);
}

Rollback Functionality

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);
}

Error Collection and Reporting

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}`);
      });
    }
  }
}

Custom Error Handling per Task

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: []
});

Prompt Error Handling

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
        }
      }
    }
  }
]);

Advanced Error Analysis

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));
}

Types

/**
 * 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[];
}

docs

error-handling.md

event-management.md

index.md

renderers.md

task-configuration.md

task-management.md

tile.json