Files
ccs/src/errors/error-handler.ts
T
kaitranntt 22dbfd91c5 refactor(errors): centralize error handling infrastructure
- add errors/ directory with error types, handler, cleanup registry

- extract claude-spawner.ts from auth modules

- update imports across auth, ccs, web-server

Phase 2 & 4: Error handling consolidation
2025-12-19 11:47:43 -05:00

168 lines
4.0 KiB
TypeScript

/**
* Centralized error handler for CCS CLI
*
* Provides unified error handling with:
* - Consistent error formatting
* - Exit code management
* - Cleanup callback execution
* - Debug mode support
*/
import { ExitCode, EXIT_CODE_DESCRIPTIONS } from './exit-codes';
import { isCCSError } from './error-types';
import { runCleanup } from './cleanup-registry';
/**
* Debug mode flag - set via CCS_DEBUG environment variable
*/
const isDebugMode = (): boolean => {
return process.env['CCS_DEBUG'] === '1' || process.env['CCS_DEBUG'] === 'true';
};
/**
* Format error message for display
* Uses ASCII-only formatting per codebase standards (no emojis)
*/
function formatErrorMessage(error: unknown): string {
if (isCCSError(error)) {
return `[X] ${error.message}`;
}
if (error instanceof Error) {
return `[X] ${error.message}`;
}
if (typeof error === 'string') {
return `[X] ${error}`;
}
return '[X] An unexpected error occurred';
}
/**
* Get exit code from error
* CCSError types have their own codes, others default to GENERAL_ERROR
*/
function getExitCode(error: unknown): ExitCode {
if (isCCSError(error)) {
return error.code;
}
return ExitCode.GENERAL_ERROR;
}
/**
* Log debug information for an error
* Only outputs when CCS_DEBUG is enabled
*/
function logDebugInfo(error: unknown, code: ExitCode): void {
if (!isDebugMode()) return;
console.error('');
console.error('[i] Debug information:');
console.error(` Exit code: ${code} (${EXIT_CODE_DESCRIPTIONS[code] || 'Unknown'})`);
if (error instanceof Error) {
console.error(` Error type: ${error.constructor.name}`);
if (error.stack) {
console.error(' Stack trace:');
const stackLines = error.stack.split('\n').slice(1, 6);
for (const line of stackLines) {
console.error(` ${line.trim()}`);
}
}
}
// Log additional properties for CCSError types
if (isCCSError(error)) {
console.error(` Recoverable: ${error.recoverable}`);
}
}
/**
* Central error handler
* Formats error, runs cleanup, and exits with appropriate code
*
* @param error - The error to handle
* @returns never - Always exits the process
*/
export function handleError(error: unknown): never {
// Run cleanup callbacks first
runCleanup();
const code = getExitCode(error);
const message = formatErrorMessage(error);
// Output error message to stderr
console.error(message);
// Log debug info if enabled
logDebugInfo(error, code);
// Exit with appropriate code
process.exit(code);
}
/**
* Exit with an error message and code
* Convenience function for simple error exits
*
* @param message - Error message to display
* @param code - Exit code (defaults to GENERAL_ERROR)
* @returns never - Always exits the process
*/
export function exitWithError(message: string, code: ExitCode = ExitCode.GENERAL_ERROR): never {
runCleanup();
console.error(`[X] ${message}`);
if (isDebugMode()) {
console.error('');
console.error(`[i] Exit code: ${code} (${EXIT_CODE_DESCRIPTIONS[code] || 'Unknown'})`);
}
process.exit(code);
}
/**
* Exit with success
* Runs cleanup and exits with SUCCESS code
*
* @param message - Optional success message to display
* @returns never - Always exits the process
*/
export function exitWithSuccess(message?: string): never {
runCleanup();
if (message) {
console.log(`[OK] ${message}`);
}
process.exit(ExitCode.SUCCESS);
}
/**
* Create a wrapped error handler for async operations
* Wraps an async function to catch and handle errors
*/
export function withErrorHandling<T extends unknown[]>(
fn: (...args: T) => Promise<void>
): (...args: T) => Promise<void> {
return async (...args: T): Promise<void> => {
try {
await fn(...args);
} catch (error) {
handleError(error);
}
};
}
/**
* Assert a condition, throwing a CCSError if false
*/
export function assertOrExit(
condition: boolean,
message: string,
code: ExitCode = ExitCode.GENERAL_ERROR
): asserts condition {
if (!condition) {
exitWithError(message, code);
}
}