@tamyla/clodo-framework 2.0.20 → 3.0.2

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