@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,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;
|