CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-commander

The complete solution for Node.js command-line interfaces

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

errors.mddocs/

Error Handling

Specialized error classes for handling command-line parsing errors, invalid arguments, custom error scenarios with exit code management, and comprehensive error handling patterns.

Capabilities

CommanderError Class

Base error class for all Commander-related errors with exit code and error code support.

/**
 * Base error class for Commander errors
 * Extends the standard Error class with exit codes and error identification
 */
class CommanderError extends Error {
  /**
   * Create a new CommanderError
   * @param exitCode - Suggested exit code for process.exit
   * @param code - Error code identifier for programmatic handling
   * @param message - Human-readable error message
   */
  constructor(exitCode: number, code: string, message: string);
  
  // Properties
  exitCode: number;         // Suggested exit code
  code: string;            // Error code identifier
  message: string;         // Error message
  nestedError?: string;    // Optional nested error information
}

Usage Examples:

import { CommanderError } from 'commander';

// Custom error handling
try {
  program.parse(process.argv);
} catch (error) {
  if (error instanceof CommanderError) {
    console.error(`Error ${error.code}: ${error.message}`);
    process.exit(error.exitCode);
  }
  throw error;
}

// Creating custom CommanderError
throw new CommanderError(2, 'custom.validation', 'Custom validation failed');

InvalidArgumentError Class

Specialized error for invalid command arguments or option values.

/**
 * Error for invalid command arguments or option values
 * Extends CommanderError with predefined exit code and error code
 */
class InvalidArgumentError extends CommanderError {
  /**
   * Create a new InvalidArgumentError
   * @param message - Explanation of why the argument is invalid
   */
  constructor(message: string);
}

Usage Examples:

import { InvalidArgumentError } from 'commander';

// In custom argument parser
const parsePort = (value) => {
  const port = parseInt(value);
  if (isNaN(port) || port < 1 || port > 65535) {
    throw new InvalidArgumentError(`Port must be a number between 1 and 65535, got: ${value}`);
  }
  return port;
};

// In option argument parser
const parsePercentage = (value) => {
  const num = parseFloat(value);
  if (isNaN(num) || num < 0 || num > 100) {
    throw new InvalidArgumentError(`Percentage must be between 0 and 100, got: ${value}`);
  }
  return num;
};

program
  .option('-p, --port <number>', 'server port', parsePort)
  .option('--opacity <percent>', 'opacity percentage', parsePercentage);

InvalidOptionArgumentError (Deprecated)

Deprecated alias for InvalidArgumentError, maintained for backward compatibility.

/**
 * Deprecated alias for InvalidArgumentError
 * @deprecated Use InvalidArgumentError instead
 */
const InvalidOptionArgumentError = InvalidArgumentError;

Command Error Handling

Built-in error handling methods on the Command class.

interface Command {
  /**
   * Display error message and exit
   * @param message - Error message to display
   * @param errorOptions - Error configuration options
   */
  error(message: string, errorOptions?: ErrorOptions): never;
  
  /**
   * Override exit behavior for error handling
   * @param callback - Custom exit handler
   * @returns this command for chaining
   */
  exitOverride(callback?: (err: CommanderError) => never | void): this;
}

interface ErrorOptions {
  /** Error code identifier */
  code?: string;
  /** Suggested exit code */
  exitCode?: number;
}

Built-in Error Codes

Commander uses specific error codes for different parsing and validation scenarios:

// Built-in error codes used by Commander
const COMMANDER_ERROR_CODES = {
  // Command parsing errors
  'commander.unknownCommand': 'Unknown command specified',
  'commander.unknownOption': 'Unknown option specified',
  'commander.missingMandatoryOptionValue': 'Required option value not provided',
  'commander.optionMissingArgument': 'Option requires an argument',
  'commander.invalidArgument': 'Invalid argument value provided',
  'commander.missingArgument': 'Required argument not provided',
  'commander.excessArguments': 'Too many arguments provided',
  
  // Option conflicts and requirements
  'commander.conflictingOption': 'Conflicting options specified',
  'commander.missingMandatoryOption': 'Required option not specified',
  
  // Help and version
  'commander.help': 'Help requested',
  'commander.version': 'Version requested'
};

Usage Examples:

// Display error and exit
if (!process.env.API_KEY) {
  program.error('API_KEY environment variable is required', {
    code: 'missing.apikey',
    exitCode: 1
  });
}

// Custom exit handling
program.exitOverride((err) => {
  if (err.code === 'commander.invalidArgument') {
    console.error(`Invalid input: ${err.message}`);
    console.error('Run with --help for usage information');
  }
  process.exit(err.exitCode);
});

Common Error Patterns

Validation Errors

// File existence validation
const validateFileExists = (filepath) => {
  if (!fs.existsSync(filepath)) {
    throw new InvalidArgumentError(`File not found: ${filepath}`);
  }
  return filepath;
};

// Format validation
const validateEmail = (email) => {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!emailRegex.test(email)) {
    throw new InvalidArgumentError(`Invalid email format: ${email}`);
  }
  return email;
};

// Range validation
const validateRange = (min, max) => (value) => {
  const num = Number(value);
  if (isNaN(num) || num < min || num > max) {
    throw new InvalidArgumentError(`Value must be between ${min} and ${max}, got: ${value}`);
  }
  return num;
};

