@tamyla/clodo-framework 3.1.9 → 3.1.11

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