@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,411 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConfirmationEngine - Tier 2: Smart Confirmations
|
|
3
|
+
*
|
|
4
|
+
* Takes the 6 core inputs and generates 15 derived confirmation values
|
|
5
|
+
* that users can review and modify before service generation.
|
|
6
|
+
*
|
|
7
|
+
* Core Inputs (6):
|
|
8
|
+
* 1. Service Name
|
|
9
|
+
* 2. Service Type
|
|
10
|
+
* 3. Domain Name
|
|
11
|
+
* 4. Cloudflare Token
|
|
12
|
+
* 5. Cloudflare Account ID
|
|
13
|
+
* 6. Cloudflare Zone ID
|
|
14
|
+
* 7. Environment
|
|
15
|
+
*
|
|
16
|
+
* Derived Confirmations (15):
|
|
17
|
+
* 1. Display Name
|
|
18
|
+
* 2. Description
|
|
19
|
+
* 3. Version
|
|
20
|
+
* 4. Author
|
|
21
|
+
* 5. Production URL
|
|
22
|
+
* 6. Staging URL
|
|
23
|
+
* 7. Development URL
|
|
24
|
+
* 8. Features Configuration
|
|
25
|
+
* 9. Database Name
|
|
26
|
+
* 10. Worker Name
|
|
27
|
+
* 11. Package Name
|
|
28
|
+
* 12. Git Repository URL
|
|
29
|
+
* 13. Documentation URL
|
|
30
|
+
* 14. Health Check Path
|
|
31
|
+
* 15. API Base Path
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { createInterface } from 'readline';
|
|
35
|
+
import chalk from 'chalk';
|
|
36
|
+
import { validateServiceName, validateDomainName } from '../utils/validation.js';
|
|
37
|
+
export class ConfirmationEngine {
|
|
38
|
+
constructor(options = {}) {
|
|
39
|
+
this.interactive = options.interactive !== false;
|
|
40
|
+
this.rl = this.interactive ? createInterface({
|
|
41
|
+
input: process.stdin,
|
|
42
|
+
output: process.stdout
|
|
43
|
+
}) : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Generate and confirm all derived values from core inputs
|
|
48
|
+
*/
|
|
49
|
+
async generateAndConfirm(coreInputs) {
|
|
50
|
+
console.log(chalk.cyan('\n🔍 Tier 2: Smart Confirmations'));
|
|
51
|
+
console.log(chalk.white('Reviewing and confirming 15 derived configuration values...\n'));
|
|
52
|
+
|
|
53
|
+
// Generate smart defaults
|
|
54
|
+
const derivedValues = this.generateSmartDefaults(coreInputs);
|
|
55
|
+
|
|
56
|
+
// Interactive confirmation if enabled
|
|
57
|
+
if (this.interactive) {
|
|
58
|
+
return await this.interactiveConfirmation(derivedValues, coreInputs);
|
|
59
|
+
} else {
|
|
60
|
+
console.log(chalk.gray('⚠️ Non-interactive mode: Using generated defaults'));
|
|
61
|
+
return derivedValues;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generate smart defaults for all 15 confirmation values
|
|
67
|
+
*/
|
|
68
|
+
generateSmartDefaults(coreInputs) {
|
|
69
|
+
const {
|
|
70
|
+
serviceName,
|
|
71
|
+
serviceType,
|
|
72
|
+
domainName,
|
|
73
|
+
environment
|
|
74
|
+
} = coreInputs;
|
|
75
|
+
return {
|
|
76
|
+
// 1. Display Name - Convert kebab-case to Title Case
|
|
77
|
+
displayName: this.generateDisplayName(serviceName),
|
|
78
|
+
// 2. Description - Based on service type
|
|
79
|
+
description: this.generateDescription(serviceType),
|
|
80
|
+
// 3. Version - Standard semantic versioning
|
|
81
|
+
version: '1.0.0',
|
|
82
|
+
// 4. Author - Default framework author
|
|
83
|
+
author: 'Clodo Framework',
|
|
84
|
+
// 5-7. URLs - Derived from domain
|
|
85
|
+
productionUrl: `https://api.${domainName}`,
|
|
86
|
+
stagingUrl: `https://staging-api.${domainName}`,
|
|
87
|
+
developmentUrl: `https://dev-api.${domainName}`,
|
|
88
|
+
// 8. Features - Based on service type
|
|
89
|
+
features: this.generateFeaturesForType(serviceType),
|
|
90
|
+
// 9. Database Name - Cloudflare D1 naming
|
|
91
|
+
databaseName: `${serviceName}-db`,
|
|
92
|
+
// 10. Worker Name - Cloudflare Worker naming
|
|
93
|
+
workerName: `${serviceName}-worker`,
|
|
94
|
+
// 11. Package Name - NPM package naming
|
|
95
|
+
packageName: `@clodo/${serviceName}`,
|
|
96
|
+
// 12. Git Repository URL - GitHub naming
|
|
97
|
+
gitRepositoryUrl: `https://github.com/tamylaa/${serviceName}`,
|
|
98
|
+
// 13. Documentation URL - Based on domain
|
|
99
|
+
documentationUrl: `https://docs.${domainName}`,
|
|
100
|
+
// 14. Health Check Path - Standard health endpoint
|
|
101
|
+
healthCheckPath: '/health',
|
|
102
|
+
// 15. API Base Path - Service-specific API path
|
|
103
|
+
apiBasePath: `/api/v1/${serviceName.replace('-', '/')}`
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Interactive confirmation process
|
|
109
|
+
*/
|
|
110
|
+
async interactiveConfirmation(derivedValues, coreInputs) {
|
|
111
|
+
console.log(chalk.cyan('Please review and confirm the following derived values:'));
|
|
112
|
+
console.log(chalk.gray('Press Enter to accept default, or type new value to modify.\n'));
|
|
113
|
+
const confirmed = {
|
|
114
|
+
...derivedValues
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Group confirmations for better UX
|
|
118
|
+
const confirmationGroups = [{
|
|
119
|
+
title: '📋 Basic Information',
|
|
120
|
+
items: [{
|
|
121
|
+
key: 'displayName',
|
|
122
|
+
label: 'Display Name',
|
|
123
|
+
description: 'Human-readable service name'
|
|
124
|
+
}, {
|
|
125
|
+
key: 'description',
|
|
126
|
+
label: 'Description',
|
|
127
|
+
description: 'Service description'
|
|
128
|
+
}, {
|
|
129
|
+
key: 'version',
|
|
130
|
+
label: 'Version',
|
|
131
|
+
description: 'Initial version number'
|
|
132
|
+
}, {
|
|
133
|
+
key: 'author',
|
|
134
|
+
label: 'Author',
|
|
135
|
+
description: 'Service author/maintainer'
|
|
136
|
+
}]
|
|
137
|
+
}, {
|
|
138
|
+
title: '🌐 URLs & Endpoints',
|
|
139
|
+
items: [{
|
|
140
|
+
key: 'productionUrl',
|
|
141
|
+
label: 'Production URL',
|
|
142
|
+
description: 'Live production endpoint'
|
|
143
|
+
}, {
|
|
144
|
+
key: 'stagingUrl',
|
|
145
|
+
label: 'Staging URL',
|
|
146
|
+
description: 'Staging environment endpoint'
|
|
147
|
+
}, {
|
|
148
|
+
key: 'developmentUrl',
|
|
149
|
+
label: 'Development URL',
|
|
150
|
+
description: 'Development environment endpoint'
|
|
151
|
+
}, {
|
|
152
|
+
key: 'documentationUrl',
|
|
153
|
+
label: 'Documentation URL',
|
|
154
|
+
description: 'API documentation location'
|
|
155
|
+
}]
|
|
156
|
+
}, {
|
|
157
|
+
title: '⚙️ Service Configuration',
|
|
158
|
+
items: [{
|
|
159
|
+
key: 'databaseName',
|
|
160
|
+
label: 'Database Name',
|
|
161
|
+
description: 'Cloudflare D1 database name'
|
|
162
|
+
}, {
|
|
163
|
+
key: 'workerName',
|
|
164
|
+
label: 'Worker Name',
|
|
165
|
+
description: 'Cloudflare Worker script name'
|
|
166
|
+
}, {
|
|
167
|
+
key: 'packageName',
|
|
168
|
+
label: 'Package Name',
|
|
169
|
+
description: 'NPM package identifier'
|
|
170
|
+
}, {
|
|
171
|
+
key: 'healthCheckPath',
|
|
172
|
+
label: 'Health Check Path',
|
|
173
|
+
description: 'Health endpoint path'
|
|
174
|
+
}, {
|
|
175
|
+
key: 'apiBasePath',
|
|
176
|
+
label: 'API Base Path',
|
|
177
|
+
description: 'Base path for API endpoints'
|
|
178
|
+
}]
|
|
179
|
+
}];
|
|
180
|
+
for (const group of confirmationGroups) {
|
|
181
|
+
console.log(chalk.yellow(`\n${group.title}`));
|
|
182
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
183
|
+
for (const item of group.items) {
|
|
184
|
+
const currentValue = confirmed[item.key];
|
|
185
|
+
const newValue = await this.confirmValue(`${item.label} (${item.description})`, currentValue);
|
|
186
|
+
if (newValue !== null && newValue !== currentValue) {
|
|
187
|
+
// Validate the new value if it's a URL or name
|
|
188
|
+
if (this.validateConfirmationValue(item.key, newValue)) {
|
|
189
|
+
confirmed[item.key] = newValue;
|
|
190
|
+
console.log(chalk.green(`✓ Updated: ${newValue}`));
|
|
191
|
+
} else {
|
|
192
|
+
console.log(chalk.red(`✗ Invalid value, keeping: ${currentValue}`));
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
console.log(chalk.gray(`✓ Keeping: ${currentValue}`));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Special handling for features
|
|
201
|
+
console.log(chalk.yellow('\n🔧 Feature Configuration'));
|
|
202
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
203
|
+
console.log(chalk.white('Current features for service type:'), chalk.cyan(coreInputs.serviceType));
|
|
204
|
+
this.displayFeatures(confirmed.features);
|
|
205
|
+
const modifyFeatures = await this.confirmYesNo('\nWould you like to modify feature flags?', false);
|
|
206
|
+
if (modifyFeatures) {
|
|
207
|
+
confirmed.features = await this.interactiveFeatureConfiguration(confirmed.features, coreInputs.serviceType);
|
|
208
|
+
}
|
|
209
|
+
return confirmed;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Confirm a single value with user
|
|
214
|
+
*/
|
|
215
|
+
async confirmValue(prompt, currentValue) {
|
|
216
|
+
return new Promise(resolve => {
|
|
217
|
+
const displayValue = typeof currentValue === 'object' ? JSON.stringify(currentValue, null, 2) : String(currentValue);
|
|
218
|
+
this.rl.question(`${prompt} [${displayValue}]: `, answer => {
|
|
219
|
+
resolve(answer.trim() || null);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Confirm yes/no question
|
|
226
|
+
*/
|
|
227
|
+
async confirmYesNo(question, defaultValue = true) {
|
|
228
|
+
return new Promise(resolve => {
|
|
229
|
+
const defaultText = defaultValue ? '[Y/n]' : '[y/N]';
|
|
230
|
+
this.rl.question(`${question} ${defaultText}: `, answer => {
|
|
231
|
+
const normalized = answer.toLowerCase().trim();
|
|
232
|
+
if (normalized === '') {
|
|
233
|
+
resolve(defaultValue);
|
|
234
|
+
} else if (normalized === 'y' || normalized === 'yes') {
|
|
235
|
+
resolve(true);
|
|
236
|
+
} else if (normalized === 'n' || normalized === 'no') {
|
|
237
|
+
resolve(false);
|
|
238
|
+
} else {
|
|
239
|
+
resolve(defaultValue);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Interactive feature configuration
|
|
247
|
+
*/
|
|
248
|
+
async interactiveFeatureConfiguration(currentFeatures, serviceType) {
|
|
249
|
+
console.log(chalk.cyan('\nFeature Configuration:'));
|
|
250
|
+
console.log(chalk.white('Type feature name to toggle, or "done" to finish'));
|
|
251
|
+
const features = {
|
|
252
|
+
...currentFeatures
|
|
253
|
+
};
|
|
254
|
+
for (;;) {
|
|
255
|
+
console.log(chalk.gray('\nCurrent features:'));
|
|
256
|
+
this.displayFeatures(features);
|
|
257
|
+
const input = await new Promise(resolve => {
|
|
258
|
+
this.rl.question('Feature to toggle (or "done"): ', resolve);
|
|
259
|
+
});
|
|
260
|
+
const feature = input.trim().toLowerCase();
|
|
261
|
+
if (feature === 'done' || feature === '') {
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
if (Object.prototype.hasOwnProperty.call(features, feature)) {
|
|
265
|
+
features[feature] = !features[feature];
|
|
266
|
+
console.log(chalk.green(`✓ ${feature}: ${features[feature] ? 'ENABLED' : 'DISABLED'}`));
|
|
267
|
+
} else {
|
|
268
|
+
console.log(chalk.red(`✗ Unknown feature: ${feature}`));
|
|
269
|
+
console.log(chalk.gray('Available features:'), Object.keys(features).join(', '));
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return features;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Display features in a nice format
|
|
277
|
+
*/
|
|
278
|
+
displayFeatures(features) {
|
|
279
|
+
const enabled = Object.entries(features).filter(([, enabled]) => enabled).map(([name]) => name);
|
|
280
|
+
const disabled = Object.entries(features).filter(([, enabled]) => !enabled).map(([name]) => name);
|
|
281
|
+
if (enabled.length > 0) {
|
|
282
|
+
console.log(chalk.green(' Enabled:'), enabled.join(', '));
|
|
283
|
+
}
|
|
284
|
+
if (disabled.length > 0) {
|
|
285
|
+
console.log(chalk.gray(' Disabled:'), disabled.join(', '));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Validate confirmation value based on type
|
|
291
|
+
*/
|
|
292
|
+
validateConfirmationValue(key, value) {
|
|
293
|
+
switch (key) {
|
|
294
|
+
case 'displayName':
|
|
295
|
+
return value.length > 0 && value.length <= 100;
|
|
296
|
+
case 'description':
|
|
297
|
+
return value.length > 0 && value.length <= 500;
|
|
298
|
+
case 'version':
|
|
299
|
+
return /^\d+\.\d+\.\d+$/.test(value);
|
|
300
|
+
case 'productionUrl':
|
|
301
|
+
case 'stagingUrl':
|
|
302
|
+
case 'developmentUrl':
|
|
303
|
+
case 'documentationUrl':
|
|
304
|
+
return /^https?:\/\/.+/.test(value);
|
|
305
|
+
case 'databaseName':
|
|
306
|
+
case 'workerName':
|
|
307
|
+
return validateServiceName(value);
|
|
308
|
+
case 'packageName':
|
|
309
|
+
return /^@?[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/.test(value);
|
|
310
|
+
case 'healthCheckPath':
|
|
311
|
+
case 'apiBasePath':
|
|
312
|
+
return value.startsWith('/');
|
|
313
|
+
default:
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Generate display name from service name
|
|
320
|
+
*/
|
|
321
|
+
generateDisplayName(serviceName) {
|
|
322
|
+
return serviceName.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Generate description based on service type
|
|
327
|
+
*/
|
|
328
|
+
generateDescription(serviceType) {
|
|
329
|
+
const descriptions = {
|
|
330
|
+
'data-service': 'A comprehensive data service providing CRUD operations, search, filtering, and pagination capabilities',
|
|
331
|
+
'auth-service': 'Authentication and authorization service with user management and security features',
|
|
332
|
+
'content-service': 'Content management service with file storage, search, and delivery capabilities',
|
|
333
|
+
'api-gateway': 'API gateway providing routing, rate limiting, authentication, and monitoring',
|
|
334
|
+
'generic': 'A Clodo Framework service providing core functionality and extensibility'
|
|
335
|
+
};
|
|
336
|
+
return descriptions[serviceType] || descriptions.generic;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Generate features based on service type
|
|
341
|
+
*/
|
|
342
|
+
generateFeaturesForType(serviceType) {
|
|
343
|
+
const baseFeatures = {
|
|
344
|
+
logging: true,
|
|
345
|
+
monitoring: true,
|
|
346
|
+
errorReporting: true,
|
|
347
|
+
metrics: true,
|
|
348
|
+
healthChecks: true
|
|
349
|
+
};
|
|
350
|
+
const typeSpecificFeatures = {
|
|
351
|
+
'data-service': {
|
|
352
|
+
authentication: true,
|
|
353
|
+
authorization: true,
|
|
354
|
+
database: true,
|
|
355
|
+
search: true,
|
|
356
|
+
filtering: true,
|
|
357
|
+
pagination: true,
|
|
358
|
+
caching: true,
|
|
359
|
+
backup: true
|
|
360
|
+
},
|
|
361
|
+
'auth-service': {
|
|
362
|
+
authentication: true,
|
|
363
|
+
authorization: true,
|
|
364
|
+
userProfiles: true,
|
|
365
|
+
emailNotifications: true,
|
|
366
|
+
magicLinkAuth: true,
|
|
367
|
+
passwordReset: true,
|
|
368
|
+
sessionManagement: true,
|
|
369
|
+
rateLimiting: true
|
|
370
|
+
},
|
|
371
|
+
'content-service': {
|
|
372
|
+
fileStorage: true,
|
|
373
|
+
search: true,
|
|
374
|
+
filtering: true,
|
|
375
|
+
pagination: true,
|
|
376
|
+
caching: true,
|
|
377
|
+
cdn: true,
|
|
378
|
+
imageProcessing: true,
|
|
379
|
+
metadata: true
|
|
380
|
+
},
|
|
381
|
+
'api-gateway': {
|
|
382
|
+
authentication: true,
|
|
383
|
+
authorization: true,
|
|
384
|
+
rateLimiting: true,
|
|
385
|
+
caching: true,
|
|
386
|
+
monitoring: true,
|
|
387
|
+
loadBalancing: true,
|
|
388
|
+
requestRouting: true,
|
|
389
|
+
responseTransformation: true
|
|
390
|
+
},
|
|
391
|
+
'generic': {
|
|
392
|
+
extensibility: true,
|
|
393
|
+
configuration: true,
|
|
394
|
+
deployment: true
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
return {
|
|
398
|
+
...baseFeatures,
|
|
399
|
+
...(typeSpecificFeatures[serviceType] || typeSpecificFeatures.generic)
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Close readline interface
|
|
405
|
+
*/
|
|
406
|
+
close() {
|
|
407
|
+
if (this.rl) {
|
|
408
|
+
this.rl.close();
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ErrorTracker - Comprehensive Error Handling and Recovery System
|
|
3
|
+
*
|
|
4
|
+
* Captures failures, tracks input states, and provides recovery mechanisms
|
|
5
|
+
* for the Clodo Framework service lifecycle management.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'fs/promises';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
export class ErrorTracker {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.errors = [];
|
|
14
|
+
this.errorLogPath = './clodo-service-errors.log';
|
|
15
|
+
this.maxErrors = 100; // Keep last 100 errors
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Capture an error with context and input state
|
|
20
|
+
*/
|
|
21
|
+
captureError(error, context = {}) {
|
|
22
|
+
const errorEntry = {
|
|
23
|
+
timestamp: new Date().toISOString(),
|
|
24
|
+
error: {
|
|
25
|
+
message: error.message,
|
|
26
|
+
stack: error.stack,
|
|
27
|
+
name: error.name
|
|
28
|
+
},
|
|
29
|
+
context: {
|
|
30
|
+
command: context.command || 'unknown',
|
|
31
|
+
servicePath: context.servicePath || process.cwd(),
|
|
32
|
+
inputState: context.inputState || {},
|
|
33
|
+
userInputs: context.userInputs || {},
|
|
34
|
+
action: context.action || 'unknown',
|
|
35
|
+
options: context.options || {}
|
|
36
|
+
},
|
|
37
|
+
recovery: this.generateRecoverySuggestions(error, context),
|
|
38
|
+
severity: this.determineSeverity(error, context)
|
|
39
|
+
};
|
|
40
|
+
this.errors.push(errorEntry);
|
|
41
|
+
|
|
42
|
+
// Keep only recent errors
|
|
43
|
+
if (this.errors.length > this.maxErrors) {
|
|
44
|
+
this.errors = this.errors.slice(-this.maxErrors);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Log to file asynchronously (don't block)
|
|
48
|
+
this.logErrorToFile(errorEntry).catch(err => {
|
|
49
|
+
console.warn(chalk.yellow(`Failed to write error log: ${err.message}`));
|
|
50
|
+
});
|
|
51
|
+
return errorEntry;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Generate recovery suggestions based on error type and context
|
|
56
|
+
*/
|
|
57
|
+
generateRecoverySuggestions(error, context) {
|
|
58
|
+
const suggestions = [];
|
|
59
|
+
|
|
60
|
+
// Network/API errors
|
|
61
|
+
if (error.message.includes('fetch') || error.message.includes('network') || error.message.includes('API')) {
|
|
62
|
+
suggestions.push('Check your internet connection');
|
|
63
|
+
suggestions.push('Verify Cloudflare API token is valid and has required permissions');
|
|
64
|
+
suggestions.push('Confirm Cloudflare account and zone IDs are correct');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Authentication errors
|
|
68
|
+
if (error.message.includes('auth') || error.message.includes('token') || error.message.includes('401') || error.message.includes('403')) {
|
|
69
|
+
suggestions.push('Verify Cloudflare API token has not expired');
|
|
70
|
+
suggestions.push('Check that the token has permissions for the required operations');
|
|
71
|
+
suggestions.push('Regenerate API token if necessary');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// File system errors
|
|
75
|
+
if (error.message.includes('ENOENT') || error.message.includes('permission') || error.message.includes('access')) {
|
|
76
|
+
suggestions.push('Check file permissions on the service directory');
|
|
77
|
+
suggestions.push('Ensure you have write access to the target directory');
|
|
78
|
+
suggestions.push('Verify the service path exists and is accessible');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Validation errors
|
|
82
|
+
if (error.message.includes('validation') || error.message.includes('invalid')) {
|
|
83
|
+
suggestions.push('Review input values for correctness');
|
|
84
|
+
suggestions.push('Use clodo-service validate <path> to check service configuration');
|
|
85
|
+
suggestions.push('Run clodo-service diagnose <path> for detailed issue analysis');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Configuration errors
|
|
89
|
+
if (error.message.includes('config') || error.message.includes('configuration')) {
|
|
90
|
+
suggestions.push('Run clodo-service update --fix-errors to attempt automatic fixes');
|
|
91
|
+
suggestions.push('Check domain configuration in src/config/domains.js');
|
|
92
|
+
suggestions.push('Verify package.json has all required fields');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Template errors
|
|
96
|
+
if (error.message.includes('template') || error.message.includes('variable')) {
|
|
97
|
+
suggestions.push('Check that all required template variables are provided');
|
|
98
|
+
suggestions.push('Verify template files exist and are readable');
|
|
99
|
+
suggestions.push('Regenerate service with clodo-service update --regenerate-configs');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Service creation/update specific
|
|
103
|
+
if (context.action === 'create' || context.action === 'update') {
|
|
104
|
+
suggestions.push('Try running the command again with --non-interactive flag');
|
|
105
|
+
suggestions.push('Use clodo-service diagnose to identify specific issues');
|
|
106
|
+
suggestions.push('Check that service name follows naming conventions (lowercase, hyphens only)');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Cloudflare specific
|
|
110
|
+
if (context.action && context.action.includes('cloudflare')) {
|
|
111
|
+
suggestions.push('Verify Cloudflare account has D1 database enabled');
|
|
112
|
+
suggestions.push('Check zone ID corresponds to the correct domain');
|
|
113
|
+
suggestions.push('Ensure API token has D1:Edit permission');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Add generic suggestions if none specific found
|
|
117
|
+
if (suggestions.length === 0) {
|
|
118
|
+
suggestions.push('Check the error message for specific details');
|
|
119
|
+
suggestions.push('Review the Clodo Framework documentation');
|
|
120
|
+
suggestions.push('Try the operation again after reviewing inputs');
|
|
121
|
+
suggestions.push('Contact support if the issue persists');
|
|
122
|
+
}
|
|
123
|
+
return suggestions;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Determine error severity level
|
|
128
|
+
*/
|
|
129
|
+
determineSeverity(error, context) {
|
|
130
|
+
// Critical errors that prevent operation
|
|
131
|
+
if (error.message.includes('authentication') || error.message.includes('permission') || error.message.includes('access denied') || error.message.includes('critical')) {
|
|
132
|
+
return 'critical';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// High severity - operation fails but may be recoverable
|
|
136
|
+
if (error.message.includes('network') || error.message.includes('timeout') || error.message.includes('connection') || error.message.includes('validation')) {
|
|
137
|
+
return 'high';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Medium severity - partial failure or warnings
|
|
141
|
+
if (error.message.includes('warning') || error.message.includes('deprecated') || error.message.includes('not found')) {
|
|
142
|
+
return 'medium';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Low severity - minor issues
|
|
146
|
+
return 'low';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Log error to file
|
|
151
|
+
*/
|
|
152
|
+
async logErrorToFile(errorEntry) {
|
|
153
|
+
try {
|
|
154
|
+
const logEntry = JSON.stringify(errorEntry, null, 2) + '\n---\n';
|
|
155
|
+
await fs.appendFile(this.errorLogPath, logEntry);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
// If we can't write to the log file, at least show a warning
|
|
158
|
+
console.warn(chalk.yellow(`Could not write to error log: ${error.message}`));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get recent errors
|
|
164
|
+
*/
|
|
165
|
+
getRecentErrors(limit = 10) {
|
|
166
|
+
return this.errors.slice(-limit);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get errors by severity
|
|
171
|
+
*/
|
|
172
|
+
getErrorsBySeverity(severity) {
|
|
173
|
+
return this.errors.filter(error => error.severity === severity);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get errors by command
|
|
178
|
+
*/
|
|
179
|
+
getErrorsByCommand(command) {
|
|
180
|
+
return this.errors.filter(error => error.context.command === command);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Clear error history
|
|
185
|
+
*/
|
|
186
|
+
clearErrors() {
|
|
187
|
+
this.errors = [];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Export error report
|
|
192
|
+
*/
|
|
193
|
+
async exportErrorReport(filePath) {
|
|
194
|
+
const report = {
|
|
195
|
+
generated: new Date().toISOString(),
|
|
196
|
+
totalErrors: this.errors.length,
|
|
197
|
+
errorsBySeverity: {
|
|
198
|
+
critical: this.getErrorsBySeverity('critical').length,
|
|
199
|
+
high: this.getErrorsBySeverity('high').length,
|
|
200
|
+
medium: this.getErrorsBySeverity('medium').length,
|
|
201
|
+
low: this.getErrorsBySeverity('low').length
|
|
202
|
+
},
|
|
203
|
+
recentErrors: this.getRecentErrors(20),
|
|
204
|
+
summary: this.generateErrorSummary()
|
|
205
|
+
};
|
|
206
|
+
await fs.writeFile(filePath, JSON.stringify(report, null, 2), 'utf8');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Generate error summary
|
|
211
|
+
*/
|
|
212
|
+
generateErrorSummary() {
|
|
213
|
+
const summary = {
|
|
214
|
+
mostCommonErrors: [],
|
|
215
|
+
mostProblematicCommands: [],
|
|
216
|
+
recentTrends: 'Analysis not implemented yet'
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// Count error types
|
|
220
|
+
const errorCounts = {};
|
|
221
|
+
this.errors.forEach(error => {
|
|
222
|
+
const key = error.error.name || 'Unknown';
|
|
223
|
+
errorCounts[key] = (errorCounts[key] || 0) + 1;
|
|
224
|
+
});
|
|
225
|
+
summary.mostCommonErrors = Object.entries(errorCounts).sort(([, a], [, b]) => b - a).slice(0, 5).map(([type, count]) => ({
|
|
226
|
+
type,
|
|
227
|
+
count
|
|
228
|
+
}));
|
|
229
|
+
|
|
230
|
+
// Count problematic commands
|
|
231
|
+
const commandCounts = {};
|
|
232
|
+
this.errors.forEach(error => {
|
|
233
|
+
const command = error.context.command || 'unknown';
|
|
234
|
+
commandCounts[command] = (commandCounts[command] || 0) + 1;
|
|
235
|
+
});
|
|
236
|
+
summary.mostProblematicCommands = Object.entries(commandCounts).sort(([, a], [, b]) => b - a).slice(0, 5).map(([command, count]) => ({
|
|
237
|
+
command,
|
|
238
|
+
count
|
|
239
|
+
}));
|
|
240
|
+
return summary;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Display error summary to console
|
|
245
|
+
*/
|
|
246
|
+
displayErrorSummary() {
|
|
247
|
+
const summary = this.generateErrorSummary();
|
|
248
|
+
console.log(chalk.cyan('\n📊 Error Summary'));
|
|
249
|
+
console.log(chalk.white(`Total Errors: ${this.errors.length}`));
|
|
250
|
+
if (summary.mostCommonErrors.length > 0) {
|
|
251
|
+
console.log(chalk.cyan('\nMost Common Errors:'));
|
|
252
|
+
summary.mostCommonErrors.forEach(({
|
|
253
|
+
type,
|
|
254
|
+
count
|
|
255
|
+
}) => {
|
|
256
|
+
console.log(chalk.white(` ${type}: ${count} times`));
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
if (summary.mostProblematicCommands.length > 0) {
|
|
260
|
+
console.log(chalk.cyan('\nMost Problematic Commands:'));
|
|
261
|
+
summary.mostProblematicCommands.forEach(({
|
|
262
|
+
command,
|
|
263
|
+
count
|
|
264
|
+
}) => {
|
|
265
|
+
console.log(chalk.white(` ${command}: ${count} errors`));
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Attempt automatic error recovery
|
|
272
|
+
*/
|
|
273
|
+
async attemptRecovery(errorEntry) {
|
|
274
|
+
const recoveries = [];
|
|
275
|
+
try {
|
|
276
|
+
// Try common recovery actions based on error type
|
|
277
|
+
if (errorEntry.error.message.includes('permission') || errorEntry.error.message.includes('access')) {
|
|
278
|
+
// Try to fix permissions (limited in what we can do)
|
|
279
|
+
recoveries.push('Checked permissions - manual intervention may be required');
|
|
280
|
+
}
|
|
281
|
+
if (errorEntry.error.message.includes('network') || errorEntry.error.message.includes('timeout')) {
|
|
282
|
+
// Wait and retry logic could be implemented here
|
|
283
|
+
recoveries.push('Network error detected - consider retrying the operation');
|
|
284
|
+
}
|
|
285
|
+
if (errorEntry.context.action === 'validate' || errorEntry.context.action === 'create') {
|
|
286
|
+
// Try to validate/fix configuration
|
|
287
|
+
recoveries.push('Consider running clodo-service update --fix-errors');
|
|
288
|
+
}
|
|
289
|
+
} catch (recoveryError) {
|
|
290
|
+
recoveries.push(`Recovery attempt failed: ${recoveryError.message}`);
|
|
291
|
+
}
|
|
292
|
+
return recoveries;
|
|
293
|
+
}
|
|
294
|
+
}
|