@tamyla/clodo-framework 3.1.21 → 3.1.23
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 +17 -0
- package/README.md +283 -1
- package/dist/{bin → cli}/clodo-service.js +47 -15
- package/dist/cli/commands/assess.js +183 -0
- package/dist/{bin → cli}/commands/create.js +5 -5
- package/dist/{bin → cli}/commands/deploy.js +122 -90
- package/dist/{bin → cli}/commands/diagnose.js +5 -5
- package/dist/cli/commands/helpers/deployment-ui.js +138 -0
- package/dist/cli/commands/helpers/deployment-verification.js +250 -0
- package/dist/cli/commands/helpers/error-recovery.js +80 -0
- package/dist/cli/commands/helpers/resource-detection.js +113 -0
- package/dist/{bin → cli}/commands/helpers.js +0 -28
- package/dist/cli/commands/init-config.js +57 -0
- package/dist/{bin → cli}/commands/update.js +5 -5
- package/dist/{bin → cli}/commands/validate.js +5 -5
- package/dist/cli/security-cli.js +118 -0
- package/dist/config/FeatureManager.js +6 -0
- package/dist/config/clodo-create.example.json +26 -0
- package/dist/config/clodo-deploy.example.json +41 -0
- package/dist/config/clodo-update.example.json +46 -0
- package/dist/config/clodo-validate.example.json +41 -0
- package/dist/config/customers/template/development.env.template +37 -0
- package/dist/config/customers/template/production.env.template +39 -0
- package/dist/config/customers/template/staging.env.template +37 -0
- package/dist/config/customers.js +28 -26
- package/dist/config/domain-examples/README.md +464 -0
- package/dist/config/domain-examples/environment-mapped.json +168 -0
- package/dist/config/domain-examples/multi-domain.json +144 -0
- package/dist/config/domain-examples/single-domain.json +50 -0
- package/dist/config/examples +12 -0
- package/dist/config/features.js +61 -0
- package/dist/config/staging-deployment.json +60 -0
- package/dist/config/validation-config.json +347 -0
- package/dist/deployment/wrangler-deployer.js +1 -1
- package/dist/{bin → lib}/deployment/modules/DeploymentOrchestrator.js +2 -2
- package/dist/{bin → lib}/deployment/modules/EnvironmentManager.js +2 -2
- package/dist/lib/deployment/orchestration/EnterpriseOrchestrator.js +21 -0
- package/dist/lib/shared/cache/configuration-cache.js +82 -0
- package/dist/{bin → lib}/shared/cloudflare/domain-discovery.js +1 -1
- package/dist/{bin → lib}/shared/cloudflare/domain-manager.js +1 -1
- package/dist/{bin → lib}/shared/cloudflare/index.js +1 -1
- package/dist/{bin → lib}/shared/cloudflare/ops.js +10 -8
- package/dist/{bin → lib}/shared/config/ConfigurationManager.js +23 -1
- package/dist/{bin → lib}/shared/config/command-config-manager.js +19 -3
- package/dist/{bin → lib}/shared/config/index.js +1 -1
- package/dist/{bin → lib}/shared/deployment/credential-collector.js +30 -7
- package/dist/lib/shared/deployment/index.js +10 -0
- package/dist/lib/shared/deployment/rollback-manager.js +7 -0
- package/dist/lib/shared/deployment/utilities/d1-error-recovery.js +177 -0
- package/dist/{bin → lib}/shared/deployment/validator.js +40 -10
- package/dist/lib/shared/deployment/workflows/deployment-summary.js +214 -0
- package/dist/lib/shared/deployment/workflows/interactive-confirmation.js +188 -0
- package/dist/lib/shared/deployment/workflows/interactive-database-workflow.js +234 -0
- package/dist/lib/shared/deployment/workflows/interactive-domain-info-gatherer.js +240 -0
- package/dist/lib/shared/deployment/workflows/interactive-secret-workflow.js +228 -0
- package/dist/lib/shared/deployment/workflows/interactive-testing-workflow.js +235 -0
- package/dist/lib/shared/deployment/workflows/interactive-validation.js +218 -0
- package/dist/lib/shared/error-handling/error-classifier.js +46 -0
- package/dist/{bin → lib}/shared/monitoring/health-checker.js +129 -1
- package/dist/{bin → lib}/shared/monitoring/memory-manager.js +17 -6
- package/dist/{bin → lib}/shared/routing/domain-router.js +1 -1
- package/dist/lib/shared/utils/deployment-validator.js +97 -0
- package/dist/{bin → lib}/shared/utils/formatters.js +10 -0
- package/dist/{bin → lib}/shared/utils/index.js +13 -1
- package/dist/{bin → lib}/shared/utils/interactive-prompts.js +34 -18
- package/dist/{bin → lib}/shared/utils/progress-manager.js +2 -2
- package/dist/lib/shared/utils/progress-spinner.js +53 -0
- package/dist/lib/shared/utils/sensitive-redactor.js +91 -0
- package/dist/{bin → lib}/shared/validation/ValidationRegistry.js +1 -1
- package/dist/migration/MigrationAdapters.js +50 -4
- package/dist/orchestration/cross-domain-coordinator.js +5 -5
- package/dist/orchestration/multi-domain-orchestrator.js +63 -22
- package/dist/security/index.js +2 -2
- package/dist/security/patterns/insecure-patterns.js +1 -1
- package/dist/service-management/ConfirmationEngine.js +1 -1
- package/dist/service-management/ErrorTracker.js +1 -1
- package/dist/service-management/InputCollector.js +1 -1
- package/dist/service-management/ServiceCreator.js +11 -255
- package/dist/service-management/ServiceOrchestrator.js +0 -2
- package/dist/service-management/generators/testing/UnitTestsGenerator.js +4 -4
- package/dist/service-management/index.js +1 -1
- package/dist/utils/cloudflare/ops.js +1 -1
- package/dist/utils/constants.js +102 -0
- package/dist/utils/deployment/wrangler-config-manager.js +215 -48
- package/dist/utils/file-manager.js +1 -1
- package/dist/utils/formatters.js +1 -1
- package/dist/utils/framework-config.js +2 -2
- package/dist/utils/interactive-prompts.js +10 -59
- package/dist/utils/logger.js +1 -1
- package/dist/version/VersionDetector.js +99 -9
- package/dist/worker/integration.js +1 -1
- package/package.json +10 -10
- 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/database/enterprise-db-manager.js +0 -457
- 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/orchestration/EnterpriseOrchestrator.js +0 -401
- package/dist/bin/deployment/test-interactive-utils.js +0 -66
- package/dist/bin/portfolio/portfolio-manager.js +0 -487
- package/dist/bin/security/security-cli.js +0 -108
- package/dist/bin/service-management/create-service.js +0 -122
- package/dist/bin/service-management/init-service.js +0 -79
- package/dist/bin/shared/deployment/index.js +0 -10
- package/dist/bin/shared/deployment/rollback-manager.js +0 -523
- package/dist/deployment/orchestration/EnterpriseOrchestrator.js +0 -401
- package/dist/service-management/ServiceInitializer.js +0 -453
- /package/dist/{bin → lib}/database/deployment-db-manager.js +0 -0
- /package/dist/{bin → lib}/database/wrangler-d1-manager.js +0 -0
- /package/dist/{bin → lib}/deployment/modules/DeploymentConfiguration.js +0 -0
- /package/dist/{bin → lib}/deployment/modules/MonitoringIntegration.js +0 -0
- /package/dist/{bin → lib}/deployment/modules/ValidationManager.js +0 -0
- /package/dist/{bin → lib}/deployment/orchestration/BaseDeploymentOrchestrator.js +0 -0
- /package/dist/{bin → lib}/deployment/orchestration/PortfolioOrchestrator.js +0 -0
- /package/dist/{bin → lib}/deployment/orchestration/SingleServiceOrchestrator.js +0 -0
- /package/dist/{bin → lib}/deployment/orchestration/UnifiedDeploymentOrchestrator.js +0 -0
- /package/dist/{bin → lib}/shared/config/cache.js +0 -0
- /package/dist/{bin → lib}/shared/config/cloudflare-service-validator.js +0 -0
- /package/dist/{bin → lib}/shared/config/manager.js +0 -0
- /package/dist/{bin → lib}/shared/config/manifest-loader.js +0 -0
- /package/dist/{bin → lib}/shared/database/connection-manager.js +0 -0
- /package/dist/{bin → lib}/shared/database/index.js +0 -0
- /package/dist/{bin → lib}/shared/database/orchestrator.js +0 -0
- /package/dist/{bin → lib}/shared/deployment/auditor.js +0 -0
- /package/dist/{bin → lib}/shared/index.js +0 -0
- /package/dist/{bin → lib}/shared/logging/Logger.js +0 -0
- /package/dist/{bin → lib}/shared/monitoring/index.js +0 -0
- /package/dist/{bin → lib}/shared/monitoring/production-monitor.js +0 -0
- /package/dist/{bin → lib}/shared/production-tester/api-tester.js +0 -0
- /package/dist/{bin → lib}/shared/production-tester/auth-tester.js +0 -0
- /package/dist/{bin → lib}/shared/production-tester/core.js +0 -0
- /package/dist/{bin → lib}/shared/production-tester/database-tester.js +0 -0
- /package/dist/{bin → lib}/shared/production-tester/index.js +0 -0
- /package/dist/{bin → lib}/shared/production-tester/load-tester.js +0 -0
- /package/dist/{bin → lib}/shared/production-tester/performance-tester.js +0 -0
- /package/dist/{bin → lib}/shared/security/api-token-manager.js +0 -0
- /package/dist/{bin → lib}/shared/security/index.js +0 -0
- /package/dist/{bin → lib}/shared/security/secret-generator.js +0 -0
- /package/dist/{bin → lib}/shared/security/secure-token-manager.js +0 -0
- /package/dist/{bin → lib}/shared/utils/ErrorHandler.js +0 -0
- /package/dist/{bin → lib}/shared/utils/cli-options.js +0 -0
- /package/dist/{bin → lib}/shared/utils/config-loader.js +0 -0
- /package/dist/{bin → lib}/shared/utils/error-recovery.js +0 -0
- /package/dist/{bin → lib}/shared/utils/file-manager.js +0 -0
- /package/dist/{bin → lib}/shared/utils/graceful-shutdown-manager.js +0 -0
- /package/dist/{bin → lib}/shared/utils/interactive-utils.js +0 -0
- /package/dist/{bin → lib}/shared/utils/output-formatter.js +0 -0
- /package/dist/{bin → lib}/shared/utils/rate-limiter.js +0 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deployment Verification Helpers (UI Wrapper)
|
|
3
|
+
* Provides UI-specific deployment verification by delegating to shared infrastructure
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { verifyWorkerDeployment, healthCheckWithBackoff, checkHealth } from '../../lib/shared/monitoring/health-checker.js';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import readline from 'readline';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Run comprehensive post-deployment verification
|
|
12
|
+
* @param {string} serviceName - Name of the service
|
|
13
|
+
* @param {Object} deploymentResult - Result from deployment
|
|
14
|
+
* @param {Object} credentials - Cloudflare credentials
|
|
15
|
+
* @param {Object} options - Verification options
|
|
16
|
+
* @returns {Promise<Object>} Verification result
|
|
17
|
+
*/
|
|
18
|
+
export async function runPostDeploymentVerification(serviceName, deploymentResult, credentials, options = {}) {
|
|
19
|
+
console.log('\n🔍 Post-deployment Verification');
|
|
20
|
+
|
|
21
|
+
// Skip health checks in dry-run mode with clear explanation
|
|
22
|
+
if (options.dryRun) {
|
|
23
|
+
console.log(chalk.blue(' ℹ️ Health checks skipped in dry-run mode'));
|
|
24
|
+
console.log(chalk.gray(' 💡 Run without --dry-run to deploy and verify the worker'));
|
|
25
|
+
return {
|
|
26
|
+
success: true,
|
|
27
|
+
healthCheckPassed: null,
|
|
28
|
+
reason: 'dry-run-skipped'
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
// Wait for deployment to propagate
|
|
33
|
+
console.log(' ⏳ Waiting for deployment to propagate...');
|
|
34
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
35
|
+
|
|
36
|
+
// Run health checks with enhanced URL discovery
|
|
37
|
+
console.log(' 🏥 Running health checks...');
|
|
38
|
+
|
|
39
|
+
// Try multiple sources for worker URL (in priority order)
|
|
40
|
+
const workerUrl = await discoverWorkerUrl(deploymentResult, credentials, options);
|
|
41
|
+
if (workerUrl) {
|
|
42
|
+
console.log(chalk.gray(` 🔗 Testing: ${workerUrl}`));
|
|
43
|
+
const health = await checkHealth(workerUrl).catch(err => {
|
|
44
|
+
console.log(chalk.yellow(` ⚠️ Health check failed: ${err.message}`));
|
|
45
|
+
return {
|
|
46
|
+
status: 'unknown',
|
|
47
|
+
error: err.message
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
if (health && health.status === 'ok') {
|
|
51
|
+
console.log(` ✅ Deployment verified: ${health.framework?.models?.length || 0} models active`);
|
|
52
|
+
return {
|
|
53
|
+
success: true,
|
|
54
|
+
healthCheckPassed: true,
|
|
55
|
+
healthData: health,
|
|
56
|
+
url: workerUrl
|
|
57
|
+
};
|
|
58
|
+
} else {
|
|
59
|
+
console.log(` ⚠️ Health check returned: ${health?.status || 'unknown'}`);
|
|
60
|
+
if (health.error) {
|
|
61
|
+
console.log(chalk.gray(` Error: ${health.error}`));
|
|
62
|
+
}
|
|
63
|
+
// Don't fail deployment for health check issues
|
|
64
|
+
return {
|
|
65
|
+
success: true,
|
|
66
|
+
healthCheckPassed: false,
|
|
67
|
+
healthData: health,
|
|
68
|
+
url: workerUrl
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
console.log(chalk.yellow(' ⚠️ No worker URL could be determined'));
|
|
73
|
+
console.log(chalk.gray(' 💡 To fix: Add route in wrangler.toml or check Cloudflare dashboard'));
|
|
74
|
+
return {
|
|
75
|
+
success: true,
|
|
76
|
+
healthCheckPassed: null,
|
|
77
|
+
reason: 'no-url-discovered'
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.log(chalk.yellow(` ⚠️ Post-deployment verification warning: ${error.message}`));
|
|
82
|
+
// Don't fail overall deployment for verification issues
|
|
83
|
+
return {
|
|
84
|
+
success: true,
|
|
85
|
+
healthCheckPassed: false,
|
|
86
|
+
error: error.message
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Discover worker URL from multiple sources
|
|
93
|
+
* Priority: deploymentResult.url > workers.dev convention > Cloudflare API discovery
|
|
94
|
+
* @param {Object} deploymentResult - Result from deployment
|
|
95
|
+
* @param {Object} credentials - Cloudflare credentials
|
|
96
|
+
* @param {Object} options - Discovery options
|
|
97
|
+
* @returns {Promise<string|null>} Discovered worker URL or null
|
|
98
|
+
*/
|
|
99
|
+
async function discoverWorkerUrl(deploymentResult, credentials, options = {}) {
|
|
100
|
+
// Source 1: Direct URL from deployment result
|
|
101
|
+
if (deploymentResult?.url) {
|
|
102
|
+
return deploymentResult.url;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Source 2: Construct workers.dev URL from credentials
|
|
106
|
+
if (credentials?.cloudflareSettings?.accountId && options.serviceName) {
|
|
107
|
+
const accountHash = credentials.cloudflareSettings.accountId;
|
|
108
|
+
const scriptName = options.serviceName || 'my-worker';
|
|
109
|
+
const workersDevUrl = `https://${scriptName}.${accountHash}.workers.dev`;
|
|
110
|
+
|
|
111
|
+
// Verify this URL exists by checking if we can reach it
|
|
112
|
+
const urlExists = await verifyUrlExists(workersDevUrl);
|
|
113
|
+
if (urlExists) {
|
|
114
|
+
return workersDevUrl;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Source 3: Query Cloudflare API for worker routes
|
|
119
|
+
if (credentials?.cloudflareSettings) {
|
|
120
|
+
const discoveredUrl = await discoverUrlFromCloudflare(credentials.cloudflareSettings, options);
|
|
121
|
+
if (discoveredUrl) {
|
|
122
|
+
return discoveredUrl;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Source 4: Custom domain from options
|
|
127
|
+
if (options.customDomain) {
|
|
128
|
+
return `https://${options.customDomain}`;
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Verify if a URL exists (can be reached)
|
|
135
|
+
* @param {string} url - URL to verify
|
|
136
|
+
* @returns {Promise<boolean>} True if URL responds
|
|
137
|
+
*/
|
|
138
|
+
async function verifyUrlExists(url) {
|
|
139
|
+
try {
|
|
140
|
+
const response = await fetch(url, {
|
|
141
|
+
method: 'HEAD',
|
|
142
|
+
signal: AbortSignal.timeout(5000)
|
|
143
|
+
});
|
|
144
|
+
return response.ok || response.status === 404; // 404 means route exists but no handler
|
|
145
|
+
} catch {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Discover worker URL from Cloudflare API
|
|
152
|
+
* @param {Object} cloudflareSettings - Cloudflare credentials
|
|
153
|
+
* @param {Object} options - Discovery options
|
|
154
|
+
* @returns {Promise<string|null>} Discovered URL or null
|
|
155
|
+
*/
|
|
156
|
+
async function discoverUrlFromCloudflare(cloudflareSettings, options = {}) {
|
|
157
|
+
try {
|
|
158
|
+
const {
|
|
159
|
+
CloudflareAPI
|
|
160
|
+
} = await import('../../utils/cloudflare/api.js');
|
|
161
|
+
const api = new CloudflareAPI(cloudflareSettings.token);
|
|
162
|
+
|
|
163
|
+
// Get worker routes for the zone
|
|
164
|
+
if (cloudflareSettings.zoneId) {
|
|
165
|
+
const response = await api.request(`/zones/${cloudflareSettings.zoneId}/workers/routes`);
|
|
166
|
+
const routes = response.result || [];
|
|
167
|
+
if (routes.length > 0) {
|
|
168
|
+
// Find route for this worker
|
|
169
|
+
const scriptName = options.serviceName || options.workerName;
|
|
170
|
+
const matchingRoute = routes.find(r => r.script && (r.script === scriptName || r.script.includes(scriptName)));
|
|
171
|
+
if (matchingRoute && matchingRoute.pattern) {
|
|
172
|
+
// Convert route pattern to URL (basic conversion)
|
|
173
|
+
const url = matchingRoute.pattern.replace('*', '').replace('//', '//');
|
|
174
|
+
return url.startsWith('http') ? url : `https://${url}`;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Fallback: Get worker info from scripts API
|
|
180
|
+
const scriptsResponse = await api.request(`/accounts/${cloudflareSettings.accountId}/workers/scripts`);
|
|
181
|
+
const scripts = scriptsResponse.result || [];
|
|
182
|
+
const scriptName = options.serviceName || options.workerName;
|
|
183
|
+
const script = scripts.find(s => s.id === scriptName || s.id.includes(scriptName));
|
|
184
|
+
if (script && script.url) {
|
|
185
|
+
return script.url;
|
|
186
|
+
}
|
|
187
|
+
} catch (error) {
|
|
188
|
+
// API discovery failed - not critical
|
|
189
|
+
if (options.verbose) {
|
|
190
|
+
console.log(chalk.gray(` Could not discover URL from Cloudflare API: ${error.message}`));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Verify deployment via Cloudflare API (UI wrapper)
|
|
198
|
+
* @param {string} workerName - Name of the worker to verify
|
|
199
|
+
* @param {Object} credentials - Cloudflare credentials
|
|
200
|
+
* @param {Object} options - Verification options
|
|
201
|
+
* @returns {Promise<Object>} Verification result
|
|
202
|
+
*/
|
|
203
|
+
export async function verifyDeployment(workerName, credentials, options = {}) {
|
|
204
|
+
// Delegate to shared infrastructure
|
|
205
|
+
return await verifyWorkerDeployment(workerName, credentials, options);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Perform health check with user permission prompt
|
|
210
|
+
* @param {string} baseUrl - Base URL to check
|
|
211
|
+
* @param {Object} options - Health check options
|
|
212
|
+
* @returns {Promise<Object>} Health check result
|
|
213
|
+
*/
|
|
214
|
+
export async function performHealthCheckWithPermission(baseUrl, options = {}) {
|
|
215
|
+
const {
|
|
216
|
+
skipPrompt = false,
|
|
217
|
+
...healthOptions
|
|
218
|
+
} = options;
|
|
219
|
+
if (!skipPrompt) {
|
|
220
|
+
const permission = await promptForHealthCheck();
|
|
221
|
+
if (!permission) {
|
|
222
|
+
console.log(chalk.yellow('\n⏭️ Health check skipped by user'));
|
|
223
|
+
return {
|
|
224
|
+
healthy: null,
|
|
225
|
+
skipped: true
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
console.log(chalk.gray('\n🏥 Running health checks with exponential backoff...\n'));
|
|
230
|
+
|
|
231
|
+
// Delegate to shared infrastructure
|
|
232
|
+
return await healthCheckWithBackoff(baseUrl, healthOptions);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Prompt user for health check permission
|
|
237
|
+
* @returns {Promise<boolean>} Whether to proceed with health check
|
|
238
|
+
*/
|
|
239
|
+
async function promptForHealthCheck() {
|
|
240
|
+
const rl = readline.createInterface({
|
|
241
|
+
input: process.stdin,
|
|
242
|
+
output: process.stdout
|
|
243
|
+
});
|
|
244
|
+
return new Promise(resolve => {
|
|
245
|
+
rl.question(chalk.cyan('\n❓ Run health checks to verify deployment? (Y/n): '), answer => {
|
|
246
|
+
rl.close();
|
|
247
|
+
resolve(!answer || answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
}
|
|
@@ -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 '../../lib/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
|
+
}
|
|
@@ -6,34 +6,6 @@ import chalk from 'chalk';
|
|
|
6
6
|
import { existsSync, readFileSync } from 'fs';
|
|
7
7
|
import { join, resolve } from 'path';
|
|
8
8
|
|
|
9
|
-
/**
|
|
10
|
-
* Load JSON configuration file
|
|
11
|
-
*/
|
|
12
|
-
export function loadJsonConfig(configPath) {
|
|
13
|
-
try {
|
|
14
|
-
const fullPath = resolve(configPath);
|
|
15
|
-
if (!existsSync(fullPath)) {
|
|
16
|
-
throw new Error(`Configuration file not found: ${fullPath}`);
|
|
17
|
-
}
|
|
18
|
-
const content = readFileSync(fullPath, 'utf8');
|
|
19
|
-
const config = JSON.parse(content);
|
|
20
|
-
|
|
21
|
-
// Validate required fields
|
|
22
|
-
const required = ['customer', 'environment', 'domainName', 'cloudflareToken'];
|
|
23
|
-
const missing = required.filter(field => !config[field]);
|
|
24
|
-
if (missing.length > 0) {
|
|
25
|
-
throw new Error(`Missing required configuration fields: ${missing.join(', ')}`);
|
|
26
|
-
}
|
|
27
|
-
console.log(chalk.green(`✅ Loaded configuration from: ${fullPath}`));
|
|
28
|
-
return config;
|
|
29
|
-
} catch (error) {
|
|
30
|
-
if (error instanceof SyntaxError) {
|
|
31
|
-
throw new Error(`Invalid JSON in configuration file: ${error.message}`);
|
|
32
|
-
}
|
|
33
|
-
throw error;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
9
|
/**
|
|
38
10
|
* Show progress indicator for deployment steps
|
|
39
11
|
*/
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Initialize Configuration Command
|
|
5
|
+
* Copies the framework's validation-config.json to the service directory for customization
|
|
6
|
+
*/
|
|
7
|
+
import { copyFile, access } from 'fs/promises';
|
|
8
|
+
import { join, dirname } from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
|
|
14
|
+
// Path to framework's bundled config
|
|
15
|
+
const FRAMEWORK_CONFIG_PATH = join(__dirname, '../config/validation-config.json');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Register the init-config command with the CLI
|
|
19
|
+
*/
|
|
20
|
+
export function registerInitConfigCommand(program) {
|
|
21
|
+
program.command('init-config').description('Initialize validation-config.json in your service directory').option('-f, --force', 'Overwrite existing validation-config.json', false).action(handler);
|
|
22
|
+
}
|
|
23
|
+
async function handler(options) {
|
|
24
|
+
const targetPath = join(process.cwd(), 'validation-config.json');
|
|
25
|
+
try {
|
|
26
|
+
// Check if file already exists
|
|
27
|
+
try {
|
|
28
|
+
await access(targetPath);
|
|
29
|
+
if (!options.force) {
|
|
30
|
+
console.log(chalk.yellow('⚠️ validation-config.json already exists.'));
|
|
31
|
+
console.log(chalk.gray(' Use --force to overwrite.'));
|
|
32
|
+
process.exit(0);
|
|
33
|
+
} else {
|
|
34
|
+
console.log(chalk.yellow('🔄 Overwriting existing validation-config.json...'));
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
// File doesn't exist, proceed
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Copy framework config to service directory
|
|
41
|
+
await copyFile(FRAMEWORK_CONFIG_PATH, targetPath);
|
|
42
|
+
console.log(chalk.green('✅ Successfully initialized validation-config.json'));
|
|
43
|
+
console.log(chalk.gray('\n📝 Configuration file details:'));
|
|
44
|
+
console.log(chalk.gray(` Location: ${targetPath}`));
|
|
45
|
+
console.log(chalk.gray('\n💡 Usage:'));
|
|
46
|
+
console.log(chalk.gray(' • Customize timing values (timeouts, intervals)'));
|
|
47
|
+
console.log(chalk.gray(' • Add service-specific endpoints for validation'));
|
|
48
|
+
console.log(chalk.gray(' • Configure platform-specific commands'));
|
|
49
|
+
console.log(chalk.gray(' • Set environment-specific requirements'));
|
|
50
|
+
console.log(chalk.gray('\n⚠️ Note: Most services don\'t need this file.'));
|
|
51
|
+
console.log(chalk.gray(' The framework provides sensible defaults.'));
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error(chalk.red('❌ Failed to initialize configuration:'));
|
|
54
|
+
console.error(chalk.red(` ${error.message}`));
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import chalk from 'chalk';
|
|
6
|
-
import { ServiceOrchestrator } from
|
|
7
|
-
import { StandardOptions } from '../shared/utils/cli-options.js';
|
|
8
|
-
import { ConfigLoader } from '../shared/utils/config-loader.js';
|
|
6
|
+
import { ServiceOrchestrator } from '../service-management/ServiceOrchestrator.js';
|
|
7
|
+
import { StandardOptions } from '../lib/shared/utils/cli-options.js';
|
|
8
|
+
import { ConfigLoader } from '../lib/shared/utils/config-loader.js';
|
|
9
9
|
export function registerUpdateCommand(program) {
|
|
10
10
|
const command = program.command('update [service-path]').description('Update an existing service configuration').option('-i, --interactive', 'Run in interactive mode to select what to update').option('--domain-name <domain>', 'Update domain name').option('--cloudflare-token <token>', 'Update Cloudflare API token').option('--cloudflare-account-id <id>', 'Update Cloudflare account ID').option('--cloudflare-zone-id <id>', 'Update Cloudflare zone ID').option('--environment <env>', 'Update target environment: development, staging, production').option('--add-feature <feature>', 'Add a feature flag').option('--remove-feature <feature>', 'Remove a feature flag').option('--regenerate-configs', 'Regenerate all configuration files').option('--fix-errors', 'Attempt to fix common configuration errors').option('--preview', 'Show what would be changed without applying').option('--force', 'Skip confirmation prompts');
|
|
11
11
|
|
|
12
12
|
// Add standard options (--verbose, --quiet, --json, --no-color, --config-file)
|
|
13
13
|
StandardOptions.define(command).action(async (servicePath, options) => {
|
|
14
14
|
try {
|
|
15
|
-
const output = new (await import('../shared/utils/output-formatter.js')).OutputFormatter(options);
|
|
15
|
+
const output = new (await import('../lib/shared/utils/output-formatter.js')).OutputFormatter(options);
|
|
16
16
|
const configLoader = new ConfigLoader({
|
|
17
17
|
verbose: options.verbose,
|
|
18
18
|
quiet: options.quiet,
|
|
@@ -60,7 +60,7 @@ export function registerUpdateCommand(program) {
|
|
|
60
60
|
}
|
|
61
61
|
output.success('Service update completed successfully!');
|
|
62
62
|
} catch (error) {
|
|
63
|
-
const output = new (await import('../shared/utils/output-formatter.js')).OutputFormatter(options || {});
|
|
63
|
+
const output = new (await import('../lib/shared/utils/output-formatter.js')).OutputFormatter(options || {});
|
|
64
64
|
output.error(`Service update failed: ${error.message}`);
|
|
65
65
|
if (error.details) {
|
|
66
66
|
output.warning(`Details: ${error.details}`);
|
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import chalk from 'chalk';
|
|
6
|
-
import { ServiceOrchestrator } from
|
|
7
|
-
import { StandardOptions } from '../shared/utils/cli-options.js';
|
|
8
|
-
import { ConfigLoader } from '../shared/utils/config-loader.js';
|
|
6
|
+
import { ServiceOrchestrator } from '../service-management/ServiceOrchestrator.js';
|
|
7
|
+
import { StandardOptions } from '../lib/shared/utils/cli-options.js';
|
|
8
|
+
import { ConfigLoader } from '../lib/shared/utils/config-loader.js';
|
|
9
9
|
export function registerValidateCommand(program) {
|
|
10
10
|
const command = program.command('validate <service-path>').description('Validate an existing service configuration').option('--deep-scan', 'Run comprehensive validation checks').option('--export-report <file>', 'Export validation report to JSON file');
|
|
11
11
|
|
|
12
12
|
// Add standard options (--verbose, --quiet, --json, --no-color, --config-file)
|
|
13
13
|
StandardOptions.define(command).action(async (servicePath, options) => {
|
|
14
14
|
try {
|
|
15
|
-
const output = new (await import('../shared/utils/output-formatter.js')).OutputFormatter(options);
|
|
15
|
+
const output = new (await import('../lib/shared/utils/output-formatter.js')).OutputFormatter(options);
|
|
16
16
|
const configLoader = new ConfigLoader({
|
|
17
17
|
verbose: options.verbose,
|
|
18
18
|
quiet: options.quiet,
|
|
@@ -43,7 +43,7 @@ export function registerValidateCommand(program) {
|
|
|
43
43
|
process.exit(1);
|
|
44
44
|
}
|
|
45
45
|
} catch (error) {
|
|
46
|
-
const output = new (await import('../shared/utils/output-formatter.js')).OutputFormatter(options || {});
|
|
46
|
+
const output = new (await import('../lib/shared/utils/output-formatter.js')).OutputFormatter(options || {});
|
|
47
47
|
output.error(`Validation failed: ${error.message}`);
|
|
48
48
|
process.exit(1);
|
|
49
49
|
}
|