@tamyla/clodo-framework 3.1.21 → 3.1.22

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 (169) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +53 -0
  3. package/dist/bin/clodo-service.js +47 -15
  4. package/dist/bin/commands/deploy.js +115 -83
  5. package/dist/bin/commands/helpers/deployment-ui.js +138 -0
  6. package/dist/bin/commands/helpers/deployment-verification.js +251 -0
  7. package/dist/bin/commands/helpers/error-recovery.js +80 -0
  8. package/dist/bin/commands/helpers/resource-detection.js +113 -0
  9. package/dist/bin/commands/validate.js +1 -1
  10. package/dist/bin/security/security-cli.js +1 -1
  11. package/dist/bin/shared/cache/configuration-cache.js +82 -0
  12. package/dist/bin/shared/cloudflare/domain-manager.js +1 -1
  13. package/dist/bin/shared/cloudflare/index.js +1 -1
  14. package/dist/bin/shared/cloudflare/ops.js +6 -4
  15. package/dist/bin/shared/config/ConfigurationManager.js +23 -1
  16. package/dist/bin/shared/config/command-config-manager.js +19 -3
  17. package/dist/bin/shared/config/index.js +1 -1
  18. package/dist/bin/shared/deployment/credential-collector.js +30 -7
  19. package/dist/bin/shared/deployment/index.js +2 -2
  20. package/dist/bin/shared/deployment/rollback-manager.js +4 -520
  21. package/dist/bin/shared/deployment/utilities/d1-error-recovery.js +177 -0
  22. package/dist/bin/shared/deployment/validator.js +40 -10
  23. package/dist/bin/shared/deployment/workflows/deployment-summary.js +214 -0
  24. package/dist/bin/shared/deployment/workflows/interactive-confirmation.js +188 -0
  25. package/dist/bin/shared/deployment/workflows/interactive-database-workflow.js +234 -0
  26. package/dist/bin/shared/deployment/workflows/interactive-domain-info-gatherer.js +240 -0
  27. package/dist/bin/shared/deployment/workflows/interactive-secret-workflow.js +228 -0
  28. package/dist/bin/shared/deployment/workflows/interactive-testing-workflow.js +235 -0
  29. package/dist/bin/shared/deployment/workflows/interactive-validation.js +218 -0
  30. package/dist/bin/shared/error-handling/error-classifier.js +46 -0
  31. package/dist/bin/shared/monitoring/health-checker.js +129 -1
  32. package/dist/bin/shared/monitoring/memory-manager.js +17 -6
  33. package/dist/bin/shared/routing/domain-router.js +1 -1
  34. package/dist/bin/shared/utils/deployment-validator.js +97 -0
  35. package/dist/bin/shared/utils/formatters.js +10 -0
  36. package/dist/bin/shared/utils/index.js +13 -1
  37. package/dist/bin/shared/utils/interactive-prompts.js +34 -18
  38. package/dist/bin/shared/utils/progress-manager.js +2 -2
  39. package/dist/bin/shared/utils/progress-spinner.js +53 -0
  40. package/dist/bin/shared/utils/sensitive-redactor.js +91 -0
  41. package/dist/bin/shared/validation/ValidationRegistry.js +1 -1
  42. package/dist/security/index.js +1 -1
  43. package/dist/security/patterns/insecure-patterns.js +1 -1
  44. package/dist/utils/constants.js +102 -0
  45. package/dist/utils/deployment/wrangler-config-manager.js +215 -48
  46. package/dist/utils/framework-config.js +2 -2
  47. package/dist/utils/interactive-prompts.js +10 -59
  48. package/package.json +16 -8
  49. package/dist/bin/clodo-service-old.js +0 -868
  50. package/dist/bin/clodo-service-test.js +0 -10
  51. package/dist/bin/commands/assess.js +0 -91
  52. package/dist/bin/commands/create.js +0 -77
  53. package/dist/bin/commands/diagnose.js +0 -83
  54. package/dist/bin/commands/helpers.js +0 -138
  55. package/dist/bin/commands/update.js +0 -75
  56. package/dist/bin/database/deployment-db-manager.js +0 -423
  57. package/dist/bin/database/enterprise-db-manager.js +0 -457
  58. package/dist/bin/database/wrangler-d1-manager.js +0 -685
  59. package/dist/bin/deployment/enterprise-deploy.js +0 -877
  60. package/dist/bin/deployment/master-deploy.js +0 -1376
  61. package/dist/bin/deployment/modular-enterprise-deploy.js +0 -466
  62. package/dist/bin/deployment/modules/DeploymentConfiguration.js +0 -395
  63. package/dist/bin/deployment/modules/DeploymentOrchestrator.js +0 -492
  64. package/dist/bin/deployment/modules/EnvironmentManager.js +0 -517
  65. package/dist/bin/deployment/modules/MonitoringIntegration.js +0 -560
  66. package/dist/bin/deployment/modules/ValidationManager.js +0 -342
  67. package/dist/bin/deployment/orchestration/BaseDeploymentOrchestrator.js +0 -426
  68. package/dist/bin/deployment/orchestration/EnterpriseOrchestrator.js +0 -401
  69. package/dist/bin/deployment/orchestration/PortfolioOrchestrator.js +0 -273
  70. package/dist/bin/deployment/orchestration/SingleServiceOrchestrator.js +0 -231
  71. package/dist/bin/deployment/orchestration/UnifiedDeploymentOrchestrator.js +0 -662
  72. package/dist/bin/deployment/test-interactive-utils.js +0 -66
  73. package/dist/bin/portfolio/portfolio-manager.js +0 -487
  74. package/dist/bin/service-management/create-service.js +0 -122
  75. package/dist/bin/service-management/init-service.js +0 -79
  76. package/dist/config/customers.js +0 -623
  77. package/dist/config/domains.js +0 -186
  78. package/dist/config/index.js +0 -6
  79. package/dist/database/database-orchestrator.js +0 -795
  80. package/dist/database/index.js +0 -4
  81. package/dist/deployment/index.js +0 -11
  82. package/dist/deployment/orchestration/BaseDeploymentOrchestrator.js +0 -426
  83. package/dist/deployment/orchestration/EnterpriseOrchestrator.js +0 -401
  84. package/dist/deployment/orchestration/PortfolioOrchestrator.js +0 -273
  85. package/dist/deployment/orchestration/SingleServiceOrchestrator.js +0 -231
  86. package/dist/deployment/orchestration/UnifiedDeploymentOrchestrator.js +0 -662
  87. package/dist/deployment/orchestration/index.js +0 -17
  88. package/dist/deployment/rollback-manager.js +0 -36
  89. package/dist/deployment/wrangler-deployer.js +0 -640
  90. package/dist/handlers/GenericRouteHandler.js +0 -532
  91. package/dist/migration/MigrationAdapters.js +0 -562
  92. package/dist/modules/ModuleManager.js +0 -668
  93. package/dist/modules/security.js +0 -96
  94. package/dist/orchestration/cross-domain-coordinator.js +0 -1083
  95. package/dist/orchestration/index.js +0 -5
  96. package/dist/orchestration/modules/DeploymentCoordinator.js +0 -368
  97. package/dist/orchestration/modules/DomainResolver.js +0 -198
  98. package/dist/orchestration/modules/StateManager.js +0 -332
  99. package/dist/orchestration/multi-domain-orchestrator.js +0 -724
  100. package/dist/routing/EnhancedRouter.js +0 -158
  101. package/dist/schema/SchemaManager.js +0 -778
  102. package/dist/service-management/ConfirmationEngine.js +0 -412
  103. package/dist/service-management/ErrorTracker.js +0 -299
  104. package/dist/service-management/GenerationEngine.js +0 -447
  105. package/dist/service-management/InputCollector.js +0 -619
  106. package/dist/service-management/ServiceCreator.js +0 -265
  107. package/dist/service-management/ServiceInitializer.js +0 -453
  108. package/dist/service-management/ServiceOrchestrator.js +0 -633
  109. package/dist/service-management/generators/BaseGenerator.js +0 -233
  110. package/dist/service-management/generators/GeneratorRegistry.js +0 -254
  111. package/dist/service-management/generators/cicd/CiWorkflowGenerator.js +0 -87
  112. package/dist/service-management/generators/cicd/DeployWorkflowGenerator.js +0 -106
  113. package/dist/service-management/generators/code/ServiceHandlersGenerator.js +0 -235
  114. package/dist/service-management/generators/code/ServiceMiddlewareGenerator.js +0 -116
  115. package/dist/service-management/generators/code/ServiceUtilsGenerator.js +0 -246
  116. package/dist/service-management/generators/code/WorkerIndexGenerator.js +0 -143
  117. package/dist/service-management/generators/config/DevelopmentEnvGenerator.js +0 -101
  118. package/dist/service-management/generators/config/DomainsConfigGenerator.js +0 -175
  119. package/dist/service-management/generators/config/EnvExampleGenerator.js +0 -178
  120. package/dist/service-management/generators/config/ProductionEnvGenerator.js +0 -97
  121. package/dist/service-management/generators/config/StagingEnvGenerator.js +0 -97
  122. package/dist/service-management/generators/config/WranglerTomlGenerator.js +0 -238
  123. package/dist/service-management/generators/core/PackageJsonGenerator.js +0 -243
  124. package/dist/service-management/generators/core/SiteConfigGenerator.js +0 -115
  125. package/dist/service-management/generators/documentation/ApiDocsGenerator.js +0 -331
  126. package/dist/service-management/generators/documentation/ConfigurationDocsGenerator.js +0 -294
  127. package/dist/service-management/generators/documentation/DeploymentDocsGenerator.js +0 -244
  128. package/dist/service-management/generators/documentation/ReadmeGenerator.js +0 -196
  129. package/dist/service-management/generators/schemas/ServiceSchemaGenerator.js +0 -190
  130. package/dist/service-management/generators/scripts/DeployScriptGenerator.js +0 -123
  131. package/dist/service-management/generators/scripts/HealthCheckScriptGenerator.js +0 -101
  132. package/dist/service-management/generators/scripts/SetupScriptGenerator.js +0 -88
  133. package/dist/service-management/generators/service-types/StaticSiteGenerator.js +0 -342
  134. package/dist/service-management/generators/testing/EslintConfigGenerator.js +0 -85
  135. package/dist/service-management/generators/testing/IntegrationTestsGenerator.js +0 -237
  136. package/dist/service-management/generators/testing/JestConfigGenerator.js +0 -72
  137. package/dist/service-management/generators/testing/UnitTestsGenerator.js +0 -277
  138. package/dist/service-management/generators/tooling/DockerComposeGenerator.js +0 -71
  139. package/dist/service-management/generators/tooling/GitignoreGenerator.js +0 -143
  140. package/dist/service-management/generators/utils/FileWriter.js +0 -179
  141. package/dist/service-management/generators/utils/PathResolver.js +0 -157
  142. package/dist/service-management/generators/utils/ServiceManifestGenerator.js +0 -111
  143. package/dist/service-management/generators/utils/TemplateEngine.js +0 -185
  144. package/dist/service-management/generators/utils/index.js +0 -18
  145. package/dist/service-management/handlers/ConfirmationHandler.js +0 -71
  146. package/dist/service-management/handlers/GenerationHandler.js +0 -80
  147. package/dist/service-management/handlers/InputHandler.js +0 -59
  148. package/dist/service-management/handlers/ValidationHandler.js +0 -203
  149. package/dist/service-management/index.js +0 -14
  150. package/dist/service-management/routing/DomainRouteMapper.js +0 -311
  151. package/dist/service-management/routing/RouteGenerator.js +0 -266
  152. package/dist/service-management/routing/WranglerRoutesBuilder.js +0 -273
  153. package/dist/service-management/routing/index.js +0 -14
  154. package/dist/service-management/services/DirectoryStructureService.js +0 -56
  155. package/dist/service-management/services/GenerationCoordinator.js +0 -208
  156. package/dist/service-management/services/GeneratorRegistry.js +0 -174
  157. package/dist/services/GenericDataService.js +0 -501
  158. package/dist/ui-structures/concepts/second-order-acquisition-strategy.md +0 -286
  159. package/dist/ui-structures/concepts/service-lifecycle-management.md +0 -150
  160. package/dist/ui-structures/concepts/service-manifest-guide.md +0 -309
  161. package/dist/ui-structures/concepts/three-tier-categorization-strategy.md +0 -231
  162. package/dist/ui-structures/creation/automated-generation-ui.json +0 -246
  163. package/dist/ui-structures/creation/core-inputs-ui.json +0 -217
  164. package/dist/ui-structures/creation/smart-confirmable-ui.json +0 -451
  165. package/dist/ui-structures/reference/absolutely-required-inputs.json +0 -315
  166. package/dist/ui-structures/reference/service-manifest-template.json +0 -342
  167. package/dist/version/VersionDetector.js +0 -723
  168. package/dist/worker/index.js +0 -4
  169. package/dist/worker/integration.js +0 -351
