@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,574 @@
1
+ import { spawn } from 'child_process';
2
+ import { execSync } from 'child_process';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+
6
+ /**
7
+ * WranglerDeployer - Executes actual Cloudflare Workers deployments using wrangler CLI
8
+ * Provides integration between Clodo Framework orchestration and wrangler deployment
9
+ */
10
+ export class WranglerDeployer {
11
+ constructor(options = {}) {
12
+ this.cwd = options.cwd || process.cwd();
13
+ this.configPath = options.configPath || 'wrangler.toml';
14
+ this.timeout = options.timeout || 300000; // 5 minutes
15
+ this.maxRetries = options.maxRetries || 1;
16
+ this.environment = options.environment || this.detectEnvironment();
17
+ this.serviceInfo = options.serviceInfo || this.discoverServiceInfo();
18
+ }
19
+
20
+ /**
21
+ * Detect deployment environment from various sources
22
+ * @returns {string} Detected environment
23
+ */
24
+ detectEnvironment() {
25
+ // Priority: explicit option > env var > git branch > default
26
+ if (process.env.NODE_ENV) return process.env.NODE_ENV;
27
+ if (process.env.ENVIRONMENT) return process.env.ENVIRONMENT;
28
+ if (process.env.CF_PAGES_BRANCH) return process.env.CF_PAGES_BRANCH;
29
+
30
+ // Try to detect from git branch
31
+ try {
32
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', {
33
+ cwd: this.cwd
34
+ }).toString().trim();
35
+ if (branch === 'main' || branch === 'master') return 'production';
36
+ if (branch === 'develop' || branch === 'dev') return 'development';
37
+ if (branch.includes('staging')) return 'staging';
38
+ return 'development';
39
+ } catch {
40
+ return 'development';
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Discover service information from project files
46
+ * @returns {Object} Service information
47
+ */
48
+ discoverServiceInfo() {
49
+ const info = {
50
+ name: null,
51
+ version: null,
52
+ domain: null,
53
+ accountId: null
54
+ };
55
+ try {
56
+ // Read package.json
57
+ const packagePath = path.join(this.cwd, 'package.json');
58
+ if (fs.existsSync(packagePath)) {
59
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
60
+ info.name = packageJson.name;
61
+ info.version = packageJson.version;
62
+ }
63
+
64
+ // Read environment variables
65
+ info.accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
66
+ info.domain = process.env.SERVICE_DOMAIN || process.env.DOMAIN;
67
+
68
+ // Try to read from wrangler config
69
+ const configPath = path.join(this.cwd, 'wrangler.toml');
70
+ if (fs.existsSync(configPath)) {
71
+ const content = fs.readFileSync(configPath, 'utf8');
72
+ const accountMatch = content.match(/account_id\s*=\s*["']([^"']+)["']/);
73
+ if (accountMatch) {
74
+ info.accountId = info.accountId || accountMatch[1];
75
+ }
76
+ }
77
+ } catch (error) {
78
+ // Ignore discovery errors
79
+ }
80
+ return info;
81
+ }
82
+
83
+ /**
84
+ * Deploy worker to specified environment with intelligent configuration discovery
85
+ * @param {string} environment - Environment to deploy to (production, staging, development)
86
+ * @param {Object} options - Additional deployment options
87
+ * @returns {Promise<Object>} Deployment result with URL and metadata
88
+ */
89
+ async deploy(environment = 'production', options = {}) {
90
+ const startTime = Date.now();
91
+ try {
92
+ console.log(` 🚀 Executing wrangler deploy --env ${environment}`);
93
+
94
+ // Discover deployment configuration intelligently
95
+ const deployConfig = await this.discoverDeploymentConfig(environment);
96
+
97
+ // Build wrangler command arguments based on discovered config
98
+ const args = await this.buildWranglerCommand(environment, deployConfig, options);
99
+ console.log(` 📋 Command: npx ${args.join(' ')}`);
100
+ console.log(` 📁 Config: ${deployConfig.configPath}`);
101
+ console.log(` 🌍 Environment: ${environment}`);
102
+
103
+ // Execute wrangler command
104
+ const result = await this.executeWranglerCommand(args, {
105
+ cwd: this.cwd,
106
+ timeout: this.timeout
107
+ });
108
+ const duration = Date.now() - startTime;
109
+ if (result.success) {
110
+ // Extract deployment URL with enhanced parsing
111
+ const deploymentUrl = this.extractDeploymentUrl(result.output, deployConfig);
112
+ console.log(` ✅ Deployment successful in ${duration}ms`);
113
+ console.log(` 🌐 URL: ${deploymentUrl}`);
114
+ return {
115
+ success: true,
116
+ url: deploymentUrl,
117
+ environment,
118
+ duration,
119
+ deploymentId: this.generateDeploymentId(),
120
+ output: result.output,
121
+ config: deployConfig,
122
+ timestamp: new Date().toISOString()
123
+ };
124
+ } else {
125
+ console.error(` ❌ Deployment failed after ${duration}ms`);
126
+ console.error(` Error: ${result.error}`);
127
+ throw new Error(`Wrangler deployment failed: ${result.error}`);
128
+ }
129
+ } catch (error) {
130
+ const duration = Date.now() - startTime;
131
+ console.error(` ❌ Deployment error after ${duration}ms: ${error.message}`);
132
+ throw new Error(`Deployment failed: ${error.message}`);
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Execute wrangler command and capture output
138
+ * @param {Array<string>} args - Command arguments
139
+ * @param {Object} options - Execution options
140
+ * @returns {Promise<Object>} Command execution result
141
+ */
142
+ async executeWranglerCommand(args, options = {}) {
143
+ return new Promise((resolve, reject) => {
144
+ const child = spawn('npx', args, {
145
+ cwd: options.cwd || this.cwd,
146
+ stdio: ['inherit', 'pipe', 'pipe'],
147
+ shell: true,
148
+ timeout: options.timeout || this.timeout
149
+ });
150
+ let stdout = '';
151
+ let stderr = '';
152
+ child.stdout.on('data', data => {
153
+ const output = data.toString();
154
+ stdout += output;
155
+ console.log(` ${output.trim()}`);
156
+ });
157
+ child.stderr.on('data', data => {
158
+ const output = data.toString();
159
+ stderr += output;
160
+ console.error(` ${output.trim()}`);
161
+ });
162
+ child.on('close', code => {
163
+ const success = code === 0;
164
+ resolve({
165
+ success,
166
+ code,
167
+ output: stdout,
168
+ error: stderr,
169
+ fullOutput: stdout + stderr
170
+ });
171
+ });
172
+ child.on('error', error => {
173
+ reject(new Error(`Failed to execute wrangler: ${error.message}`));
174
+ });
175
+
176
+ // Handle timeout
177
+ if (options.timeout) {
178
+ setTimeout(() => {
179
+ child.kill('SIGTERM');
180
+ reject(new Error(`Wrangler command timed out after ${options.timeout}ms`));
181
+ }, options.timeout);
182
+ }
183
+ });
184
+ }
185
+
186
+ /**
187
+ * Extract deployment URL from wrangler output with intelligent parsing
188
+ * @param {string} output - Wrangler command output
189
+ * @param {Object} config - Deployment configuration
190
+ * @returns {string|null} Deployment URL or null if not found
191
+ */
192
+ extractDeploymentUrl(output, config) {
193
+ // Strategy 1: Look for explicit URL patterns in output
194
+ const urlPatterns = [/https:\/\/[^\s\n]+/g, /Deployed to:\s*(https:\/\/[^\s\n]+)/i, /Your worker has been deployed to:\s*(https:\/\/[^\s\n]+)/i, /Worker URL:\s*(https:\/\/[^\s\n]+)/i, /Available at:\s*(https:\/\/[^\s\n]+)/i];
195
+ for (const pattern of urlPatterns) {
196
+ const matches = output.match(pattern);
197
+ if (matches && matches.length > 0) {
198
+ for (const match of matches) {
199
+ const url = match.replace(/^.*?https:\/\//, 'https://').split(/[>\s\n]/)[0];
200
+ if (url.startsWith('https://') && this.isValidDeploymentUrl(url, config)) {
201
+ return url;
202
+ }
203
+ }
204
+ }
205
+ }
206
+
207
+ // Strategy 2: Use routes from config if available
208
+ if (config.routes && config.routes.length > 0) {
209
+ for (const route of config.routes) {
210
+ if (route.startsWith('https://')) {
211
+ return route;
212
+ }
213
+ }
214
+ }
215
+
216
+ // Strategy 3: Construct URL from worker name
217
+ if (config.workerName) {
218
+ // Check if it's already a full domain
219
+ if (config.workerName.includes('.')) {
220
+ return `https://${config.workerName}`;
221
+ }
222
+
223
+ // Construct workers.dev URL
224
+ return `https://${config.workerName}.workers.dev`;
225
+ }
226
+
227
+ // Strategy 4: Fallback URL construction from environment and output
228
+ const lines = output.split('\n');
229
+ for (const line of lines) {
230
+ if (line.includes('https://') && (line.includes('.workers.dev') || line.includes(config.environment) || line.includes('deployed'))) {
231
+ const urlMatch = line.match(/https:\/\/[^\s\n>]+/);
232
+ if (urlMatch) {
233
+ return urlMatch[0];
234
+ }
235
+ }
236
+ }
237
+ return null;
238
+ }
239
+
240
+ /**
241
+ * Validate if a URL is a likely deployment URL
242
+ * @param {string} url - URL to validate
243
+ * @param {Object} config - Deployment config
244
+ * @returns {boolean} True if URL appears valid for deployment
245
+ */
246
+ isValidDeploymentUrl(url, config) {
247
+ try {
248
+ const urlObj = new URL(url);
249
+
250
+ // Must be HTTPS
251
+ if (urlObj.protocol !== 'https:') return false;
252
+
253
+ // Should be workers.dev or custom domain
254
+ const hostname = urlObj.hostname;
255
+ return hostname.includes('.workers.dev') || hostname.includes('.dev') || hostname.includes(config.environment) || config.workerName && hostname.includes(config.workerName);
256
+ } catch {
257
+ return false;
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Generate unique deployment ID
263
+ * @returns {string} Unique deployment identifier
264
+ */
265
+ generateDeploymentId() {
266
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
267
+ const random = Math.random().toString(36).substring(2, 8);
268
+ return `deploy-${timestamp}-${random}`;
269
+ }
270
+
271
+ /**
272
+ * Intelligently discover deployment configuration for the given environment
273
+ * @param {string} environment - Target environment
274
+ * @returns {Promise<Object>} Discovered configuration
275
+ */
276
+ async discoverDeploymentConfig(environment) {
277
+ const config = {
278
+ configPath: null,
279
+ hasEnvironmentConfig: false,
280
+ workerName: null,
281
+ routes: [],
282
+ environment
283
+ };
284
+
285
+ // Strategy 1: Check for environment-specific wrangler.toml
286
+ const envConfigPath = path.join(this.cwd, 'config', 'wrangler.toml');
287
+ if (fs.existsSync(envConfigPath)) {
288
+ config.configPath = 'config/wrangler.toml';
289
+ config.hasEnvironmentConfig = true;
290
+ try {
291
+ // Try to parse basic config info (without TOML dependency)
292
+ const content = fs.readFileSync(envConfigPath, 'utf8');
293
+ config.workerName = this.extractWorkerNameFromConfig(content);
294
+ config.routes = this.extractRoutesFromConfig(content);
295
+ } catch (error) {
296
+ console.warn(` ⚠️ Could not parse config file: ${error.message}`);
297
+ }
298
+ }
299
+
300
+ // Strategy 2: Check for root wrangler.toml
301
+ if (!config.configPath) {
302
+ const rootConfigPath = path.join(this.cwd, 'wrangler.toml');
303
+ if (fs.existsSync(rootConfigPath)) {
304
+ config.configPath = 'wrangler.toml';
305
+ try {
306
+ const content = fs.readFileSync(rootConfigPath, 'utf8');
307
+ config.workerName = this.extractWorkerNameFromConfig(content);
308
+ config.routes = this.extractRoutesFromConfig(content);
309
+
310
+ // Check if root config has environment-specific sections
311
+ config.hasEnvironmentConfig = content.includes(`[env.${environment}]`);
312
+ } catch (error) {
313
+ console.warn(` ⚠️ Could not parse root config: ${error.message}`);
314
+ }
315
+ }
316
+ }
317
+
318
+ // Strategy 3: Fallback to package.json for worker name
319
+ if (!config.workerName) {
320
+ try {
321
+ const packagePath = path.join(this.cwd, 'package.json');
322
+ if (fs.existsSync(packagePath)) {
323
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
324
+ config.workerName = packageJson.name;
325
+ }
326
+ } catch (error) {
327
+ // Ignore package.json parsing errors
328
+ }
329
+ }
330
+ return config;
331
+ }
332
+
333
+ /**
334
+ * Build wrangler command arguments based on discovered configuration
335
+ * @param {string} environment - Target environment
336
+ * @param {Object} config - Discovered configuration
337
+ * @param {Object} options - Additional options
338
+ * @returns {Promise<Array<string>>} Command arguments
339
+ */
340
+ async buildWranglerCommand(environment, config, options = {}) {
341
+ const args = ['wrangler', 'deploy'];
342
+
343
+ // Add config path if not default
344
+ if (config.configPath && config.configPath !== 'wrangler.toml') {
345
+ args.push('--config', config.configPath);
346
+ }
347
+
348
+ // Add environment flag based on configuration
349
+ if (config.hasEnvironmentConfig) {
350
+ args.push('--env', environment);
351
+ } else if (environment !== 'production') {
352
+ // For non-production without explicit env config, still try
353
+ args.push('--env', environment);
354
+ }
355
+
356
+ // Add deployment options
357
+ if (options.dryRun) {
358
+ args.push('--dry-run');
359
+ }
360
+ if (options.legacyAssets) {
361
+ args.push('--legacy-assets');
362
+ }
363
+
364
+ // Add environment variables from process.env that might be relevant
365
+ const envVars = this.extractRelevantEnvironmentVars(environment);
366
+ Object.entries(envVars).forEach(([key, value]) => {
367
+ if (value) {
368
+ args.push('--var', `${key}:${value}`);
369
+ }
370
+ });
371
+
372
+ // Add any environment-specific overrides from options
373
+ if (options.vars) {
374
+ Object.entries(options.vars).forEach(([key, value]) => {
375
+ args.push('--var', `${key}:${value}`);
376
+ });
377
+ }
378
+ return args;
379
+ }
380
+
381
+ /**
382
+ * Extract worker name from wrangler config content (basic parsing)
383
+ * @param {string} content - Config file content
384
+ * @returns {string|null} Worker name or null
385
+ */
386
+ extractWorkerNameFromConfig(content) {
387
+ const nameMatch = content.match(/name\s*=\s*["']([^"']+)["']/);
388
+ return nameMatch ? nameMatch[1] : null;
389
+ }
390
+
391
+ /**
392
+ * Extract routes from wrangler config content
393
+ * @param {string} content - Config file content
394
+ * @returns {Array<string>} Routes
395
+ */
396
+ extractRoutesFromConfig(content) {
397
+ const routes = [];
398
+ const routeMatches = content.match(/route\s*=\s*["']([^"']+)["']/g);
399
+ if (routeMatches) {
400
+ routeMatches.forEach(match => {
401
+ const routeMatch = match.match(/route\s*=\s*["']([^"']+)["']/);
402
+ if (routeMatch) {
403
+ routes.push(routeMatch[1]);
404
+ }
405
+ });
406
+ }
407
+ return routes;
408
+ }
409
+
410
+ /**
411
+ * Extract relevant environment variables for deployment
412
+ * @param {string} environment - Target environment
413
+ * @returns {Object} Relevant environment variables
414
+ */
415
+ extractRelevantEnvironmentVars(environment) {
416
+ const relevantVars = {};
417
+
418
+ // Cloudflare-specific variables
419
+ const cfVars = ['CLOUDFLARE_ACCOUNT_ID', 'CLOUDFLARE_ZONE_ID', 'CLOUDFLARE_API_TOKEN', 'CF_API_TOKEN', 'CF_ACCOUNT_ID'];
420
+ cfVars.forEach(varName => {
421
+ if (process.env[varName]) {
422
+ relevantVars[varName] = process.env[varName];
423
+ }
424
+ });
425
+
426
+ // Service-specific variables
427
+ const serviceVars = ['SERVICE_DOMAIN', 'SERVICE_NAME', 'NODE_ENV', 'ENVIRONMENT', 'LOG_LEVEL', 'CORS_ORIGINS', 'DATA_SERVICE_URL', 'AUTH_SERVICE_URL', 'CONTENT_STORE_SERVICE_URL', 'FRONTEND_URL', 'MAGIC_LINK_EXPIRY_MINUTES', 'RATE_LIMIT_WINDOW_MS', 'RATE_LIMIT_MAX', 'MAX_FILE_SIZE', 'ALLOWED_FILE_TYPES', 'SKIP_WEBHOOK_AUTH'];
428
+ serviceVars.forEach(varName => {
429
+ if (process.env[varName]) {
430
+ relevantVars[varName] = process.env[varName];
431
+ }
432
+ });
433
+
434
+ // Environment-specific variables
435
+ const envSpecificVars = [`${environment.toUpperCase()}_URL`, `${environment.toUpperCase()}_DOMAIN`, `CF_${environment.toUpperCase()}_TOKEN`];
436
+ envSpecificVars.forEach(varName => {
437
+ if (process.env[varName]) {
438
+ relevantVars[varName] = process.env[varName];
439
+ }
440
+ });
441
+ return relevantVars;
442
+ }
443
+
444
+ /**
445
+ * Validate wrangler installation and configuration with intelligent discovery
446
+ * @param {string} environment - Environment to validate for
447
+ * @returns {Promise<Object>} Validation result with detailed configuration info
448
+ */
449
+ async validateWranglerSetup(environment = 'production') {
450
+ try {
451
+ console.log(' 🔍 Validating wrangler setup...');
452
+
453
+ // Check if wrangler is available
454
+ const versionResult = await this.executeWranglerCommand(['wrangler', '--version']);
455
+ if (!versionResult.success) {
456
+ return {
457
+ valid: false,
458
+ error: 'Wrangler CLI not found or not working',
459
+ details: versionResult.error
460
+ };
461
+ }
462
+
463
+ // Discover deployment configuration
464
+ const deployConfig = await this.discoverDeploymentConfig(environment);
465
+ if (!deployConfig.configPath) {
466
+ return {
467
+ valid: false,
468
+ error: 'No wrangler configuration found',
469
+ details: 'Expected wrangler.toml or config/wrangler.toml',
470
+ suggestions: ['Run: wrangler init', 'Create wrangler.toml in project root', 'Create config/wrangler.toml for environment-specific config']
471
+ };
472
+ }
473
+
474
+ // Validate authentication
475
+ const authResult = await this.executeWranglerCommand(['wrangler', 'whoami']);
476
+ if (!authResult.success) {
477
+ return {
478
+ valid: false,
479
+ error: 'Wrangler authentication failed',
480
+ details: 'You need to login to Cloudflare',
481
+ suggestions: ['Run: wrangler auth login', 'Or set CLOUDFLARE_API_TOKEN environment variable']
482
+ };
483
+ }
484
+
485
+ // Extract account information
486
+ const accountInfo = this.extractAccountInfo(authResult.output);
487
+
488
+ // Validate configuration can be parsed
489
+ const configValidation = await this.validateWranglerConfig(deployConfig, environment);
490
+ return {
491
+ valid: true,
492
+ version: versionResult.output.trim(),
493
+ config: deployConfig,
494
+ account: accountInfo,
495
+ configValidation,
496
+ authenticated: true,
497
+ ready: configValidation.valid
498
+ };
499
+ } catch (error) {
500
+ return {
501
+ valid: false,
502
+ error: error.message
503
+ };
504
+ }
505
+ }
506
+
507
+ /**
508
+ * Extract account information from wrangler whoami output
509
+ * @param {string} output - wrangler whoami output
510
+ * @returns {Object} Account information
511
+ */
512
+ extractAccountInfo(output) {
513
+ const info = {};
514
+
515
+ // Extract email
516
+ const emailMatch = output.match(/(\S+@\S+\.\S+)/);
517
+ if (emailMatch) {
518
+ info.email = emailMatch[1];
519
+ }
520
+
521
+ // Extract account name/id
522
+ const accountMatch = output.match(/Account:\s*([^\n]+)/i);
523
+ if (accountMatch) {
524
+ info.account = accountMatch[1].trim();
525
+ }
526
+ return info;
527
+ }
528
+
529
+ /**
530
+ * Validate wrangler configuration for the given environment
531
+ * @param {Object} config - Deployment configuration
532
+ * @param {string} environment - Target environment
533
+ * @returns {Promise<Object>} Configuration validation result
534
+ */
535
+ async validateWranglerConfig(config, environment) {
536
+ try {
537
+ const args = ['wrangler', 'deploy', '--dry-run'];
538
+ if (config.configPath && config.configPath !== 'wrangler.toml') {
539
+ args.push('--config', config.configPath);
540
+ }
541
+ if (config.hasEnvironmentConfig) {
542
+ args.push('--env', environment);
543
+ }
544
+ const result = await this.executeWranglerCommand(args);
545
+ return {
546
+ valid: result.success,
547
+ error: result.success ? null : result.error,
548
+ warnings: this.extractConfigWarnings(result.output)
549
+ };
550
+ } catch (error) {
551
+ return {
552
+ valid: false,
553
+ error: error.message
554
+ };
555
+ }
556
+ }
557
+
558
+ /**
559
+ * Extract configuration warnings from wrangler output
560
+ * @param {string} output - Wrangler output
561
+ * @returns {Array<string>} Warning messages
562
+ */
563
+ extractConfigWarnings(output) {
564
+ const warnings = [];
565
+ const lines = output.split('\n');
566
+ for (const line of lines) {
567
+ if (line.includes('Warning') || line.includes('warning') || line.includes('WARN') || line.includes('⚠️')) {
568
+ warnings.push(line.trim());
569
+ }
570
+ }
571
+ return warnings;
572
+ }
573
+ }
574
+ export default WranglerDeployer;