@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
553 lines (552 loc) • 20.1 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ResourceManager = exports.ResourcePriority = exports.ResourceType = void 0;
exports.createResourceManager = createResourceManager;
exports.getGlobalResourceManager = getGlobalResourceManager;
exports.setGlobalResourceManager = setGlobalResourceManager;
const events_1 = require("events");
const fs = __importStar(require("fs-extra"));
var ResourceType;
(function (ResourceType) {
ResourceType["FILE_HANDLE"] = "file_handle";
ResourceType["STREAM"] = "stream";
ResourceType["NETWORK_CONNECTION"] = "network_connection";
ResourceType["CHILD_PROCESS"] = "child_process";
ResourceType["TIMER"] = "timer";
ResourceType["EVENT_LISTENER"] = "event_listener";
ResourceType["TEMPORARY_FILE"] = "temporary_file";
ResourceType["TEMPORARY_DIRECTORY"] = "temporary_directory";
ResourceType["CACHE_ENTRY"] = "cache_entry";
ResourceType["MEMORY_BUFFER"] = "memory_buffer";
ResourceType["DATABASE_CONNECTION"] = "database_connection";
ResourceType["CUSTOM"] = "custom";
})(ResourceType || (exports.ResourceType = ResourceType = {}));
var ResourcePriority;
(function (ResourcePriority) {
ResourcePriority[ResourcePriority["LOW"] = 0] = "LOW";
ResourcePriority[ResourcePriority["NORMAL"] = 1] = "NORMAL";
ResourcePriority[ResourcePriority["HIGH"] = 2] = "HIGH";
ResourcePriority[ResourcePriority["CRITICAL"] = 3] = "CRITICAL";
})(ResourcePriority || (exports.ResourcePriority = ResourcePriority = {}));
class ResourceManager extends events_1.EventEmitter {
constructor() {
super();
this.resources = new Map();
this.stateSnapshots = new Map();
this.recoveryPlans = new Map();
this.activeOperations = new Map();
this.cleanupInProgress = false;
this.shutdownHooksRegistered = false;
this.registerShutdownHooks();
}
registerShutdownHooks() {
if (this.shutdownHooksRegistered)
return;
process.on('exit', () => {
this.performEmergencyCleanup();
});
process.on('SIGINT', () => {
this.performGracefulShutdown().then(() => process.exit(0));
});
process.on('SIGTERM', () => {
this.performGracefulShutdown().then(() => process.exit(0));
});
process.on('uncaughtException', (error) => {
console.error('Uncaught exception, performing emergency cleanup:', error);
this.performEmergencyCleanup();
process.exit(1);
});
process.on('unhandledRejection', (reason) => {
console.error('Unhandled rejection, performing emergency cleanup:', reason);
this.performEmergencyCleanup();
process.exit(1);
});
this.shutdownHooksRegistered = true;
}
registerResource(type, resource, cleanup, options = {}) {
const id = this.generateResourceId();
const now = new Date();
const handle = {
id,
type,
resource,
metadata: {
created: now,
lastAccessed: now,
size: options.size,
tags: options.tags || [],
priority: options.priority || ResourcePriority.NORMAL
},
cleanup
};
this.resources.set(id, handle);
this.emit('resource:registered', handle);
return id;
}
unregisterResource(id) {
const handle = this.resources.get(id);
if (handle) {
this.resources.delete(id);
this.emit('resource:unregistered', handle);
return true;
}
return false;
}
async cleanupResource(id, force = false) {
const handle = this.resources.get(id);
if (!handle)
return false;
try {
this.emit('resource:cleanup:start', handle);
if (force) {
await this.forceCleanupResource(handle);
}
else {
await this.gracefulCleanupResource(handle);
}
this.resources.delete(id);
this.emit('resource:cleanup:success', handle);
return true;
}
catch (error) {
this.emit('resource:cleanup:error', { handle, error });
throw error;
}
}
async gracefulCleanupResource(handle) {
const timeout = this.getTimeoutForPriority(handle.metadata.priority);
await this.withTimeout(async () => {
await handle.cleanup();
}, timeout);
}
async forceCleanupResource(handle) {
try {
// Try graceful cleanup first with shorter timeout
await this.withTimeout(async () => {
await handle.cleanup();
}, 1000);
}
catch (error) {
// Force cleanup for specific resource types
await this.performForceCleanup(handle);
}
}
async performForceCleanup(handle) {
switch (handle.type) {
case ResourceType.FILE_HANDLE:
if (handle.resource && typeof handle.resource.close === 'function') {
handle.resource.close();
}
break;
case ResourceType.STREAM:
if (handle.resource && typeof handle.resource.destroy === 'function') {
handle.resource.destroy();
}
break;
case ResourceType.CHILD_PROCESS:
if (handle.resource && typeof handle.resource.kill === 'function') {
handle.resource.kill('SIGKILL');
}
break;
case ResourceType.TIMER:
if (handle.resource) {
clearTimeout(handle.resource);
clearInterval(handle.resource);
}
break;
case ResourceType.TEMPORARY_FILE:
try {
await fs.unlink(handle.resource);
}
catch {
// Ignore errors
}
break;
case ResourceType.TEMPORARY_DIRECTORY:
try {
await fs.remove(handle.resource);
}
catch {
// Ignore errors
}
break;
default:
// Custom cleanup
try {
await handle.cleanup();
}
catch {
// Ignore errors in force mode
}
break;
}
}
getTimeoutForPriority(priority) {
switch (priority) {
case ResourcePriority.LOW: return 1000;
case ResourcePriority.NORMAL: return 5000;
case ResourcePriority.HIGH: return 10000;
case ResourcePriority.CRITICAL: return 30000;
default: return 5000;
}
}
async cleanupByType(type, options = {}) {
const handles = Array.from(this.resources.values())
.filter(h => h.type === type);
if (options.dryRun) {
this.emit('cleanup:dry_run', { type, count: handles.length });
return handles.length;
}
let cleaned = 0;
for (const handle of handles) {
try {
await this.cleanupResource(handle.id);
cleaned++;
}
catch (error) {
this.emit('cleanup:error', { handle, error });
}
}
return cleaned;
}
async cleanupByTags(tags, options = {}) {
const handles = Array.from(this.resources.values())
.filter(h => h.metadata.tags && tags.some(tag => h.metadata.tags.includes(tag)));
if (options.dryRun) {
this.emit('cleanup:dry_run', { tags, count: handles.length });
return handles.length;
}
let cleaned = 0;
for (const handle of handles) {
try {
await this.cleanupResource(handle.id);
cleaned++;
}
catch (error) {
this.emit('cleanup:error', { handle, error });
}
}
return cleaned;
}
async cleanupAll(options = {}) {
if (this.cleanupInProgress) {
return 0;
}
this.cleanupInProgress = true;
const defaultOptions = {
gracefulTimeout: 30000,
forceTimeout: 5000,
priority: ResourcePriority.LOW,
preserveTypes: [],
dryRun: false
};
const opts = { ...defaultOptions, ...options };
try {
const handles = Array.from(this.resources.values())
.filter(h => !opts.preserveTypes.includes(h.type))
.filter(h => h.metadata.priority >= opts.priority)
.sort((a, b) => b.metadata.priority - a.metadata.priority);
if (opts.dryRun) {
this.emit('cleanup:dry_run', { count: handles.length });
return handles.length;
}
this.emit('cleanup:start', { count: handles.length });
// Phase 1: Graceful cleanup
let cleaned = 0;
const gracefulPromises = handles.map(async (handle) => {
try {
await this.withTimeout(async () => {
await this.cleanupResource(handle.id);
}, opts.gracefulTimeout);
cleaned++;
}
catch (error) {
this.emit('cleanup:graceful_failed', { handle, error });
}
});
await Promise.allSettled(gracefulPromises);
// Phase 2: Force cleanup remaining resources
const remainingHandles = Array.from(this.resources.values())
.filter(h => !opts.preserveTypes.includes(h.type));
if (remainingHandles.length > 0) {
this.emit('cleanup:force_start', { count: remainingHandles.length });
const forcePromises = remainingHandles.map(async (handle) => {
try {
await this.cleanupResource(handle.id, true);
cleaned++;
}
catch (error) {
this.emit('cleanup:force_failed', { handle, error });
}
});
await Promise.allSettled(forcePromises);
}
this.emit('cleanup:complete', { cleaned });
return cleaned;
}
finally {
this.cleanupInProgress = false;
}
}
performEmergencyCleanup() {
const handles = Array.from(this.resources.values());
for (const handle of handles) {
try {
if (typeof handle.cleanup === 'function') {
const result = handle.cleanup();
if (result && typeof result.then === 'function') {
// Don't wait for async cleanup in emergency
result.catch(() => { });
}
}
}
catch {
// Ignore errors in emergency cleanup
}
}
this.resources.clear();
}
async performGracefulShutdown() {
try {
await this.cleanupAll({
gracefulTimeout: 10000,
forceTimeout: 2000,
priority: ResourcePriority.LOW
});
}
catch (error) {
console.error('Error during graceful shutdown:', error);
this.performEmergencyCleanup();
}
}
// State management
createSnapshot(operation, state) {
const id = this.generateSnapshotId();
const resources = Array.from(this.resources.keys());
const checksum = this.calculateChecksum(state);
const snapshot = {
id,
timestamp: new Date(),
operation,
state: this.deepClone(state),
resources,
checksum
};
this.stateSnapshots.set(id, snapshot);
this.emit('snapshot:created', snapshot);
// Keep only recent snapshots
this.cleanupOldSnapshots();
return id;
}
restoreSnapshot(id) {
const snapshot = this.stateSnapshots.get(id);
if (!snapshot) {
throw new Error(`Snapshot ${id} not found`);
}
// Verify checksum
const currentChecksum = this.calculateChecksum(snapshot.state);
if (currentChecksum !== snapshot.checksum) {
throw new Error(`Snapshot ${id} is corrupted`);
}
this.emit('snapshot:restored', snapshot);
return this.deepClone(snapshot.state);
}
createRecoveryPlan(operation, steps) {
const id = this.generateRecoveryPlanId();
const resources = Array.from(this.resources.keys());
const estimatedDuration = steps.reduce((total, step) => total + step.timeout, 0);
const plan = {
id,
operation,
steps,
rollbackSteps: steps.filter(s => s.rollback).reverse(),
resources,
metadata: {
created: new Date(),
estimatedDuration,
dependencies: []
}
};
this.recoveryPlans.set(id, plan);
this.emit('recovery_plan:created', plan);
return id;
}
async executeRecoveryPlan(id) {
const plan = this.recoveryPlans.get(id);
if (!plan) {
throw new Error(`Recovery plan ${id} not found`);
}
this.emit('recovery:start', plan);
const executedSteps = [];
try {
for (const step of plan.steps) {
this.emit('recovery:step:start', step);
try {
await this.withTimeout(step.action, step.timeout);
executedSteps.push(step);
this.emit('recovery:step:success', step);
}
catch (error) {
this.emit('recovery:step:error', { step, error });
if (step.critical) {
throw new Error(`Critical recovery step failed: ${step.description}`);
}
}
}
this.emit('recovery:success', plan);
}
catch (error) {
this.emit('recovery:failure', { plan, error });
// Execute rollback
await this.executeRollback(executedSteps);
throw error;
}
}
async executeRollback(executedSteps) {
this.emit('rollback:start', { steps: executedSteps.length });
for (const step of executedSteps.reverse()) {
if (step.rollback) {
try {
await this.withTimeout(step.rollback, step.timeout);
this.emit('rollback:step:success', step);
}
catch (error) {
this.emit('rollback:step:error', { step, error });
}
}
}
this.emit('rollback:complete');
}
// Utility methods
generateResourceId() {
return `res_${Date.now()}_${Math.random().toString(36).substr(2, 8)}`;
}
generateSnapshotId() {
return `snap_${Date.now()}_${Math.random().toString(36).substr(2, 8)}`;
}
generateRecoveryPlanId() {
return `plan_${Date.now()}_${Math.random().toString(36).substr(2, 8)}`;
}
calculateChecksum(data) {
const crypto = require('crypto');
return crypto.createHash('sha256').update(JSON.stringify(data)).digest('hex');
}
deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
}
cleanupOldSnapshots() {
const snapshots = Array.from(this.stateSnapshots.entries())
.sort(([, a], [, b]) => b.timestamp.getTime() - a.timestamp.getTime());
// Keep only last 100 snapshots
if (snapshots.length > 100) {
const toDelete = snapshots.slice(100);
for (const [id] of toDelete) {
this.stateSnapshots.delete(id);
}
}
}
async withTimeout(operation, timeoutMs) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error(`Operation timed out after ${timeoutMs}ms`));
}, timeoutMs);
operation()
.then(result => {
clearTimeout(timer);
resolve(result);
})
.catch(error => {
clearTimeout(timer);
reject(error);
});
});
}
// Query methods
getResourceCount() {
return this.resources.size;
}
getResourcesByType(type) {
return Array.from(this.resources.values())
.filter(h => h.type === type);
}
getResourcesByTags(tags) {
return Array.from(this.resources.values())
.filter(h => h.metadata.tags && tags.some(tag => h.metadata.tags.includes(tag)));
}
getResourceStats() {
const resources = Array.from(this.resources.values());
const byType = {};
const byPriority = {};
let totalSize = 0;
for (const resource of resources) {
byType[resource.type] = (byType[resource.type] || 0) + 1;
byPriority[ResourcePriority[resource.metadata.priority]] =
(byPriority[ResourcePriority[resource.metadata.priority]] || 0) + 1;
if (resource.metadata.size) {
totalSize += resource.metadata.size;
}
}
return {
total: resources.length,
byType,
byPriority,
totalSize
};
}
getSnapshots() {
return Array.from(this.stateSnapshots.values())
.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
}
getRecoveryPlans() {
return Array.from(this.recoveryPlans.values())
.sort((a, b) => b.metadata.created.getTime() - a.metadata.created.getTime());
}
}
exports.ResourceManager = ResourceManager;
// Global resource manager
let globalResourceManager = null;
function createResourceManager() {
return new ResourceManager();
}
function getGlobalResourceManager() {
if (!globalResourceManager) {
globalResourceManager = new ResourceManager();
}
return globalResourceManager;
}
function setGlobalResourceManager(manager) {
globalResourceManager = manager;
}