@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,632 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Database Orchestrator Module
5
+ * Enterprise-grade database management across multiple environments
6
+ *
7
+ * Extracted from manage-migrations.ps1 and manage-data-cleanup.ps1 with enhancements
8
+ */
9
+ import { exec } from 'child_process';
10
+ import { readFile, writeFile, access, mkdir, readdir, stat, appendFile } from 'fs/promises';
11
+ import { existsSync } from 'fs';
12
+ import { join, dirname } from 'path';
13
+ import { fileURLToPath } from 'url';
14
+ import { promisify } from 'util';
15
+ const execAsync = promisify(exec);
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = dirname(__filename);
18
+
19
+ /**
20
+ * Advanced Database Orchestrator
21
+ * Manages database operations across development, staging, and production environments
22
+ */
23
+ export class DatabaseOrchestrator {
24
+ constructor(options = {}) {
25
+ // Detect if running as a dependency (in node_modules)
26
+ const isDependency = __dirname.includes('node_modules');
27
+ this.projectRoot = options.projectRoot || (isDependency ? null : join(__dirname, '..', '..'));
28
+ this.dryRun = options.dryRun || false;
29
+ this.options = options;
30
+ this.config = null;
31
+
32
+ // Environment configurations
33
+ this.environments = {
34
+ development: {
35
+ name: 'development',
36
+ isRemote: false,
37
+ description: 'Local development database',
38
+ defaultDatabase: 'local-db'
39
+ },
40
+ staging: {
41
+ name: 'staging',
42
+ isRemote: true,
43
+ description: 'Staging environment database',
44
+ requiresConfirmation: true
45
+ },
46
+ production: {
47
+ name: 'production',
48
+ isRemote: true,
49
+ description: 'PRODUCTION environment database - USE WITH EXTREME CAUTION',
50
+ requiresConfirmation: true,
51
+ requiresBackup: true
52
+ }
53
+ };
54
+
55
+ // Backup and audit configuration - only set paths if not running as dependency
56
+ if (this.projectRoot) {
57
+ this.backupPaths = {
58
+ root: join(this.projectRoot, 'backups', 'database'),
59
+ migrations: join(this.projectRoot, 'backups', 'migrations'),
60
+ audit: join(this.projectRoot, 'logs', 'database-audit.log')
61
+ };
62
+ this.migrationPaths = {
63
+ root: join(this.projectRoot, 'migrations'),
64
+ templates: join(this.projectRoot, 'migration-templates')
65
+ };
66
+ } else {
67
+ // When used as dependency, disable file-based logging
68
+ this.backupPaths = null;
69
+ this.migrationPaths = null;
70
+ console.log('📦 Running as dependency - file logging disabled');
71
+ }
72
+ this.initializeOrchestrator();
73
+ }
74
+
75
+ /**
76
+ * Initialize database orchestrator
77
+ */
78
+ initializeOrchestrator() {
79
+ console.log('🗄️ Database Orchestrator v1.0');
80
+ console.log('==============================');
81
+ if (this.projectRoot) {
82
+ console.log(`📁 Project Root: ${this.projectRoot}`);
83
+ } else {
84
+ console.log('📦 Running as dependency - limited functionality');
85
+ }
86
+ console.log(`🔍 Mode: ${this.dryRun ? 'DRY RUN' : 'LIVE OPERATIONS'}`);
87
+ console.log(`🔄 Retry Attempts: ${this.config ? this.config.retryAttempts : 3}`);
88
+ console.log('');
89
+
90
+ // Create necessary directories (only if not running as dependency)
91
+ if (this.backupPaths && this.migrationPaths) {
92
+ Object.values(this.backupPaths).forEach(path => {
93
+ if (!path.endsWith('.log')) {
94
+ this.ensureDirectory(path);
95
+ }
96
+ });
97
+ this.ensureDirectory(this.migrationPaths.root);
98
+ this.ensureDirectory(dirname(this.backupPaths.audit));
99
+ }
100
+ this.logAuditEvent('ORCHESTRATOR_INITIALIZED', 'SYSTEM', {
101
+ mode: this.dryRun ? 'DRY_RUN' : 'LIVE',
102
+ environments: Object.keys(this.environments)
103
+ });
104
+ }
105
+
106
+ /**
107
+ * Initialize with framework configuration
108
+ */
109
+ async initialize() {
110
+ // Import framework config for consistent timing and database settings
111
+ const {
112
+ frameworkConfig
113
+ } = await import('../../../src/utils/framework-config.js');
114
+ const timing = frameworkConfig.getTiming();
115
+ const database = frameworkConfig.getDatabaseConfig();
116
+ this.config = {
117
+ retryAttempts: this.options.retryAttempts || timing.retryAttempts,
118
+ retryDelay: this.options.retryDelay || timing.retryDelay,
119
+ executionTimeout: this.options.executionTimeout || database.executionTimeout,
120
+ ...this.options
121
+ };
122
+ }
123
+
124
+ /**
125
+ * Apply migrations across multiple environments with coordination
126
+ * @param {Object} options - Migration options
127
+ * @returns {Promise<Object>} Migration results
128
+ */
129
+ async applyMigrationsAcrossEnvironments(options = {}) {
130
+ const {
131
+ environments = ['development', 'staging', 'production'],
132
+ domainConfigs = [],
133
+ skipBackup = false,
134
+ continueOnError = false
135
+ } = options;
136
+ console.log('🔄 Cross-Environment Migration Orchestration');
137
+ console.log('===========================================');
138
+ console.log(`🌍 Environments: ${environments.join(', ')}`);
139
+ console.log(`📋 Domains: ${domainConfigs.length} configured`);
140
+ console.log('');
141
+ const results = {
142
+ orchestrationId: this.generateOrchestrationId(),
143
+ environments: {},
144
+ summary: {
145
+ total: 0,
146
+ successful: 0,
147
+ failed: 0,
148
+ skipped: 0
149
+ },
150
+ startTime: new Date()
151
+ };
152
+ try {
153
+ for (const env of environments) {
154
+ if (!this.environments[env]) {
155
+ console.log(`⚠️ Unknown environment: ${env}, skipping`);
156
+ continue;
157
+ }
158
+ console.log(`\n🌍 Processing ${env} environment...`);
159
+ results.summary.total++;
160
+ try {
161
+ // Create backup if required
162
+ if (this.environments[env].requiresBackup && !skipBackup) {
163
+ await this.createEnvironmentBackup(env, domainConfigs);
164
+ }
165
+
166
+ // Apply migrations for environment
167
+ const envResult = await this.applyEnvironmentMigrations(env, domainConfigs, options);
168
+ results.environments[env] = {
169
+ status: 'completed',
170
+ ...envResult
171
+ };
172
+ results.summary.successful++;
173
+ console.log(`✅ ${env} environment completed successfully`);
174
+ } catch (error) {
175
+ console.error(`❌ ${env} environment failed: ${error.message}`);
176
+ results.environments[env] = {
177
+ status: 'failed',
178
+ error: error.message,
179
+ timestamp: new Date()
180
+ };
181
+ results.summary.failed++;
182
+ if (!continueOnError) {
183
+ throw new Error(`Migration failed in ${env} environment: ${error.message}`);
184
+ }
185
+ }
186
+ }
187
+ results.endTime = new Date();
188
+ results.summary.duration = (results.endTime - results.startTime) / 1000;
189
+ this.logAuditEvent('MIGRATION_ORCHESTRATION_COMPLETED', 'ALL', results.summary);
190
+ console.log('\n📊 MIGRATION ORCHESTRATION SUMMARY');
191
+ console.log('==================================');
192
+ console.log(`✅ Successful: ${results.summary.successful}`);
193
+ console.log(`❌ Failed: ${results.summary.failed}`);
194
+ console.log(`⏸️ Skipped: ${results.summary.skipped}`);
195
+ console.log(`⏱️ Duration: ${results.summary.duration.toFixed(1)}s`);
196
+ return results;
197
+ } catch (error) {
198
+ this.logAuditEvent('MIGRATION_ORCHESTRATION_FAILED', 'ALL', {
199
+ error: error.message
200
+ });
201
+ throw error;
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Apply migrations to specific environment
207
+ * @param {string} environment - Environment name
208
+ * @param {Array} domainConfigs - Domain configurations
209
+ * @param {Object} options - Migration options
210
+ * @returns {Promise<Object>} Environment migration result
211
+ */
212
+ async applyEnvironmentMigrations(environment, domainConfigs, options = {}) {
213
+ const envConfig = this.environments[environment];
214
+ const results = {
215
+ environment,
216
+ databases: {},
217
+ migrationsApplied: 0,
218
+ startTime: new Date()
219
+ };
220
+ console.log(` 📋 Environment: ${envConfig.description}`);
221
+ console.log(` 🌐 Remote: ${envConfig.isRemote ? 'Yes' : 'No'}`);
222
+
223
+ // Process each domain's databases
224
+ if (domainConfigs.length > 0) {
225
+ for (const domainConfig of domainConfigs) {
226
+ const dbConfig = domainConfig.databases?.[environment];
227
+ if (!dbConfig) {
228
+ console.log(` ⚠️ No ${environment} database config for ${domainConfig.name}`);
229
+ continue;
230
+ }
231
+ try {
232
+ const dbResult = await this.applyDatabaseMigrations(dbConfig.name, environment, envConfig.isRemote);
233
+ results.databases[dbConfig.name] = dbResult;
234
+ results.migrationsApplied += dbResult.migrationsApplied || 0;
235
+ } catch (error) {
236
+ console.error(` ❌ Database ${dbConfig.name} migration failed: ${error.message}`);
237
+ results.databases[dbConfig.name] = {
238
+ status: 'failed',
239
+ error: error.message
240
+ };
241
+ throw error;
242
+ }
243
+ }
244
+ } else {
245
+ // Apply to default database
246
+ const defaultDb = envConfig.defaultDatabase || 'default-db';
247
+ const dbResult = await this.applyDatabaseMigrations(defaultDb, environment, envConfig.isRemote);
248
+ results.databases[defaultDb] = dbResult;
249
+ results.migrationsApplied = dbResult.migrationsApplied || 0;
250
+ }
251
+ results.endTime = new Date();
252
+ results.duration = (results.endTime - results.startTime) / 1000;
253
+ this.logAuditEvent('ENVIRONMENT_MIGRATION_COMPLETED', environment, {
254
+ databases: Object.keys(results.databases),
255
+ migrationsApplied: results.migrationsApplied,
256
+ duration: results.duration
257
+ });
258
+ return results;
259
+ }
260
+
261
+ /**
262
+ * Apply migrations to specific database
263
+ * @param {string} databaseName - Database name
264
+ * @param {string} environment - Environment
265
+ * @param {boolean} isRemote - Whether database is remote
266
+ * @returns {Promise<Object>} Database migration result
267
+ */
268
+ async applyDatabaseMigrations(databaseName, environment, isRemote) {
269
+ console.log(` 🗄️ Applying migrations to ${databaseName}...`);
270
+ if (this.dryRun) {
271
+ console.log(` 🔍 DRY RUN: Would apply migrations to ${databaseName}`);
272
+ return {
273
+ status: 'dry-run',
274
+ databaseName,
275
+ environment,
276
+ migrationsApplied: 0
277
+ };
278
+ }
279
+ try {
280
+ const command = this.buildMigrationCommand(databaseName, environment, isRemote);
281
+ const output = await this.executeWithRetry(command, 120000); // 2 minute timeout
282
+
283
+ // Parse migration output
284
+ const migrationsApplied = this.parseMigrationOutput(output);
285
+ console.log(` ✅ Applied ${migrationsApplied} migrations to ${databaseName}`);
286
+ this.logAuditEvent('DATABASE_MIGRATION_APPLIED', environment, {
287
+ databaseName,
288
+ migrationsApplied,
289
+ isRemote
290
+ });
291
+ return {
292
+ status: 'completed',
293
+ databaseName,
294
+ environment,
295
+ migrationsApplied,
296
+ output: output.substring(0, 500) // Truncate for storage
297
+ };
298
+ } catch (error) {
299
+ this.logAuditEvent('DATABASE_MIGRATION_FAILED', environment, {
300
+ databaseName,
301
+ error: error.message
302
+ });
303
+ throw new Error(`Migration failed for ${databaseName}: ${error.message}`);
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Create comprehensive backup across environments
309
+ * @param {string} environment - Environment to backup
310
+ * @param {Array} domainConfigs - Domain configurations
311
+ * @returns {Promise<Object>} Backup results
312
+ */
313
+ async createEnvironmentBackup(environment, domainConfigs) {
314
+ console.log(` 💾 Creating ${environment} environment backup...`);
315
+ const backupId = this.generateBackupId(environment);
316
+ const backupDir = join(this.backupPaths.root, environment, backupId);
317
+ this.ensureDirectory(backupDir);
318
+ const backupResults = {
319
+ backupId,
320
+ environment,
321
+ backupDir,
322
+ databases: {},
323
+ startTime: new Date()
324
+ };
325
+ try {
326
+ if (domainConfigs.length > 0) {
327
+ for (const domainConfig of domainConfigs) {
328
+ const dbConfig = domainConfig.databases?.[environment];
329
+ if (dbConfig && dbConfig.id) {
330
+ try {
331
+ const dbBackup = await this.createDatabaseBackup(dbConfig.name, environment, backupDir);
332
+ backupResults.databases[dbConfig.name] = dbBackup;
333
+ } catch (error) {
334
+ console.log(` ⚠️ Backup failed for ${dbConfig.name}: ${error.message}`);
335
+ backupResults.databases[dbConfig.name] = {
336
+ status: 'failed',
337
+ error: error.message
338
+ };
339
+ }
340
+ }
341
+ }
342
+ }
343
+ backupResults.endTime = new Date();
344
+ backupResults.duration = (backupResults.endTime - backupResults.startTime) / 1000;
345
+
346
+ // Save backup manifest
347
+ const manifestPath = join(backupDir, 'backup-manifest.json');
348
+ await writeFile(manifestPath, JSON.stringify(backupResults, null, 2));
349
+ this.logAuditEvent('ENVIRONMENT_BACKUP_CREATED', environment, {
350
+ backupId,
351
+ databases: Object.keys(backupResults.databases),
352
+ duration: backupResults.duration
353
+ });
354
+ console.log(` ✅ Environment backup completed: ${backupId}`);
355
+ return backupResults;
356
+ } catch (error) {
357
+ this.logAuditEvent('ENVIRONMENT_BACKUP_FAILED', environment, {
358
+ backupId,
359
+ error: error.message
360
+ });
361
+ throw error;
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Create backup of specific database
367
+ * @param {string} databaseName - Database name
368
+ * @param {string} environment - Environment
369
+ * @param {string} backupDir - Backup directory
370
+ * @returns {Promise<Object>} Database backup result
371
+ */
372
+ async createDatabaseBackup(databaseName, environment, backupDir) {
373
+ const backupFile = join(backupDir, `${databaseName}-${environment}.sql`);
374
+ if (this.dryRun) {
375
+ console.log(` 🔍 DRY RUN: Would backup ${databaseName} to ${backupFile}`);
376
+ return {
377
+ status: 'dry-run',
378
+ backupFile
379
+ };
380
+ }
381
+ try {
382
+ const isRemote = this.environments[environment].isRemote;
383
+ const command = this.buildBackupCommand(databaseName, environment, backupFile, isRemote);
384
+ await this.executeWithRetry(command, 300000); // 5 minute timeout for backups
385
+
386
+ if (existsSync(backupFile)) {
387
+ const stats = await stat(backupFile);
388
+ console.log(` 💾 Backup created: ${backupFile} (${(stats.size / 1024).toFixed(1)}KB)`);
389
+ return {
390
+ status: 'completed',
391
+ backupFile,
392
+ sizeKB: (stats.size / 1024).toFixed(1),
393
+ timestamp: new Date()
394
+ };
395
+ } else {
396
+ throw new Error('Backup file was not created');
397
+ }
398
+ } catch (error) {
399
+ throw new Error(`Database backup failed: ${error.message}`);
400
+ }
401
+ }
402
+
403
+ /**
404
+ * Perform safe data cleanup with backup and confirmation
405
+ * @param {Object} options - Cleanup options
406
+ * @returns {Promise<Object>} Cleanup results
407
+ */
408
+ async performSafeDataCleanup(options = {}) {
409
+ const {
410
+ environment = 'development',
411
+ domainConfigs = [],
412
+ cleanupType = 'partial',
413
+ // 'partial', 'full', 'logs-only'
414
+ skipBackup = false,
415
+ force = false
416
+ } = options;
417
+ console.log('🧹 Safe Data Cleanup Operation');
418
+ console.log('==============================');
419
+ console.log(`🌍 Environment: ${environment}`);
420
+ console.log(`🧽 Cleanup Type: ${cleanupType}`);
421
+ console.log(`💾 Skip Backup: ${skipBackup}`);
422
+ console.log('');
423
+ const envConfig = this.environments[environment];
424
+ if (!envConfig) {
425
+ throw new Error(`Unknown environment: ${environment}`);
426
+ }
427
+
428
+ // Safety confirmation for production
429
+ if (environment === 'production' && !force) {
430
+ const confirmed = await this.confirmDangerousOperation(`${cleanupType} data cleanup in PRODUCTION`, 'This will permanently delete data and cannot be undone');
431
+ if (!confirmed) {
432
+ console.log('❌ Operation cancelled by user');
433
+ return {
434
+ status: 'cancelled',
435
+ reason: 'User declined confirmation'
436
+ };
437
+ }
438
+ }
439
+ const cleanupResults = {
440
+ cleanupId: this.generateCleanupId(environment),
441
+ environment,
442
+ cleanupType,
443
+ operations: {},
444
+ startTime: new Date()
445
+ };
446
+ try {
447
+ // Create backup if required
448
+ if (!skipBackup && envConfig.requiresBackup) {
449
+ const backupResult = await this.createEnvironmentBackup(environment, domainConfigs);
450
+ cleanupResults.backup = backupResult;
451
+ }
452
+
453
+ // Perform cleanup operations
454
+ for (const domainConfig of domainConfigs) {
455
+ const dbConfig = domainConfig.databases?.[environment];
456
+ if (dbConfig) {
457
+ try {
458
+ const cleanupResult = await this.performDatabaseCleanup(dbConfig.name, environment, cleanupType);
459
+ cleanupResults.operations[dbConfig.name] = cleanupResult;
460
+ } catch (error) {
461
+ console.error(`❌ Cleanup failed for ${dbConfig.name}: ${error.message}`);
462
+ cleanupResults.operations[dbConfig.name] = {
463
+ status: 'failed',
464
+ error: error.message
465
+ };
466
+ }
467
+ }
468
+ }
469
+ cleanupResults.endTime = new Date();
470
+ cleanupResults.duration = (cleanupResults.endTime - cleanupResults.startTime) / 1000;
471
+ this.logAuditEvent('DATA_CLEANUP_COMPLETED', environment, {
472
+ cleanupId: cleanupResults.cleanupId,
473
+ cleanupType,
474
+ operations: Object.keys(cleanupResults.operations),
475
+ duration: cleanupResults.duration
476
+ });
477
+ console.log('\n✅ Data cleanup completed successfully');
478
+ return cleanupResults;
479
+ } catch (error) {
480
+ this.logAuditEvent('DATA_CLEANUP_FAILED', environment, {
481
+ cleanupId: cleanupResults.cleanupId,
482
+ error: error.message
483
+ });
484
+ throw error;
485
+ }
486
+ }
487
+
488
+ /**
489
+ * Perform cleanup on specific database
490
+ * @param {string} databaseName - Database name
491
+ * @param {string} environment - Environment
492
+ * @param {string} cleanupType - Type of cleanup
493
+ * @returns {Promise<Object>} Cleanup result
494
+ */
495
+ async performDatabaseCleanup(databaseName, environment, cleanupType) {
496
+ console.log(` 🧹 Cleaning ${databaseName} (${cleanupType})...`);
497
+ if (this.dryRun) {
498
+ console.log(` 🔍 DRY RUN: Would perform ${cleanupType} cleanup on ${databaseName}`);
499
+ return {
500
+ status: 'dry-run',
501
+ cleanupType
502
+ };
503
+ }
504
+ const commands = this.getCleanupCommands(cleanupType, environment);
505
+ let executedCommands = 0;
506
+ try {
507
+ for (const command of commands) {
508
+ const fullCommand = this.buildDatabaseCommand(command, databaseName, environment);
509
+ await this.executeWithRetry(fullCommand, 60000);
510
+ executedCommands++;
511
+ }
512
+ console.log(` ✅ Cleanup completed: ${executedCommands} operations`);
513
+ return {
514
+ status: 'completed',
515
+ cleanupType,
516
+ operationsExecuted: executedCommands,
517
+ timestamp: new Date()
518
+ };
519
+ } catch (error) {
520
+ throw new Error(`Database cleanup failed after ${executedCommands} operations: ${error.message}`);
521
+ }
522
+ }
523
+
524
+ // Command builders and utility methods
525
+
526
+ buildMigrationCommand(databaseName, environment, isRemote) {
527
+ const remoteFlag = isRemote ? '--remote' : '--local';
528
+ return `npx wrangler d1 migrations apply ${databaseName} --env ${environment} ${remoteFlag}`;
529
+ }
530
+ buildBackupCommand(databaseName, environment, backupFile, isRemote) {
531
+ const remoteFlag = isRemote ? '--remote' : '--local';
532
+ return `npx wrangler d1 export ${databaseName} --env ${environment} ${remoteFlag} --output ${backupFile}`;
533
+ }
534
+ buildDatabaseCommand(sqlCommand, databaseName, environment) {
535
+ const envConfig = this.environments[environment];
536
+ const remoteFlag = envConfig.isRemote ? '--remote' : '--local';
537
+ return `npx wrangler d1 execute ${databaseName} --env ${environment} ${remoteFlag} --command "${sqlCommand}"`;
538
+ }
539
+ getCleanupCommands(cleanupType) {
540
+ const commands = {
541
+ 'logs-only': ['DELETE FROM logs WHERE created_at < datetime("now", "-30 days");'],
542
+ 'partial': ['DELETE FROM logs WHERE created_at < datetime("now", "-7 days");', 'DELETE FROM sessions WHERE expires_at < datetime("now");', 'UPDATE users SET last_cleanup = datetime("now") WHERE last_cleanup IS NULL;'],
543
+ 'full': ['DELETE FROM logs;', 'DELETE FROM sessions;', 'DELETE FROM files;', 'DELETE FROM user_profiles;', 'DELETE FROM users;']
544
+ };
545
+ return commands[cleanupType] || commands['partial'];
546
+ }
547
+ parseMigrationOutput(output) {
548
+ // Parse wrangler migration output to count applied migrations
549
+ const matches = output.match(/Applied (\d+) migration/);
550
+ return matches ? parseInt(matches[1]) : 0;
551
+ }
552
+ async executeWithRetry(command, timeout = null) {
553
+ const actualTimeout = timeout || (this.config ? this.config.executionTimeout : 30000);
554
+ for (let attempt = 1; attempt <= (this.config ? this.config.retryAttempts : 3); attempt++) {
555
+ try {
556
+ const {
557
+ stdout
558
+ } = await execAsync(command, {
559
+ encoding: 'utf8',
560
+ timeout: actualTimeout,
561
+ stdio: 'pipe'
562
+ });
563
+ return stdout;
564
+ } catch (error) {
565
+ if (attempt === this.retryAttempts) {
566
+ throw error;
567
+ }
568
+ console.log(` ⚠️ Attempt ${attempt} failed, retrying...`);
569
+ await new Promise(resolve => setTimeout(resolve, this.retryDelay));
570
+ }
571
+ }
572
+ }
573
+ async confirmDangerousOperation(operation, impact) {
574
+ // In a real implementation, this would use a proper input library
575
+ console.log(`\n⚠️ DANGER: ${operation}`);
576
+ console.log(`Impact: ${impact}`);
577
+ console.log('Type "YES" to confirm this operation:');
578
+
579
+ // For now, return false to prevent accidental execution
580
+ return false;
581
+ }
582
+
583
+ // Utility methods
584
+
585
+ generateOrchestrationId() {
586
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
587
+ return `orchestration-${timestamp}`;
588
+ }
589
+ generateBackupId(environment) {
590
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
591
+ return `backup-${environment}-${timestamp}`;
592
+ }
593
+ generateCleanupId(environment) {
594
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
595
+ return `cleanup-${environment}-${timestamp}`;
596
+ }
597
+ async ensureDirectory(path) {
598
+ try {
599
+ await access(path);
600
+ } catch {
601
+ await mkdir(path, {
602
+ recursive: true
603
+ });
604
+ }
605
+ }
606
+ logAuditEvent(event, environment, details = {}) {
607
+ const logEntry = {
608
+ timestamp: new Date().toISOString(),
609
+ event,
610
+ environment,
611
+ details,
612
+ user: process.env.USER || process.env.USERNAME || 'system'
613
+ };
614
+
615
+ // Skip logging if running as dependency (no file access)
616
+ if (!this.backupPaths) {
617
+ console.log(`📊 Audit: ${event} (${environment})`);
618
+ return;
619
+ }
620
+ try {
621
+ const logLine = JSON.stringify(logEntry) + '\n';
622
+ if (existsSync(this.backupPaths.audit)) {
623
+ appendFile(this.backupPaths.audit, logLine);
624
+ } else {
625
+ writeFile(this.backupPaths.audit, logLine);
626
+ }
627
+ } catch (error) {
628
+ console.warn(`⚠️ Failed to log audit event: ${error.message}`);
629
+ }
630
+ }
631
+ }
632
+ export default DatabaseOrchestrator;