@tamyla/clodo-framework 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +564 -0
- package/LICENSE +21 -0
- package/README.md +1393 -0
- package/bin/README.md +71 -0
- package/bin/clodo-service.js +416 -0
- package/bin/security/security-cli.js +96 -0
- package/bin/service-management/README.md +74 -0
- package/bin/service-management/create-service.js +129 -0
- package/bin/service-management/init-service.js +102 -0
- package/bin/service-management/init-service.js.backup +889 -0
- package/bin/shared/config/customer-cli.js +293 -0
- package/dist/config/ConfigurationManager.js +159 -0
- package/dist/config/CustomerConfigCLI.js +220 -0
- package/dist/config/FeatureManager.js +426 -0
- package/dist/config/customers.js +441 -0
- package/dist/config/domains.js +180 -0
- package/dist/config/features.js +225 -0
- package/dist/config/index.js +6 -0
- package/dist/database/database-orchestrator.js +730 -0
- package/dist/database/index.js +4 -0
- package/dist/deployment/auditor.js +971 -0
- package/dist/deployment/index.js +10 -0
- package/dist/deployment/rollback-manager.js +523 -0
- package/dist/deployment/testers/api-tester.js +80 -0
- package/dist/deployment/testers/auth-tester.js +129 -0
- package/dist/deployment/testers/core.js +217 -0
- package/dist/deployment/testers/database-tester.js +105 -0
- package/dist/deployment/testers/index.js +74 -0
- package/dist/deployment/testers/load-tester.js +120 -0
- package/dist/deployment/testers/performance-tester.js +105 -0
- package/dist/deployment/validator.js +558 -0
- package/dist/deployment/wrangler-deployer.js +574 -0
- package/dist/handlers/GenericRouteHandler.js +532 -0
- package/dist/index.js +39 -0
- package/dist/migration/MigrationAdapters.js +562 -0
- package/dist/modules/ModuleManager.js +668 -0
- package/dist/modules/security.js +98 -0
- package/dist/orchestration/cross-domain-coordinator.js +1083 -0
- package/dist/orchestration/index.js +5 -0
- package/dist/orchestration/modules/DeploymentCoordinator.js +258 -0
- package/dist/orchestration/modules/DomainResolver.js +196 -0
- package/dist/orchestration/modules/StateManager.js +332 -0
- package/dist/orchestration/multi-domain-orchestrator.js +255 -0
- package/dist/routing/EnhancedRouter.js +158 -0
- package/dist/schema/SchemaManager.js +778 -0
- package/dist/security/ConfigurationValidator.js +490 -0
- package/dist/security/DeploymentManager.js +208 -0
- package/dist/security/SecretGenerator.js +142 -0
- package/dist/security/SecurityCLI.js +228 -0
- package/dist/security/index.js +51 -0
- package/dist/security/patterns/environment-rules.js +66 -0
- package/dist/security/patterns/insecure-patterns.js +21 -0
- package/dist/service-management/ConfirmationEngine.js +411 -0
- package/dist/service-management/ErrorTracker.js +294 -0
- package/dist/service-management/GenerationEngine.js +3109 -0
- package/dist/service-management/InputCollector.js +237 -0
- package/dist/service-management/ServiceCreator.js +229 -0
- package/dist/service-management/ServiceInitializer.js +448 -0
- package/dist/service-management/ServiceOrchestrator.js +638 -0
- package/dist/service-management/handlers/ConfigMutator.js +130 -0
- package/dist/service-management/handlers/ConfirmationHandler.js +71 -0
- package/dist/service-management/handlers/GenerationHandler.js +80 -0
- package/dist/service-management/handlers/InputHandler.js +59 -0
- package/dist/service-management/handlers/ValidationHandler.js +203 -0
- package/dist/service-management/index.js +7 -0
- package/dist/services/GenericDataService.js +488 -0
- package/dist/shared/cloudflare/domain-discovery.js +562 -0
- package/dist/shared/cloudflare/domain-manager.js +912 -0
- package/dist/shared/cloudflare/index.js +8 -0
- package/dist/shared/cloudflare/ops.js +387 -0
- package/dist/shared/config/cache.js +1167 -0
- package/dist/shared/config/command-config-manager.js +174 -0
- package/dist/shared/config/customer-cli.js +258 -0
- package/dist/shared/config/index.js +9 -0
- package/dist/shared/config/manager.js +289 -0
- package/dist/shared/database/connection-manager.js +338 -0
- package/dist/shared/database/index.js +7 -0
- package/dist/shared/database/orchestrator.js +632 -0
- package/dist/shared/deployment/auditor.js +971 -0
- package/dist/shared/deployment/index.js +10 -0
- package/dist/shared/deployment/rollback-manager.js +523 -0
- package/dist/shared/deployment/validator.js +558 -0
- package/dist/shared/index.js +32 -0
- package/dist/shared/monitoring/health-checker.js +250 -0
- package/dist/shared/monitoring/index.js +8 -0
- package/dist/shared/monitoring/memory-manager.js +382 -0
- package/dist/shared/monitoring/production-monitor.js +390 -0
- package/dist/shared/production-tester/api-tester.js +80 -0
- package/dist/shared/production-tester/auth-tester.js +129 -0
- package/dist/shared/production-tester/core.js +217 -0
- package/dist/shared/production-tester/database-tester.js +105 -0
- package/dist/shared/production-tester/index.js +74 -0
- package/dist/shared/production-tester/load-tester.js +120 -0
- package/dist/shared/production-tester/performance-tester.js +105 -0
- package/dist/shared/security/api-token-manager.js +296 -0
- package/dist/shared/security/index.js +8 -0
- package/dist/shared/security/secret-generator.js +918 -0
- package/dist/shared/security/secure-token-manager.js +379 -0
- package/dist/shared/utils/error-recovery.js +240 -0
- package/dist/shared/utils/graceful-shutdown-manager.js +380 -0
- package/dist/shared/utils/index.js +9 -0
- package/dist/shared/utils/interactive-prompts.js +134 -0
- package/dist/shared/utils/rate-limiter.js +249 -0
- package/dist/utils/ErrorHandler.js +173 -0
- package/dist/utils/deployment/config-cache.js +1160 -0
- package/dist/utils/deployment/index.js +6 -0
- package/dist/utils/deployment/interactive-prompts.js +97 -0
- package/dist/utils/deployment/secret-generator.js +896 -0
- package/dist/utils/dirname-helper.js +35 -0
- package/dist/utils/domain-config.js +159 -0
- package/dist/utils/error-recovery.js +240 -0
- package/dist/utils/esm-helper.js +52 -0
- package/dist/utils/framework-config.js +481 -0
- package/dist/utils/graceful-shutdown-manager.js +379 -0
- package/dist/utils/health-checker.js +114 -0
- package/dist/utils/index.js +36 -0
- package/dist/utils/prompt-handler.js +98 -0
- package/dist/utils/usage-tracker.js +252 -0
- package/dist/utils/validation.js +112 -0
- package/dist/version/VersionDetector.js +723 -0
- package/dist/worker/index.js +4 -0
- package/dist/worker/integration.js +332 -0
- package/docs/FRAMEWORK-ARCHITECTURE-OVERVIEW.md +206 -0
- package/docs/INTEGRATION_GUIDE.md +2045 -0
- package/docs/README.md +82 -0
- package/docs/SECURITY.md +242 -0
- package/docs/deployment/deployment-guide.md +540 -0
- package/docs/overview.md +280 -0
- package/package.json +176 -0
- package/types/index.d.ts +575 -0
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Domain Discovery Module
|
|
5
|
+
* Enterprise-grade runtime domain configuration discovery and management
|
|
6
|
+
*
|
|
7
|
+
* Extracted from dynamic-config-builder.js with enhancements
|
|
8
|
+
*/
|
|
9
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
|
|
10
|
+
import { join, dirname } from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = dirname(__filename);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Advanced Domain Discovery Manager
|
|
17
|
+
* Discovers and manages domain configurations across Cloudflare infrastructure
|
|
18
|
+
*/
|
|
19
|
+
export class DomainDiscovery {
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
this.apiToken = options.apiToken;
|
|
22
|
+
this.timeout = options.timeout || 30000;
|
|
23
|
+
this.retryAttempts = options.retryAttempts || 3;
|
|
24
|
+
this.retryDelay = options.retryDelay || 2000;
|
|
25
|
+
|
|
26
|
+
// Configuration cache and templates
|
|
27
|
+
this.configCache = new Map();
|
|
28
|
+
this.discoveredConfigs = new Map();
|
|
29
|
+
this.templates = new Map();
|
|
30
|
+
|
|
31
|
+
// Paths for configuration management
|
|
32
|
+
this.configPaths = {
|
|
33
|
+
domains: join(__dirname, '..', '..', 'src', 'config', 'domains.js'),
|
|
34
|
+
runtime: join(__dirname, '..', '..', 'runtime-config'),
|
|
35
|
+
templates: join(__dirname, '..', '..', 'config-templates'),
|
|
36
|
+
cache: join(__dirname, '..', '..', '.config-cache')
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Ensure directories exist
|
|
40
|
+
Object.values(this.configPaths).forEach(path => {
|
|
41
|
+
if (path.endsWith('.js')) return; // Skip files
|
|
42
|
+
if (!existsSync(path)) {
|
|
43
|
+
mkdirSync(path, {
|
|
44
|
+
recursive: true
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
this.initializeDiscovery();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Initialize discovery system
|
|
53
|
+
*/
|
|
54
|
+
initializeDiscovery() {
|
|
55
|
+
console.log('🔍 Domain Discovery System v1.0');
|
|
56
|
+
console.log('===============================');
|
|
57
|
+
console.log(`⚙️ API Token: ${this.apiToken ? 'Configured' : 'Not set'}`);
|
|
58
|
+
console.log(`📁 Cache Directory: ${this.configPaths.cache}`);
|
|
59
|
+
console.log(`🔄 Retry Attempts: ${this.retryAttempts}`);
|
|
60
|
+
console.log('');
|
|
61
|
+
|
|
62
|
+
// Load cached configurations
|
|
63
|
+
this.loadConfigCache();
|
|
64
|
+
|
|
65
|
+
// Load configuration templates
|
|
66
|
+
this.loadConfigTemplates();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Discover complete domain configuration from Cloudflare
|
|
71
|
+
* @param {string} domainName - Domain to discover
|
|
72
|
+
* @param {string} apiToken - Cloudflare API token (optional if set in constructor)
|
|
73
|
+
* @returns {Promise<Object>} Complete domain configuration
|
|
74
|
+
*/
|
|
75
|
+
async discoverDomainConfig(domainName, apiToken = null) {
|
|
76
|
+
const token = apiToken || this.apiToken;
|
|
77
|
+
if (!token) {
|
|
78
|
+
throw new Error('Cloudflare API token is required for domain discovery');
|
|
79
|
+
}
|
|
80
|
+
console.log(`🔍 Discovering configuration for ${domainName}...`);
|
|
81
|
+
|
|
82
|
+
// Check cache first
|
|
83
|
+
const cacheKey = `${domainName}-${this.getCacheKeyHash(token)}`;
|
|
84
|
+
if (this.configCache.has(cacheKey)) {
|
|
85
|
+
const cached = this.configCache.get(cacheKey);
|
|
86
|
+
const age = Date.now() - cached.timestamp;
|
|
87
|
+
|
|
88
|
+
// Use cached config if less than 1 hour old
|
|
89
|
+
if (age < 3600000) {
|
|
90
|
+
console.log(` 📋 Using cached configuration (${Math.round(age / 60000)}m old)`);
|
|
91
|
+
return cached.config;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
// Discovery process
|
|
96
|
+
const discoveredConfig = await this.performDomainDiscovery(domainName, token);
|
|
97
|
+
|
|
98
|
+
// Cache the result
|
|
99
|
+
this.cacheConfiguration(cacheKey, discoveredConfig);
|
|
100
|
+
|
|
101
|
+
// Store in discovered configs
|
|
102
|
+
this.discoveredConfigs.set(domainName, discoveredConfig);
|
|
103
|
+
console.log(`✅ Configuration discovered for ${domainName}`);
|
|
104
|
+
console.log(` 📊 Account: ${discoveredConfig.accountName}`);
|
|
105
|
+
console.log(` 🌐 Zone: ${discoveredConfig.zoneName}`);
|
|
106
|
+
console.log(` 🆔 Zone ID: ${discoveredConfig.zoneId}`);
|
|
107
|
+
return discoveredConfig;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error(`❌ Failed to discover configuration for ${domainName}:`, error.message);
|
|
110
|
+
throw new Error(`Domain discovery failed: ${error.message}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Perform the actual domain discovery process
|
|
116
|
+
* @param {string} domainName - Domain name
|
|
117
|
+
* @param {string} apiToken - API token
|
|
118
|
+
* @returns {Promise<Object>} Discovered configuration
|
|
119
|
+
*/
|
|
120
|
+
async performDomainDiscovery(domainName, apiToken) {
|
|
121
|
+
// Step 1: Get Cloudflare accounts
|
|
122
|
+
const accounts = await this.fetchCloudflareAccounts(apiToken);
|
|
123
|
+
if (!accounts.length) {
|
|
124
|
+
throw new Error('No Cloudflare accounts found');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Use first account or find specific one
|
|
128
|
+
const account = accounts[0];
|
|
129
|
+
console.log(` ✅ Found account: ${account.name} (${account.id})`);
|
|
130
|
+
|
|
131
|
+
// Step 2: Get zones for account
|
|
132
|
+
const zones = await this.fetchAccountZones(account.id, apiToken);
|
|
133
|
+
|
|
134
|
+
// Step 3: Find zone for domain
|
|
135
|
+
const zone = zones.find(z => z.name === domainName || domainName.endsWith(z.name));
|
|
136
|
+
if (!zone) {
|
|
137
|
+
throw new Error(`No zone found for domain: ${domainName}. Available zones: ${zones.map(z => z.name).join(', ')}`);
|
|
138
|
+
}
|
|
139
|
+
console.log(` ✅ Found zone: ${zone.name} (${zone.id})`);
|
|
140
|
+
|
|
141
|
+
// Step 4: Build comprehensive configuration
|
|
142
|
+
const discoveredConfig = await this.buildDomainConfiguration(domainName, account, zone, apiToken);
|
|
143
|
+
return discoveredConfig;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Fetch Cloudflare accounts
|
|
148
|
+
* @param {string} apiToken - API token
|
|
149
|
+
* @returns {Promise<Array>} Account list
|
|
150
|
+
*/
|
|
151
|
+
async fetchCloudflareAccounts(apiToken) {
|
|
152
|
+
console.log(' 🔍 Fetching Cloudflare accounts...');
|
|
153
|
+
const response = await this.makeCloudflareRequest('https://api.cloudflare.com/client/v4/accounts', apiToken);
|
|
154
|
+
if (!response.success || !response.result.length) {
|
|
155
|
+
throw new Error('No Cloudflare accounts accessible with this token');
|
|
156
|
+
}
|
|
157
|
+
return response.result;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Fetch zones for account
|
|
162
|
+
* @param {string} accountId - Account ID
|
|
163
|
+
* @param {string} apiToken - API token
|
|
164
|
+
* @returns {Promise<Array>} Zone list
|
|
165
|
+
*/
|
|
166
|
+
async fetchAccountZones(accountId, apiToken) {
|
|
167
|
+
console.log(' 🌐 Fetching account zones...');
|
|
168
|
+
const response = await this.makeCloudflareRequest(`https://api.cloudflare.com/client/v4/zones?account.id=${accountId}`, apiToken);
|
|
169
|
+
if (!response.success) {
|
|
170
|
+
throw new Error('Failed to fetch zones for account');
|
|
171
|
+
}
|
|
172
|
+
return response.result;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Build complete domain configuration
|
|
177
|
+
* @param {string} domainName - Domain name
|
|
178
|
+
* @param {Object} account - Cloudflare account
|
|
179
|
+
* @param {Object} zone - Cloudflare zone
|
|
180
|
+
* @param {string} apiToken - API token
|
|
181
|
+
* @returns {Promise<Object>} Complete configuration
|
|
182
|
+
*/
|
|
183
|
+
async buildDomainConfiguration(domainName, account, zone, apiToken) {
|
|
184
|
+
const cleanDomainName = domainName.replace('.com', '').replace(/[^a-zA-Z0-9]/g, '');
|
|
185
|
+
const config = {
|
|
186
|
+
// Basic domain information
|
|
187
|
+
name: cleanDomainName,
|
|
188
|
+
displayName: this.capitalizeFirst(cleanDomainName),
|
|
189
|
+
fullDomain: domainName,
|
|
190
|
+
// Cloudflare infrastructure
|
|
191
|
+
accountId: account.id,
|
|
192
|
+
accountName: account.name,
|
|
193
|
+
zoneId: zone.id,
|
|
194
|
+
zoneName: zone.name,
|
|
195
|
+
// Discovery metadata
|
|
196
|
+
discoveredAt: new Date().toISOString(),
|
|
197
|
+
discoveryVersion: '1.0',
|
|
198
|
+
// Multi-environment domains
|
|
199
|
+
domains: {
|
|
200
|
+
production: domainName,
|
|
201
|
+
staging: `staging.${domainName}`,
|
|
202
|
+
development: `dev.${domainName}`
|
|
203
|
+
},
|
|
204
|
+
// Service URLs
|
|
205
|
+
services: {
|
|
206
|
+
frontend: {
|
|
207
|
+
production: `https://${domainName}`,
|
|
208
|
+
staging: `https://staging.${domainName}`,
|
|
209
|
+
development: `https://dev.${domainName}`
|
|
210
|
+
},
|
|
211
|
+
api: {
|
|
212
|
+
production: `https://api.${domainName}`,
|
|
213
|
+
staging: `https://api-staging.${domainName}`,
|
|
214
|
+
development: `https://api-dev.${domainName}`
|
|
215
|
+
},
|
|
216
|
+
auth: {
|
|
217
|
+
production: `https://auth.${domainName}`,
|
|
218
|
+
staging: `https://auth-staging.${domainName}`,
|
|
219
|
+
development: `https://auth-dev.${domainName}`
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
// CORS configuration
|
|
223
|
+
corsOrigins: {
|
|
224
|
+
production: [`https://${domainName}`, `https://*.${domainName}`],
|
|
225
|
+
staging: [`https://staging.${domainName}`, `https://*.staging.${domainName}`],
|
|
226
|
+
development: [`http://localhost:*`, `https://dev.${domainName}`]
|
|
227
|
+
},
|
|
228
|
+
// Database configuration
|
|
229
|
+
databases: {
|
|
230
|
+
production: {
|
|
231
|
+
name: `${cleanDomainName}-auth-db`,
|
|
232
|
+
id: null,
|
|
233
|
+
binding: 'DB'
|
|
234
|
+
},
|
|
235
|
+
staging: {
|
|
236
|
+
name: `${cleanDomainName}-auth-db-staging`,
|
|
237
|
+
id: null,
|
|
238
|
+
binding: 'DB'
|
|
239
|
+
},
|
|
240
|
+
development: {
|
|
241
|
+
name: `${cleanDomainName}-auth-db-local`,
|
|
242
|
+
id: null,
|
|
243
|
+
binding: 'DB'
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
// Worker configuration
|
|
247
|
+
workers: {
|
|
248
|
+
production: {
|
|
249
|
+
name: `${cleanDomainName}-data-service`,
|
|
250
|
+
routes: [`${domainName}/api/*`, `api.${domainName}/*`]
|
|
251
|
+
},
|
|
252
|
+
staging: {
|
|
253
|
+
name: `${cleanDomainName}-data-service-staging`,
|
|
254
|
+
routes: [`staging.${domainName}/api/*`, `api-staging.${domainName}/*`]
|
|
255
|
+
},
|
|
256
|
+
development: {
|
|
257
|
+
name: `${cleanDomainName}-data-service-dev`,
|
|
258
|
+
routes: []
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
// Feature flags
|
|
262
|
+
features: {
|
|
263
|
+
magicLinkAuth: true,
|
|
264
|
+
fileStorage: true,
|
|
265
|
+
userProfiles: true,
|
|
266
|
+
logging: true,
|
|
267
|
+
webhooks: true,
|
|
268
|
+
enhancedFramework: true,
|
|
269
|
+
crossDomainAuth: true
|
|
270
|
+
},
|
|
271
|
+
// Application settings
|
|
272
|
+
settings: {
|
|
273
|
+
magicLinkExpiryMinutes: 15,
|
|
274
|
+
rateLimitWindowMs: 900000,
|
|
275
|
+
rateLimitMax: 100,
|
|
276
|
+
maxFileSize: 26214400,
|
|
277
|
+
allowedFileTypes: ['image/jpeg', 'image/png', 'image/webp', 'image/gif', 'application/pdf', 'video/mp4', 'video/webm', 'audio/mpeg', 'audio/wav', 'text/plain', 'application/json'],
|
|
278
|
+
sessionExpiryHours: 24,
|
|
279
|
+
maxConcurrentSessions: 5
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// Discover existing infrastructure if available
|
|
284
|
+
await this.discoverExistingInfrastructure(config, apiToken);
|
|
285
|
+
return config;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Discover existing Cloudflare infrastructure for domain
|
|
290
|
+
* @param {Object} config - Configuration to enhance
|
|
291
|
+
* @param {string} apiToken - API token
|
|
292
|
+
*/
|
|
293
|
+
async discoverExistingInfrastructure(config, apiToken) {
|
|
294
|
+
try {
|
|
295
|
+
console.log(' 🔍 Discovering existing infrastructure...');
|
|
296
|
+
|
|
297
|
+
// Discover D1 databases
|
|
298
|
+
await this.discoverD1Databases(config, apiToken);
|
|
299
|
+
|
|
300
|
+
// Discover Workers
|
|
301
|
+
await this.discoverWorkers(config, apiToken);
|
|
302
|
+
|
|
303
|
+
// Discover KV namespaces (if needed)
|
|
304
|
+
await this.discoverKVNamespaces(config, apiToken);
|
|
305
|
+
} catch (error) {
|
|
306
|
+
console.log(` ⚠️ Infrastructure discovery failed: ${error.message}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Discover existing D1 databases
|
|
312
|
+
* @param {Object} config - Configuration object
|
|
313
|
+
* @param {string} apiToken - API token
|
|
314
|
+
*/
|
|
315
|
+
async discoverD1Databases(config, apiToken) {
|
|
316
|
+
try {
|
|
317
|
+
const response = await this.makeCloudflareRequest(`https://api.cloudflare.com/client/v4/accounts/${config.accountId}/d1/database`, apiToken);
|
|
318
|
+
if (response.success) {
|
|
319
|
+
const databases = response.result;
|
|
320
|
+
|
|
321
|
+
// Match databases by name
|
|
322
|
+
Object.keys(config.databases).forEach(env => {
|
|
323
|
+
const expectedName = config.databases[env].name;
|
|
324
|
+
const foundDb = databases.find(db => db.name === expectedName);
|
|
325
|
+
if (foundDb) {
|
|
326
|
+
config.databases[env].id = foundDb.uuid;
|
|
327
|
+
console.log(` 📋 Found ${env} database: ${foundDb.name}`);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
} catch (error) {
|
|
332
|
+
// Handle different error types gracefully
|
|
333
|
+
if (error.message.includes('401') || error.message.includes('Unauthorized')) {
|
|
334
|
+
console.log(` ℹ️ D1 database discovery requires additional API token permissions`);
|
|
335
|
+
console.log(` 💡 To enable D1 discovery, ensure your API token has 'Account:Read' or 'Cloudflare D1:Edit' permissions`);
|
|
336
|
+
} else {
|
|
337
|
+
console.log(` ⚠️ D1 database discovery failed: ${error.message}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Discover existing Workers
|
|
344
|
+
* @param {Object} config - Configuration object
|
|
345
|
+
* @param {string} apiToken - API token
|
|
346
|
+
*/
|
|
347
|
+
async discoverWorkers(config, apiToken) {
|
|
348
|
+
try {
|
|
349
|
+
const response = await this.makeCloudflareRequest(`https://api.cloudflare.com/client/v4/accounts/${config.accountId}/workers/scripts`, apiToken);
|
|
350
|
+
if (response.success) {
|
|
351
|
+
const workers = response.result;
|
|
352
|
+
|
|
353
|
+
// Match workers by name
|
|
354
|
+
Object.keys(config.workers).forEach(env => {
|
|
355
|
+
const expectedName = config.workers[env].name;
|
|
356
|
+
const foundWorker = workers.find(w => w.id === expectedName);
|
|
357
|
+
if (foundWorker) {
|
|
358
|
+
config.workers[env].exists = true;
|
|
359
|
+
config.workers[env].createdAt = foundWorker.created_on;
|
|
360
|
+
console.log(` ⚡ Found ${env} worker: ${foundWorker.id}`);
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
} catch (error) {
|
|
365
|
+
console.log(` ⚠️ Worker discovery failed: ${error.message}`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Discover KV namespaces (placeholder for future enhancement)
|
|
371
|
+
* @param {Object} config - Configuration object
|
|
372
|
+
* @param {string} apiToken - API token
|
|
373
|
+
*/
|
|
374
|
+
async discoverKVNamespaces(config, apiToken) {
|
|
375
|
+
// Placeholder for KV namespace discovery if needed
|
|
376
|
+
console.log(' 🗂️ KV namespace discovery (not implemented)');
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Generate domain configuration from template
|
|
381
|
+
* @param {string} domainName - Domain name
|
|
382
|
+
* @param {string} templateName - Template name
|
|
383
|
+
* @returns {Object} Generated configuration
|
|
384
|
+
*/
|
|
385
|
+
generateConfigFromTemplate(domainName, templateName = 'standard') {
|
|
386
|
+
console.log(`🏗️ Generating configuration for ${domainName} using template: ${templateName}`);
|
|
387
|
+
const template = this.templates.get(templateName) || this.getStandardTemplate();
|
|
388
|
+
const cleanDomainName = domainName.replace('.com', '').replace(/[^a-zA-Z0-9]/g, '');
|
|
389
|
+
|
|
390
|
+
// Replace template variables
|
|
391
|
+
const configStr = JSON.stringify(template).replace(/{DOMAIN_NAME}/g, domainName).replace(/{CLEAN_DOMAIN_NAME}/g, cleanDomainName).replace(/{DISPLAY_NAME}/g, this.capitalizeFirst(cleanDomainName)).replace(/{TIMESTAMP}/g, new Date().toISOString());
|
|
392
|
+
const config = JSON.parse(configStr);
|
|
393
|
+
console.log(`✅ Configuration generated from template`);
|
|
394
|
+
return config;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Validate domain configuration
|
|
399
|
+
* @param {string} domainName - Domain name
|
|
400
|
+
* @param {Object} config - Configuration to validate
|
|
401
|
+
* @returns {Object} Validation result
|
|
402
|
+
*/
|
|
403
|
+
validateDomainConfig(domainName, config) {
|
|
404
|
+
console.log(`🔍 Validating configuration for ${domainName}...`);
|
|
405
|
+
const errors = [];
|
|
406
|
+
const warnings = [];
|
|
407
|
+
|
|
408
|
+
// Required fields validation
|
|
409
|
+
if (!config.accountId) errors.push('Missing Cloudflare account ID');
|
|
410
|
+
if (!config.zoneId) errors.push('Missing Cloudflare zone ID');
|
|
411
|
+
if (!config.domains?.production) errors.push('Missing production domain URL');
|
|
412
|
+
|
|
413
|
+
// Services validation
|
|
414
|
+
const requiredServices = ['frontend', 'api', 'auth'];
|
|
415
|
+
for (const service of requiredServices) {
|
|
416
|
+
if (!config.services?.[service]?.production) {
|
|
417
|
+
errors.push(`Missing ${service} production URL`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Database validation
|
|
422
|
+
const environments = ['production', 'staging', 'development'];
|
|
423
|
+
environments.forEach(env => {
|
|
424
|
+
if (!config.databases?.[env]?.name) {
|
|
425
|
+
errors.push(`Missing ${env} database name`);
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// Worker validation
|
|
430
|
+
environments.forEach(env => {
|
|
431
|
+
if (!config.workers?.[env]?.name) {
|
|
432
|
+
errors.push(`Missing ${env} worker name`);
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// CORS validation
|
|
437
|
+
if (!config.corsOrigins?.production?.length) {
|
|
438
|
+
warnings.push('No CORS origins configured for production');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Feature validation
|
|
442
|
+
if (!config.features) {
|
|
443
|
+
warnings.push('No feature flags configured');
|
|
444
|
+
}
|
|
445
|
+
const result = {
|
|
446
|
+
valid: errors.length === 0,
|
|
447
|
+
errors,
|
|
448
|
+
warnings,
|
|
449
|
+
score: this.calculateConfigScore(config, errors, warnings)
|
|
450
|
+
};
|
|
451
|
+
if (result.valid) {
|
|
452
|
+
console.log(`✅ Configuration validation passed (Score: ${result.score}/100)`);
|
|
453
|
+
} else {
|
|
454
|
+
console.log(`❌ Configuration validation failed (${errors.length} errors, ${warnings.length} warnings)`);
|
|
455
|
+
}
|
|
456
|
+
return result;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Calculate configuration quality score
|
|
461
|
+
* @param {Object} config - Configuration
|
|
462
|
+
* @param {Array} errors - Validation errors
|
|
463
|
+
* @param {Array} warnings - Validation warnings
|
|
464
|
+
* @returns {number} Quality score (0-100)
|
|
465
|
+
*/
|
|
466
|
+
calculateConfigScore(config, errors, warnings) {
|
|
467
|
+
let score = 100;
|
|
468
|
+
|
|
469
|
+
// Deduct for errors and warnings
|
|
470
|
+
score -= errors.length * 20;
|
|
471
|
+
score -= warnings.length * 5;
|
|
472
|
+
|
|
473
|
+
// Bonus for completeness
|
|
474
|
+
if (config.databases?.production?.id) score += 5;
|
|
475
|
+
if (config.workers?.production?.exists) score += 5;
|
|
476
|
+
if (config.features && Object.keys(config.features).length > 5) score += 5;
|
|
477
|
+
if (config.settings && Object.keys(config.settings).length > 5) score += 5;
|
|
478
|
+
return Math.max(0, Math.min(100, score));
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Utility methods
|
|
482
|
+
|
|
483
|
+
async makeCloudflareRequest(url, apiToken) {
|
|
484
|
+
for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
|
|
485
|
+
try {
|
|
486
|
+
const response = await fetch(url, {
|
|
487
|
+
headers: {
|
|
488
|
+
'Authorization': `Bearer ${apiToken}`,
|
|
489
|
+
'Content-Type': 'application/json'
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
if (!response.ok) {
|
|
493
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
494
|
+
}
|
|
495
|
+
return await response.json();
|
|
496
|
+
} catch (error) {
|
|
497
|
+
if (attempt === this.retryAttempts) {
|
|
498
|
+
throw error;
|
|
499
|
+
}
|
|
500
|
+
console.log(` ⚠️ Request attempt ${attempt} failed, retrying...`);
|
|
501
|
+
await new Promise(resolve => setTimeout(resolve, this.retryDelay));
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
cacheConfiguration(key, config) {
|
|
506
|
+
this.configCache.set(key, {
|
|
507
|
+
config,
|
|
508
|
+
timestamp: Date.now()
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
// Save to disk cache
|
|
512
|
+
const cacheFile = join(this.configPaths.cache, `${key}.json`);
|
|
513
|
+
writeFileSync(cacheFile, JSON.stringify({
|
|
514
|
+
config,
|
|
515
|
+
timestamp: Date.now()
|
|
516
|
+
}, null, 2));
|
|
517
|
+
}
|
|
518
|
+
loadConfigCache() {
|
|
519
|
+
if (!existsSync(this.configPaths.cache)) return;
|
|
520
|
+
const files = readdirSync(this.configPaths.cache);
|
|
521
|
+
let loaded = 0;
|
|
522
|
+
files.forEach(file => {
|
|
523
|
+
if (file.endsWith('.json')) {
|
|
524
|
+
try {
|
|
525
|
+
const content = JSON.parse(readFileSync(join(this.configPaths.cache, file), 'utf8'));
|
|
526
|
+
const key = file.replace('.json', '');
|
|
527
|
+
this.configCache.set(key, content);
|
|
528
|
+
loaded++;
|
|
529
|
+
} catch (error) {
|
|
530
|
+
console.warn(`⚠️ Failed to load cache file ${file}`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
if (loaded > 0) {
|
|
535
|
+
console.log(`📋 Loaded ${loaded} cached configurations`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
loadConfigTemplates() {
|
|
539
|
+
// Load standard template
|
|
540
|
+
this.templates.set('standard', this.getStandardTemplate());
|
|
541
|
+
|
|
542
|
+
// TODO: Load custom templates from templates directory
|
|
543
|
+
console.log(`📋 Loaded ${this.templates.size} configuration templates`);
|
|
544
|
+
}
|
|
545
|
+
getStandardTemplate() {
|
|
546
|
+
return {
|
|
547
|
+
name: '{CLEAN_DOMAIN_NAME}',
|
|
548
|
+
displayName: '{DISPLAY_NAME}',
|
|
549
|
+
fullDomain: '{DOMAIN_NAME}',
|
|
550
|
+
templateVersion: '1.0',
|
|
551
|
+
generatedAt: '{TIMESTAMP}'
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
getCacheKeyHash(token) {
|
|
555
|
+
// Simple hash of token for cache key (first 8 chars)
|
|
556
|
+
return token.substring(0, 8);
|
|
557
|
+
}
|
|
558
|
+
capitalizeFirst(str) {
|
|
559
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
export default DomainDiscovery;
|