@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,379 @@
|
|
|
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('./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 function initializeGracefulShutdown(services = {}, options = {}) {
|
|
362
|
+
const manager = getShutdownManager(options);
|
|
363
|
+
manager.setupStandardHandlers(services);
|
|
364
|
+
manager.register();
|
|
365
|
+
return manager;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Quick shutdown for simple scripts
|
|
370
|
+
*/
|
|
371
|
+
export async function withGracefulShutdown(callback, services = {}, options = {}) {
|
|
372
|
+
const manager = initializeGracefulShutdown(services, options);
|
|
373
|
+
try {
|
|
374
|
+
await callback();
|
|
375
|
+
} catch (error) {
|
|
376
|
+
console.error('Error in main execution:', error);
|
|
377
|
+
await manager.initiateShutdown('error', error);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health Checker Module
|
|
3
|
+
* Endpoint health checking and validation utilities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import https from 'https';
|
|
7
|
+
import http from 'http';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Make HTTP request with timeout
|
|
11
|
+
* @param {string} url - URL to request
|
|
12
|
+
* @param {string} method - HTTP method
|
|
13
|
+
* @param {number} timeout - Request timeout in ms
|
|
14
|
+
* @returns {Promise<Object>} Response data and status
|
|
15
|
+
*/
|
|
16
|
+
function makeHttpRequest(url, method = 'GET', timeout = 5000) {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const protocol = url.startsWith('https:') ? https : http;
|
|
19
|
+
const req = protocol.request(url, {
|
|
20
|
+
method,
|
|
21
|
+
timeout
|
|
22
|
+
}, res => {
|
|
23
|
+
let data = '';
|
|
24
|
+
res.on('data', chunk => data += chunk);
|
|
25
|
+
res.on('end', () => {
|
|
26
|
+
resolve({
|
|
27
|
+
data,
|
|
28
|
+
statusCode: res.statusCode
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
req.on('error', err => {
|
|
33
|
+
reject(err);
|
|
34
|
+
});
|
|
35
|
+
req.on('timeout', () => {
|
|
36
|
+
req.destroy();
|
|
37
|
+
reject(new Error('Request timed out'));
|
|
38
|
+
});
|
|
39
|
+
req.end();
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check health of a deployment endpoint
|
|
45
|
+
* @param {string} url - Base URL to check
|
|
46
|
+
* @param {number} timeout - Health check timeout in ms
|
|
47
|
+
* @returns {Promise<Object>} Health check result
|
|
48
|
+
*/
|
|
49
|
+
export async function checkHealth(url, timeout = 10000) {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const protocol = url.startsWith('https:') ? https : http;
|
|
52
|
+
const healthUrl = `${url.replace(/\/$/, '')}/health`;
|
|
53
|
+
const req = protocol.get(healthUrl, {
|
|
54
|
+
timeout
|
|
55
|
+
}, res => {
|
|
56
|
+
let data = '';
|
|
57
|
+
res.on('data', chunk => data += chunk);
|
|
58
|
+
res.on('end', () => {
|
|
59
|
+
try {
|
|
60
|
+
const result = {
|
|
61
|
+
url: healthUrl,
|
|
62
|
+
status: res.statusCode,
|
|
63
|
+
healthy: res.statusCode >= 200 && res.statusCode < 300,
|
|
64
|
+
responseTime: Date.now() - req.startTime,
|
|
65
|
+
data: data ? JSON.parse(data) : null
|
|
66
|
+
};
|
|
67
|
+
resolve(result);
|
|
68
|
+
} catch (parseError) {
|
|
69
|
+
resolve({
|
|
70
|
+
url: healthUrl,
|
|
71
|
+
status: res.statusCode,
|
|
72
|
+
healthy: res.statusCode >= 200 && res.statusCode < 300,
|
|
73
|
+
responseTime: Date.now() - req.startTime,
|
|
74
|
+
data: null,
|
|
75
|
+
parseError: parseError.message
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
req.startTime = Date.now();
|
|
81
|
+
req.on('error', err => {
|
|
82
|
+
reject({
|
|
83
|
+
url: healthUrl,
|
|
84
|
+
error: err.message,
|
|
85
|
+
healthy: false,
|
|
86
|
+
responseTime: Date.now() - req.startTime
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
req.on('timeout', () => {
|
|
90
|
+
req.destroy();
|
|
91
|
+
reject({
|
|
92
|
+
url: healthUrl,
|
|
93
|
+
error: 'Health check timed out',
|
|
94
|
+
healthy: false,
|
|
95
|
+
responseTime: timeout
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Check if URL is accessible
|
|
103
|
+
* @param {string} url - URL to check
|
|
104
|
+
* @param {number} timeout - Timeout in ms
|
|
105
|
+
* @returns {Promise<boolean>} Whether URL is accessible
|
|
106
|
+
*/
|
|
107
|
+
export async function isUrlAccessible(url, timeout = 5000) {
|
|
108
|
+
try {
|
|
109
|
+
const result = await makeHttpRequest(url, 'HEAD', timeout);
|
|
110
|
+
return result.statusCode >= 200 && result.statusCode < 400;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Utility Functions
|
|
2
|
+
// Common utilities used across the framework
|
|
3
|
+
|
|
4
|
+
export const deepMerge = (target, source) => {
|
|
5
|
+
const result = {
|
|
6
|
+
...target
|
|
7
|
+
};
|
|
8
|
+
for (const key in source) {
|
|
9
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
10
|
+
result[key] = deepMerge(result[key] || {}, source[key]);
|
|
11
|
+
} else {
|
|
12
|
+
result[key] = source[key];
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return result;
|
|
16
|
+
};
|
|
17
|
+
export const validateRequired = (obj, requiredFields) => {
|
|
18
|
+
const missing = requiredFields.filter(field => !obj[field]);
|
|
19
|
+
if (missing.length > 0) {
|
|
20
|
+
throw new Error(`Missing required fields: ${missing.join(', ')}`);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
export const createLogger = (prefix = 'ClodoFramework') => {
|
|
24
|
+
return {
|
|
25
|
+
info: (message, ...args) => console.log(`[${prefix}] ${message}`, ...args),
|
|
26
|
+
warn: (message, ...args) => console.warn(`[${prefix}] ${message}`, ...args),
|
|
27
|
+
error: (message, ...args) => console.error(`[${prefix}] ${message}`, ...args),
|
|
28
|
+
debug: (message, ...args) => console.debug(`[${prefix}] ${message}`, ...args)
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Health checking utilities
|
|
33
|
+
export * from './health-checker.js';
|
|
34
|
+
|
|
35
|
+
// Deployment utilities
|
|
36
|
+
export * from './deployment/index.js';
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Prompt Utilities
|
|
3
|
+
* Common prompt handling functionality used across the framework
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import readline from 'readline/promises';
|
|
7
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Base prompt handler for interactive user input
|
|
11
|
+
*/
|
|
12
|
+
export class PromptHandler {
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this.interactive = options.interactive !== false;
|
|
15
|
+
this.rl = null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Initialize readline interface
|
|
20
|
+
*/
|
|
21
|
+
async initialize() {
|
|
22
|
+
if (this.interactive && !this.rl) {
|
|
23
|
+
this.rl = readline.createInterface({
|
|
24
|
+
input,
|
|
25
|
+
output
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Prompt user for input
|
|
32
|
+
*/
|
|
33
|
+
async prompt(question) {
|
|
34
|
+
if (!this.interactive) {
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
await this.initialize();
|
|
38
|
+
try {
|
|
39
|
+
const answer = await this.rl.question(question);
|
|
40
|
+
return answer.trim();
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error('Prompt error:', error.message);
|
|
43
|
+
return '';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Prompt with choices
|
|
49
|
+
*/
|
|
50
|
+
async promptChoice(question, choices, defaultIndex = 0) {
|
|
51
|
+
if (!this.interactive) {
|
|
52
|
+
return choices[defaultIndex];
|
|
53
|
+
}
|
|
54
|
+
console.log(question);
|
|
55
|
+
choices.forEach((choice, index) => {
|
|
56
|
+
const marker = index === defaultIndex ? ' (default)' : '';
|
|
57
|
+
console.log(` ${index + 1}. ${choice}${marker}`);
|
|
58
|
+
});
|
|
59
|
+
const answer = await this.prompt('Enter choice: ');
|
|
60
|
+
if (!answer) return choices[defaultIndex];
|
|
61
|
+
const choiceIndex = parseInt(answer) - 1;
|
|
62
|
+
if (choiceIndex >= 0 && choiceIndex < choices.length) {
|
|
63
|
+
return choices[choiceIndex];
|
|
64
|
+
}
|
|
65
|
+
return choices[defaultIndex];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Confirm yes/no question
|
|
70
|
+
*/
|
|
71
|
+
async confirm(question, defaultValue = false) {
|
|
72
|
+
if (!this.interactive) {
|
|
73
|
+
return defaultValue;
|
|
74
|
+
}
|
|
75
|
+
const suffix = defaultValue ? ' (Y/n)' : ' (y/N)';
|
|
76
|
+
const answer = await this.prompt(question + suffix);
|
|
77
|
+
if (!answer) return defaultValue;
|
|
78
|
+
const normalized = answer.toLowerCase();
|
|
79
|
+
return normalized === 'y' || normalized === 'yes';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Cleanup readline interface
|
|
84
|
+
*/
|
|
85
|
+
async close() {
|
|
86
|
+
if (this.rl) {
|
|
87
|
+
this.rl.close();
|
|
88
|
+
this.rl = null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Factory for creating prompt handlers
|
|
95
|
+
*/
|
|
96
|
+
export function createPromptHandler(options = {}) {
|
|
97
|
+
return new PromptHandler(options);
|
|
98
|
+
}
|