UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

211 lines (210 loc) 7.43 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.processManager = exports.ValidationError = exports.FileSystemError = exports.NetworkError = exports.CLIError = void 0; exports.withTimeout = withTimeout; exports.handleError = handleError; exports.createAsyncCommand = createAsyncCommand; exports.setupStreamErrorHandlers = setupStreamErrorHandlers; const chalk_1 = __importDefault(require("chalk")); // Enhanced error types for better handling class CLIError extends Error { constructor(message, code = 'UNKNOWN_ERROR', exitCode = 1, originalError) { super(message); this.code = code; this.exitCode = exitCode; this.originalError = originalError; this.name = 'CLIError'; } } exports.CLIError = CLIError; class NetworkError extends CLIError { constructor(message, originalError) { super(message, 'NETWORK_ERROR', 1, originalError); this.name = 'NetworkError'; } } exports.NetworkError = NetworkError; class FileSystemError extends CLIError { constructor(message, originalError) { super(message, 'FILESYSTEM_ERROR', 1, originalError); this.name = 'FileSystemError'; } } exports.FileSystemError = FileSystemError; class ValidationError extends CLIError { constructor(message) { super(message, 'VALIDATION_ERROR', 1); this.name = 'ValidationError'; } } exports.ValidationError = ValidationError; // Signal handling to prevent terminal hanging class ProcessManager { constructor() { this.cleanup = []; this.isExiting = false; this.setupSignalHandlers(); } static getInstance() { if (!ProcessManager.instance) { ProcessManager.instance = new ProcessManager(); } return ProcessManager.instance; } addCleanup(fn) { this.cleanup.push(fn); } setupSignalHandlers() { const signals = ['SIGINT', 'SIGTERM', 'SIGQUIT']; signals.forEach(signal => { process.on(signal, () => { if (this.isExiting) return; this.isExiting = true; console.log(chalk_1.default.yellow(`\nReceived ${signal}, cleaning up...`)); // Run cleanup functions this.cleanup.forEach(fn => { try { fn(); } catch (error) { // Ignore cleanup errors } }); // Ensure terminal state is restored if (process.stdout.isTTY) { process.stdout.write('\x1b[?25h'); // Show cursor process.stdout.write('\x1b[0m'); // Reset colors } process.exit(0); }); }); // Handle uncaught exceptions process.on('uncaughtException', (error) => { console.error(chalk_1.default.red('Uncaught Exception:'), error); this.cleanup.forEach(fn => { try { fn(); } catch { } }); process.exit(1); }); // Handle unhandled promise rejections process.on('unhandledRejection', (reason, promise) => { console.error(chalk_1.default.red('Unhandled Rejection at:'), promise, 'reason:', reason); this.cleanup.forEach(fn => { try { fn(); } catch { } }); process.exit(1); }); // Handle EPIPE errors (broken pipe) to prevent terminal hanging process.stdout.on('error', (err) => { if (err.code === 'EPIPE') { // Broken pipe - terminal was closed, exit gracefully process.exit(0); } }); process.stderr.on('error', (err) => { if (err.code === 'EPIPE') { // Broken pipe - terminal was closed, exit gracefully process.exit(0); } }); } } // Initialize process manager const processManager = ProcessManager.getInstance(); exports.processManager = processManager; // Enhanced async operation wrapper with timeout and retry async function withTimeout(operation, timeoutMs = 30000, retries = 0) { const attemptOperation = async (attempt) => { return Promise.race([ operation(), new Promise((_, reject) => setTimeout(() => reject(new Error(`Operation timed out after ${timeoutMs}ms`)), timeoutMs)) ]); }; let lastError; for (let attempt = 0; attempt <= retries; attempt++) { try { return await attemptOperation(attempt); } catch (error) { lastError = error; if (attempt < retries) { // Exponential backoff const delay = Math.min(1000 * Math.pow(2, attempt), 5000); await new Promise(resolve => setTimeout(resolve, delay)); } } } throw lastError; } // Enhanced error handler with better UX function handleError(error, spinner) { if (spinner) { spinner.stop(); processManager.addCleanup(() => spinner.stop()); } // Ensure terminal state is restored if (process.stdout.isTTY) { process.stdout.write('\x1b[?25h'); // Show cursor process.stdout.write('\x1b[0m'); // Reset colors } if (error instanceof CLIError) { console.error(chalk_1.default.red(`Error: ${error.message}`)); if (error.originalError && process.env.DEBUG) { console.error(chalk_1.default.gray('Original error:'), error.originalError); } process.exit(error.exitCode); } else { console.error(chalk_1.default.red('Unexpected error:'), error.message); if (process.env.DEBUG) { console.error(chalk_1.default.gray('Stack trace:'), error.stack); } process.exit(1); } } // Async command wrapper with proper error handling function createAsyncCommand(fn) { return async (...args) => { let spinner; try { // Extract spinner from last argument if it exists const lastArg = args[args.length - 1]; if (lastArg && typeof lastArg === 'object' && 'spinner' in lastArg) { spinner = lastArg.spinner; processManager.addCleanup(() => spinner?.stop()); } await fn(...args); } catch (error) { handleError(error, spinner); } }; } // Stream error handler to prevent EPIPE and other stream issues function setupStreamErrorHandlers() { // Handle stdout/stderr errors gracefully const handleStreamError = (err) => { if (err.code === 'EPIPE') { // Broken pipe - terminal was closed, exit gracefully process.exit(0); } // For other stream errors, log but don't crash if (process.env.DEBUG) { console.error('Stream error:', err); } }; process.stdout.on('error', handleStreamError); process.stderr.on('error', handleStreamError); } // Initialize stream error handlers setupStreamErrorHandlers();