@@ -1,795 +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
- import { exec } from 'child_process';
10
- import { writeFile, access, mkdir, 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
- import { databaseExists, createDatabase } from '../utils/cloudflare/index.js';
16
- const execAsync = promisify(exec);
17
-
18
- // ESM-compatible __dirname and __filename
19
- let __dirname;
20
- let __filename;
21
- try {
22
- __filename = fileURLToPath(import.meta.url);
23
- __dirname = dirname(__filename);
24
- } catch {
25
- // Fallback for test environments
26
- __dirname = process.cwd();
27
- __filename = __filename || 'database-orchestrator.js';
28
- }
29
-
30
- /**
31
- * Advanced Database Orchestrator
32
- * Manages database operations across development, staging, and production environments
33
- */
34
- export class DatabaseOrchestrator {
35
- constructor(options = {}) {
36
- // Enhanced project root detection
37
- this.projectRoot = this.detectProjectRoot(options.projectRoot);
38
- this.dryRun = options.dryRun || false;
39
- this.options = options;
40
- this.config = null;
41
-
42
- // Cloudflare API credentials for database operations
43
- this.cloudflareToken = options.cloudflareToken;
44
- this.cloudflareAccountId = options.cloudflareAccountId;
45
-
46
- // Environment configurations
47
- this.environments = {
48
- development: {
49
- name: 'development',
50
- isRemote: false,
51
- description: 'Local development database',
52
- defaultDatabase: 'local-db'
53
- },
54
- staging: {
55
- name: 'staging',
56
- isRemote: true,
57
- description: 'Staging environment database',
58
- requiresConfirmation: true
59
- },
60
- production: {
61
- name: 'production',
62
- isRemote: true,
63
- description: 'PRODUCTION environment database - USE WITH EXTREME CAUTION',
64
- requiresConfirmation: true,
65
- requiresBackup: true
66
- }
67
- };
68
-
69
- // Backup and audit configuration - only set paths if not running as dependency
70
- if (this.projectRoot) {
71
- // Use configurable paths from options or defaults
72
- const baseLogsDir = options.logsDir || 'logs';
73
- const baseBackupDir = options.backupDir || 'backups';
74
- this.backupPaths = {
75
- root: join(this.projectRoot, baseBackupDir, 'database'),
76
- migrations: join(this.projectRoot, baseBackupDir, 'migrations'),
77
- audit: join(this.projectRoot, baseLogsDir, 'database-audit.log')
78
- };
79
- this.migrationPaths = {
80
- root: join(this.projectRoot, 'migrations'),
81
- templates: join(this.projectRoot, 'migration-templates')
82
- };
83
- } else {
84
- // When used as dependency, disable file-based logging
85
- this.backupPaths = null;
86
- this.migrationPaths = null;
87
- console.log('📦 Running as dependency - file logging disabled');
88
- }
89
- this.initializeOrchestrator();
90
- }
91
-
92
- /**
93
- * Initialize database orchestrator
94
- */
95
- initializeOrchestrator() {
96
- console.log('🗄️ Database Orchestrator v1.0');
97
- console.log('==============================');
98
- if (this.projectRoot) {
99
- console.log(`📁 Project Root: ${this.projectRoot}`);
100
- } else {
101
- console.log('📦 Running as dependency - limited functionality');
102
- }
103
- console.log(`🔍 Mode: ${this.dryRun ? 'DRY RUN' : 'LIVE OPERATIONS'}`);
104
- console.log(`🔄 Retry Attempts: ${this.config ? this.config.retryAttempts : 3}`);
105
- console.log('');
106
-
107
- // Create necessary directories (only if not running as dependency)
108
- if (this.backupPaths && this.migrationPaths) {
109
- Object.values(this.backupPaths).forEach(path => {
110
- if (!path.endsWith('.log')) {
111
- this.ensureDirectory(path);
112
- }
113
- });
114
- this.ensureDirectory(this.migrationPaths.root);
115
- this.ensureDirectory(dirname(this.backupPaths.audit));
116
- }
117
- this.logAuditEvent('ORCHESTRATOR_INITIALIZED', 'SYSTEM', {
118
- mode: this.dryRun ? 'DRY_RUN' : 'LIVE',
119
- environments: Object.keys(this.environments)
120
- }).catch(err => console.warn('⚠️ Audit logging failed:', err.message));
121
- }
122
-
123
- /**
124
- * Initialize with framework configuration
125
- */
126
- async initialize() {
127
- try {
128
- // Import framework config for consistent timing and database settings
129
- const {
130
- frameworkConfig
131
- } = await import('../utils/framework-config.js');
132
- const timing = frameworkConfig.getTiming();
133
- const database = frameworkConfig.getDatabaseConfig();
134
- const configPaths = frameworkConfig.getPaths();
135
- this.config = {
136
- retryAttempts: this.options.retryAttempts || timing.retryAttempts,
137
- retryDelay: this.options.retryDelay || timing.retryDelay,
138
- executionTimeout: this.options.executionTimeout || database.executionTimeout,
139
- ...this.options
140
- };
141
-
142
- // Update paths if framework config is available and we have project root
143
- if (this.projectRoot) {
144
- this.backupPaths = {
145
- root: join(this.projectRoot, configPaths.backups, 'database'),
146
- migrations: join(this.projectRoot, configPaths.backups, 'migrations'),
147
- audit: join(this.projectRoot, configPaths.logs, 'database-audit.log')
148
- };
149
- console.log(`📁 Database orchestrator paths updated with framework config`);
150
- console.log(` Backups: ${this.backupPaths.root}`);
151
- console.log(` Audit: ${this.backupPaths.audit}`);
152
- }
153
- } catch (error) {
154
- console.warn(`⚠️ Could not load framework config: ${error.message}. Using existing paths.`);
155
- }
156
-
157
- // Ensure directories exist for logging and backups
158
- if (this.projectRoot && this.backupPaths) {
159
- await this.ensureDirectoryExists(this.backupPaths.root);
160
- await this.ensureDirectoryExists(this.backupPaths.migrations);
161
- await this.ensureDirectoryExists(dirname(this.backupPaths.audit));
162
- }
163
- }
164
-
165
- /**
166
- * Detect project root with enhanced logic
167
- */
168
- detectProjectRoot(providedRoot) {
169
- if (providedRoot) {
170
- console.log(`📁 Using provided project root: ${providedRoot}`);
171
- return providedRoot;
172
- }
173
-
174
- // Check if running as dependency (in node_modules)
175
- const isDependency = __dirname.includes('node_modules');
176
- if (isDependency) {
177
- console.log('📦 Running as dependency - limited functionality mode');
178
- return null;
179
- }
180
-
181
- // Try multiple strategies to detect project root
182
- let candidates = [
183
- // Standard clodo-framework structure
184
- join(__dirname, '..', '..'),
185
- // Alternative if running from dist/
186
- join(__dirname, '..', '..', '..'),
187
- // Current working directory
188
- process.cwd(),
189
- // Environment variable override
190
- process.env.FRAMEWORK_PROJECT_ROOT];
191
-
192
- // Filter out null/undefined candidates
193
- candidates = candidates.filter(Boolean);
194
- for (const candidate of candidates) {
195
- if (this.isValidProjectRoot(candidate)) {
196
- console.log(`📁 Detected project root: ${candidate}`);
197
- return candidate;
198
- }
199
- }
200
- console.warn('⚠️ Could not detect project root. Some features may be limited.');
201
- return null;
202
- }
203
-
204
- /**
205
- * Check if a directory appears to be a valid project root
206
- */
207
- isValidProjectRoot(path) {
208
- try {
209
- // Check for common project indicators
210
- const indicators = ['package.json', 'validation-config.json', 'src', 'bin'];
211
- const hasIndicators = indicators.some(indicator => existsSync(join(path, indicator)));
212
- return hasIndicators;
213
- } catch (error) {
214
- return false;
215
- }
216
- }
217
-
218
- /**
219
- * Ensure a directory exists, creating it if necessary
220
- */
221
- async ensureDirectoryExists(dirPath) {
222
- try {
223
- await mkdir(dirPath, {
224
- recursive: true
225
- });
226
- } catch (error) {
227
- if (error.code !== 'EEXIST') {
228
- console.error(`❌ Failed to create directory ${dirPath}: ${error.message}`);
229
- throw error;
230
- }
231
- }
232
- }
233
-
234
- /**
235
- * Apply migrations across multiple environments with coordination
236
- * @param {Object} options - Migration options
237
- * @returns {Promise<Object>} Migration results
238
- */
239
- async applyMigrationsAcrossEnvironments(options = {}) {
240
- const {
241
- environments = ['development', 'staging', 'production'],
242
- domainConfigs = [],
243
- skipBackup = false,
244
- continueOnError = false
245
- } = options;
246
- console.log('🔄 Cross-Environment Migration Orchestration');
247
- console.log('===========================================');
248
- console.log(`🌍 Environments: ${environments.join(', ')}`);
249
- console.log(`📋 Domains: ${domainConfigs.length} configured`);
250
- console.log('');
251
- const results = {
252
- orchestrationId: this.generateOrchestrationId(),
253
- environments: {},
254
- summary: {
255
- total: 0,
256
- successful: 0,
257
- failed: 0,
258
- skipped: 0
259
- },
260
- startTime: new Date()
261
- };
262
- try {
263
- for (const env of environments) {
264
- if (!this.environments[env]) {
265
- console.log(`⚠️ Unknown environment: ${env}, skipping`);
266
- continue;
267
- }
268
- console.log(`\n🌍 Processing ${env} environment...`);
269
- results.summary.total++;
270
- try {
271
- // Create backup if required
272
- if (this.environments[env].requiresBackup && !skipBackup) {
273
- await this.createEnvironmentBackup(env, domainConfigs);
274
- }
275
-
276
- // Apply migrations for environment
277
- const envResult = await this.applyEnvironmentMigrations(env, domainConfigs, options);
278
- results.environments[env] = {
279
- status: 'completed',
280
- ...envResult
281
- };
282
- results.summary.successful++;
283
- console.log(`✅ ${env} environment completed successfully`);
284
- } catch (error) {
285
- console.error(`❌ ${env} environment failed: ${error.message}`);
286
- results.environments[env] = {
287
- status: 'failed',
288
- error: error.message,
289
- timestamp: new Date()
290
- };
291
- results.summary.failed++;
292
- if (!continueOnError) {
293
- throw new Error(`Migration failed in ${env} environment: ${error.message}`);
294
- }
295
- }
296
- }
297
- results.endTime = new Date();
298
- results.summary.duration = (results.endTime - results.startTime) / 1000;
299
- await this.logAuditEvent('MIGRATION_ORCHESTRATION_COMPLETED', 'ALL', results.summary);
300
- console.log('\n📊 MIGRATION ORCHESTRATION SUMMARY');
301
- console.log('==================================');
302
- console.log(`✅ Successful: ${results.summary.successful}`);
303
- console.log(`❌ Failed: ${results.summary.failed}`);
304
- console.log(`⏸️ Skipped: ${results.summary.skipped}`);
305
- console.log(`⏱️ Duration: ${results.summary.duration.toFixed(1)}s`);
306
- return results;
307
- } catch (error) {
308
- await this.logAuditEvent('MIGRATION_ORCHESTRATION_FAILED', 'ALL', {
309
- error: error.message
310
- });
311
- throw error;
312
- }
313
- }
314
-
315
- /**
316
- * Apply migrations to specific environment
317
- * @param {string} environment - Environment name
318
- * @param {Array} domainConfigs - Domain configurations
319
- * @returns {Promise<Object>} Environment migration result
320
- */
321
- async applyEnvironmentMigrations(environment, domainConfigs) {
322
- const envConfig = this.environments[environment];
323
- const results = {
324
- environment,
325
- databases: {},
326
- migrationsApplied: 0,
327
- startTime: new Date()
328
- };
329
- console.log(` 📋 Environment: ${envConfig.description}`);
330
- console.log(` 🌐 Remote: ${envConfig.isRemote ? 'Yes' : 'No'}`);
331
-
332
- // Process each domain's databases
333
- if (domainConfigs.length > 0) {
334
- for (const domainConfig of domainConfigs) {
335
- const dbConfig = domainConfig.databases?.[environment];
336
- if (!dbConfig) {
337
- console.log(` ⚠️ No ${environment} database config for ${domainConfig.name}`);
338
- continue;
339
- }
340
- try {
341
- const dbResult = await this.applyDatabaseMigrations(dbConfig.name, 'DB',
342
- // bindingName
343
- environment, envConfig.isRemote);
344
- results.databases[dbConfig.name] = dbResult;
345
- results.migrationsApplied += dbResult.migrationsApplied || 0;
346
- } catch (error) {
347
- console.error(` ❌ Database ${dbConfig.name} migration failed: ${error.message}`);
348
- results.databases[dbConfig.name] = {
349
- status: 'failed',
350
- error: error.message
351
- };
352
- throw error;
353
- }
354
- }
355
- } else {
356
- // Apply to default database
357
- const defaultDb = envConfig.defaultDatabase || 'default-db';
358
- const dbResult = await this.applyDatabaseMigrations(defaultDb, 'DB',
359
- // bindingName
360
- environment, envConfig.isRemote);
361
- results.databases[defaultDb] = dbResult;
362
- results.migrationsApplied = dbResult.migrationsApplied || 0;
363
- }
364
- results.endTime = new Date();
365
- results.duration = (results.endTime - results.startTime) / 1000;
366
- await this.logAuditEvent('ENVIRONMENT_MIGRATION_COMPLETED', environment, {
367
- databases: Object.keys(results.databases),
368
- migrationsApplied: results.migrationsApplied,
369
- duration: results.duration
370
- });
371
- return results;
372
- }
373
-
374
- /**
375
- * Apply migrations to specific database
376
- * @param {string} databaseName - Database name (for display/logging)
377
- * @param {string} bindingName - Wrangler binding name (from wrangler.toml, default 'DB')
378
- * @param {string} environment - Environment
379
- * @param {boolean} isRemote - Whether database is remote
380
- * @returns {Promise<Object>} Database migration result
381
- */
382
- async applyDatabaseMigrations(databaseName, bindingName = 'DB', environment, isRemote) {
383
- console.log(` � Applying migrations to ${databaseName}...`);
384
- if (this.dryRun) {
385
- console.log(` 🔍 DRY RUN: Would apply migrations to ${databaseName} (binding: ${bindingName})`);
386
- return {
387
- status: 'dry-run',
388
- databaseName,
389
- bindingName,
390
- environment,
391
- migrationsApplied: 0
392
- };
393
- }
394
- try {
395
- // Validate database exists before attempting migrations
396
- const exists = await databaseExists(databaseName, {
397
- apiToken: this.cloudflareToken,
398
- accountId: this.cloudflareAccountId
399
- });
400
- if (!exists) {
401
- console.log(` 📦 Database ${databaseName} does not exist, creating...`);
402
- if (!this.cloudflareToken || !this.cloudflareAccountId) {
403
- throw new Error(`Database ${databaseName} does not exist and no Cloudflare API credentials provided. ` + `Cannot create database automatically. Please provide cloudflareToken and cloudflareAccountId.`);
404
- }
405
-
406
- // Create the database using API
407
- await createDatabase(databaseName, {
408
- apiToken: this.cloudflareToken,
409
- accountId: this.cloudflareAccountId
410
- });
411
- console.log(` ✅ Database ${databaseName} created successfully`);
412
- } else {
413
- console.log(` ✅ Database ${databaseName} validated`);
414
- }
415
-
416
- // Use DATABASE name for wrangler command
417
- const command = this.buildMigrationCommand(databaseName, environment, isRemote);
418
- console.log(` 📋 Migration command: ${command}`);
419
- console.log(` 📁 Command will run from: ${this.projectRoot}`);
420
- console.log(` 📁 Current working directory: ${process.cwd()}`);
421
- const output = await this.executeWithRetry(command, 120000, this.projectRoot); // 2 minute timeout
422
-
423
- // Parse migration output
424
- const migrationsApplied = this.parseMigrationOutput(output);
425
- console.log(` ✅ Applied ${migrationsApplied} migrations to ${databaseName}`);
426
- await this.logAuditEvent('DATABASE_MIGRATION_APPLIED', environment, {
427
- databaseName,
428
- bindingName,
429
- migrationsApplied,
430
- isRemote
431
- });
432
- return {
433
- status: 'completed',
434
- databaseName,
435
- bindingName,
436
- environment,
437
- migrationsApplied,
438
- output: output.substring(0, 500) // Truncate for storage
439
- };
440
- } catch (error) {
441
- await this.logAuditEvent('DATABASE_MIGRATION_FAILED', environment, {
442
- databaseName,
443
- bindingName,
444
- error: error.message
445
- });
446
- throw new Error(`Migration failed for ${databaseName}: ${error.message}`);
447
- }
448
- }
449
-
450
- /**
451
- * Create comprehensive backup across environments
452
- * @param {string} environment - Environment to backup
453
- * @param {Array} domainConfigs - Domain configurations
454
- * @returns {Promise<Object>} Backup results
455
- */
456
- async createEnvironmentBackup(environment, domainConfigs) {
457
- console.log(` 💾 Creating ${environment} environment backup...`);
458
- const backupId = this.generateBackupId(environment);
459
- const backupDir = join(this.backupPaths.root, environment, backupId);
460
- this.ensureDirectory(backupDir);
461
- const backupResults = {
462
- backupId,
463
- environment,
464
- backupDir,
465
- databases: {},
466
- startTime: new Date()
467
- };
468
- try {
469
- if (domainConfigs.length > 0) {
470
- for (const domainConfig of domainConfigs) {
471
- const dbConfig = domainConfig.databases?.[environment];
472
- if (dbConfig && dbConfig.id) {
473
- try {
474
- const dbBackup = await this.createDatabaseBackup(dbConfig.name, environment, backupDir);
475
- backupResults.databases[dbConfig.name] = dbBackup;
476
- } catch (error) {
477
- console.log(` ⚠️ Backup failed for ${dbConfig.name}: ${error.message}`);
478
- backupResults.databases[dbConfig.name] = {
479
- status: 'failed',
480
- error: error.message
481
- };
482
- }
483
- }
484
- }
485
- }
486
- backupResults.endTime = new Date();
487
- backupResults.duration = (backupResults.endTime - backupResults.startTime) / 1000;
488
-
489
- // Save backup manifest
490
- const manifestPath = join(backupDir, 'backup-manifest.json');
491
- await writeFile(manifestPath, JSON.stringify(backupResults, null, 2));
492
- await this.logAuditEvent('ENVIRONMENT_BACKUP_CREATED', environment, {
493
- backupId,
494
- databases: Object.keys(backupResults.databases),
495
- duration: backupResults.duration
496
- });
497
- console.log(` ✅ Environment backup completed: ${backupId}`);
498
- return backupResults;
499
- } catch (error) {
500
- await this.logAuditEvent('ENVIRONMENT_BACKUP_FAILED', environment, {
501
- backupId,
502
- error: error.message
503
- });
504
- throw error;
505
- }
506
- }
507
-
508
- /**
509
- * Create backup of specific database
510
- * @param {string} databaseName - Database name
511
- * @param {string} environment - Environment
512
- * @param {string} backupDir - Backup directory
513
- * @returns {Promise<Object>} Database backup result
514
- */
515
- async createDatabaseBackup(databaseName, environment, backupDir) {
516
- const backupFile = join(backupDir, `${databaseName}-${environment}.sql`);
517
- if (this.dryRun) {
518
- console.log(` 🔍 DRY RUN: Would backup ${databaseName} to ${backupFile}`);
519
- return {
520
- status: 'dry-run',
521
- backupFile
522
- };
523
- }
524
- try {
525
- const isRemote = this.environments[environment].isRemote;
526
- const command = this.buildBackupCommand(databaseName, environment, backupFile, isRemote);
527
- await this.executeWithRetry(command, 300000, this.projectRoot); // 5 minute timeout for backups
528
-
529
- if (existsSync(backupFile)) {
530
- const stats = await stat(backupFile);
531
- console.log(` 💾 Backup created: ${backupFile} (${(stats.size / 1024).toFixed(1)}KB)`);
532
- return {
533
- status: 'completed',
534
- backupFile,
535
- sizeKB: (stats.size / 1024).toFixed(1),
536
- timestamp: new Date()
537
- };
538
- } else {
539
- throw new Error('Backup file was not created');
540
- }
541
- } catch (error) {
542
- throw new Error(`Database backup failed: ${error.message}`);
543
- }
544
- }
545
-
546
- /**
547
- * Perform safe data cleanup with backup and confirmation
548
- * @param {Object} options - Cleanup options
549
- * @returns {Promise<Object>} Cleanup results
550
- */
551
- async performSafeDataCleanup(options = {}) {
552
- const {
553
- environment = 'development',
554
- domainConfigs = [],
555
- cleanupType = 'partial',
556
- // 'partial', 'full', 'logs-only'
557
- skipBackup = false,
558
- force = false
559
- } = options;
560
- console.log('🧹 Safe Data Cleanup Operation');
561
- console.log('==============================');
562
- console.log(`🌍 Environment: ${environment}`);
563
- console.log(`🧽 Cleanup Type: ${cleanupType}`);
564
- console.log(`💾 Skip Backup: ${skipBackup}`);
565
- console.log('');
566
- const envConfig = this.environments[environment];
567
- if (!envConfig) {
568
- throw new Error(`Unknown environment: ${environment}`);
569
- }
570
-
571
- // Safety confirmation for production
572
- if (environment === 'production' && !force) {
573
- const confirmed = await this.confirmDangerousOperation(`${cleanupType} data cleanup in PRODUCTION`, 'This will permanently delete data and cannot be undone');
574
- if (!confirmed) {
575
- console.log('❌ Operation cancelled by user');
576
- return {
577
- status: 'cancelled',
578
- reason: 'User declined confirmation'
579
- };
580
- }
581
- }
582
- const cleanupResults = {
583
- cleanupId: this.generateCleanupId(environment),
584
- environment,
585
- cleanupType,
586
- operations: {},
587
- startTime: new Date()
588
- };
589
- try {
590
- // Create backup if required
591
- if (!skipBackup && envConfig.requiresBackup) {
592
- const backupResult = await this.createEnvironmentBackup(environment, domainConfigs);
593
- cleanupResults.backup = backupResult;
594
- }
595
-
596
- // Perform cleanup operations
597
- for (const domainConfig of domainConfigs) {
598
- const dbConfig = domainConfig.databases?.[environment];
599
- if (dbConfig) {
600
- try {
601
- const cleanupResult = await this.performDatabaseCleanup(dbConfig.name, environment, cleanupType);
602
- cleanupResults.operations[dbConfig.name] = cleanupResult;
603
- } catch (error) {
604
- console.error(`❌ Cleanup failed for ${dbConfig.name}: ${error.message}`);
605
- cleanupResults.operations[dbConfig.name] = {
606
- status: 'failed',
607
- error: error.message
608
- };
609
- }
610
- }
611
- }
612
- cleanupResults.endTime = new Date();
613
- cleanupResults.duration = (cleanupResults.endTime - cleanupResults.startTime) / 1000;
614
- await this.logAuditEvent('DATA_CLEANUP_COMPLETED', environment, {
615
- cleanupId: cleanupResults.cleanupId,
616
- cleanupType,
617
- operations: Object.keys(cleanupResults.operations),
618
- duration: cleanupResults.duration
619
- });
620
- console.log('\n✅ Data cleanup completed successfully');
621
- return cleanupResults;
622
- } catch (error) {
623
- await this.logAuditEvent('DATA_CLEANUP_FAILED', environment, {
624
- cleanupId: cleanupResults.cleanupId,
625
- error: error.message
626
- });
627
- throw error;
628
- }
629
- }
630
-
631
- /**
632
- * Perform cleanup on specific database
633
- * @param {string} databaseName - Database name
634
- * @param {string} environment - Environment
635
- * @param {string} cleanupType - Type of cleanup
636
- * @returns {Promise<Object>} Cleanup result
637
- */
638
- async performDatabaseCleanup(databaseName, environment, cleanupType) {
639
- console.log(` 🧹 Cleaning ${databaseName} (${cleanupType})...`);
640
- if (this.dryRun) {
641
- console.log(` 🔍 DRY RUN: Would perform ${cleanupType} cleanup on ${databaseName}`);
642
- return {
643
- status: 'dry-run',
644
- cleanupType
645
- };
646
- }
647
- const commands = this.getCleanupCommands(cleanupType, environment);
648
- let executedCommands = 0;
649
- try {
650
- for (const command of commands) {
651
- const fullCommand = this.buildDatabaseCommand(command, databaseName, environment);
652
- await this.executeWithRetry(fullCommand, 60000, this.projectRoot);
653
- executedCommands++;
654
- }
655
- console.log(` ✅ Cleanup completed: ${executedCommands} operations`);
656
- return {
657
- status: 'completed',
658
- cleanupType,
659
- operationsExecuted: executedCommands,
660
- timestamp: new Date()
661
- };
662
- } catch (error) {
663
- throw new Error(`Database cleanup failed after ${executedCommands} operations: ${error.message}`);
664
- }
665
- }
666
-
667
- // Command builders and utility methods
668
-
669
- buildMigrationCommand(databaseName, environment, isRemote) {
670
- // Use DATABASE name, NOT binding name
671
- // Wrangler expects: "npx wrangler d1 migrations apply database-name --remote --env environment"
672
- // NOT: "npx wrangler d1 migrations apply binding-name --local"
673
- let command = `npx wrangler d1 migrations apply ${databaseName}`;
674
-
675
- // Add environment flag for all environments (consistent with bin version)
676
- command += ` --env ${environment}`;
677
-
678
- // For remote environments, add --remote flag
679
- // For local development, use --local
680
- if (isRemote) {
681
- command += ` --remote`;
682
- } else {
683
- command += ` --local`;
684
- }
685
- return command;
686
- }
687
- buildBackupCommand(databaseName, environment, backupFile, isRemote) {
688
- const remoteFlag = isRemote ? '--remote' : '--local';
689
- return `npx wrangler d1 export ${databaseName} --env ${environment} ${remoteFlag} --output ${backupFile}`;
690
- }
691
- buildDatabaseCommand(sqlCommand, databaseName, environment) {
692
- const envConfig = this.environments[environment];
693
- const remoteFlag = envConfig.isRemote ? '--remote' : '--local';
694
- return `npx wrangler d1 execute ${databaseName} --env ${environment} ${remoteFlag} --command "${sqlCommand}"`;
695
- }
696
- getCleanupCommands(cleanupType) {
697
- const commands = {
698
- 'logs-only': ['DELETE FROM logs WHERE created_at < datetime("now", "-30 days");'],
699
- '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;'],
700
- 'full': ['DELETE FROM logs;', 'DELETE FROM sessions;', 'DELETE FROM files;', 'DELETE FROM user_profiles;', 'DELETE FROM users;']
701
- };
702
- return commands[cleanupType] || commands['partial'];
703
- }
704
- parseMigrationOutput(output) {
705
- // Parse wrangler migration output to count applied migrations
706
- const matches = output.match(/Applied (\d+) migration/);
707
- return matches ? parseInt(matches[1]) : 0;
708
- }
709
- async executeWithRetry(command, timeout = null, workingDir = null) {
710
- const actualTimeout = timeout || (this.config ? this.config.executionTimeout : 30000);
711
- const maxAttempts = this.config ? this.config.retryAttempts : 3;
712
- const retryDelay = this.config ? this.config.retryDelay : 1000;
713
- const cwd = workingDir || this.projectRoot;
714
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
715
- try {
716
- const {
717
- stdout
718
- } = await execAsync(command, {
719
- encoding: 'utf8',
720
- timeout: actualTimeout,
721
- stdio: 'pipe',
722
- cwd: cwd
723
- });
724
- return stdout;
725
- } catch (error) {
726
- if (attempt === maxAttempts) {
727
- throw error;
728
- }
729
- console.log(` ⚠️ Attempt ${attempt} failed, retrying...`);
730
- await new Promise(resolve => setTimeout(resolve, retryDelay));
731
- }
732
- }
733
- }
734
- async confirmDangerousOperation(operation, impact) {
735
- // In a real implementation, this would use a proper input library
736
- console.log(`\n⚠️ DANGER: ${operation}`);
737
- console.log(`Impact: ${impact}`);
738
- console.log('Type "YES" to confirm this operation:');
739
-
740
- // For now, return false to prevent accidental execution
741
- return false;
742
- }
743
-
744
- // Utility methods
745
-
746
- generateOrchestrationId() {
747
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
748
- return `orchestration-${timestamp}`;
749
- }
750
- generateBackupId(environment) {
751
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
752
- return `backup-${environment}-${timestamp}`;
753
- }
754
- generateCleanupId(environment) {
755
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
756
- return `cleanup-${environment}-${timestamp}`;
757
- }
758
- async ensureDirectory(path) {
759
- try {
760
- await access(path);
761
- } catch {
762
- await mkdir(path, {
763
- recursive: true
764
- });
765
- }
766
- }
767
- async logAuditEvent(event, environment, details = {}) {
768
- const logEntry = {
769
- timestamp: new Date().toISOString(),
770
- event,
771
- environment,
772
- details,
773
- user: process.env.USER || process.env.USERNAME || 'system'
774
- };
775
-
776
- // Skip logging if running as dependency (no file access)
777
- if (!this.backupPaths) {
778
- console.log(`📊 Audit: ${event} (${environment})`);
779
- return;
780
- }
781
- try {
782
- // Ensure the audit log directory exists
783
- await this.ensureDirectory(dirname(this.backupPaths.audit));
784
- const logLine = JSON.stringify(logEntry) + '\n';
785
- if (existsSync(this.backupPaths.audit)) {
786
- await appendFile(this.backupPaths.audit, logLine);
787
- } else {
788
- await writeFile(this.backupPaths.audit, logLine);
789
- }
790
- } catch (error) {
791
- console.warn(`⚠️ Failed to log audit event: ${error.message}`);
792
- }
793
- }
794
- }
795
- export default DatabaseOrchestrator;