@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,912 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Domain & Service Manager
|
|
3
|
+
* Comprehensive domain verification and service status management
|
|
4
|
+
*
|
|
5
|
+
* Handles:
|
|
6
|
+
* 1. Cloudflare authentication verification
|
|
7
|
+
* 2. Domain availability checking
|
|
8
|
+
* 3. Existing service discovery and matching
|
|
9
|
+
* 4. Service status verification
|
|
10
|
+
* 5. Deployment permission management
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { execSync } from 'child_process';
|
|
14
|
+
import { promisify } from 'util';
|
|
15
|
+
import { exec } from 'child_process';
|
|
16
|
+
import { askChoice, askYesNo } from '../utils/interactive-prompts.js';
|
|
17
|
+
import { DomainDiscovery } from './domain-discovery.js';
|
|
18
|
+
import { MultiDomainOrchestrator } from '../../../src/orchestration/multi-domain-orchestrator.js';
|
|
19
|
+
import { getCommandConfig } from '../config/command-config-manager.js';
|
|
20
|
+
import { CloudflareTokenManager } from '../security/api-token-manager.js';
|
|
21
|
+
const execAsync = promisify(exec);
|
|
22
|
+
export class CloudflareDomainManager {
|
|
23
|
+
constructor(options = {}) {
|
|
24
|
+
this.apiToken = options.apiToken;
|
|
25
|
+
this.accountId = options.accountId;
|
|
26
|
+
this.isAuthenticated = false;
|
|
27
|
+
this.availableDomains = [];
|
|
28
|
+
this.deployedServices = [];
|
|
29
|
+
|
|
30
|
+
// Initialize command configuration
|
|
31
|
+
this.cmdConfig = getCommandConfig();
|
|
32
|
+
|
|
33
|
+
// Initialize API token manager
|
|
34
|
+
this.tokenManager = new CloudflareTokenManager();
|
|
35
|
+
|
|
36
|
+
// Initialize existing modules
|
|
37
|
+
this.domainDiscovery = new DomainDiscovery({
|
|
38
|
+
apiToken: this.apiToken,
|
|
39
|
+
enableCaching: true
|
|
40
|
+
});
|
|
41
|
+
this.orchestrator = new MultiDomainOrchestrator({
|
|
42
|
+
maxConcurrentDeployments: 1,
|
|
43
|
+
timeout: 300000
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Step 1: Verify Cloudflare authentication
|
|
49
|
+
*/
|
|
50
|
+
async verifyAuthentication() {
|
|
51
|
+
console.log('🔐 Verifying Cloudflare authentication...');
|
|
52
|
+
try {
|
|
53
|
+
const whoamiCmd = this.cmdConfig.getCloudflareCommand('whoami');
|
|
54
|
+
const {
|
|
55
|
+
stdout
|
|
56
|
+
} = await execAsync(whoamiCmd);
|
|
57
|
+
if (stdout.includes('You are not authenticated') || stdout.includes('not logged in')) {
|
|
58
|
+
return await this.handleAuthenticationRequired();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Extract and display account information
|
|
62
|
+
const accountInfo = this.parseAccountInfo(stdout);
|
|
63
|
+
console.log(' ✅ Cloudflare: authenticated');
|
|
64
|
+
if (accountInfo.email) {
|
|
65
|
+
console.log(` 📧 Account: ${accountInfo.email}`);
|
|
66
|
+
}
|
|
67
|
+
if (accountInfo.accountId) {
|
|
68
|
+
console.log(` 🆔 Account ID: ${accountInfo.accountId}`);
|
|
69
|
+
this.accountId = accountInfo.accountId;
|
|
70
|
+
}
|
|
71
|
+
if (accountInfo.accountName) {
|
|
72
|
+
console.log(` 🏢 Account Name: ${accountInfo.accountName}`);
|
|
73
|
+
}
|
|
74
|
+
this.isAuthenticated = true;
|
|
75
|
+
return true;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
return await this.handleAuthenticationRequired();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Handle authentication requirement
|
|
83
|
+
*/
|
|
84
|
+
async handleAuthenticationRequired() {
|
|
85
|
+
console.log(' ❌ Cloudflare authentication required');
|
|
86
|
+
const authChoice = await askChoice('Cloudflare authentication needed. What would you like to do?', ['Login to Cloudflare now', 'Provide API token manually', 'Skip Cloudflare verification (limited features)', 'Cancel deployment'], 0);
|
|
87
|
+
switch (authChoice) {
|
|
88
|
+
case 0:
|
|
89
|
+
return await this.performCloudflareLogin();
|
|
90
|
+
case 1:
|
|
91
|
+
return await this.setApiToken();
|
|
92
|
+
case 2:
|
|
93
|
+
console.log(' ⚠️ Skipping Cloudflare verification - some features unavailable');
|
|
94
|
+
return false;
|
|
95
|
+
case 3:
|
|
96
|
+
throw new Error('Deployment cancelled - authentication required');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Perform Cloudflare login
|
|
102
|
+
*/
|
|
103
|
+
async performCloudflareLogin() {
|
|
104
|
+
try {
|
|
105
|
+
console.log('🔑 Opening Cloudflare authentication...');
|
|
106
|
+
const authCmd = this.cmdConfig.getCloudflareCommand('auth_login');
|
|
107
|
+
await execAsync(authCmd, {
|
|
108
|
+
stdio: 'inherit'
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Verify login worked
|
|
112
|
+
const whoamiCmd = this.cmdConfig.getCloudflareCommand('whoami');
|
|
113
|
+
const {
|
|
114
|
+
stdout
|
|
115
|
+
} = await execAsync(whoamiCmd);
|
|
116
|
+
if (!stdout.includes('You are not authenticated')) {
|
|
117
|
+
console.log(' ✅ Cloudflare authentication successful');
|
|
118
|
+
this.isAuthenticated = true;
|
|
119
|
+
return true;
|
|
120
|
+
} else {
|
|
121
|
+
throw new Error('Authentication verification failed');
|
|
122
|
+
}
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.log(` ❌ Authentication failed: ${error.message}`);
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Set API token manually
|
|
131
|
+
*/
|
|
132
|
+
async setApiToken() {
|
|
133
|
+
const {
|
|
134
|
+
askUser
|
|
135
|
+
} = await import('../utils/interactive-prompts.js');
|
|
136
|
+
const token = await askUser('Enter your Cloudflare API Token:');
|
|
137
|
+
if (!token || token.trim() === '') {
|
|
138
|
+
console.log(' ❌ API token is required');
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Test the token
|
|
143
|
+
try {
|
|
144
|
+
const whoamiCmd = this.cmdConfig.getCloudflareCommand('whoami');
|
|
145
|
+
const {
|
|
146
|
+
stdout
|
|
147
|
+
} = await execAsync(`CLOUDFLARE_API_TOKEN=${token} ${whoamiCmd}`);
|
|
148
|
+
console.log(' ✅ API token verified successfully');
|
|
149
|
+
this.apiToken = token;
|
|
150
|
+
this.isAuthenticated = true;
|
|
151
|
+
return true;
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.log(' ❌ Invalid API token');
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Step 2: Get available domains from Cloudflare using existing domain discovery
|
|
160
|
+
*/
|
|
161
|
+
async getAvailableDomains() {
|
|
162
|
+
if (!this.isAuthenticated) {
|
|
163
|
+
console.log(' ⚠️ Skipping domain discovery - not authenticated');
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
console.log('🌐 Discovering available domains from Cloudflare...');
|
|
167
|
+
try {
|
|
168
|
+
// Use existing domain discovery module
|
|
169
|
+
await this.domainDiscovery.initializeDiscovery();
|
|
170
|
+
|
|
171
|
+
// Get domains from wrangler deployments (updated command)
|
|
172
|
+
let services = [];
|
|
173
|
+
try {
|
|
174
|
+
const deploymentsCmd = this.cmdConfig.getCloudflareCommand('deployments_list');
|
|
175
|
+
const {
|
|
176
|
+
stdout
|
|
177
|
+
} = await execAsync(deploymentsCmd);
|
|
178
|
+
services = this.parseWranglerDeployments(stdout);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
// Fallback: try to get workers list
|
|
181
|
+
try {
|
|
182
|
+
const listWorkersCmd = this.cmdConfig.getCloudflareCommand('list_workers');
|
|
183
|
+
const {
|
|
184
|
+
stdout: workersOutput
|
|
185
|
+
} = await execAsync(listWorkersCmd);
|
|
186
|
+
// If wrangler dev works, try alternate commands
|
|
187
|
+
console.log(' 📋 Using alternative service discovery...');
|
|
188
|
+
} catch {
|
|
189
|
+
console.log(' 📋 No existing deployments found');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Extract unique domains from services
|
|
194
|
+
const serviceDomains = [...new Set(services.map(s => s.domain).filter(Boolean))];
|
|
195
|
+
|
|
196
|
+
// Try to get additional domains from orchestrator
|
|
197
|
+
let orchestratorDomains = [];
|
|
198
|
+
try {
|
|
199
|
+
const portfolioInfo = await this.orchestrator.getPortfolioStatus();
|
|
200
|
+
orchestratorDomains = portfolioInfo.domains || [];
|
|
201
|
+
} catch (error) {
|
|
202
|
+
// Orchestrator domains not available, continue with service domains
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Combine and deduplicate domains
|
|
206
|
+
const allDomains = [...new Set([...serviceDomains, ...orchestratorDomains])];
|
|
207
|
+
this.availableDomains = allDomains;
|
|
208
|
+
console.log(` 📋 Found ${allDomains.length} available domains`);
|
|
209
|
+
allDomains.forEach(domain => console.log(` - ${domain}`));
|
|
210
|
+
return allDomains;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.log(` ⚠️ Could not retrieve domains: ${error.message}`);
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Step 3: Verify domain availability and match existing services
|
|
219
|
+
*/
|
|
220
|
+
async verifyDomainAndMatchServices(requestedDomain) {
|
|
221
|
+
console.log(`🔍 Verifying domain: ${requestedDomain}`);
|
|
222
|
+
|
|
223
|
+
// Check if domain exists in Cloudflare (with root domain analysis)
|
|
224
|
+
const domainCheck = await this.checkDomainInCloudflare(requestedDomain);
|
|
225
|
+
if (!domainCheck.found) {
|
|
226
|
+
return await this.handleNewDomain(requestedDomain, domainCheck);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Domain/root domain exists - check for existing services
|
|
230
|
+
if (domainCheck.isSubdomain) {
|
|
231
|
+
console.log(' ✅ Root domain exists - service subdomain can be deployed');
|
|
232
|
+
} else {
|
|
233
|
+
console.log(' ✅ Domain found in Cloudflare');
|
|
234
|
+
}
|
|
235
|
+
return await this.checkExistingServices(requestedDomain, domainCheck);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Handle completely new domain or missing root domain
|
|
240
|
+
*/
|
|
241
|
+
async handleNewDomain(domain, domainCheck) {
|
|
242
|
+
if (domainCheck.isSubdomain) {
|
|
243
|
+
console.log(' ❌ Root domain not found in Cloudflare account');
|
|
244
|
+
console.log(` ⚠️ Cannot deploy ${domain} because ${domainCheck.rootDomain} is not managed in this account`);
|
|
245
|
+
|
|
246
|
+
// Show what domains ARE available in this account
|
|
247
|
+
await this.showAvailableDomainsInAccount();
|
|
248
|
+
|
|
249
|
+
// Generate domain suggestions
|
|
250
|
+
await this.showDomainSuggestions(domain, domainCheck);
|
|
251
|
+
} else {
|
|
252
|
+
console.log(' 🆕 This is a new root domain - ready for first deployment');
|
|
253
|
+
console.log(' 💡 This is normal for newly created domains');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Show account context for troubleshooting
|
|
257
|
+
console.log(' 📋 Account Context:');
|
|
258
|
+
const accountDetails = await this.getAccountDetails();
|
|
259
|
+
if (accountDetails.error) {
|
|
260
|
+
console.log(` ⚠️ Could not fetch account details: ${accountDetails.error}`);
|
|
261
|
+
} else {
|
|
262
|
+
if (accountDetails.accountId) {
|
|
263
|
+
console.log(` 🆔 Account ID: ${accountDetails.accountId}`);
|
|
264
|
+
}
|
|
265
|
+
if (accountDetails.totalZones !== undefined) {
|
|
266
|
+
console.log(` 🌐 Total zones in account: ${accountDetails.totalZones}`);
|
|
267
|
+
if (accountDetails.zones && accountDetails.zones.length > 0) {
|
|
268
|
+
console.log(` 📄 Sample zones: ${accountDetails.zones.slice(0, 3).join(', ')}${accountDetails.totalZones > 3 ? '...' : ''}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Different choices based on domain type
|
|
274
|
+
let choicePrompt, choiceOptions;
|
|
275
|
+
if (domainCheck.isSubdomain) {
|
|
276
|
+
choicePrompt = `Root domain ${domainCheck.rootDomain} not found. What would you like to do?`;
|
|
277
|
+
choiceOptions = ['Choose a different service name with an available root domain', 'View available domains in your account', 'Cancel deployment (add root domain to Cloudflare first)'];
|
|
278
|
+
} else {
|
|
279
|
+
choicePrompt = `Ready to deploy new root domain ${domain}?`;
|
|
280
|
+
choiceOptions = ['Yes, proceed with first deployment', 'Choose a different domain from available list', 'Cancel deployment'];
|
|
281
|
+
}
|
|
282
|
+
const choice = await askChoice(choicePrompt, choiceOptions, 0);
|
|
283
|
+
switch (choice) {
|
|
284
|
+
case 0:
|
|
285
|
+
console.log(' ✅ Proceeding with first deployment');
|
|
286
|
+
return {
|
|
287
|
+
status: 'new',
|
|
288
|
+
action: 'deploy',
|
|
289
|
+
services: []
|
|
290
|
+
};
|
|
291
|
+
case 1:
|
|
292
|
+
throw new Error('CHOOSE_DIFFERENT_DOMAIN');
|
|
293
|
+
case 2:
|
|
294
|
+
throw new Error('DEPLOYMENT_CANCELLED');
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Check for existing services on domain using orchestrator
|
|
300
|
+
*/
|
|
301
|
+
async checkExistingServices(domain, domainCheck = null) {
|
|
302
|
+
console.log(' 🔍 Checking for existing services...');
|
|
303
|
+
try {
|
|
304
|
+
// Use orchestrator to get comprehensive service info
|
|
305
|
+
let domainServices = [];
|
|
306
|
+
try {
|
|
307
|
+
const portfolioStatus = await this.orchestrator.getPortfolioStatus();
|
|
308
|
+
const domainInfo = portfolioStatus.domainDetails?.find(d => d.domain === domain);
|
|
309
|
+
if (domainInfo && domainInfo.services) {
|
|
310
|
+
domainServices = domainInfo.services.map(s => ({
|
|
311
|
+
name: s.name,
|
|
312
|
+
status: s.status || 'unknown',
|
|
313
|
+
domain: domain,
|
|
314
|
+
lastDeployed: s.lastDeployed,
|
|
315
|
+
health: s.health
|
|
316
|
+
}));
|
|
317
|
+
}
|
|
318
|
+
} catch (orchestratorError) {
|
|
319
|
+
// Fallback to wrangler deployments
|
|
320
|
+
console.log(' 📋 Using fallback service discovery...');
|
|
321
|
+
try {
|
|
322
|
+
const deploymentsCmd = this.cmdConfig.getCloudflareCommand('deployments_list');
|
|
323
|
+
const {
|
|
324
|
+
stdout
|
|
325
|
+
} = await execAsync(deploymentsCmd);
|
|
326
|
+
const services = this.parseWranglerDeployments(stdout);
|
|
327
|
+
domainServices = services.filter(s => s.domain === domain);
|
|
328
|
+
} catch (fallbackError) {
|
|
329
|
+
console.log(' 📝 No deployment history found - treating as new domain');
|
|
330
|
+
domainServices = [];
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (domainServices.length === 0) {
|
|
334
|
+
console.log(' 📝 No existing services found - fresh deployment');
|
|
335
|
+
return {
|
|
336
|
+
status: 'available',
|
|
337
|
+
action: 'deploy',
|
|
338
|
+
services: []
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
console.log(` 📋 Found ${domainServices.length} existing service(s):`);
|
|
342
|
+
domainServices.forEach(service => {
|
|
343
|
+
const healthInfo = service.health ? ` (${service.health})` : '';
|
|
344
|
+
const deployedInfo = service.lastDeployed ? ` - Last deployed: ${service.lastDeployed}` : '';
|
|
345
|
+
console.log(` - ${service.name} (${service.status})${healthInfo}${deployedInfo}`);
|
|
346
|
+
});
|
|
347
|
+
return await this.handleExistingServices(domain, domainServices);
|
|
348
|
+
} catch (error) {
|
|
349
|
+
console.log(` ⚠️ Could not check services: ${error.message}`);
|
|
350
|
+
return {
|
|
351
|
+
status: 'unknown',
|
|
352
|
+
action: 'deploy',
|
|
353
|
+
services: []
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Handle existing services - ask for permission
|
|
360
|
+
*/
|
|
361
|
+
async handleExistingServices(domain, services) {
|
|
362
|
+
const activeServices = services.filter(s => s.status === 'active' || s.status === 'deployed');
|
|
363
|
+
if (activeServices.length > 0) {
|
|
364
|
+
console.log(' ⚠️ Active services detected on this domain');
|
|
365
|
+
const updateConfirm = await askYesNo(`Update/overwrite ${activeServices.length} active service(s) on ${domain}?`);
|
|
366
|
+
if (!updateConfirm) {
|
|
367
|
+
throw new Error('DEPLOYMENT_CANCELLED - User declined to update active services');
|
|
368
|
+
}
|
|
369
|
+
console.log(' ✅ Permission granted to update existing services');
|
|
370
|
+
return {
|
|
371
|
+
status: 'update',
|
|
372
|
+
action: 'update',
|
|
373
|
+
services: activeServices
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
console.log(' ✅ Existing services are inactive - safe to deploy');
|
|
377
|
+
return {
|
|
378
|
+
status: 'replace',
|
|
379
|
+
action: 'deploy',
|
|
380
|
+
services
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Parse wrangler deployments list output
|
|
386
|
+
*/
|
|
387
|
+
parseWranglerDeployments(stdout) {
|
|
388
|
+
// Handle new wrangler deployments format
|
|
389
|
+
const lines = stdout.split('\n').filter(line => line.trim());
|
|
390
|
+
const services = [];
|
|
391
|
+
for (const line of lines) {
|
|
392
|
+
if (line.includes('worker') || line.includes('deployment')) {
|
|
393
|
+
// Extract service info from deployment output
|
|
394
|
+
const parts = line.split(/\s+/);
|
|
395
|
+
if (parts.length >= 2) {
|
|
396
|
+
services.push({
|
|
397
|
+
name: parts[0] || 'unknown',
|
|
398
|
+
status: 'deployed',
|
|
399
|
+
domain: this.extractDomainFromService(parts[0]),
|
|
400
|
+
lastDeployed: parts[1] || 'unknown'
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return services;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Parse wrangler list output (legacy - keep for fallback)
|
|
410
|
+
*/
|
|
411
|
+
parseWranglerList(stdout) {
|
|
412
|
+
const lines = stdout.split('\\n').filter(line => line.trim());
|
|
413
|
+
const services = [];
|
|
414
|
+
|
|
415
|
+
// Skip header lines
|
|
416
|
+
const dataLines = lines.slice(2);
|
|
417
|
+
for (const line of dataLines) {
|
|
418
|
+
if (line.trim()) {
|
|
419
|
+
const parts = line.split(/\\s+/);
|
|
420
|
+
if (parts.length >= 2) {
|
|
421
|
+
services.push({
|
|
422
|
+
name: parts[0],
|
|
423
|
+
status: parts[1] || 'unknown',
|
|
424
|
+
domain: this.extractDomainFromService(parts[0])
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return services;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Extract domain from service name (basic heuristic)
|
|
434
|
+
*/
|
|
435
|
+
extractDomainFromService(serviceName) {
|
|
436
|
+
// Try to extract domain from service name patterns
|
|
437
|
+
// This is a heuristic - may need refinement based on naming conventions
|
|
438
|
+
if (serviceName.includes('.')) {
|
|
439
|
+
return serviceName.split('-')[0] || serviceName;
|
|
440
|
+
}
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Parse account information from wrangler whoami output
|
|
446
|
+
*/
|
|
447
|
+
parseAccountInfo(stdout) {
|
|
448
|
+
const accountInfo = {
|
|
449
|
+
email: null,
|
|
450
|
+
accountId: null,
|
|
451
|
+
accountName: null
|
|
452
|
+
};
|
|
453
|
+
const lines = stdout.split('\n');
|
|
454
|
+
let inAccountTable = false;
|
|
455
|
+
for (const line of lines) {
|
|
456
|
+
const trimmedLine = line.trim();
|
|
457
|
+
|
|
458
|
+
// Extract email from OAuth message
|
|
459
|
+
if (trimmedLine.includes('associated with the email')) {
|
|
460
|
+
const emailMatch = trimmedLine.match(/associated with the email (.+@.+?)\.?$/);
|
|
461
|
+
if (emailMatch) {
|
|
462
|
+
accountInfo.email = emailMatch[1].trim();
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Detect account table start
|
|
467
|
+
if (trimmedLine.includes('Account Name') && trimmedLine.includes('Account ID')) {
|
|
468
|
+
inAccountTable = true;
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Extract from account table
|
|
473
|
+
if (inAccountTable && trimmedLine.includes('│') && !trimmedLine.includes('Account Name')) {
|
|
474
|
+
// Stop at table end
|
|
475
|
+
if (trimmedLine.startsWith('└')) {
|
|
476
|
+
inAccountTable = false;
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Skip separator line
|
|
481
|
+
if (trimmedLine.includes('├') || trimmedLine.includes('─')) {
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Parse account data line: │ Account Name │ Account ID │
|
|
486
|
+
const parts = trimmedLine.split('│');
|
|
487
|
+
if (parts.length >= 3) {
|
|
488
|
+
const name = parts[1]?.trim();
|
|
489
|
+
const id = parts[2]?.trim();
|
|
490
|
+
if (name && name !== 'Account Name') {
|
|
491
|
+
accountInfo.accountName = name;
|
|
492
|
+
}
|
|
493
|
+
if (id && id !== 'Account ID' && id.length >= 30) {
|
|
494
|
+
accountInfo.accountId = id;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return accountInfo;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Extract root domain from service subdomain
|
|
504
|
+
* Handles cases where www.domain.com is the root, not domain.com
|
|
505
|
+
* e.g., data-service.greatidude.com -> check both www.greatidude.com AND greatidude.com
|
|
506
|
+
*/
|
|
507
|
+
extractRootDomain(fullDomain) {
|
|
508
|
+
const parts = fullDomain.split('.');
|
|
509
|
+
if (parts.length >= 3) {
|
|
510
|
+
// For service subdomains like data-service.greatidude.com
|
|
511
|
+
// We need to check if www.greatidude.com exists (not just greatidude.com)
|
|
512
|
+
const domainWithoutService = parts.slice(1).join('.'); // greatidude.com
|
|
513
|
+
const wwwDomain = `www.${domainWithoutService}`; // www.greatidude.com
|
|
514
|
+
|
|
515
|
+
return {
|
|
516
|
+
bareRoot: domainWithoutService,
|
|
517
|
+
// greatidude.com
|
|
518
|
+
wwwRoot: wwwDomain,
|
|
519
|
+
// www.greatidude.com
|
|
520
|
+
domainsToCheck: [wwwDomain, domainWithoutService],
|
|
521
|
+
needsBothChecks: true
|
|
522
|
+
};
|
|
523
|
+
} else if (parts.length === 3 && parts[0] === 'www') {
|
|
524
|
+
// For www.greatidude.com - this IS the root domain
|
|
525
|
+
const bareRoot = parts.slice(1).join('.'); // greatidude.com
|
|
526
|
+
return {
|
|
527
|
+
bareRoot: bareRoot,
|
|
528
|
+
wwwRoot: fullDomain,
|
|
529
|
+
// www.greatidude.com
|
|
530
|
+
domainsToCheck: [fullDomain, bareRoot],
|
|
531
|
+
isWwwRoot: true,
|
|
532
|
+
needsBothChecks: false
|
|
533
|
+
};
|
|
534
|
+
} else if (parts.length === 2) {
|
|
535
|
+
// For greatidude.com - check both bare and www
|
|
536
|
+
const wwwDomain = `www.${fullDomain}`;
|
|
537
|
+
return {
|
|
538
|
+
bareRoot: fullDomain,
|
|
539
|
+
// greatidude.com
|
|
540
|
+
wwwRoot: wwwDomain,
|
|
541
|
+
// www.greatidude.com
|
|
542
|
+
domainsToCheck: [wwwDomain, fullDomain],
|
|
543
|
+
needsBothChecks: true
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
return {
|
|
547
|
+
bareRoot: fullDomain,
|
|
548
|
+
wwwRoot: fullDomain,
|
|
549
|
+
domainsToCheck: [fullDomain],
|
|
550
|
+
needsBothChecks: false
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Ensure API token is available for domain verification
|
|
556
|
+
*/
|
|
557
|
+
async ensureApiToken() {
|
|
558
|
+
if (!this.apiToken) {
|
|
559
|
+
try {
|
|
560
|
+
console.log(' 🔑 API token required for domain verification');
|
|
561
|
+
this.apiToken = await this.tokenManager.getCloudflareToken();
|
|
562
|
+
|
|
563
|
+
// Also set it in domain discovery
|
|
564
|
+
this.domainDiscovery.apiToken = this.apiToken;
|
|
565
|
+
return true;
|
|
566
|
+
} catch (error) {
|
|
567
|
+
console.log(` ❌ Failed to get API token: ${error.message}`);
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return true;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Check if domain exists in Cloudflare zones using domain discovery
|
|
576
|
+
* Handles both www.domain.com and domain.com as potential roots
|
|
577
|
+
*/
|
|
578
|
+
async checkDomainInCloudflare(domain) {
|
|
579
|
+
if (!this.isAuthenticated) return false;
|
|
580
|
+
const rootInfo = this.extractRootDomain(domain);
|
|
581
|
+
const isServiceSubdomain = rootInfo.needsBothChecks || rootInfo.isWwwRoot === undefined;
|
|
582
|
+
console.log(` 🔍 Analyzing domain structure:`);
|
|
583
|
+
if (isServiceSubdomain && !rootInfo.isWwwRoot) {
|
|
584
|
+
console.log(` 📍 Service subdomain: ${domain}`);
|
|
585
|
+
console.log(` 🔍 Checking potential root domains:`);
|
|
586
|
+
console.log(` • www root: ${rootInfo.wwwRoot}`);
|
|
587
|
+
console.log(` • bare root: ${rootInfo.bareRoot}`);
|
|
588
|
+
} else if (rootInfo.isWwwRoot) {
|
|
589
|
+
console.log(` 🌐 WWW root domain: ${domain}`);
|
|
590
|
+
} else {
|
|
591
|
+
console.log(` 🌐 Domain: ${domain}`);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Try to find which root domain exists
|
|
595
|
+
let foundDomain = null;
|
|
596
|
+
let domainConfig = null;
|
|
597
|
+
try {
|
|
598
|
+
// First try www version if it's different
|
|
599
|
+
if (rootInfo.wwwRoot !== rootInfo.bareRoot) {
|
|
600
|
+
try {
|
|
601
|
+
console.log(` 🔍 Checking: ${rootInfo.wwwRoot}...`);
|
|
602
|
+
domainConfig = await this.domainDiscovery.discoverDomainConfig(rootInfo.wwwRoot, this.apiToken);
|
|
603
|
+
if (domainConfig && domainConfig.zoneId) {
|
|
604
|
+
foundDomain = rootInfo.wwwRoot;
|
|
605
|
+
console.log(` ✅ Found www root: ${rootInfo.wwwRoot} (Zone: ${domainConfig.zoneId})`);
|
|
606
|
+
}
|
|
607
|
+
} catch (wwwError) {
|
|
608
|
+
if (wwwError.message.includes('API token is required')) {
|
|
609
|
+
console.log(` 🔑 API token required to verify ${rootInfo.wwwRoot}`);
|
|
610
|
+
|
|
611
|
+
// Automatically attempt to get API token
|
|
612
|
+
const tokenObtained = await this.ensureApiToken();
|
|
613
|
+
if (tokenObtained) {
|
|
614
|
+
console.log(` 🔄 Retrying www verification with API token...`);
|
|
615
|
+
try {
|
|
616
|
+
domainConfig = await this.domainDiscovery.discoverDomainConfig(rootInfo.wwwRoot, this.apiToken);
|
|
617
|
+
if (domainConfig && domainConfig.zoneId) {
|
|
618
|
+
foundDomain = rootInfo.wwwRoot;
|
|
619
|
+
console.log(` ✅ Found www root: ${rootInfo.wwwRoot} (Zone: ${domainConfig.zoneId})`);
|
|
620
|
+
}
|
|
621
|
+
} catch (retryError) {
|
|
622
|
+
console.log(` 📝 WWW root ${rootInfo.wwwRoot} not found after token verification`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
} else {
|
|
626
|
+
console.log(` 📝 WWW root ${rootInfo.wwwRoot} not found`);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// If www not found, try bare domain
|
|
632
|
+
if (!foundDomain) {
|
|
633
|
+
try {
|
|
634
|
+
console.log(` 🔍 Checking: ${rootInfo.bareRoot}...`);
|
|
635
|
+
domainConfig = await this.domainDiscovery.discoverDomainConfig(rootInfo.bareRoot, this.apiToken);
|
|
636
|
+
if (domainConfig && domainConfig.zoneId) {
|
|
637
|
+
foundDomain = rootInfo.bareRoot;
|
|
638
|
+
console.log(` ✅ Found bare root: ${rootInfo.bareRoot} (Zone: ${domainConfig.zoneId})`);
|
|
639
|
+
}
|
|
640
|
+
} catch (bareError) {
|
|
641
|
+
if (bareError.message.includes('API token is required')) {
|
|
642
|
+
console.log(` 🔑 API token required to verify ${rootInfo.bareRoot}`);
|
|
643
|
+
|
|
644
|
+
// Automatically attempt to get API token
|
|
645
|
+
const tokenObtained = await this.ensureApiToken();
|
|
646
|
+
if (tokenObtained) {
|
|
647
|
+
console.log(` 🔄 Retrying verification with API token...`);
|
|
648
|
+
try {
|
|
649
|
+
domainConfig = await this.domainDiscovery.discoverDomainConfig(rootInfo.bareRoot, this.apiToken);
|
|
650
|
+
if (domainConfig && domainConfig.zoneId) {
|
|
651
|
+
foundDomain = rootInfo.bareRoot;
|
|
652
|
+
console.log(` ✅ Found bare root: ${rootInfo.bareRoot} (Zone: ${domainConfig.zoneId})`);
|
|
653
|
+
}
|
|
654
|
+
} catch (retryError) {
|
|
655
|
+
console.log(` 📝 Bare root ${rootInfo.bareRoot} not found after token verification`);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
} else {
|
|
659
|
+
console.log(` 📝 Bare root ${rootInfo.bareRoot} not found`);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
if (foundDomain && domainConfig) {
|
|
664
|
+
if (isServiceSubdomain && !rootInfo.isWwwRoot) {
|
|
665
|
+
console.log(` 📋 Service ${domain} can be deployed to zone: ${foundDomain}`);
|
|
666
|
+
}
|
|
667
|
+
if (this.accountId) {
|
|
668
|
+
console.log(` 🔗 Checked in account: ${this.accountId}`);
|
|
669
|
+
}
|
|
670
|
+
return {
|
|
671
|
+
found: true,
|
|
672
|
+
rootDomain: foundDomain,
|
|
673
|
+
actualRootFound: foundDomain,
|
|
674
|
+
zoneId: domainConfig.zoneId,
|
|
675
|
+
isSubdomain: isServiceSubdomain,
|
|
676
|
+
serviceDomain: domain
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Neither root found - but this might be due to API token limitation
|
|
681
|
+
console.log(` ⚠️ Cannot verify domain via API (requires explicit API token)`);
|
|
682
|
+
console.log(` 🔍 OAuth authentication doesn't support direct domain verification`);
|
|
683
|
+
console.log(` 💡 Domains checked: ${rootInfo.wwwRoot}, ${rootInfo.bareRoot}`);
|
|
684
|
+
if (this.accountId) {
|
|
685
|
+
console.log(` 🔍 Account: ${this.accountId}`);
|
|
686
|
+
}
|
|
687
|
+
console.log(` 📋 To verify if domain exists:`);
|
|
688
|
+
console.log(` → Check Cloudflare Dashboard: https://dash.cloudflare.com`);
|
|
689
|
+
console.log(` → Look for ${rootInfo.bareRoot} or ${rootInfo.wwwRoot} in your domains`);
|
|
690
|
+
return {
|
|
691
|
+
found: false,
|
|
692
|
+
rootDomain: rootInfo.bareRoot,
|
|
693
|
+
alternateRoot: rootInfo.wwwRoot,
|
|
694
|
+
isSubdomain: isServiceSubdomain,
|
|
695
|
+
serviceDomain: domain,
|
|
696
|
+
requiresApiToken: true
|
|
697
|
+
};
|
|
698
|
+
} catch (error) {
|
|
699
|
+
console.log(` ❌ Error checking domains: ${error.message}`);
|
|
700
|
+
return {
|
|
701
|
+
found: false,
|
|
702
|
+
rootDomain: rootInfo.wwwRoot,
|
|
703
|
+
alternateRoot: rootInfo.bareRoot,
|
|
704
|
+
isSubdomain: isServiceSubdomain,
|
|
705
|
+
serviceDomain: domain,
|
|
706
|
+
error: error.message
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Complete domain verification workflow
|
|
713
|
+
*/
|
|
714
|
+
async verifyDomainWorkflow(requestedDomain) {
|
|
715
|
+
try {
|
|
716
|
+
// Step 1: Verify authentication
|
|
717
|
+
const authSuccess = await this.verifyAuthentication();
|
|
718
|
+
|
|
719
|
+
// Step 2: Get available domains (if authenticated)
|
|
720
|
+
if (authSuccess) {
|
|
721
|
+
await this.getAvailableDomains();
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Step 3: Verify domain and check services
|
|
725
|
+
const result = await this.verifyDomainAndMatchServices(requestedDomain);
|
|
726
|
+
return {
|
|
727
|
+
authenticated: this.isAuthenticated,
|
|
728
|
+
domainStatus: result.status,
|
|
729
|
+
recommendedAction: result.action,
|
|
730
|
+
existingServices: result.services,
|
|
731
|
+
availableDomains: this.availableDomains
|
|
732
|
+
};
|
|
733
|
+
} catch (error) {
|
|
734
|
+
if (error.message.includes('CHOOSE_DIFFERENT_DOMAIN')) {
|
|
735
|
+
return {
|
|
736
|
+
action: 'choose_different',
|
|
737
|
+
availableDomains: this.availableDomains
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
throw error;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Get comprehensive account details for troubleshooting
|
|
746
|
+
*/
|
|
747
|
+
async getAccountDetails() {
|
|
748
|
+
if (!this.isAuthenticated) {
|
|
749
|
+
return {
|
|
750
|
+
error: 'Not authenticated'
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
try {
|
|
754
|
+
const details = {
|
|
755
|
+
accountId: this.accountId,
|
|
756
|
+
authenticated: this.isAuthenticated,
|
|
757
|
+
zones: [],
|
|
758
|
+
totalZones: 0
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
// Try to get zone information using the working API method
|
|
762
|
+
try {
|
|
763
|
+
const zones = await this.fetchAccountZonesViaAPI();
|
|
764
|
+
details.zones = zones.map(z => z.name);
|
|
765
|
+
details.totalZones = zones.length;
|
|
766
|
+
details.zoneListUnavailable = false;
|
|
767
|
+
} catch (zoneError) {
|
|
768
|
+
details.zoneError = 'Could not fetch zone information';
|
|
769
|
+
details.zoneListUnavailable = true;
|
|
770
|
+
}
|
|
771
|
+
return details;
|
|
772
|
+
} catch (error) {
|
|
773
|
+
return {
|
|
774
|
+
error: error.message
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Parse zone list output for account context
|
|
781
|
+
* Falls back to alternative methods if direct zone listing not available
|
|
782
|
+
*/
|
|
783
|
+
parseZoneList(stdout) {
|
|
784
|
+
const zones = [];
|
|
785
|
+
let totalZones = 0;
|
|
786
|
+
try {
|
|
787
|
+
// Check if zone listing is not available
|
|
788
|
+
if (stdout.includes('Zone listing not available') || stdout.includes('Unknown arguments')) {
|
|
789
|
+
return {
|
|
790
|
+
zones: [],
|
|
791
|
+
totalZones: 0,
|
|
792
|
+
unavailable: true
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
const lines = stdout.split('\n').filter(line => line.trim());
|
|
796
|
+
for (const line of lines) {
|
|
797
|
+
// Look for zone entries (domain names)
|
|
798
|
+
if (line.includes('.') && !line.includes('│') && !line.toLowerCase().includes('zone')) {
|
|
799
|
+
const parts = line.trim().split(/\s+/);
|
|
800
|
+
if (parts.length > 0 && parts[0].includes('.')) {
|
|
801
|
+
zones.push(parts[0]);
|
|
802
|
+
totalZones++;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// Also check for table format
|
|
807
|
+
if (line.includes('│') && line.includes('.')) {
|
|
808
|
+
const match = line.match(/│\s*([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})\s*│/);
|
|
809
|
+
if (match && !zones.includes(match[1])) {
|
|
810
|
+
zones.push(match[1]);
|
|
811
|
+
totalZones++;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
} catch (error) {
|
|
816
|
+
// Ignore parsing errors
|
|
817
|
+
}
|
|
818
|
+
return {
|
|
819
|
+
zones: zones.slice(0, 5),
|
|
820
|
+
totalZones
|
|
821
|
+
}; // Limit to first 5 zones
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* Fetch account zones using Cloudflare API (the working method!)
|
|
826
|
+
*/
|
|
827
|
+
async fetchAccountZonesViaAPI() {
|
|
828
|
+
if (!this.accountId) {
|
|
829
|
+
throw new Error('Account ID not available');
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// Use the domain discovery module's API method since it works
|
|
833
|
+
try {
|
|
834
|
+
const zones = await this.domainDiscovery.fetchAccountZones(this.accountId, this.apiToken);
|
|
835
|
+
return zones || [];
|
|
836
|
+
} catch (error) {
|
|
837
|
+
// Fallback: try without explicit API token (use wrangler's auth)
|
|
838
|
+
try {
|
|
839
|
+
const response = await this.domainDiscovery.makeCloudflareRequest(`https://api.cloudflare.com/client/v4/zones?account.id=${this.accountId}`, null // Let it use default auth
|
|
840
|
+
);
|
|
841
|
+
if (response.success) {
|
|
842
|
+
return response.result || [];
|
|
843
|
+
}
|
|
844
|
+
} catch (fallbackError) {
|
|
845
|
+
// If all else fails, return empty array
|
|
846
|
+
}
|
|
847
|
+
throw error;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* Show what domains are actually available in the Cloudflare account
|
|
853
|
+
*/
|
|
854
|
+
async showAvailableDomainsInAccount() {
|
|
855
|
+
console.log('');
|
|
856
|
+
console.log(' 📋 Domains found in your Cloudflare account:');
|
|
857
|
+
const accountDetails = await this.getAccountDetails();
|
|
858
|
+
if (accountDetails.error) {
|
|
859
|
+
console.log(' ⚠️ Could not fetch account domains');
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
if (accountDetails.zoneListUnavailable && accountDetails.zoneError) {
|
|
863
|
+
console.log(' ⚠️ API-based domain listing requires explicit API token');
|
|
864
|
+
console.log(' 🔍 Using OAuth authentication - cannot directly list zones');
|
|
865
|
+
console.log(' 💡 To check your domains:');
|
|
866
|
+
console.log(' → Visit Cloudflare Dashboard: https://dash.cloudflare.com');
|
|
867
|
+
console.log(' → Check the "Websites" section for your domains');
|
|
868
|
+
console.log(' 📧 Account: ' + (this.accountId || 'Unknown'));
|
|
869
|
+
} else if (accountDetails.totalZones === 0) {
|
|
870
|
+
console.log(' 📝 No domains found in this Cloudflare account');
|
|
871
|
+
console.log(' 💡 This account has no DNS zones configured');
|
|
872
|
+
console.log(' 🌐 To add a domain:');
|
|
873
|
+
console.log(' → Go to Cloudflare Dashboard → Add a Site');
|
|
874
|
+
} else {
|
|
875
|
+
console.log(` 🌐 Found ${accountDetails.totalZones} domain(s):`);
|
|
876
|
+
if (accountDetails.zones && accountDetails.zones.length > 0) {
|
|
877
|
+
accountDetails.zones.forEach((zone, index) => {
|
|
878
|
+
console.log(` ${index + 1}. ${zone}`);
|
|
879
|
+
});
|
|
880
|
+
if (accountDetails.totalZones > accountDetails.zones.length) {
|
|
881
|
+
console.log(` ... and ${accountDetails.totalZones - accountDetails.zones.length} more`);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* Show actionable domain suggestions based on account status
|
|
889
|
+
*/
|
|
890
|
+
async showDomainSuggestions(serviceDomain, domainCheck) {
|
|
891
|
+
console.log(' 💡 Solutions:');
|
|
892
|
+
const accountDetails = await this.getAccountDetails();
|
|
893
|
+
if (accountDetails.totalZones > 0) {
|
|
894
|
+
console.log(` 1. 🔄 Use an existing domain from your account:`);
|
|
895
|
+
accountDetails.zones.forEach(zone => {
|
|
896
|
+
const suggestedService = serviceDomain.replace(domainCheck.rootDomain, zone);
|
|
897
|
+
console.log(` → ${suggestedService}`);
|
|
898
|
+
});
|
|
899
|
+
console.log(` 2. ➕ Add ${domainCheck.rootDomain} to your Cloudflare account`);
|
|
900
|
+
console.log(` 3. 🌐 Transfer ${domainCheck.rootDomain} DNS to Cloudflare`);
|
|
901
|
+
} else {
|
|
902
|
+
console.log(` 1. ➕ Add ${domainCheck.rootDomain} to your Cloudflare account first`);
|
|
903
|
+
console.log(` 2. 🌐 Set up DNS for ${domainCheck.rootDomain} in Cloudflare`);
|
|
904
|
+
console.log(` 3. 🔄 Or use a different domain that you own`);
|
|
905
|
+
}
|
|
906
|
+
console.log('');
|
|
907
|
+
console.log(' 📚 How to add a domain to Cloudflare:');
|
|
908
|
+
console.log(' → Go to Cloudflare Dashboard → Add Site → Enter your domain');
|
|
909
|
+
console.log(' → Update nameservers at your domain registrar');
|
|
910
|
+
console.log(' → Wait for DNS propagation (usually 24-48 hours)');
|
|
911
|
+
}
|
|
912
|
+
}
|