@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
JavaScript
;
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();