@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.
Files changed (130) hide show
  1. package/CHANGELOG.md +564 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1393 -0
  4. package/bin/README.md +71 -0
  5. package/bin/clodo-service.js +416 -0
  6. package/bin/security/security-cli.js +96 -0
  7. package/bin/service-management/README.md +74 -0
  8. package/bin/service-management/create-service.js +129 -0
  9. package/bin/service-management/init-service.js +102 -0
  10. package/bin/service-management/init-service.js.backup +889 -0
  11. package/bin/shared/config/customer-cli.js +293 -0
  12. package/dist/config/ConfigurationManager.js +159 -0
  13. package/dist/config/CustomerConfigCLI.js +220 -0
  14. package/dist/config/FeatureManager.js +426 -0
  15. package/dist/config/customers.js +441 -0
  16. package/dist/config/domains.js +180 -0
  17. package/dist/config/features.js +225 -0
  18. package/dist/config/index.js +6 -0
  19. package/dist/database/database-orchestrator.js +730 -0
  20. package/dist/database/index.js +4 -0
  21. package/dist/deployment/auditor.js +971 -0
  22. package/dist/deployment/index.js +10 -0
  23. package/dist/deployment/rollback-manager.js +523 -0
  24. package/dist/deployment/testers/api-tester.js +80 -0
  25. package/dist/deployment/testers/auth-tester.js +129 -0
  26. package/dist/deployment/testers/core.js +217 -0
  27. package/dist/deployment/testers/database-tester.js +105 -0
  28. package/dist/deployment/testers/index.js +74 -0
  29. package/dist/deployment/testers/load-tester.js +120 -0
  30. package/dist/deployment/testers/performance-tester.js +105 -0
  31. package/dist/deployment/validator.js +558 -0
  32. package/dist/deployment/wrangler-deployer.js +574 -0
  33. package/dist/handlers/GenericRouteHandler.js +532 -0
  34. package/dist/index.js +39 -0
  35. package/dist/migration/MigrationAdapters.js +562 -0
  36. package/dist/modules/ModuleManager.js +668 -0
  37. package/dist/modules/security.js +98 -0
  38. package/dist/orchestration/cross-domain-coordinator.js +1083 -0
  39. package/dist/orchestration/index.js +5 -0
  40. package/dist/orchestration/modules/DeploymentCoordinator.js +258 -0
  41. package/dist/orchestration/modules/DomainResolver.js +196 -0
  42. package/dist/orchestration/modules/StateManager.js +332 -0
  43. package/dist/orchestration/multi-domain-orchestrator.js +255 -0
  44. package/dist/routing/EnhancedRouter.js +158 -0
  45. package/dist/schema/SchemaManager.js +778 -0
  46. package/dist/security/ConfigurationValidator.js +490 -0
  47. package/dist/security/DeploymentManager.js +208 -0
  48. package/dist/security/SecretGenerator.js +142 -0
  49. package/dist/security/SecurityCLI.js +228 -0
  50. package/dist/security/index.js +51 -0
  51. package/dist/security/patterns/environment-rules.js +66 -0
  52. package/dist/security/patterns/insecure-patterns.js +21 -0
  53. package/dist/service-management/ConfirmationEngine.js +411 -0
  54. package/dist/service-management/ErrorTracker.js +294 -0
  55. package/dist/service-management/GenerationEngine.js +3109 -0
  56. package/dist/service-management/InputCollector.js +237 -0
  57. package/dist/service-management/ServiceCreator.js +229 -0
  58. package/dist/service-management/ServiceInitializer.js +448 -0
  59. package/dist/service-management/ServiceOrchestrator.js +638 -0
  60. package/dist/service-management/handlers/ConfigMutator.js +130 -0
  61. package/dist/service-management/handlers/ConfirmationHandler.js +71 -0
  62. package/dist/service-management/handlers/GenerationHandler.js +80 -0
  63. package/dist/service-management/handlers/InputHandler.js +59 -0
  64. package/dist/service-management/handlers/ValidationHandler.js +203 -0
  65. package/dist/service-management/index.js +7 -0
  66. package/dist/services/GenericDataService.js +488 -0
  67. package/dist/shared/cloudflare/domain-discovery.js +562 -0
  68. package/dist/shared/cloudflare/domain-manager.js +912 -0
  69. package/dist/shared/cloudflare/index.js +8 -0
  70. package/dist/shared/cloudflare/ops.js +387 -0
  71. package/dist/shared/config/cache.js +1167 -0
  72. package/dist/shared/config/command-config-manager.js +174 -0
  73. package/dist/shared/config/customer-cli.js +258 -0
  74. package/dist/shared/config/index.js +9 -0
  75. package/dist/shared/config/manager.js +289 -0
  76. package/dist/shared/database/connection-manager.js +338 -0
  77. package/dist/shared/database/index.js +7 -0
  78. package/dist/shared/database/orchestrator.js +632 -0
  79. package/dist/shared/deployment/auditor.js +971 -0
  80. package/dist/shared/deployment/index.js +10 -0
  81. package/dist/shared/deployment/rollback-manager.js +523 -0
  82. package/dist/shared/deployment/validator.js +558 -0
  83. package/dist/shared/index.js +32 -0
  84. package/dist/shared/monitoring/health-checker.js +250 -0
  85. package/dist/shared/monitoring/index.js +8 -0
  86. package/dist/shared/monitoring/memory-manager.js +382 -0
  87. package/dist/shared/monitoring/production-monitor.js +390 -0
  88. package/dist/shared/production-tester/api-tester.js +80 -0
  89. package/dist/shared/production-tester/auth-tester.js +129 -0
  90. package/dist/shared/production-tester/core.js +217 -0
  91. package/dist/shared/production-tester/database-tester.js +105 -0
  92. package/dist/shared/production-tester/index.js +74 -0
  93. package/dist/shared/production-tester/load-tester.js +120 -0
  94. package/dist/shared/production-tester/performance-tester.js +105 -0
  95. package/dist/shared/security/api-token-manager.js +296 -0
  96. package/dist/shared/security/index.js +8 -0
  97. package/dist/shared/security/secret-generator.js +918 -0
  98. package/dist/shared/security/secure-token-manager.js +379 -0
  99. package/dist/shared/utils/error-recovery.js +240 -0
  100. package/dist/shared/utils/graceful-shutdown-manager.js +380 -0
  101. package/dist/shared/utils/index.js +9 -0
  102. package/dist/shared/utils/interactive-prompts.js +134 -0
  103. package/dist/shared/utils/rate-limiter.js +249 -0
  104. package/dist/utils/ErrorHandler.js +173 -0
  105. package/dist/utils/deployment/config-cache.js +1160 -0
  106. package/dist/utils/deployment/index.js +6 -0
  107. package/dist/utils/deployment/interactive-prompts.js +97 -0
  108. package/dist/utils/deployment/secret-generator.js +896 -0
  109. package/dist/utils/dirname-helper.js +35 -0
  110. package/dist/utils/domain-config.js +159 -0
  111. package/dist/utils/error-recovery.js +240 -0
  112. package/dist/utils/esm-helper.js +52 -0
  113. package/dist/utils/framework-config.js +481 -0
  114. package/dist/utils/graceful-shutdown-manager.js +379 -0
  115. package/dist/utils/health-checker.js +114 -0
  116. package/dist/utils/index.js +36 -0
  117. package/dist/utils/prompt-handler.js +98 -0
  118. package/dist/utils/usage-tracker.js +252 -0
  119. package/dist/utils/validation.js +112 -0
  120. package/dist/version/VersionDetector.js +723 -0
  121. package/dist/worker/index.js +4 -0
  122. package/dist/worker/integration.js +332 -0
  123. package/docs/FRAMEWORK-ARCHITECTURE-OVERVIEW.md +206 -0
  124. package/docs/INTEGRATION_GUIDE.md +2045 -0
  125. package/docs/README.md +82 -0
  126. package/docs/SECURITY.md +242 -0
  127. package/docs/deployment/deployment-guide.md +540 -0
  128. package/docs/overview.md +280 -0
  129. package/package.json +176 -0
  130. package/types/index.d.ts +575 -0
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Deployment Module
3
+ * Exports all deployment-related orchestrators, validators, and managers
4
+ */
5
+
6
+ export { DeploymentValidator } from './validator.js';
7
+ export { MultiDomainOrchestrator } from '../../src/orchestration/multi-domain-orchestrator.js';
8
+ export { CrossDomainCoordinator } from '../../src/orchestration/cross-domain-coordinator.js';
9
+ export { DeploymentAuditor } from './auditor.js';
10
+ export { RollbackManager } from './rollback-manager.js';
@@ -0,0 +1,523 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Rollback Manager Module
5
+ * Enterprise-grade rollback system for safe deployment recovery
6
+ *
7
+ * Extracted from bulletproof-deploy.js with enhancements
8
+ */
9
+ import { access, readFile, writeFile, mkdir, copyFile } from 'fs/promises';
10
+ import { promisify } from 'util';
11
+ import { exec } from 'child_process';
12
+ const execAsync = promisify(exec);
13
+ import { join, dirname } from 'path';
14
+
15
+ /**
16
+ * Advanced Rollback Manager
17
+ * Provides comprehensive rollback capabilities for deployment failures
18
+ */
19
+ export class RollbackManager {
20
+ constructor(options = {}) {
21
+ this.deploymentId = options.deploymentId || this.generateRollbackId();
22
+ this.environment = options.environment || 'production';
23
+ this.dryRun = options.dryRun || false;
24
+ this.retryAttempts = options.retryAttempts || 3;
25
+ this.retryDelay = options.retryDelay || 2000;
26
+
27
+ // Rollback state tracking
28
+ this.rollbackPlan = {
29
+ id: this.deploymentId,
30
+ created: new Date(),
31
+ actions: [],
32
+ backups: new Map(),
33
+ status: 'initialized',
34
+ executedActions: [],
35
+ failedActions: [],
36
+ totalActions: 0
37
+ };
38
+
39
+ // Backup directories
40
+ this.backupPaths = {
41
+ root: 'backups',
42
+ deployment: join('backups', 'deployments', this.deploymentId),
43
+ configs: join('backups', 'configs', this.deploymentId),
44
+ secrets: join('backups', 'secrets', this.deploymentId),
45
+ database: join('backups', 'database', this.deploymentId)
46
+ };
47
+
48
+ // Note: Async initialization required - call initialize() after construction
49
+ }
50
+
51
+ /**
52
+ * Initialize the rollback manager asynchronously
53
+ */
54
+ async initialize() {
55
+ await this.initializeRollbackSystem();
56
+ }
57
+
58
+ /**
59
+ * Generate unique rollback identifier
60
+ * @returns {string} Rollback ID
61
+ */
62
+ generateRollbackId() {
63
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
64
+ const random = Math.random().toString(36).substring(2, 8);
65
+ return `rollback-${timestamp}-${random}`;
66
+ }
67
+
68
+ /**
69
+ * Initialize rollback system and create backup directories
70
+ */
71
+ async initializeRollbackSystem() {
72
+ console.log('๐Ÿ”„ Rollback System v1.0');
73
+ console.log('========================');
74
+ console.log(`๐Ÿ†” Rollback ID: ${this.rollbackPlan.id}`);
75
+ console.log(`๐ŸŒ Environment: ${this.environment}`);
76
+ console.log(`๐Ÿ” Mode: ${this.dryRun ? 'DRY RUN' : 'LIVE ROLLBACK'}`);
77
+ console.log('');
78
+
79
+ // Create backup directories
80
+ for (const path of Object.values(this.backupPaths)) {
81
+ try {
82
+ await access(path);
83
+ } catch {
84
+ await mkdir(path, {
85
+ recursive: true
86
+ });
87
+ }
88
+ }
89
+ this.logRollbackEvent('SYSTEM_INITIALIZED', {
90
+ backupPaths: this.backupPaths,
91
+ environment: this.environment
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Add rollback action to the plan
97
+ * @param {Object} action - Rollback action configuration
98
+ */
99
+ addRollbackAction(action) {
100
+ const rollbackAction = {
101
+ id: `action-${this.rollbackPlan.actions.length + 1}`,
102
+ timestamp: new Date(),
103
+ ...action
104
+ };
105
+ this.rollbackPlan.actions.push(rollbackAction);
106
+ this.rollbackPlan.totalActions++;
107
+ this.logRollbackEvent('ACTION_ADDED', rollbackAction);
108
+ console.log(`๐Ÿ“ Rollback action added: ${action.type} - ${action.description || 'No description'}`);
109
+ }
110
+
111
+ /**
112
+ * Create backup of current state before deployment
113
+ * @param {Object} options - Backup options
114
+ * @returns {Promise<Object>} Backup manifest
115
+ */
116
+ async createStateBackup(options = {}) {
117
+ console.log('๐Ÿ’พ Creating deployment state backup...');
118
+ const backupManifest = {
119
+ id: this.deploymentId,
120
+ timestamp: new Date(),
121
+ environment: this.environment,
122
+ files: [],
123
+ cloudflareState: {},
124
+ databaseState: {}
125
+ };
126
+ try {
127
+ // Backup configuration files
128
+ await this.backupConfigurationFiles(backupManifest);
129
+
130
+ // Backup Cloudflare state
131
+ if (options.includeCloudflare !== false) {
132
+ await this.backupCloudflareState(backupManifest);
133
+ }
134
+
135
+ // Backup database state
136
+ if (options.includeDatabase !== false) {
137
+ await this.backupDatabaseState(backupManifest);
138
+ }
139
+
140
+ // Save backup manifest
141
+ const manifestPath = join(this.backupPaths.deployment, 'backup-manifest.json');
142
+ await writeFile(manifestPath, JSON.stringify(backupManifest, null, 2));
143
+ this.rollbackPlan.backups.set('state', backupManifest);
144
+ this.logRollbackEvent('BACKUP_CREATED', {
145
+ files: backupManifest.files.length,
146
+ manifestPath
147
+ });
148
+ console.log(`โœ… State backup created: ${backupManifest.files.length} files backed up`);
149
+ return backupManifest;
150
+ } catch (error) {
151
+ this.logRollbackEvent('BACKUP_FAILED', {
152
+ error: error.message
153
+ });
154
+ throw new Error(`State backup failed: ${error.message}`);
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Backup configuration files
160
+ * @param {Object} manifest - Backup manifest to update
161
+ */
162
+ async backupConfigurationFiles(manifest) {
163
+ const configFiles = ['package.json', 'wrangler.toml', '.env', 'src/config/domains.js'];
164
+ for (const file of configFiles) {
165
+ try {
166
+ await access(file);
167
+ const backupPath = join(this.backupPaths.configs, file.replace(/[/\\]/g, '_'));
168
+ const backupDir = dirname(backupPath);
169
+ try {
170
+ await access(backupDir);
171
+ } catch {
172
+ await mkdir(backupDir, {
173
+ recursive: true
174
+ });
175
+ }
176
+ await copyFile(file, backupPath);
177
+ manifest.files.push({
178
+ original: file,
179
+ backup: backupPath,
180
+ timestamp: new Date()
181
+ });
182
+ console.log(` ๐Ÿ“„ Backed up: ${file}`);
183
+
184
+ // Add restoration action
185
+ this.addRollbackAction({
186
+ type: 'restore-file',
187
+ description: `Restore ${file}`,
188
+ original: file,
189
+ backup: backupPath,
190
+ command: `copy "${backupPath}" "${file}"`,
191
+ priority: 1
192
+ });
193
+ } catch (error) {
194
+ console.warn(` โš ๏ธ Failed to backup ${file}: ${error.message}`);
195
+ }
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Backup Cloudflare worker and secrets state
201
+ * @param {Object} manifest - Backup manifest to update
202
+ */
203
+ async backupCloudflareState(manifest) {
204
+ try {
205
+ console.log(' โ˜๏ธ Backing up Cloudflare state...');
206
+
207
+ // Get current worker list
208
+ const workerList = await this.executeCommand('npx wrangler list', {
209
+ timeout: 30000
210
+ });
211
+ manifest.cloudflareState.workers = workerList;
212
+
213
+ // Get current secrets (we can't read values, but we can list keys)
214
+ try {
215
+ const secretsList = await this.executeCommand('npx wrangler secret list', {
216
+ timeout: 30000
217
+ });
218
+ manifest.cloudflareState.secrets = secretsList;
219
+ } catch (error) {
220
+ console.log(` โš ๏ธ Could not backup secrets list: ${error.message}`);
221
+ }
222
+
223
+ // Save Cloudflare state
224
+ const cloudflareBackupPath = join(this.backupPaths.deployment, 'cloudflare-state.json');
225
+ await writeFile(cloudflareBackupPath, JSON.stringify(manifest.cloudflareState, null, 2));
226
+ console.log(' โœ… Cloudflare state backed up');
227
+ } catch (error) {
228
+ console.log(` โš ๏ธ Cloudflare backup failed: ${error.message}`);
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Backup database state
234
+ * @param {Object} manifest - Backup manifest to update
235
+ */
236
+ async backupDatabaseState(manifest) {
237
+ try {
238
+ console.log(' ๐Ÿ—„๏ธ Backing up database state...');
239
+
240
+ // Get D1 database list
241
+ const dbList = await this.executeCommand('npx wrangler d1 list', {
242
+ timeout: 30000
243
+ });
244
+ manifest.databaseState.databases = dbList;
245
+
246
+ // Save database state
247
+ const dbBackupPath = join(this.backupPaths.database, 'database-state.json');
248
+ await writeFile(dbBackupPath, JSON.stringify(manifest.databaseState, null, 2));
249
+ console.log(' โœ… Database state backed up');
250
+ } catch (error) {
251
+ console.log(` โš ๏ธ Database backup failed: ${error.message}`);
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Execute rollback plan
257
+ * @returns {Promise<Object>} Rollback results
258
+ */
259
+ async executeRollback() {
260
+ console.log('\n๐Ÿ”„ EXECUTING ROLLBACK PLAN');
261
+ console.log('===========================');
262
+ console.log(`๐Ÿ“Š Total Actions: ${this.rollbackPlan.totalActions}`);
263
+ console.log(`๐Ÿ†” Rollback ID: ${this.rollbackPlan.id}`);
264
+ console.log('');
265
+ this.rollbackPlan.status = 'executing';
266
+ this.rollbackPlan.startTime = new Date();
267
+ const results = {
268
+ rollbackId: this.rollbackPlan.id,
269
+ successful: [],
270
+ failed: [],
271
+ skipped: [],
272
+ totalActions: this.rollbackPlan.totalActions
273
+ };
274
+ try {
275
+ // Sort actions by priority (higher priority first)
276
+ const sortedActions = this.rollbackPlan.actions.sort((a, b) => (b.priority || 0) - (a.priority || 0));
277
+ for (const action of sortedActions) {
278
+ const actionResult = await this.executeRollbackAction(action);
279
+ if (actionResult.success) {
280
+ results.successful.push(actionResult);
281
+ this.rollbackPlan.executedActions.push(action);
282
+ } else {
283
+ results.failed.push(actionResult);
284
+ this.rollbackPlan.failedActions.push(action);
285
+
286
+ // Stop on critical failures unless forced to continue
287
+ if (action.critical !== false && !action.continueOnFailure) {
288
+ console.log(`โŒ Critical rollback action failed: ${action.type}`);
289
+ break;
290
+ }
291
+ }
292
+ }
293
+ this.rollbackPlan.status = results.failed.length === 0 ? 'completed' : 'partial';
294
+ this.rollbackPlan.endTime = new Date();
295
+ const duration = (this.rollbackPlan.endTime - this.rollbackPlan.startTime) / 1000;
296
+ console.log('\n๐Ÿ“Š ROLLBACK SUMMARY');
297
+ console.log('===================');
298
+ console.log(`โœ… Successful: ${results.successful.length}`);
299
+ console.log(`โŒ Failed: ${results.failed.length}`);
300
+ console.log(`โธ๏ธ Skipped: ${results.skipped.length}`);
301
+ console.log(`โฑ๏ธ Duration: ${duration.toFixed(1)}s`);
302
+ console.log(`๐Ÿ Status: ${this.rollbackPlan.status.toUpperCase()}`);
303
+ if (results.failed.length > 0) {
304
+ console.log('\nโŒ Failed Actions:');
305
+ results.failed.forEach(failure => {
306
+ console.log(` - ${failure.action.type}: ${failure.error}`);
307
+ });
308
+ }
309
+
310
+ // Save rollback report
311
+ await this.saveRollbackReport(results);
312
+ this.logRollbackEvent('ROLLBACK_COMPLETED', {
313
+ status: this.rollbackPlan.status,
314
+ successful: results.successful.length,
315
+ failed: results.failed.length,
316
+ duration
317
+ });
318
+ return results;
319
+ } catch (error) {
320
+ this.rollbackPlan.status = 'failed';
321
+ this.rollbackPlan.endTime = new Date();
322
+ this.logRollbackEvent('ROLLBACK_FAILED', {
323
+ error: error.message
324
+ });
325
+ throw new Error(`Rollback execution failed: ${error.message}`);
326
+ }
327
+ }
328
+
329
+ /**
330
+ * Execute individual rollback action
331
+ * @param {Object} action - Action to execute
332
+ * @returns {Promise<Object>} Action result
333
+ */
334
+ async executeRollbackAction(action) {
335
+ console.log(`๐Ÿ”„ Rolling back: ${action.type} - ${action.description || action.id}`);
336
+ const result = {
337
+ actionId: action.id,
338
+ action: action,
339
+ success: false,
340
+ error: null,
341
+ timestamp: new Date()
342
+ };
343
+ try {
344
+ if (this.dryRun) {
345
+ console.log(` ๐Ÿ” DRY RUN: Would execute ${action.type}`);
346
+ result.success = true;
347
+ result.dryRun = true;
348
+ return result;
349
+ }
350
+
351
+ // Execute based on action type
352
+ switch (action.type) {
353
+ case 'restore-file':
354
+ await this.restoreFile(action);
355
+ break;
356
+ case 'delete-secret':
357
+ await this.deleteSecret(action);
358
+ break;
359
+ case 'delete-database':
360
+ await this.deleteDatabase(action);
361
+ break;
362
+ case 'delete-worker':
363
+ await this.deleteWorker(action);
364
+ break;
365
+ case 'custom-command':
366
+ await this.executeCustomCommand(action);
367
+ break;
368
+ default:
369
+ if (action.command) {
370
+ await this.executeCommand(action.command, {
371
+ timeout: action.timeout || 30000
372
+ });
373
+ } else {
374
+ throw new Error(`Unknown rollback action type: ${action.type}`);
375
+ }
376
+ }
377
+ result.success = true;
378
+ console.log(` โœ… Rollback completed: ${action.type}`);
379
+ } catch (error) {
380
+ result.error = error.message;
381
+ console.log(` โŒ Rollback failed: ${action.type} - ${error.message}`);
382
+ }
383
+ return result;
384
+ }
385
+
386
+ // Individual rollback action implementations
387
+
388
+ async restoreFile(action) {
389
+ try {
390
+ await access(action.backup);
391
+ } catch {
392
+ throw new Error(`Backup file not found: ${action.backup}`);
393
+ }
394
+ await copyFile(action.backup, action.original);
395
+ console.log(` ๐Ÿ“„ Restored ${action.original}`);
396
+ }
397
+ async deleteSecret(action) {
398
+ const command = action.command || `npx wrangler secret delete ${action.key} --env ${this.environment}`;
399
+ await this.executeCommand(command, {
400
+ timeout: 30000
401
+ });
402
+ console.log(` ๐Ÿ” Deleted secret: ${action.key}`);
403
+ }
404
+ async deleteDatabase(action) {
405
+ const command = action.command || `npx wrangler d1 delete ${action.name} --skip-confirmation`;
406
+ await this.executeCommand(command, {
407
+ timeout: 60000
408
+ });
409
+ console.log(` ๐Ÿ—„๏ธ Deleted database: ${action.name}`);
410
+ }
411
+ async deleteWorker(action) {
412
+ const command = action.command || `npx wrangler delete ${action.name} --env ${this.environment}`;
413
+ await this.executeCommand(command, {
414
+ timeout: 60000
415
+ });
416
+ console.log(` โšก Deleted worker: ${action.name}`);
417
+ }
418
+ async executeCustomCommand(action) {
419
+ await this.executeCommand(action.command, {
420
+ timeout: action.timeout || 30000
421
+ });
422
+ console.log(` ๐Ÿ”ง Executed: ${action.description}`);
423
+ }
424
+
425
+ // Utility methods
426
+
427
+ async executeCommand(command, options = {}) {
428
+ const timeout = options.timeout || 30000;
429
+ for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
430
+ try {
431
+ const {
432
+ stdout
433
+ } = await execAsync(command, {
434
+ encoding: 'utf8',
435
+ stdio: options.stdio || 'pipe',
436
+ timeout
437
+ });
438
+ return stdout;
439
+ } catch (error) {
440
+ if (attempt === this.retryAttempts) {
441
+ throw error;
442
+ }
443
+ console.log(` โš ๏ธ Attempt ${attempt}/${this.retryAttempts} failed, retrying...`);
444
+ await new Promise(resolve => setTimeout(resolve, this.retryDelay));
445
+ }
446
+ }
447
+ }
448
+ async saveRollbackReport(results) {
449
+ const report = {
450
+ rollbackId: this.rollbackPlan.id,
451
+ environment: this.environment,
452
+ timestamp: new Date(),
453
+ plan: this.rollbackPlan,
454
+ results,
455
+ summary: {
456
+ totalActions: results.totalActions,
457
+ successful: results.successful.length,
458
+ failed: results.failed.length,
459
+ successRate: (results.successful.length / results.totalActions * 100).toFixed(1)
460
+ }
461
+ };
462
+ const reportPath = join(this.backupPaths.deployment, 'rollback-report.json');
463
+ await writeFile(reportPath, JSON.stringify(report, null, 2));
464
+ console.log(`๐Ÿ“Š Rollback report saved: ${reportPath}`);
465
+ }
466
+ logRollbackEvent(event, details = {}) {
467
+ const logEntry = {
468
+ timestamp: new Date().toISOString(),
469
+ rollbackId: this.rollbackPlan.id,
470
+ event,
471
+ details
472
+ };
473
+
474
+ // Log to file if not in dry run mode (fire and forget)
475
+ if (!this.dryRun) {
476
+ (async () => {
477
+ try {
478
+ const logPath = join(this.backupPaths.deployment, 'rollback-log.json');
479
+ let logs = [];
480
+ try {
481
+ const logData = await readFile(logPath, 'utf8');
482
+ logs = JSON.parse(logData);
483
+ } catch {
484
+ // File doesn't exist, start with empty logs
485
+ }
486
+ logs.push(logEntry);
487
+ await writeFile(logPath, JSON.stringify(logs, null, 2));
488
+ } catch (error) {
489
+ console.warn(`โš ๏ธ Failed to log rollback event: ${error.message}`);
490
+ }
491
+ })();
492
+ }
493
+ }
494
+
495
+ /**
496
+ * Get rollback plan status
497
+ * @returns {Object} Current rollback plan status
498
+ */
499
+ getStatus() {
500
+ return {
501
+ id: this.rollbackPlan.id,
502
+ status: this.rollbackPlan.status,
503
+ totalActions: this.rollbackPlan.totalActions,
504
+ executedActions: this.rollbackPlan.executedActions.length,
505
+ failedActions: this.rollbackPlan.failedActions.length,
506
+ created: this.rollbackPlan.created,
507
+ lastUpdated: new Date()
508
+ };
509
+ }
510
+
511
+ /**
512
+ * Clear rollback plan (use with caution)
513
+ */
514
+ clearRollbackPlan() {
515
+ this.rollbackPlan.actions = [];
516
+ this.rollbackPlan.totalActions = 0;
517
+ this.rollbackPlan.executedActions = [];
518
+ this.rollbackPlan.failedActions = [];
519
+ this.logRollbackEvent('PLAN_CLEARED');
520
+ console.log('๐Ÿงน Rollback plan cleared');
521
+ }
522
+ }
523
+ export default RollbackManager;