@tamyla/clodo-framework 3.1.21 → 3.1.22
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 +9 -0
- package/README.md +53 -0
- package/dist/bin/clodo-service.js +47 -15
- package/dist/bin/commands/deploy.js +115 -83
- package/dist/bin/commands/helpers/deployment-ui.js +138 -0
- package/dist/bin/commands/helpers/deployment-verification.js +251 -0
- package/dist/bin/commands/helpers/error-recovery.js +80 -0
- package/dist/bin/commands/helpers/resource-detection.js +113 -0
- package/dist/bin/commands/validate.js +1 -1
- package/dist/bin/security/security-cli.js +1 -1
- package/dist/bin/shared/cache/configuration-cache.js +82 -0
- package/dist/bin/shared/cloudflare/domain-manager.js +1 -1
- package/dist/bin/shared/cloudflare/index.js +1 -1
- package/dist/bin/shared/cloudflare/ops.js +6 -4
- package/dist/bin/shared/config/ConfigurationManager.js +23 -1
- package/dist/bin/shared/config/command-config-manager.js +19 -3
- package/dist/bin/shared/config/index.js +1 -1
- package/dist/bin/shared/deployment/credential-collector.js +30 -7
- package/dist/bin/shared/deployment/index.js +2 -2
- package/dist/bin/shared/deployment/rollback-manager.js +4 -520
- package/dist/bin/shared/deployment/utilities/d1-error-recovery.js +177 -0
- package/dist/bin/shared/deployment/validator.js +40 -10
- package/dist/bin/shared/deployment/workflows/deployment-summary.js +214 -0
- package/dist/bin/shared/deployment/workflows/interactive-confirmation.js +188 -0
- package/dist/bin/shared/deployment/workflows/interactive-database-workflow.js +234 -0
- package/dist/bin/shared/deployment/workflows/interactive-domain-info-gatherer.js +240 -0
- package/dist/bin/shared/deployment/workflows/interactive-secret-workflow.js +228 -0
- package/dist/bin/shared/deployment/workflows/interactive-testing-workflow.js +235 -0
- package/dist/bin/shared/deployment/workflows/interactive-validation.js +218 -0
- package/dist/bin/shared/error-handling/error-classifier.js +46 -0
- package/dist/bin/shared/monitoring/health-checker.js +129 -1
- package/dist/bin/shared/monitoring/memory-manager.js +17 -6
- package/dist/bin/shared/routing/domain-router.js +1 -1
- package/dist/bin/shared/utils/deployment-validator.js +97 -0
- package/dist/bin/shared/utils/formatters.js +10 -0
- package/dist/bin/shared/utils/index.js +13 -1
- package/dist/bin/shared/utils/interactive-prompts.js +34 -18
- package/dist/bin/shared/utils/progress-manager.js +2 -2
- package/dist/bin/shared/utils/progress-spinner.js +53 -0
- package/dist/bin/shared/utils/sensitive-redactor.js +91 -0
- package/dist/bin/shared/validation/ValidationRegistry.js +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/patterns/insecure-patterns.js +1 -1
- package/dist/utils/constants.js +102 -0
- package/dist/utils/deployment/wrangler-config-manager.js +215 -48
- package/dist/utils/framework-config.js +2 -2
- package/dist/utils/interactive-prompts.js +10 -59
- package/package.json +16 -8
- package/dist/bin/clodo-service-old.js +0 -868
- package/dist/bin/clodo-service-test.js +0 -10
- package/dist/bin/commands/assess.js +0 -91
- package/dist/bin/commands/create.js +0 -77
- package/dist/bin/commands/diagnose.js +0 -83
- package/dist/bin/commands/helpers.js +0 -138
- package/dist/bin/commands/update.js +0 -75
- package/dist/bin/database/deployment-db-manager.js +0 -423
- package/dist/bin/database/enterprise-db-manager.js +0 -457
- package/dist/bin/database/wrangler-d1-manager.js +0 -685
- package/dist/bin/deployment/enterprise-deploy.js +0 -877
- package/dist/bin/deployment/master-deploy.js +0 -1376
- package/dist/bin/deployment/modular-enterprise-deploy.js +0 -466
- package/dist/bin/deployment/modules/DeploymentConfiguration.js +0 -395
- package/dist/bin/deployment/modules/DeploymentOrchestrator.js +0 -492
- package/dist/bin/deployment/modules/EnvironmentManager.js +0 -517
- package/dist/bin/deployment/modules/MonitoringIntegration.js +0 -560
- package/dist/bin/deployment/modules/ValidationManager.js +0 -342
- package/dist/bin/deployment/orchestration/BaseDeploymentOrchestrator.js +0 -426
- package/dist/bin/deployment/orchestration/EnterpriseOrchestrator.js +0 -401
- package/dist/bin/deployment/orchestration/PortfolioOrchestrator.js +0 -273
- package/dist/bin/deployment/orchestration/SingleServiceOrchestrator.js +0 -231
- package/dist/bin/deployment/orchestration/UnifiedDeploymentOrchestrator.js +0 -662
- package/dist/bin/deployment/test-interactive-utils.js +0 -66
- package/dist/bin/portfolio/portfolio-manager.js +0 -487
- package/dist/bin/service-management/create-service.js +0 -122
- package/dist/bin/service-management/init-service.js +0 -79
- package/dist/config/customers.js +0 -623
- package/dist/config/domains.js +0 -186
- package/dist/config/index.js +0 -6
- package/dist/database/database-orchestrator.js +0 -795
- package/dist/database/index.js +0 -4
- package/dist/deployment/index.js +0 -11
- package/dist/deployment/orchestration/BaseDeploymentOrchestrator.js +0 -426
- package/dist/deployment/orchestration/EnterpriseOrchestrator.js +0 -401
- package/dist/deployment/orchestration/PortfolioOrchestrator.js +0 -273
- package/dist/deployment/orchestration/SingleServiceOrchestrator.js +0 -231
- package/dist/deployment/orchestration/UnifiedDeploymentOrchestrator.js +0 -662
- package/dist/deployment/orchestration/index.js +0 -17
- package/dist/deployment/rollback-manager.js +0 -36
- package/dist/deployment/wrangler-deployer.js +0 -640
- package/dist/handlers/GenericRouteHandler.js +0 -532
- package/dist/migration/MigrationAdapters.js +0 -562
- package/dist/modules/ModuleManager.js +0 -668
- package/dist/modules/security.js +0 -96
- package/dist/orchestration/cross-domain-coordinator.js +0 -1083
- package/dist/orchestration/index.js +0 -5
- package/dist/orchestration/modules/DeploymentCoordinator.js +0 -368
- package/dist/orchestration/modules/DomainResolver.js +0 -198
- package/dist/orchestration/modules/StateManager.js +0 -332
- package/dist/orchestration/multi-domain-orchestrator.js +0 -724
- package/dist/routing/EnhancedRouter.js +0 -158
- package/dist/schema/SchemaManager.js +0 -778
- package/dist/service-management/ConfirmationEngine.js +0 -412
- package/dist/service-management/ErrorTracker.js +0 -299
- package/dist/service-management/GenerationEngine.js +0 -447
- package/dist/service-management/InputCollector.js +0 -619
- package/dist/service-management/ServiceCreator.js +0 -265
- package/dist/service-management/ServiceInitializer.js +0 -453
- package/dist/service-management/ServiceOrchestrator.js +0 -633
- package/dist/service-management/generators/BaseGenerator.js +0 -233
- package/dist/service-management/generators/GeneratorRegistry.js +0 -254
- package/dist/service-management/generators/cicd/CiWorkflowGenerator.js +0 -87
- package/dist/service-management/generators/cicd/DeployWorkflowGenerator.js +0 -106
- package/dist/service-management/generators/code/ServiceHandlersGenerator.js +0 -235
- package/dist/service-management/generators/code/ServiceMiddlewareGenerator.js +0 -116
- package/dist/service-management/generators/code/ServiceUtilsGenerator.js +0 -246
- package/dist/service-management/generators/code/WorkerIndexGenerator.js +0 -143
- package/dist/service-management/generators/config/DevelopmentEnvGenerator.js +0 -101
- package/dist/service-management/generators/config/DomainsConfigGenerator.js +0 -175
- package/dist/service-management/generators/config/EnvExampleGenerator.js +0 -178
- package/dist/service-management/generators/config/ProductionEnvGenerator.js +0 -97
- package/dist/service-management/generators/config/StagingEnvGenerator.js +0 -97
- package/dist/service-management/generators/config/WranglerTomlGenerator.js +0 -238
- package/dist/service-management/generators/core/PackageJsonGenerator.js +0 -243
- package/dist/service-management/generators/core/SiteConfigGenerator.js +0 -115
- package/dist/service-management/generators/documentation/ApiDocsGenerator.js +0 -331
- package/dist/service-management/generators/documentation/ConfigurationDocsGenerator.js +0 -294
- package/dist/service-management/generators/documentation/DeploymentDocsGenerator.js +0 -244
- package/dist/service-management/generators/documentation/ReadmeGenerator.js +0 -196
- package/dist/service-management/generators/schemas/ServiceSchemaGenerator.js +0 -190
- package/dist/service-management/generators/scripts/DeployScriptGenerator.js +0 -123
- package/dist/service-management/generators/scripts/HealthCheckScriptGenerator.js +0 -101
- package/dist/service-management/generators/scripts/SetupScriptGenerator.js +0 -88
- package/dist/service-management/generators/service-types/StaticSiteGenerator.js +0 -342
- package/dist/service-management/generators/testing/EslintConfigGenerator.js +0 -85
- package/dist/service-management/generators/testing/IntegrationTestsGenerator.js +0 -237
- package/dist/service-management/generators/testing/JestConfigGenerator.js +0 -72
- package/dist/service-management/generators/testing/UnitTestsGenerator.js +0 -277
- package/dist/service-management/generators/tooling/DockerComposeGenerator.js +0 -71
- package/dist/service-management/generators/tooling/GitignoreGenerator.js +0 -143
- package/dist/service-management/generators/utils/FileWriter.js +0 -179
- package/dist/service-management/generators/utils/PathResolver.js +0 -157
- package/dist/service-management/generators/utils/ServiceManifestGenerator.js +0 -111
- package/dist/service-management/generators/utils/TemplateEngine.js +0 -185
- package/dist/service-management/generators/utils/index.js +0 -18
- package/dist/service-management/handlers/ConfirmationHandler.js +0 -71
- package/dist/service-management/handlers/GenerationHandler.js +0 -80
- package/dist/service-management/handlers/InputHandler.js +0 -59
- package/dist/service-management/handlers/ValidationHandler.js +0 -203
- package/dist/service-management/index.js +0 -14
- package/dist/service-management/routing/DomainRouteMapper.js +0 -311
- package/dist/service-management/routing/RouteGenerator.js +0 -266
- package/dist/service-management/routing/WranglerRoutesBuilder.js +0 -273
- package/dist/service-management/routing/index.js +0 -14
- package/dist/service-management/services/DirectoryStructureService.js +0 -56
- package/dist/service-management/services/GenerationCoordinator.js +0 -208
- package/dist/service-management/services/GeneratorRegistry.js +0 -174
- package/dist/services/GenericDataService.js +0 -501
- package/dist/ui-structures/concepts/second-order-acquisition-strategy.md +0 -286
- package/dist/ui-structures/concepts/service-lifecycle-management.md +0 -150
- package/dist/ui-structures/concepts/service-manifest-guide.md +0 -309
- package/dist/ui-structures/concepts/three-tier-categorization-strategy.md +0 -231
- package/dist/ui-structures/creation/automated-generation-ui.json +0 -246
- package/dist/ui-structures/creation/core-inputs-ui.json +0 -217
- package/dist/ui-structures/creation/smart-confirmable-ui.json +0 -451
- package/dist/ui-structures/reference/absolutely-required-inputs.json +0 -315
- package/dist/ui-structures/reference/service-manifest-template.json +0 -342
- package/dist/version/VersionDetector.js +0 -723
- package/dist/worker/index.js +0 -4
- package/dist/worker/integration.js +0 -351
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deployment Verification Helpers (UI Wrapper)
|
|
3
|
+
* Provides UI-specific deployment verification by delegating to shared infrastructure
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { verifyWorkerDeployment, healthCheckWithBackoff } from '../../shared/monitoring/health-checker.js';
|
|
7
|
+
import { checkHealth } from '../../shared/monitoring/health-checker.js';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import readline from 'readline';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Run comprehensive post-deployment verification
|
|
13
|
+
* @param {string} serviceName - Name of the service
|
|
14
|
+
* @param {Object} deploymentResult - Result from deployment
|
|
15
|
+
* @param {Object} credentials - Cloudflare credentials
|
|
16
|
+
* @param {Object} options - Verification options
|
|
17
|
+
* @returns {Promise<Object>} Verification result
|
|
18
|
+
*/
|
|
19
|
+
export async function runPostDeploymentVerification(serviceName, deploymentResult, credentials, options = {}) {
|
|
20
|
+
console.log('\n🔍 Post-deployment Verification');
|
|
21
|
+
|
|
22
|
+
// Skip health checks in dry-run mode with clear explanation
|
|
23
|
+
if (options.dryRun) {
|
|
24
|
+
console.log(chalk.blue(' ℹ️ Health checks skipped in dry-run mode'));
|
|
25
|
+
console.log(chalk.gray(' 💡 Run without --dry-run to deploy and verify the worker'));
|
|
26
|
+
return {
|
|
27
|
+
success: true,
|
|
28
|
+
healthCheckPassed: null,
|
|
29
|
+
reason: 'dry-run-skipped'
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
// Wait for deployment to propagate
|
|
34
|
+
console.log(' ⏳ Waiting for deployment to propagate...');
|
|
35
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
36
|
+
|
|
37
|
+
// Run health checks with enhanced URL discovery
|
|
38
|
+
console.log(' 🏥 Running health checks...');
|
|
39
|
+
|
|
40
|
+
// Try multiple sources for worker URL (in priority order)
|
|
41
|
+
const workerUrl = await discoverWorkerUrl(deploymentResult, credentials, options);
|
|
42
|
+
if (workerUrl) {
|
|
43
|
+
console.log(chalk.gray(` 🔗 Testing: ${workerUrl}`));
|
|
44
|
+
const health = await checkHealth(workerUrl).catch(err => {
|
|
45
|
+
console.log(chalk.yellow(` ⚠️ Health check failed: ${err.message}`));
|
|
46
|
+
return {
|
|
47
|
+
status: 'unknown',
|
|
48
|
+
error: err.message
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
if (health && health.status === 'ok') {
|
|
52
|
+
console.log(` ✅ Deployment verified: ${health.framework?.models?.length || 0} models active`);
|
|
53
|
+
return {
|
|
54
|
+
success: true,
|
|
55
|
+
healthCheckPassed: true,
|
|
56
|
+
healthData: health,
|
|
57
|
+
url: workerUrl
|
|
58
|
+
};
|
|
59
|
+
} else {
|
|
60
|
+
console.log(` ⚠️ Health check returned: ${health?.status || 'unknown'}`);
|
|
61
|
+
if (health.error) {
|
|
62
|
+
console.log(chalk.gray(` Error: ${health.error}`));
|
|
63
|
+
}
|
|
64
|
+
// Don't fail deployment for health check issues
|
|
65
|
+
return {
|
|
66
|
+
success: true,
|
|
67
|
+
healthCheckPassed: false,
|
|
68
|
+
healthData: health,
|
|
69
|
+
url: workerUrl
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
console.log(chalk.yellow(' ⚠️ No worker URL could be determined'));
|
|
74
|
+
console.log(chalk.gray(' 💡 To fix: Add route in wrangler.toml or check Cloudflare dashboard'));
|
|
75
|
+
return {
|
|
76
|
+
success: true,
|
|
77
|
+
healthCheckPassed: null,
|
|
78
|
+
reason: 'no-url-discovered'
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.log(chalk.yellow(` ⚠️ Post-deployment verification warning: ${error.message}`));
|
|
83
|
+
// Don't fail overall deployment for verification issues
|
|
84
|
+
return {
|
|
85
|
+
success: true,
|
|
86
|
+
healthCheckPassed: false,
|
|
87
|
+
error: error.message
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Discover worker URL from multiple sources
|
|
94
|
+
* Priority: deploymentResult.url > workers.dev convention > Cloudflare API discovery
|
|
95
|
+
* @param {Object} deploymentResult - Result from deployment
|
|
96
|
+
* @param {Object} credentials - Cloudflare credentials
|
|
97
|
+
* @param {Object} options - Discovery options
|
|
98
|
+
* @returns {Promise<string|null>} Discovered worker URL or null
|
|
99
|
+
*/
|
|
100
|
+
async function discoverWorkerUrl(deploymentResult, credentials, options = {}) {
|
|
101
|
+
// Source 1: Direct URL from deployment result
|
|
102
|
+
if (deploymentResult?.url) {
|
|
103
|
+
return deploymentResult.url;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Source 2: Construct workers.dev URL from credentials
|
|
107
|
+
if (credentials?.cloudflareSettings?.accountId && options.serviceName) {
|
|
108
|
+
const accountHash = credentials.cloudflareSettings.accountId;
|
|
109
|
+
const scriptName = options.serviceName || 'my-worker';
|
|
110
|
+
const workersDevUrl = `https://${scriptName}.${accountHash}.workers.dev`;
|
|
111
|
+
|
|
112
|
+
// Verify this URL exists by checking if we can reach it
|
|
113
|
+
const urlExists = await verifyUrlExists(workersDevUrl);
|
|
114
|
+
if (urlExists) {
|
|
115
|
+
return workersDevUrl;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Source 3: Query Cloudflare API for worker routes
|
|
120
|
+
if (credentials?.cloudflareSettings) {
|
|
121
|
+
const discoveredUrl = await discoverUrlFromCloudflare(credentials.cloudflareSettings, options);
|
|
122
|
+
if (discoveredUrl) {
|
|
123
|
+
return discoveredUrl;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Source 4: Custom domain from options
|
|
128
|
+
if (options.customDomain) {
|
|
129
|
+
return `https://${options.customDomain}`;
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Verify if a URL exists (can be reached)
|
|
136
|
+
* @param {string} url - URL to verify
|
|
137
|
+
* @returns {Promise<boolean>} True if URL responds
|
|
138
|
+
*/
|
|
139
|
+
async function verifyUrlExists(url) {
|
|
140
|
+
try {
|
|
141
|
+
const response = await fetch(url, {
|
|
142
|
+
method: 'HEAD',
|
|
143
|
+
signal: AbortSignal.timeout(5000)
|
|
144
|
+
});
|
|
145
|
+
return response.ok || response.status === 404; // 404 means route exists but no handler
|
|
146
|
+
} catch {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Discover worker URL from Cloudflare API
|
|
153
|
+
* @param {Object} cloudflareSettings - Cloudflare credentials
|
|
154
|
+
* @param {Object} options - Discovery options
|
|
155
|
+
* @returns {Promise<string|null>} Discovered URL or null
|
|
156
|
+
*/
|
|
157
|
+
async function discoverUrlFromCloudflare(cloudflareSettings, options = {}) {
|
|
158
|
+
try {
|
|
159
|
+
const {
|
|
160
|
+
CloudflareAPI
|
|
161
|
+
} = await import('../../../utils/cloudflare/api.js');
|
|
162
|
+
const api = new CloudflareAPI(cloudflareSettings.token);
|
|
163
|
+
|
|
164
|
+
// Get worker routes for the zone
|
|
165
|
+
if (cloudflareSettings.zoneId) {
|
|
166
|
+
const response = await api.request(`/zones/${cloudflareSettings.zoneId}/workers/routes`);
|
|
167
|
+
const routes = response.result || [];
|
|
168
|
+
if (routes.length > 0) {
|
|
169
|
+
// Find route for this worker
|
|
170
|
+
const scriptName = options.serviceName || options.workerName;
|
|
171
|
+
const matchingRoute = routes.find(r => r.script && (r.script === scriptName || r.script.includes(scriptName)));
|
|
172
|
+
if (matchingRoute && matchingRoute.pattern) {
|
|
173
|
+
// Convert route pattern to URL (basic conversion)
|
|
174
|
+
const url = matchingRoute.pattern.replace('*', '').replace('//', '//');
|
|
175
|
+
return url.startsWith('http') ? url : `https://${url}`;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Fallback: Get worker info from scripts API
|
|
181
|
+
const scriptsResponse = await api.request(`/accounts/${cloudflareSettings.accountId}/workers/scripts`);
|
|
182
|
+
const scripts = scriptsResponse.result || [];
|
|
183
|
+
const scriptName = options.serviceName || options.workerName;
|
|
184
|
+
const script = scripts.find(s => s.id === scriptName || s.id.includes(scriptName));
|
|
185
|
+
if (script && script.url) {
|
|
186
|
+
return script.url;
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
// API discovery failed - not critical
|
|
190
|
+
if (options.verbose) {
|
|
191
|
+
console.log(chalk.gray(` Could not discover URL from Cloudflare API: ${error.message}`));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Verify deployment via Cloudflare API (UI wrapper)
|
|
199
|
+
* @param {string} workerName - Name of the worker to verify
|
|
200
|
+
* @param {Object} credentials - Cloudflare credentials
|
|
201
|
+
* @param {Object} options - Verification options
|
|
202
|
+
* @returns {Promise<Object>} Verification result
|
|
203
|
+
*/
|
|
204
|
+
export async function verifyDeployment(workerName, credentials, options = {}) {
|
|
205
|
+
// Delegate to shared infrastructure
|
|
206
|
+
return await verifyWorkerDeployment(workerName, credentials, options);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Perform health check with user permission prompt
|
|
211
|
+
* @param {string} baseUrl - Base URL to check
|
|
212
|
+
* @param {Object} options - Health check options
|
|
213
|
+
* @returns {Promise<Object>} Health check result
|
|
214
|
+
*/
|
|
215
|
+
export async function performHealthCheckWithPermission(baseUrl, options = {}) {
|
|
216
|
+
const {
|
|
217
|
+
skipPrompt = false,
|
|
218
|
+
...healthOptions
|
|
219
|
+
} = options;
|
|
220
|
+
if (!skipPrompt) {
|
|
221
|
+
const permission = await promptForHealthCheck();
|
|
222
|
+
if (!permission) {
|
|
223
|
+
console.log(chalk.yellow('\n⏭️ Health check skipped by user'));
|
|
224
|
+
return {
|
|
225
|
+
healthy: null,
|
|
226
|
+
skipped: true
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
console.log(chalk.gray('\n🏥 Running health checks with exponential backoff...\n'));
|
|
231
|
+
|
|
232
|
+
// Delegate to shared infrastructure
|
|
233
|
+
return await healthCheckWithBackoff(baseUrl, healthOptions);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Prompt user for health check permission
|
|
238
|
+
* @returns {Promise<boolean>} Whether to proceed with health check
|
|
239
|
+
*/
|
|
240
|
+
async function promptForHealthCheck() {
|
|
241
|
+
const rl = readline.createInterface({
|
|
242
|
+
input: process.stdin,
|
|
243
|
+
output: process.stdout
|
|
244
|
+
});
|
|
245
|
+
return new Promise(resolve => {
|
|
246
|
+
rl.question(chalk.cyan('\n❓ Run health checks to verify deployment? (Y/n): '), answer => {
|
|
247
|
+
rl.close();
|
|
248
|
+
resolve(!answer || answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Recovery Helpers (UI Wrapper)
|
|
3
|
+
* Provides interactive error recovery by delegating to shared error classification
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { classifyError, getRecoverySuggestions } from '../../shared/error-handling/error-classifier.js';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import readline from 'readline';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Handle deployment error with contextual recovery options
|
|
12
|
+
* @param {Error} error - The deployment error
|
|
13
|
+
* @param {Object} context - Deployment context
|
|
14
|
+
* @returns {Promise<string>} Recovery action: 'retry', 'dry-run', or 'abort'
|
|
15
|
+
*/
|
|
16
|
+
export async function handleDeploymentError(error, context = {}) {
|
|
17
|
+
// Classify error using shared infrastructure
|
|
18
|
+
const errorType = classifyError(error);
|
|
19
|
+
const suggestions = getRecoverySuggestions(errorType);
|
|
20
|
+
|
|
21
|
+
// Display error with contextual information
|
|
22
|
+
console.error(chalk.red(`\n❌ Deployment failed: ${error.message}\n`));
|
|
23
|
+
console.log(chalk.yellow(`Error Type: ${errorType.toUpperCase()}\n`));
|
|
24
|
+
|
|
25
|
+
// Display recovery suggestions
|
|
26
|
+
console.log(chalk.cyan('💡 Suggestions:'));
|
|
27
|
+
suggestions.forEach(suggestion => {
|
|
28
|
+
console.log(chalk.gray(` • ${suggestion}`));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Prompt for recovery action
|
|
32
|
+
return await promptForRecoveryAction();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Prompt user for recovery action
|
|
37
|
+
* @returns {Promise<string>} Recovery action
|
|
38
|
+
*/
|
|
39
|
+
async function promptForRecoveryAction() {
|
|
40
|
+
const rl = readline.createInterface({
|
|
41
|
+
input: process.stdin,
|
|
42
|
+
output: process.stdout
|
|
43
|
+
});
|
|
44
|
+
console.log(chalk.cyan('\nRecovery Options:'));
|
|
45
|
+
console.log(chalk.gray(' 1) Retry deployment'));
|
|
46
|
+
console.log(chalk.gray(' 2) Run in dry-run mode to diagnose'));
|
|
47
|
+
console.log(chalk.gray(' 3) Abort'));
|
|
48
|
+
return new Promise(resolve => {
|
|
49
|
+
rl.question(chalk.cyan('\nSelect option (1-3): '), answer => {
|
|
50
|
+
rl.close();
|
|
51
|
+
switch (answer.trim()) {
|
|
52
|
+
case '1':
|
|
53
|
+
resolve('retry');
|
|
54
|
+
break;
|
|
55
|
+
case '2':
|
|
56
|
+
resolve('dry-run');
|
|
57
|
+
break;
|
|
58
|
+
case '3':
|
|
59
|
+
default:
|
|
60
|
+
resolve('abort');
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Display error summary for non-interactive mode
|
|
69
|
+
* @param {Error} error - The error
|
|
70
|
+
*/
|
|
71
|
+
export function displayErrorSummary(error) {
|
|
72
|
+
const errorType = classifyError(error);
|
|
73
|
+
const suggestions = getRecoverySuggestions(errorType);
|
|
74
|
+
console.error(chalk.red(`\n❌ ${error.message}`));
|
|
75
|
+
console.log(chalk.yellow(`\nError Type: ${errorType.toUpperCase()}`));
|
|
76
|
+
console.log(chalk.cyan('\nSuggestions:'));
|
|
77
|
+
suggestions.forEach(suggestion => {
|
|
78
|
+
console.log(chalk.gray(` • ${suggestion}`));
|
|
79
|
+
});
|
|
80
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource Detection Helpers
|
|
3
|
+
* Detects existing Cloudflare resources (workers, databases) for change tracking
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Detect existing resources to show what will change
|
|
10
|
+
* @param {string} serviceName - Name of the service
|
|
11
|
+
* @param {Object} manifest - Service manifest
|
|
12
|
+
* @param {Object} credentials - Cloudflare credentials
|
|
13
|
+
* @param {Object} output - Output formatter
|
|
14
|
+
* @param {boolean} verbose - Verbose logging flag
|
|
15
|
+
* @returns {Promise<Object>} Detected resources
|
|
16
|
+
*/
|
|
17
|
+
export async function detectExistingResources(serviceName, manifest, credentials, output, verbose = false) {
|
|
18
|
+
let existingWorker = null;
|
|
19
|
+
let existingDatabases = [];
|
|
20
|
+
let resourceDetectionFailed = false;
|
|
21
|
+
try {
|
|
22
|
+
// Import CloudflareAPI to check existing resources
|
|
23
|
+
const {
|
|
24
|
+
CloudflareAPI
|
|
25
|
+
} = await import('../../../utils/cloudflare/api.js');
|
|
26
|
+
const cfApi = new CloudflareAPI(credentials.token);
|
|
27
|
+
|
|
28
|
+
// Check if worker already exists
|
|
29
|
+
const workers = await cfApi.listWorkers(credentials.accountId);
|
|
30
|
+
existingWorker = workers.find(w => w.name === serviceName);
|
|
31
|
+
|
|
32
|
+
// Check existing databases
|
|
33
|
+
existingDatabases = await cfApi.listD1Databases(credentials.accountId);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
resourceDetectionFailed = true;
|
|
36
|
+
if (verbose && output) {
|
|
37
|
+
output.warn(`Could not detect existing resources: ${error.message}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
existingWorker,
|
|
42
|
+
existingDatabases,
|
|
43
|
+
resourceDetectionFailed
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Display deployment plan with change detection badges
|
|
49
|
+
* @param {Object} planData - Deployment plan data
|
|
50
|
+
*/
|
|
51
|
+
export function displayDeploymentPlan(planData) {
|
|
52
|
+
const {
|
|
53
|
+
serviceName,
|
|
54
|
+
serviceType,
|
|
55
|
+
selectedDomain,
|
|
56
|
+
environment,
|
|
57
|
+
credentials,
|
|
58
|
+
dryRun,
|
|
59
|
+
existingWorker,
|
|
60
|
+
existingDatabases,
|
|
61
|
+
resourceDetectionFailed,
|
|
62
|
+
manifest
|
|
63
|
+
} = planData;
|
|
64
|
+
console.log(chalk.cyan('\n📋 Deployment Plan:'));
|
|
65
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
66
|
+
console.log(chalk.white(`Service: ${serviceName}`));
|
|
67
|
+
console.log(chalk.white(`Type: ${serviceType}`));
|
|
68
|
+
console.log(chalk.white(`Domain: ${selectedDomain}`));
|
|
69
|
+
console.log(chalk.white(`Environment: ${environment || 'production'}`));
|
|
70
|
+
console.log(chalk.white(`Account: ${credentials.accountId.substring(0, 8)}...`));
|
|
71
|
+
console.log(chalk.white(`Zone: ${credentials.zoneId.substring(0, 8)}...`));
|
|
72
|
+
console.log(chalk.white(`Deployment Mode: ${dryRun ? 'DRY RUN' : 'LIVE'}`));
|
|
73
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
74
|
+
|
|
75
|
+
// Show what will be deployed with change badges
|
|
76
|
+
console.log(chalk.cyan('\n📦 Resources:'));
|
|
77
|
+
|
|
78
|
+
// Worker status
|
|
79
|
+
if (existingWorker) {
|
|
80
|
+
const lastModified = existingWorker.modifiedOn ? new Date(existingWorker.modifiedOn).toLocaleString() : 'unknown';
|
|
81
|
+
console.log(chalk.yellow(` [CHANGE] Worker: ${serviceName}`));
|
|
82
|
+
console.log(chalk.gray(` Last deployed: ${lastModified}`));
|
|
83
|
+
} else if (!resourceDetectionFailed) {
|
|
84
|
+
console.log(chalk.green(` [NEW] Worker: ${serviceName}`));
|
|
85
|
+
} else {
|
|
86
|
+
console.log(chalk.white(` [DEPLOY] Worker: ${serviceName}`));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Database status (if manifest has database config)
|
|
90
|
+
if (manifest.database && manifest.database.name) {
|
|
91
|
+
const dbName = manifest.database.name;
|
|
92
|
+
const existingDb = existingDatabases.find(db => db.name === dbName || db.name === `${serviceName}-${dbName}`);
|
|
93
|
+
if (existingDb) {
|
|
94
|
+
console.log(chalk.yellow(` [CHANGE] Database: ${dbName} (binding update)`));
|
|
95
|
+
} else if (!resourceDetectionFailed) {
|
|
96
|
+
console.log(chalk.green(` [NEW] Database: ${dbName}`));
|
|
97
|
+
} else {
|
|
98
|
+
console.log(chalk.white(` [DEPLOY] Database: ${dbName}`));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Routes
|
|
103
|
+
const workersDomain = `${serviceName}.${credentials.accountId}.workers.dev`;
|
|
104
|
+
console.log(chalk.green(` [NEW] Route: https://${workersDomain}`));
|
|
105
|
+
if (selectedDomain && selectedDomain !== 'workers.cloudflare.com') {
|
|
106
|
+
console.log(chalk.yellow(` [MANUAL] Custom domain: ${selectedDomain}`));
|
|
107
|
+
console.log(chalk.gray(` ⚠️ Requires DNS configuration (see post-deployment steps)`));
|
|
108
|
+
}
|
|
109
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
110
|
+
if (dryRun) {
|
|
111
|
+
console.log(chalk.yellow('\n🔍 DRY RUN MODE - No changes will be made\n'));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import chalk from 'chalk';
|
|
6
|
-
import { ServiceOrchestrator } from
|
|
6
|
+
import { ServiceOrchestrator } from '../../service-management/ServiceOrchestrator.js';
|
|
7
7
|
import { StandardOptions } from '../shared/utils/cli-options.js';
|
|
8
8
|
import { ConfigLoader } from '../shared/utils/config-loader.js';
|
|
9
9
|
export function registerValidateCommand(program) {
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Cache
|
|
3
|
+
*
|
|
4
|
+
* Basic configuration caching implementation for enterprise modules
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export class ConfigurationCache {
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.cache = new Map();
|
|
10
|
+
this.options = {
|
|
11
|
+
maxSize: options.maxSize || 1000,
|
|
12
|
+
ttl: options.ttl || 3600000,
|
|
13
|
+
// 1 hour default
|
|
14
|
+
...options
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
async set(key, value, ttl = null) {
|
|
18
|
+
const expiry = ttl || this.options.ttl;
|
|
19
|
+
const entry = {
|
|
20
|
+
value,
|
|
21
|
+
expiry: Date.now() + expiry,
|
|
22
|
+
created: Date.now()
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Simple LRU: if at max size, remove oldest entry
|
|
26
|
+
if (this.cache.size >= this.options.maxSize) {
|
|
27
|
+
const firstKey = this.cache.keys().next().value;
|
|
28
|
+
this.cache.delete(firstKey);
|
|
29
|
+
}
|
|
30
|
+
this.cache.set(key, entry);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
async get(key) {
|
|
34
|
+
const entry = this.cache.get(key);
|
|
35
|
+
if (!entry) return null;
|
|
36
|
+
|
|
37
|
+
// Check if expired
|
|
38
|
+
if (Date.now() > entry.expiry) {
|
|
39
|
+
this.cache.delete(key);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return entry.value;
|
|
43
|
+
}
|
|
44
|
+
async has(key) {
|
|
45
|
+
const entry = this.cache.get(key);
|
|
46
|
+
if (!entry) return false;
|
|
47
|
+
if (Date.now() > entry.expiry) {
|
|
48
|
+
this.cache.delete(key);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
async delete(key) {
|
|
54
|
+
return this.cache.delete(key);
|
|
55
|
+
}
|
|
56
|
+
async clear() {
|
|
57
|
+
this.cache.clear();
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
async size() {
|
|
61
|
+
// Clean expired entries
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
64
|
+
if (now > entry.expiry) {
|
|
65
|
+
this.cache.delete(key);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return this.cache.size;
|
|
69
|
+
}
|
|
70
|
+
async keys() {
|
|
71
|
+
const now = Date.now();
|
|
72
|
+
const validKeys = [];
|
|
73
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
74
|
+
if (now <= entry.expiry) {
|
|
75
|
+
validKeys.push(key);
|
|
76
|
+
} else {
|
|
77
|
+
this.cache.delete(key);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return validKeys;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -15,7 +15,7 @@ import { promisify } from 'util';
|
|
|
15
15
|
import { exec } from 'child_process';
|
|
16
16
|
import { askChoice, askYesNo } from '../utils/interactive-prompts.js';
|
|
17
17
|
import { DomainDiscovery } from './domain-discovery.js';
|
|
18
|
-
import { MultiDomainOrchestrator } from
|
|
18
|
+
import { MultiDomainOrchestrator } from '../../../orchestration/multi-domain-orchestrator.js';
|
|
19
19
|
import { getCommandConfig } from '../config/command-config-manager.js';
|
|
20
20
|
import { CloudflareTokenManager } from '../security/api-token-manager.js';
|
|
21
21
|
const execAsync = promisify(exec);
|
|
@@ -44,15 +44,17 @@ const dbManager = new DatabaseConnectionManager({
|
|
|
44
44
|
maxPoolSize: 5
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
-
// Memory management
|
|
48
|
-
|
|
47
|
+
// Memory management - Disabled for CLI commands (short-lived processes)
|
|
48
|
+
// Only enable for long-running server processes
|
|
49
|
+
const memoryManager = process.env.ENABLE_MEMORY_MONITORING === 'true' ? startMemoryMonitoring({
|
|
49
50
|
gcInterval: 300000,
|
|
50
51
|
// 5 minutes
|
|
51
|
-
memoryThreshold: 0.
|
|
52
|
+
memoryThreshold: 0.9,
|
|
53
|
+
// 90% threshold (increased from 80%)
|
|
52
54
|
cleanupInterval: 60000,
|
|
53
55
|
// 1 minute
|
|
54
56
|
leakDetection: true
|
|
55
|
-
});
|
|
57
|
+
}) : null;
|
|
56
58
|
|
|
57
59
|
// Graceful shutdown handling (lazy initialization to avoid sync issues)
|
|
58
60
|
let shutdownManager = null;
|
|
@@ -71,7 +71,29 @@ export class ConfigurationManager {
|
|
|
71
71
|
this._validateConfiguration();
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Deep merge two configuration objects
|
|
76
|
+
* @param {Object} defaults - Default configuration
|
|
77
|
+
* @param {Object} userConfig - User-provided configuration
|
|
78
|
+
* @returns {Object} Merged configuration
|
|
79
|
+
*/
|
|
80
|
+
mergeConfigurations(defaults, userConfig) {
|
|
81
|
+
const output = {
|
|
82
|
+
...defaults
|
|
83
|
+
};
|
|
84
|
+
for (const key in userConfig) {
|
|
85
|
+
if (userConfig[key] && typeof userConfig[key] === 'object' && !Array.isArray(userConfig[key])) {
|
|
86
|
+
// Recursively merge objects
|
|
87
|
+
output[key] = this.mergeConfigurations(defaults[key] || {}, userConfig[key]);
|
|
88
|
+
} else {
|
|
89
|
+
// Overwrite arrays and primitives
|
|
90
|
+
output[key] = userConfig[key];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return output;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ============ UTILITY METHODS ============
|
|
75
97
|
|
|
76
98
|
/**
|
|
77
99
|
* Check if a feature is enabled
|
|
@@ -7,7 +7,14 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { readFileSync } from 'fs';
|
|
10
|
-
import { join } from 'path';
|
|
10
|
+
import { join, dirname } from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
|
|
13
|
+
// Get the directory of this module (framework's bin directory)
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = dirname(__filename);
|
|
16
|
+
// Navigate up to framework root, then to config directory
|
|
17
|
+
const FRAMEWORK_CONFIG_PATH = join(__dirname, '../../../config/validation-config.json');
|
|
11
18
|
export class CommandConfigManager {
|
|
12
19
|
constructor(configPath = null) {
|
|
13
20
|
this.configPath = configPath || join(process.cwd(), 'validation-config.json');
|
|
@@ -20,12 +27,21 @@ export class CommandConfigManager {
|
|
|
20
27
|
*/
|
|
21
28
|
loadConfig() {
|
|
22
29
|
try {
|
|
30
|
+
// First try to load from service directory
|
|
23
31
|
const configData = readFileSync(this.configPath, 'utf-8');
|
|
24
32
|
this.config = JSON.parse(configData);
|
|
25
33
|
console.log('📋 Loaded command configuration from validation-config.json');
|
|
26
34
|
} catch (error) {
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
// If service config not found, try framework's internal config
|
|
36
|
+
try {
|
|
37
|
+
const frameworkConfigData = readFileSync(FRAMEWORK_CONFIG_PATH, 'utf-8');
|
|
38
|
+
this.config = JSON.parse(frameworkConfigData);
|
|
39
|
+
console.log('📋 Loaded command configuration from framework defaults');
|
|
40
|
+
} catch (frameworkError) {
|
|
41
|
+
// If neither exists, use hardcoded defaults
|
|
42
|
+
console.log('⚠️ Could not load command config, using minimal defaults');
|
|
43
|
+
this.config = this.getDefaultConfig();
|
|
44
|
+
}
|
|
29
45
|
}
|
|
30
46
|
}
|
|
31
47
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
export { ConfigCache } from './cache.js';
|
|
8
8
|
export { ConfigManager } from './manager.js';
|
|
9
9
|
export { CommandConfigManager } from './command-config-manager.js';
|
|
10
|
-
export { CustomerConfigurationManager } from '../../../
|
|
10
|
+
export { CustomerConfigurationManager } from '../../../config/customers.js';
|
|
11
11
|
|
|
12
12
|
// Phase 3.2 consolidated configuration management
|
|
13
13
|
export { ConfigurationManager, configManager, isFeatureEnabled, getEnabledFeatures, withFeature, FEATURES, COMMON_FEATURES } from './ConfigurationManager.js';
|