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