UNPKG

bktide

Version:

Command-line interface for Buildkite CI/CD workflows with rich shell completions (Fish, Bash, Zsh) and Alfred workflow integration for macOS power users

187 lines 6.74 kB
/** * CLI Error Handler - Provides error display for CLI applications */ import { logger } from '../services/logger.js'; import { FormatterFactory, FormatterType } from '../formatters/FormatterFactory.js'; // Default format to use for error output let globalErrorFormat = 'plain'; /** * Set the global error output format * @param format The format to use ('plain', 'json', 'alfred') */ export function setErrorFormat(format) { const normalizedFormat = format.toLowerCase().trim(); if (['plain', 'json', 'alfred'].includes(normalizedFormat)) { globalErrorFormat = normalizedFormat; logger.debug(`Error output format set to ${normalizedFormat}`); } else { logger.warn(`Unknown format '${format}', error output format remains as ${globalErrorFormat}`); } } /** * Display a formatted error message * * @param error The error to display * @param debug Whether to include debug information * @param format Output format (plain, json, alfred), defaults to global setting */ export function displayCLIError(error, debug = false, format) { // Use provided format or fall back to global format const outputFormat = format || globalErrorFormat; // Get the appropriate formatter based on format const formatter = FormatterFactory.getFormatter(FormatterType.ERROR, outputFormat); // Format the error using the selected formatter const formattedError = formatter.formatError(error, { debug }); // Print the formatted output try { if (outputFormat === 'plain') { // Route plain errors to stderr for CLI best practices process.stderr.write(formattedError + '\n'); } else { // Machine-readable formats to stdout process.stdout.write(formattedError + '\n'); } } catch { // Fallback to logger if direct write fails if (outputFormat === 'plain') { logger.error(formattedError); } else { logger.console(formattedError); } } process.exitCode = 1; } /** * Get the current global error output format * @returns The current format */ export function getErrorFormat() { return globalErrorFormat; } /** * Format an error object for display in the CLI * Extracts as much useful information as possible, including stack traces * * @param error The error object to format * @param debug Whether to include debug information * @returns Formatted error message */ export function formatErrorForCLI(error, debug = false) { let output = ''; // Add a separator and heading (plain text, no inline ANSI) output += '\nERROR\n\n'; if (error instanceof Error) { // Handle standard Error objects output += `${error.name}: ${error.message}\n\n`; if (error.stack) { const stackLines = error.stack.split('\n'); // First line usually contains the error message, which we've already displayed const stackTrace = stackLines.slice(1).join('\n'); output += `Stack Trace:\n${stackTrace}\n\n`; } // Handle additional properties that might be present in API errors const apiError = error; if (apiError.response?.errors) { output += 'API Errors:\n'; apiError.response.errors.forEach((e, i) => { output += ` Error ${i + 1}: ${e.message || 'Unknown error'}\n`; if (e.path) output += ` Path: ${e.path.join('.')}\n`; if (e.locations) output += ` Locations: ${JSON.stringify(e.locations)}\n`; output += '\n'; }); } if (debug && apiError.request) { output += 'Request Details:\n'; output += ` URL: ${apiError.request.url || 'N/A'}\n`; output += ` Method: ${apiError.request.method || 'N/A'}\n\n`; } // If error has a cause, include it if (apiError.cause) { output += 'Caused by:\n'; output += formatErrorForCLI(apiError.cause, debug); } } else if (error && typeof error === 'object') { // Handle non-Error objects try { output += 'Error Object:\n'; output += JSON.stringify(error, null, 2) + '\n\n'; // Try to extract more detailed information const errorObj = error; if (errorObj.message) { output += `Message: ${errorObj.message}\n`; } if (debug) { output += 'Properties:\n'; for (const key in errorObj) { try { if (key !== 'stack' && key !== 'message') { const value = errorObj[key]; output += ` ${key}: ${typeof value === 'object' ? JSON.stringify(value) : value}\n`; } } catch { output += ` ${key}: [Cannot stringify]\n`; } } output += '\n'; } } catch { output += 'Unable to stringify error object\n\n'; } } else { // Handle primitive values output += `${String(error)}\n\n`; } if (debug) { // Add debug information output += 'Debug Information:\n'; output += ` Timestamp: ${new Date().toISOString()}\n`; output += ` Node Version: ${process.version}\n`; output += ` Platform: ${process.platform} (${process.arch})\n`; try { // Get the current call stack const stack = new Error().stack; if (stack) { const stackLines = stack.split('\n').slice(2); // Skip the Error creation line and this function output += '\nCurrent Stack:\n'; output += stackLines.join('\n') + '\n'; } } catch { // Ignore stack trace errors } } // Add a closing separator output += '\n'; return output; } /** * Wraps a function with CLI error handling * * @param fn The function to wrap * @param options Error handling options * @returns A wrapped function with error handling */ export function withCLIErrorHandling(fn, options = {}) { return async function (...args) { try { return await fn(...args); } catch (error) { displayCLIError(error, options.debugMode || false, options.format); process.exitCode = 1; // Return void to satisfy TypeScript return; } }; } //# sourceMappingURL=cli-error-handler.js.map