program
  .option('-f, --file <path>', 'input file', validateFileExists)
  .option('-e, --email <address>', 'email address', validateEmail)
  .option('-p, --port <number>', 'port number', validateRange(1, 65535));

Choice Validation

// Using built-in choices validation
program
  .option('-l, --log-level <level>', 'logging level')
  .choices(['error', 'warn', 'info', 'debug']);

// Custom choice validation with suggestions
const validateLogLevel = (value) => {
  const validLevels = ['error', 'warn', 'info', 'debug', 'trace'];
  if (!validLevels.includes(value)) {
    const suggestions = validLevels.filter(level => 
      level.startsWith(value) || value.startsWith(level)
    );
    const suggestion = suggestions.length > 0 
      ? ` Did you mean: ${suggestions.join(', ')}?` 
      : '';
    throw new InvalidArgumentError(`Invalid log level: ${value}.${suggestion}`);
  }
  return value;
};

Async Validation

// Async validation in action handlers
program
  .command('deploy <environment>')
  .action(async (environment) => {
    try {
      const config = await loadEnvironmentConfig(environment);
      await deploy(config);
    } catch (error) {
      if (error.code === 'ENOENT') {
        program.error(`Environment configuration not found: ${environment}`, {
          code: 'deploy.config.missing',
          exitCode: 1
        });
      }
      throw error;
    }
  });

Error Recovery and Suggestions

// Command suggestion on unknown commands
program.exitOverride((err) => {
  if (err.code === 'commander.unknownCommand') {
    const availableCommands = program.commands.map(cmd => cmd.name());
    const suggestion = availableCommands.find(cmd => 
      cmd.startsWith(err.message.split("'")[1]?.slice(0, 3) || '')
    );
    
    console.error(err.message);
    if (suggestion) {
      console.error(`Did you mean: ${suggestion}?`);
    }
    console.error('Run --help to see available commands');
  }
  process.exit(err.exitCode);
});

// Option suggestion configuration
program.showSuggestionAfterError(true); // Default behavior

Error Context and Debugging

// Enhanced error context
const createEnhancedError = (message, context = {}) => {
  const error = new InvalidArgumentError(message);
  error.context = context;
  return error;
};

// Validation with context
const validateConfigFile = (filepath) => {
  try {
    const config = JSON.parse(fs.readFileSync(filepath, 'utf8'));
    return config;
  } catch (parseError) {
    throw createEnhancedError(
      `Invalid JSON in config file: ${filepath}`,
      { 
        parseError: parseError.message,
        line: parseError.lineNumber,
        column: parseError.columnNumber
      }
    );
  }
};

// Debug-friendly error handling
program.exitOverride((err) => {
  if (process.env.DEBUG) {
    console.error('Error details:', {
      code: err.code,
      exitCode: err.exitCode,
      stack: err.stack,
      context: err.context
    });
  } else {
    console.error(err.message);
  }
  process.exit(err.exitCode);
});

Error Handling Best Practices

Early Validation

// Validate dependencies before command execution
program
  .hook('preAction', (thisCommand) => {
    // Check required environment variables
    const requiredEnvVars = ['API_KEY', 'DATABASE_URL'];
    for (const envVar of requiredEnvVars) {
      if (!process.env[envVar]) {
        thisCommand.error(`Missing required environment variable: ${envVar}`, {
          code: 'env.missing',
          exitCode: 1
        });
      }
    }
  });

Graceful Error Messages

// User-friendly error messages
const parseSize = (value) => {
  const match = value.match(/^(\d+(?:\.\d+)?)(b|kb|mb|gb)$/i);
  if (!match) {
    throw new InvalidArgumentError(
      `Invalid size format: ${value}. Use format like: 100b, 1.5kb, 2mb, 1gb`
    );
  }
  
  const [, amount, unit] = match;
  const multipliers = { b: 1, kb: 1024, mb: 1024**2, gb: 1024**3 };
  return parseFloat(amount) * multipliers[unit.toLowerCase()];
};

Error Code Conventions

// Consistent error code naming
const ERROR_CODES = {
  VALIDATION: {
    INVALID_FORMAT: 'validation.format',
    OUT_OF_RANGE: 'validation.range',
    MISSING_REQUIRED: 'validation.required'
  },
  FILE: {
    NOT_FOUND: 'file.notfound',
    ACCESS_DENIED: 'file.access',
    INVALID_FORMAT: 'file.format'
  },
  NETWORK: {
    CONNECTION_FAILED: 'network.connection',
    TIMEOUT: 'network.timeout',
    UNAUTHORIZED: 'network.auth'
  }
};

// Usage in validators
const validateUrl = (url) => {
  try {
    new URL(url);
    return url;
  } catch {
    throw new CommanderError(1, ERROR_CODES.VALIDATION.INVALID_FORMAT, `Invalid URL: ${url}`);
  }
};

Types

interface ErrorOptions {
  /** Error code identifier for programmatic handling */
  code?: string;
  /** Suggested exit code for process termination */
  exitCode?: number;
}

docs

arguments.md

commands.md

errors.md

help.md

index.md

options.md

tile.json