@tamyla/clodo-framework 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +564 -0
- package/LICENSE +21 -0
- package/README.md +1393 -0
- package/bin/README.md +71 -0
- package/bin/clodo-service.js +416 -0
- package/bin/security/security-cli.js +96 -0
- package/bin/service-management/README.md +74 -0
- package/bin/service-management/create-service.js +129 -0
- package/bin/service-management/init-service.js +102 -0
- package/bin/service-management/init-service.js.backup +889 -0
- package/bin/shared/config/customer-cli.js +293 -0
- package/dist/config/ConfigurationManager.js +159 -0
- package/dist/config/CustomerConfigCLI.js +220 -0
- package/dist/config/FeatureManager.js +426 -0
- package/dist/config/customers.js +441 -0
- package/dist/config/domains.js +180 -0
- package/dist/config/features.js +225 -0
- package/dist/config/index.js +6 -0
- package/dist/database/database-orchestrator.js +730 -0
- package/dist/database/index.js +4 -0
- package/dist/deployment/auditor.js +971 -0
- package/dist/deployment/index.js +10 -0
- package/dist/deployment/rollback-manager.js +523 -0
- package/dist/deployment/testers/api-tester.js +80 -0
- package/dist/deployment/testers/auth-tester.js +129 -0
- package/dist/deployment/testers/core.js +217 -0
- package/dist/deployment/testers/database-tester.js +105 -0
- package/dist/deployment/testers/index.js +74 -0
- package/dist/deployment/testers/load-tester.js +120 -0
- package/dist/deployment/testers/performance-tester.js +105 -0
- package/dist/deployment/validator.js +558 -0
- package/dist/deployment/wrangler-deployer.js +574 -0
- package/dist/handlers/GenericRouteHandler.js +532 -0
- package/dist/index.js +39 -0
- package/dist/migration/MigrationAdapters.js +562 -0
- package/dist/modules/ModuleManager.js +668 -0
- package/dist/modules/security.js +98 -0
- package/dist/orchestration/cross-domain-coordinator.js +1083 -0
- package/dist/orchestration/index.js +5 -0
- package/dist/orchestration/modules/DeploymentCoordinator.js +258 -0
- package/dist/orchestration/modules/DomainResolver.js +196 -0
- package/dist/orchestration/modules/StateManager.js +332 -0
- package/dist/orchestration/multi-domain-orchestrator.js +255 -0
- package/dist/routing/EnhancedRouter.js +158 -0
- package/dist/schema/SchemaManager.js +778 -0
- package/dist/security/ConfigurationValidator.js +490 -0
- package/dist/security/DeploymentManager.js +208 -0
- package/dist/security/SecretGenerator.js +142 -0
- package/dist/security/SecurityCLI.js +228 -0
- package/dist/security/index.js +51 -0
- package/dist/security/patterns/environment-rules.js +66 -0
- package/dist/security/patterns/insecure-patterns.js +21 -0
- package/dist/service-management/ConfirmationEngine.js +411 -0
- package/dist/service-management/ErrorTracker.js +294 -0
- package/dist/service-management/GenerationEngine.js +3109 -0
- package/dist/service-management/InputCollector.js +237 -0
- package/dist/service-management/ServiceCreator.js +229 -0
- package/dist/service-management/ServiceInitializer.js +448 -0
- package/dist/service-management/ServiceOrchestrator.js +638 -0
- package/dist/service-management/handlers/ConfigMutator.js +130 -0
- package/dist/service-management/handlers/ConfirmationHandler.js +71 -0
- package/dist/service-management/handlers/GenerationHandler.js +80 -0
- package/dist/service-management/handlers/InputHandler.js +59 -0
- package/dist/service-management/handlers/ValidationHandler.js +203 -0
- package/dist/service-management/index.js +7 -0
- package/dist/services/GenericDataService.js +488 -0
- package/dist/shared/cloudflare/domain-discovery.js +562 -0
- package/dist/shared/cloudflare/domain-manager.js +912 -0
- package/dist/shared/cloudflare/index.js +8 -0
- package/dist/shared/cloudflare/ops.js +387 -0
- package/dist/shared/config/cache.js +1167 -0
- package/dist/shared/config/command-config-manager.js +174 -0
- package/dist/shared/config/customer-cli.js +258 -0
- package/dist/shared/config/index.js +9 -0
- package/dist/shared/config/manager.js +289 -0
- package/dist/shared/database/connection-manager.js +338 -0
- package/dist/shared/database/index.js +7 -0
- package/dist/shared/database/orchestrator.js +632 -0
- package/dist/shared/deployment/auditor.js +971 -0
- package/dist/shared/deployment/index.js +10 -0
- package/dist/shared/deployment/rollback-manager.js +523 -0
- package/dist/shared/deployment/validator.js +558 -0
- package/dist/shared/index.js +32 -0
- package/dist/shared/monitoring/health-checker.js +250 -0
- package/dist/shared/monitoring/index.js +8 -0
- package/dist/shared/monitoring/memory-manager.js +382 -0
- package/dist/shared/monitoring/production-monitor.js +390 -0
- package/dist/shared/production-tester/api-tester.js +80 -0
- package/dist/shared/production-tester/auth-tester.js +129 -0
- package/dist/shared/production-tester/core.js +217 -0
- package/dist/shared/production-tester/database-tester.js +105 -0
- package/dist/shared/production-tester/index.js +74 -0
- package/dist/shared/production-tester/load-tester.js +120 -0
- package/dist/shared/production-tester/performance-tester.js +105 -0
- package/dist/shared/security/api-token-manager.js +296 -0
- package/dist/shared/security/index.js +8 -0
- package/dist/shared/security/secret-generator.js +918 -0
- package/dist/shared/security/secure-token-manager.js +379 -0
- package/dist/shared/utils/error-recovery.js +240 -0
- package/dist/shared/utils/graceful-shutdown-manager.js +380 -0
- package/dist/shared/utils/index.js +9 -0
- package/dist/shared/utils/interactive-prompts.js +134 -0
- package/dist/shared/utils/rate-limiter.js +249 -0
- package/dist/utils/ErrorHandler.js +173 -0
- package/dist/utils/deployment/config-cache.js +1160 -0
- package/dist/utils/deployment/index.js +6 -0
- package/dist/utils/deployment/interactive-prompts.js +97 -0
- package/dist/utils/deployment/secret-generator.js +896 -0
- package/dist/utils/dirname-helper.js +35 -0
- package/dist/utils/domain-config.js +159 -0
- package/dist/utils/error-recovery.js +240 -0
- package/dist/utils/esm-helper.js +52 -0
- package/dist/utils/framework-config.js +481 -0
- package/dist/utils/graceful-shutdown-manager.js +379 -0
- package/dist/utils/health-checker.js +114 -0
- package/dist/utils/index.js +36 -0
- package/dist/utils/prompt-handler.js +98 -0
- package/dist/utils/usage-tracker.js +252 -0
- package/dist/utils/validation.js +112 -0
- package/dist/version/VersionDetector.js +723 -0
- package/dist/worker/index.js +4 -0
- package/dist/worker/integration.js +332 -0
- package/docs/FRAMEWORK-ARCHITECTURE-OVERVIEW.md +206 -0
- package/docs/INTEGRATION_GUIDE.md +2045 -0
- package/docs/README.md +82 -0
- package/docs/SECURITY.md +242 -0
- package/docs/deployment/deployment-guide.md +540 -0
- package/docs/overview.md +280 -0
- package/package.json +176 -0
- package/types/index.d.ts +575 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graceful Shutdown Manager
|
|
3
|
+
* Implements SIGTERM/SIGINT handlers and cleanup routines for CLI tools
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class GracefulShutdownManager {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
this.config = null;
|
|
10
|
+
this.isShuttingDown = false;
|
|
11
|
+
this.shutdownHandlers = [];
|
|
12
|
+
this.shutdownPromise = null;
|
|
13
|
+
this.registered = false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Initialize with framework configuration
|
|
18
|
+
*/
|
|
19
|
+
async initialize() {
|
|
20
|
+
// Import framework config for consistent timing
|
|
21
|
+
const {
|
|
22
|
+
frameworkConfig
|
|
23
|
+
} = await import('../../../src/utils/framework-config.js');
|
|
24
|
+
const timing = frameworkConfig.getTiming();
|
|
25
|
+
this.config = {
|
|
26
|
+
shutdownTimeout: this.options.shutdownTimeout || timing.shutdownTimeout,
|
|
27
|
+
forceShutdownTimeout: this.options.forceShutdownTimeout || timing.forceShutdownTimeout,
|
|
28
|
+
enableLogging: this.options.enableLogging !== false,
|
|
29
|
+
...this.options
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Register the shutdown manager
|
|
35
|
+
*/
|
|
36
|
+
register() {
|
|
37
|
+
if (this.registered) return;
|
|
38
|
+
this.registered = true;
|
|
39
|
+
|
|
40
|
+
// Register signal handlers
|
|
41
|
+
process.on('SIGTERM', () => this.initiateShutdown('SIGTERM'));
|
|
42
|
+
process.on('SIGINT', () => this.initiateShutdown('SIGINT'));
|
|
43
|
+
|
|
44
|
+
// Handle uncaught exceptions and unhandled rejections
|
|
45
|
+
process.on('uncaughtException', error => {
|
|
46
|
+
console.error('💥 Uncaught exception:', error);
|
|
47
|
+
this.initiateShutdown('uncaughtException', error);
|
|
48
|
+
});
|
|
49
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
50
|
+
console.error('💥 Unhandled rejection at:', promise, 'reason:', reason);
|
|
51
|
+
this.initiateShutdown('unhandledRejection', reason);
|
|
52
|
+
});
|
|
53
|
+
if (this.config.enableLogging) {
|
|
54
|
+
console.log('🛑 Graceful shutdown manager registered');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Unregister the shutdown manager
|
|
60
|
+
*/
|
|
61
|
+
unregister() {
|
|
62
|
+
if (!this.registered) return;
|
|
63
|
+
this.registered = false;
|
|
64
|
+
process.removeAllListeners('SIGTERM');
|
|
65
|
+
process.removeAllListeners('SIGINT');
|
|
66
|
+
process.removeAllListeners('uncaughtException');
|
|
67
|
+
process.removeAllListeners('unhandledRejection');
|
|
68
|
+
if (this.config.enableLogging) {
|
|
69
|
+
console.log('🛑 Graceful shutdown manager unregistered');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Add a shutdown handler
|
|
75
|
+
*/
|
|
76
|
+
addShutdownHandler(handler, priority = 0) {
|
|
77
|
+
this.shutdownHandlers.push({
|
|
78
|
+
handler,
|
|
79
|
+
priority,
|
|
80
|
+
id: `handler_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Sort by priority (higher priority first)
|
|
84
|
+
this.shutdownHandlers.sort((a, b) => b.priority - a.priority);
|
|
85
|
+
|
|
86
|
+
// Return removal function
|
|
87
|
+
return () => {
|
|
88
|
+
const index = this.shutdownHandlers.findIndex(h => h.id === this.id);
|
|
89
|
+
if (index !== -1) {
|
|
90
|
+
this.shutdownHandlers.splice(index, 1);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Remove a shutdown handler
|
|
97
|
+
*/
|
|
98
|
+
removeShutdownHandler(handlerId) {
|
|
99
|
+
const index = this.shutdownHandlers.findIndex(h => h.id === handlerId);
|
|
100
|
+
if (index !== -1) {
|
|
101
|
+
this.shutdownHandlers.splice(index, 1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Initiate graceful shutdown
|
|
107
|
+
*/
|
|
108
|
+
async initiateShutdown(signal, error = null) {
|
|
109
|
+
if (this.isShuttingDown) return;
|
|
110
|
+
this.isShuttingDown = true;
|
|
111
|
+
if (this.config.enableLogging) {
|
|
112
|
+
console.log(`🛑 Initiating graceful shutdown: ${signal}`);
|
|
113
|
+
if (error) {
|
|
114
|
+
console.error('Shutdown triggered by error:', error);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Prevent new work from starting
|
|
119
|
+
this.preventNewWork();
|
|
120
|
+
|
|
121
|
+
// Execute shutdown handlers
|
|
122
|
+
await this.executeShutdownHandlers(signal, error);
|
|
123
|
+
|
|
124
|
+
// Force exit if handlers don't complete in time
|
|
125
|
+
this.forceExitIfNeeded(signal);
|
|
126
|
+
|
|
127
|
+
// Exit with appropriate code
|
|
128
|
+
const exitCode = error ? 1 : 0;
|
|
129
|
+
process.exit(exitCode);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Prevent new work from starting
|
|
134
|
+
*/
|
|
135
|
+
preventNewWork() {
|
|
136
|
+
// Set global shutdown flag
|
|
137
|
+
if (typeof global !== 'undefined') {
|
|
138
|
+
global.isShuttingDown = true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Prevent new database connections
|
|
142
|
+
if (typeof global !== 'undefined' && global.dbManagers) {
|
|
143
|
+
global.dbManagers.forEach(manager => {
|
|
144
|
+
if (typeof manager.preventNewConnections === 'function') {
|
|
145
|
+
manager.preventNewConnections();
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Execute shutdown handlers
|
|
153
|
+
*/
|
|
154
|
+
async executeShutdownHandlers(signal, error) {
|
|
155
|
+
const shutdownPromises = [];
|
|
156
|
+
for (const {
|
|
157
|
+
handler
|
|
158
|
+
} of this.shutdownHandlers) {
|
|
159
|
+
try {
|
|
160
|
+
const promise = handler(signal, error);
|
|
161
|
+
if (promise && typeof promise.then === 'function') {
|
|
162
|
+
shutdownPromises.push(promise);
|
|
163
|
+
}
|
|
164
|
+
} catch (handlerError) {
|
|
165
|
+
console.error('Error in shutdown handler:', handlerError);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Wait for all handlers to complete or timeout
|
|
170
|
+
if (shutdownPromises.length > 0) {
|
|
171
|
+
try {
|
|
172
|
+
await Promise.race([Promise.all(shutdownPromises), new Promise(resolve => setTimeout(resolve, this.config.shutdownTimeout))]);
|
|
173
|
+
} catch (timeoutError) {
|
|
174
|
+
console.error('Shutdown handlers timed out:', timeoutError);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Force exit if needed
|
|
181
|
+
*/
|
|
182
|
+
forceExitIfNeeded(signal) {
|
|
183
|
+
setTimeout(() => {
|
|
184
|
+
console.error(`💥 Force exiting after ${this.config.forceShutdownTimeout}ms`);
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}, this.config.forceShutdownTimeout).unref();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Create a shutdown-aware interval
|
|
191
|
+
*/
|
|
192
|
+
createShutdownAwareInterval(callback, delay) {
|
|
193
|
+
const intervalId = setInterval(() => {
|
|
194
|
+
if (this.isShuttingDown) {
|
|
195
|
+
clearInterval(intervalId);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
callback();
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.error('Error in shutdown-aware interval:', error);
|
|
202
|
+
clearInterval(intervalId);
|
|
203
|
+
}
|
|
204
|
+
}, delay);
|
|
205
|
+
intervalId.unref?.();
|
|
206
|
+
|
|
207
|
+
// Add cleanup handler
|
|
208
|
+
this.addShutdownHandler(() => {
|
|
209
|
+
clearInterval(intervalId);
|
|
210
|
+
}, 100); // High priority
|
|
211
|
+
|
|
212
|
+
return intervalId;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Create a shutdown-aware timeout
|
|
217
|
+
*/
|
|
218
|
+
createShutdownAwareTimeout(callback, delay) {
|
|
219
|
+
const timeoutId = setTimeout(() => {
|
|
220
|
+
if (this.isShuttingDown) return;
|
|
221
|
+
try {
|
|
222
|
+
callback();
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.error('Error in shutdown-aware timeout:', error);
|
|
225
|
+
}
|
|
226
|
+
}, delay);
|
|
227
|
+
timeoutId.unref?.();
|
|
228
|
+
|
|
229
|
+
// Add cleanup handler
|
|
230
|
+
this.addShutdownHandler(() => {
|
|
231
|
+
clearTimeout(timeoutId);
|
|
232
|
+
}, 100); // High priority
|
|
233
|
+
|
|
234
|
+
return timeoutId;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Wait for shutdown signal
|
|
239
|
+
*/
|
|
240
|
+
async waitForShutdownSignal() {
|
|
241
|
+
return new Promise(resolve => {
|
|
242
|
+
const handler = signal => {
|
|
243
|
+
resolve(signal);
|
|
244
|
+
};
|
|
245
|
+
process.once('SIGTERM', () => handler('SIGTERM'));
|
|
246
|
+
process.once('SIGINT', () => handler('SIGINT'));
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Get shutdown status
|
|
252
|
+
*/
|
|
253
|
+
getShutdownStatus() {
|
|
254
|
+
return {
|
|
255
|
+
isShuttingDown: this.isShuttingDown,
|
|
256
|
+
handlerCount: this.shutdownHandlers.length,
|
|
257
|
+
registered: this.registered,
|
|
258
|
+
config: this.config
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Create database cleanup handler
|
|
264
|
+
*/
|
|
265
|
+
createDatabaseCleanupHandler(dbManager) {
|
|
266
|
+
return async signal => {
|
|
267
|
+
if (dbManager && typeof dbManager.closeAllConnections === 'function') {
|
|
268
|
+
console.log('🗃️ Closing database connections...');
|
|
269
|
+
await dbManager.closeAllConnections();
|
|
270
|
+
console.log('✅ Database connections closed');
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Create monitoring cleanup handler
|
|
277
|
+
*/
|
|
278
|
+
createMonitoringCleanupHandler(monitor) {
|
|
279
|
+
return async signal => {
|
|
280
|
+
if (monitor && typeof monitor.stopMonitoring === 'function') {
|
|
281
|
+
console.log('📊 Stopping monitoring...');
|
|
282
|
+
await monitor.stopMonitoring();
|
|
283
|
+
console.log('✅ Monitoring stopped');
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Create token cleanup handler
|
|
290
|
+
*/
|
|
291
|
+
createTokenCleanupHandler(tokenManager) {
|
|
292
|
+
return async signal => {
|
|
293
|
+
// Token manager doesn't need special cleanup, but we could log the event
|
|
294
|
+
console.log('🔐 Token manager cleanup completed');
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Setup standard handlers for common services
|
|
300
|
+
*/
|
|
301
|
+
setupStandardHandlers(services = {}) {
|
|
302
|
+
const {
|
|
303
|
+
dbManager,
|
|
304
|
+
monitor,
|
|
305
|
+
tokenManager,
|
|
306
|
+
memoryManager
|
|
307
|
+
} = services;
|
|
308
|
+
|
|
309
|
+
// Database cleanup (high priority)
|
|
310
|
+
if (dbManager) {
|
|
311
|
+
this.addShutdownHandler(this.createDatabaseCleanupHandler(dbManager), 1000);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Monitoring cleanup (medium priority)
|
|
315
|
+
if (monitor) {
|
|
316
|
+
this.addShutdownHandler(this.createMonitoringCleanupHandler(monitor), 500);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Memory cleanup (medium priority)
|
|
320
|
+
if (memoryManager) {
|
|
321
|
+
this.addShutdownHandler(async signal => {
|
|
322
|
+
console.log('🧠 Stopping memory monitoring...');
|
|
323
|
+
memoryManager.stopMonitoring();
|
|
324
|
+
console.log('✅ Memory monitoring stopped');
|
|
325
|
+
}, 500);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Token cleanup (low priority)
|
|
329
|
+
if (tokenManager) {
|
|
330
|
+
this.addShutdownHandler(this.createTokenCleanupHandler(tokenManager), 100);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Final cleanup (lowest priority)
|
|
334
|
+
this.addShutdownHandler(async signal => {
|
|
335
|
+
console.log('🧹 Running final cleanup...');
|
|
336
|
+
// Force garbage collection if available
|
|
337
|
+
if (global.gc) {
|
|
338
|
+
global.gc();
|
|
339
|
+
}
|
|
340
|
+
console.log('✅ Final cleanup completed');
|
|
341
|
+
}, 1);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Global shutdown manager instance
|
|
346
|
+
let globalShutdownManager = null;
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Get the global shutdown manager instance
|
|
350
|
+
*/
|
|
351
|
+
export function getShutdownManager(options = {}) {
|
|
352
|
+
if (!globalShutdownManager) {
|
|
353
|
+
globalShutdownManager = new GracefulShutdownManager(options);
|
|
354
|
+
}
|
|
355
|
+
return globalShutdownManager;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Initialize graceful shutdown handling
|
|
360
|
+
*/
|
|
361
|
+
export async function initializeGracefulShutdown(services = {}, options = {}) {
|
|
362
|
+
const manager = getShutdownManager(options);
|
|
363
|
+
await manager.initialize();
|
|
364
|
+
manager.setupStandardHandlers(services);
|
|
365
|
+
manager.register();
|
|
366
|
+
return manager;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Quick shutdown for simple scripts
|
|
371
|
+
*/
|
|
372
|
+
export async function withGracefulShutdown(callback, services = {}, options = {}) {
|
|
373
|
+
const manager = initializeGracefulShutdown(services, options);
|
|
374
|
+
try {
|
|
375
|
+
await callback();
|
|
376
|
+
} catch (error) {
|
|
377
|
+
console.error('Error in main execution:', error);
|
|
378
|
+
await manager.initiateShutdown('error', error);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities Module
|
|
3
|
+
* Exports all general utility functions and managers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { InteractivePrompts } from './interactive-prompts.js';
|
|
7
|
+
export { ErrorRecovery } from './error-recovery.js';
|
|
8
|
+
export { GracefulShutdownManager } from './graceful-shutdown-manager.js';
|
|
9
|
+
export { RateLimiter } from './rate-limiter.js';
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Prompts Module
|
|
3
|
+
* Standardized user input functions for all scripts
|
|
4
|
+
*
|
|
5
|
+
* Replaces duplicate prompt code across 10+ scripts
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import readline from 'readline';
|
|
9
|
+
|
|
10
|
+
// Create readline interface (singleton)
|
|
11
|
+
let rl = null;
|
|
12
|
+
function getReadlineInterface() {
|
|
13
|
+
if (!rl) {
|
|
14
|
+
rl = readline.createInterface({
|
|
15
|
+
input: process.stdin,
|
|
16
|
+
output: process.stdout
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return rl;
|
|
20
|
+
}
|
|
21
|
+
export function askUser(question, defaultValue = null) {
|
|
22
|
+
return new Promise(resolve => {
|
|
23
|
+
const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
|
|
24
|
+
getReadlineInterface().question(prompt, answer => {
|
|
25
|
+
resolve(answer.trim() || defaultValue);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export function askYesNo(question, defaultValue = 'n') {
|
|
30
|
+
return new Promise(resolve => {
|
|
31
|
+
const prompt = `${question} [y/N]: `;
|
|
32
|
+
getReadlineInterface().question(prompt, answer => {
|
|
33
|
+
const response = answer.trim().toLowerCase() || defaultValue;
|
|
34
|
+
resolve(response === 'y' || response === 'yes');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export function askChoice(question, choices, defaultIndex = 0) {
|
|
39
|
+
return new Promise(resolve => {
|
|
40
|
+
console.log(`\n${question}`);
|
|
41
|
+
choices.forEach((choice, index) => {
|
|
42
|
+
const marker = index === defaultIndex ? '>' : ' ';
|
|
43
|
+
console.log(`${marker} ${index + 1}. ${choice}`);
|
|
44
|
+
});
|
|
45
|
+
getReadlineInterface().question(`\nSelect option [1-${choices.length}]: `, answer => {
|
|
46
|
+
const choice = parseInt(answer) - 1;
|
|
47
|
+
if (isNaN(choice) || choice < 0 || choice >= choices.length) {
|
|
48
|
+
resolve(defaultIndex);
|
|
49
|
+
} else {
|
|
50
|
+
resolve(choice);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
export function askMultiChoice(question, choices, defaultIndices = []) {
|
|
56
|
+
return new Promise(resolve => {
|
|
57
|
+
console.log(`\n${question}`);
|
|
58
|
+
console.log('(Enter comma-separated numbers, e.g., "1,3,5")');
|
|
59
|
+
choices.forEach((choice, index) => {
|
|
60
|
+
const marker = defaultIndices.includes(index) ? '>' : ' ';
|
|
61
|
+
console.log(`${marker} ${index + 1}. ${choice}`);
|
|
62
|
+
});
|
|
63
|
+
const defaultStr = defaultIndices.map(i => i + 1).join(',');
|
|
64
|
+
getReadlineInterface().question(`\nSelect options [${defaultStr}]: `, answer => {
|
|
65
|
+
if (!answer.trim()) {
|
|
66
|
+
resolve(defaultIndices);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const selected = answer.split(',').map(s => parseInt(s.trim()) - 1).filter(i => !isNaN(i) && i >= 0 && i < choices.length);
|
|
70
|
+
resolve(selected.length > 0 ? selected : defaultIndices);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
export function closePrompts() {
|
|
75
|
+
if (rl) {
|
|
76
|
+
rl.close();
|
|
77
|
+
rl = null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Utility function for progress indicators
|
|
82
|
+
export function showProgress(message, steps = ['⏳', '⚡', '✅']) {
|
|
83
|
+
return new Promise(resolve => {
|
|
84
|
+
let stepIndex = 0;
|
|
85
|
+
const interval = setInterval(() => {
|
|
86
|
+
process.stdout.write(`\r${steps[stepIndex]} ${message}`);
|
|
87
|
+
stepIndex = (stepIndex + 1) % steps.length;
|
|
88
|
+
}, 500);
|
|
89
|
+
|
|
90
|
+
// Auto-resolve after 2 seconds or when manually resolved
|
|
91
|
+
setTimeout(() => {
|
|
92
|
+
clearInterval(interval);
|
|
93
|
+
process.stdout.write(`\r✅ ${message}\n`);
|
|
94
|
+
resolve();
|
|
95
|
+
}, 2000);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Ask for sensitive input (like API tokens) with hidden input
|
|
101
|
+
*/
|
|
102
|
+
export function askPassword(question) {
|
|
103
|
+
return new Promise(resolve => {
|
|
104
|
+
const prompt = `${question}: `;
|
|
105
|
+
process.stdout.write(prompt);
|
|
106
|
+
|
|
107
|
+
// Hide input for sensitive data
|
|
108
|
+
process.stdin.setRawMode(true);
|
|
109
|
+
process.stdin.resume();
|
|
110
|
+
let password = '';
|
|
111
|
+
const onData = char => {
|
|
112
|
+
const charCode = char[0];
|
|
113
|
+
if (charCode === 13) {
|
|
114
|
+
// Enter key
|
|
115
|
+
process.stdin.setRawMode(false);
|
|
116
|
+
process.stdin.pause();
|
|
117
|
+
process.stdin.removeListener('data', onData);
|
|
118
|
+
process.stdout.write('\n');
|
|
119
|
+
resolve(password);
|
|
120
|
+
} else if (charCode === 127 || charCode === 8) {
|
|
121
|
+
// Backspace
|
|
122
|
+
if (password.length > 0) {
|
|
123
|
+
password = password.slice(0, -1);
|
|
124
|
+
process.stdout.write('\b \b');
|
|
125
|
+
}
|
|
126
|
+
} else if (charCode >= 32 && charCode <= 126) {
|
|
127
|
+
// Printable characters
|
|
128
|
+
password += char.toString();
|
|
129
|
+
process.stdout.write('*');
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
process.stdin.on('data', onData);
|
|
133
|
+
});
|
|
134
|
+
}
|