@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,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Mutator Module
|
|
3
|
+
* Focused module for safe configuration file mutations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs/promises';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
export class ConfigMutator {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.dryRun = options.dryRun || false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Safely update domain configuration
|
|
15
|
+
*/
|
|
16
|
+
async updateDomainConfig(servicePath, currentConfig, updates) {
|
|
17
|
+
const domainConfigPath = path.join(servicePath, 'src/config/domains.js');
|
|
18
|
+
let domainConfig = await fs.readFile(domainConfigPath, 'utf8');
|
|
19
|
+
if (updates.domainName) {
|
|
20
|
+
// Use regex-based replacement for safety
|
|
21
|
+
domainConfig = domainConfig.replace(new RegExp(`name: ['"]${this.escapeRegExp(currentConfig.domainName)}['"]`, 'g'), `name: '${updates.domainName}'`);
|
|
22
|
+
|
|
23
|
+
// Update domain URLs
|
|
24
|
+
domainConfig = domainConfig.replace(new RegExp(`https://[^'"]*${this.escapeRegExp(currentConfig.domainName)}[^'"]*`, 'g'), match => match.replace(currentConfig.domainName, updates.domainName));
|
|
25
|
+
}
|
|
26
|
+
if (!this.dryRun) {
|
|
27
|
+
await fs.writeFile(domainConfigPath, domainConfig, 'utf8');
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
updated: true,
|
|
31
|
+
path: domainConfigPath
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Update Cloudflare configuration
|
|
37
|
+
*/
|
|
38
|
+
async updateCloudflareConfig(servicePath, currentConfig, updates) {
|
|
39
|
+
const domainConfigPath = path.join(servicePath, 'src/config/domains.js');
|
|
40
|
+
let domainConfig = await fs.readFile(domainConfigPath, 'utf8');
|
|
41
|
+
if (updates.accountId) {
|
|
42
|
+
domainConfig = domainConfig.replace(new RegExp(`accountId:\\s*['"]${this.escapeRegExp(currentConfig.cloudflareAccountId || '')}['"]`, 'g'), `accountId: '${updates.accountId}'`);
|
|
43
|
+
}
|
|
44
|
+
if (updates.zoneId) {
|
|
45
|
+
domainConfig = domainConfig.replace(new RegExp(`zoneId:\\s*['"]${this.escapeRegExp(currentConfig.cloudflareZoneId || '')}['"]`, 'g'), `zoneId: '${updates.zoneId}'`);
|
|
46
|
+
}
|
|
47
|
+
if (!this.dryRun) {
|
|
48
|
+
await fs.writeFile(domainConfigPath, domainConfig, 'utf8');
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
updated: true,
|
|
52
|
+
path: domainConfigPath
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Update environment configuration
|
|
58
|
+
*/
|
|
59
|
+
async updateEnvironmentConfig(servicePath, currentConfig, updates) {
|
|
60
|
+
const wranglerConfigPath = path.join(servicePath, 'wrangler.toml');
|
|
61
|
+
let wranglerConfig = await fs.readFile(wranglerConfigPath, 'utf8');
|
|
62
|
+
if (updates.environment) {
|
|
63
|
+
// Update environment-specific names
|
|
64
|
+
wranglerConfig = wranglerConfig.replace(new RegExp(`name\\s*=\\s*['"].*${this.escapeRegExp(currentConfig.environment)}.*['"]`, 'g'), `name = "${currentConfig.serviceName}-${updates.environment}"`);
|
|
65
|
+
wranglerConfig = wranglerConfig.replace(new RegExp(`\\[env\\.${this.escapeRegExp(currentConfig.environment)}\\]`, 'g'), `[env.${updates.environment}]`);
|
|
66
|
+
}
|
|
67
|
+
if (!this.dryRun) {
|
|
68
|
+
await fs.writeFile(wranglerConfigPath, wranglerConfig, 'utf8');
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
updated: true,
|
|
72
|
+
path: wranglerConfigPath
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Update feature configuration
|
|
78
|
+
*/
|
|
79
|
+
async updateFeatureConfig(servicePath, action, feature) {
|
|
80
|
+
const domainConfigPath = path.join(servicePath, 'src/config/domains.js');
|
|
81
|
+
let domainConfig = await fs.readFile(domainConfigPath, 'utf8');
|
|
82
|
+
if (action === 'add') {
|
|
83
|
+
// Add feature flag
|
|
84
|
+
const featureLine = ` ${feature}: true,`;
|
|
85
|
+
domainConfig = domainConfig.replace(/(\s+)(cors:\s*(?:true|false),?)(\n)/, `$1$2$3$1${featureLine}$3`);
|
|
86
|
+
} else if (action === 'remove') {
|
|
87
|
+
// Remove feature flag
|
|
88
|
+
const featureLinePattern = new RegExp(`\\s+${this.escapeRegExp(feature)}:\\s*(?:true|false),?\\n`, 'g');
|
|
89
|
+
domainConfig = domainConfig.replace(featureLinePattern, '');
|
|
90
|
+
}
|
|
91
|
+
if (!this.dryRun) {
|
|
92
|
+
await fs.writeFile(domainConfigPath, domainConfig, 'utf8');
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
updated: true,
|
|
96
|
+
path: domainConfigPath
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Validate configuration file exists and is writable
|
|
102
|
+
*/
|
|
103
|
+
async validateConfigFile(filePath) {
|
|
104
|
+
try {
|
|
105
|
+
await fs.access(filePath, fs.constants.R_OK | fs.constants.W_OK);
|
|
106
|
+
return {
|
|
107
|
+
valid: true
|
|
108
|
+
};
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return {
|
|
111
|
+
valid: false,
|
|
112
|
+
error: `Configuration file not accessible: ${filePath}`
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Escape special regex characters for safe replacement
|
|
119
|
+
*/
|
|
120
|
+
escapeRegExp(string) {
|
|
121
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Enable/disable dry run mode
|
|
126
|
+
*/
|
|
127
|
+
setDryRun(enabled) {
|
|
128
|
+
this.dryRun = enabled;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Confirmation Handler Module
|
|
3
|
+
* Focused module for handling smart confirmations and derived values
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ConfirmationEngine } from '../ConfirmationEngine.js';
|
|
7
|
+
import { createPromptHandler } from '../../utils/prompt-handler.js';
|
|
8
|
+
export class ConfirmationHandler {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.interactive = options.interactive !== false;
|
|
11
|
+
this.promptHandler = createPromptHandler({
|
|
12
|
+
interactive: this.interactive
|
|
13
|
+
});
|
|
14
|
+
this.confirmationEngine = new ConfirmationEngine({
|
|
15
|
+
interactive: this.interactive
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generate and confirm derived values from core inputs
|
|
21
|
+
*/
|
|
22
|
+
async generateAndConfirm(coreInputs) {
|
|
23
|
+
return await this.confirmationEngine.generateAndConfirm(coreInputs);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate smart defaults without interaction
|
|
28
|
+
*/
|
|
29
|
+
generateSmartDefaults(coreInputs) {
|
|
30
|
+
return this.confirmationEngine.generateSmartDefaults(coreInputs);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Interactive confirmation of specific values
|
|
35
|
+
*/
|
|
36
|
+
async confirmValue(field, currentValue, question) {
|
|
37
|
+
if (!this.interactive) {
|
|
38
|
+
return currentValue;
|
|
39
|
+
}
|
|
40
|
+
const confirmed = await this.promptHandler.confirm(`${question}\nCurrent value: ${currentValue}\nKeep this value?`, true);
|
|
41
|
+
if (!confirmed) {
|
|
42
|
+
return await this.promptHandler.prompt('Enter new value: ');
|
|
43
|
+
}
|
|
44
|
+
return currentValue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Confirm entire configuration
|
|
49
|
+
*/
|
|
50
|
+
async confirmConfiguration(config) {
|
|
51
|
+
if (!this.interactive) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
console.log('\n📋 Configuration Summary:');
|
|
55
|
+
Object.entries(config).forEach(([key, value]) => {
|
|
56
|
+
// Mask sensitive values
|
|
57
|
+
if (key.toLowerCase().includes('token') || key.toLowerCase().includes('secret')) {
|
|
58
|
+
value = '*'.repeat(8) + value.slice(-4);
|
|
59
|
+
}
|
|
60
|
+
console.log(` ${key}: ${value}`);
|
|
61
|
+
});
|
|
62
|
+
return await this.promptHandler.confirm('\nProceed with this configuration?', true);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Cleanup resources
|
|
67
|
+
*/
|
|
68
|
+
async cleanup() {
|
|
69
|
+
await this.promptHandler.close();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generation Handler Module
|
|
3
|
+
* Focused module for service generation and file creation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { GenerationEngine } from '../GenerationEngine.js';
|
|
7
|
+
export class GenerationHandler {
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.outputPath = options.outputPath || '.';
|
|
10
|
+
this.templatePath = options.templatePath || './templates';
|
|
11
|
+
this.generationEngine = new GenerationEngine({
|
|
12
|
+
outputDir: this.outputPath,
|
|
13
|
+
templatesDir: this.templatePath
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Generate complete service from inputs and confirmed values
|
|
19
|
+
*/
|
|
20
|
+
async generateService(coreInputs, confirmedValues, options = {}) {
|
|
21
|
+
const config = {
|
|
22
|
+
outputPath: this.outputPath,
|
|
23
|
+
...options
|
|
24
|
+
};
|
|
25
|
+
return await this.generationEngine.generateService(coreInputs, confirmedValues, config);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Generate specific component
|
|
30
|
+
*/
|
|
31
|
+
async generateComponent(componentType, config, outputPath) {
|
|
32
|
+
// Delegate to generation engine's specific methods
|
|
33
|
+
switch (componentType) {
|
|
34
|
+
case 'manifest':
|
|
35
|
+
return this.generationEngine.createServiceManifest(config.coreInputs, config.confirmedValues, config.generatedFiles || []);
|
|
36
|
+
case 'directory':
|
|
37
|
+
return this.generationEngine.createDirectoryStructure(outputPath);
|
|
38
|
+
default:
|
|
39
|
+
throw new Error(`Unknown component type: ${componentType}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Validate generation prerequisites
|
|
45
|
+
*/
|
|
46
|
+
async validatePrerequisites(coreInputs, confirmedValues) {
|
|
47
|
+
const issues = [];
|
|
48
|
+
|
|
49
|
+
// Check required inputs
|
|
50
|
+
if (!coreInputs.serviceName) {
|
|
51
|
+
issues.push('Service name is required');
|
|
52
|
+
}
|
|
53
|
+
if (!coreInputs.serviceType) {
|
|
54
|
+
issues.push('Service type is required');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Check output path permissions
|
|
58
|
+
try {
|
|
59
|
+
const fs = await import('fs/promises');
|
|
60
|
+
await fs.access(this.outputPath);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
issues.push(`Output path not accessible: ${this.outputPath}`);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
valid: issues.length === 0,
|
|
66
|
+
issues
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get generation progress information
|
|
72
|
+
*/
|
|
73
|
+
getGenerationStatus() {
|
|
74
|
+
return {
|
|
75
|
+
outputPath: this.outputPath,
|
|
76
|
+
templatePath: this.templatePath,
|
|
77
|
+
ready: true
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input Handler Module
|
|
3
|
+
* Focused module for collecting core service inputs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { InputCollector } from '../InputCollector.js';
|
|
7
|
+
import { createPromptHandler } from '../../utils/prompt-handler.js';
|
|
8
|
+
export class InputHandler {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.interactive = options.interactive !== false;
|
|
11
|
+
this.promptHandler = createPromptHandler({
|
|
12
|
+
interactive: this.interactive
|
|
13
|
+
});
|
|
14
|
+
this.inputCollector = new InputCollector({
|
|
15
|
+
interactive: this.interactive
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Collect the 6 core inputs required for service creation
|
|
21
|
+
*/
|
|
22
|
+
async collectCoreInputs() {
|
|
23
|
+
return await this.inputCollector.collectCoreInputs();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validate core inputs without prompting
|
|
28
|
+
*/
|
|
29
|
+
async validateCoreInputs(inputs) {
|
|
30
|
+
return await this.inputCollector.validateCoreInputs(inputs);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Interactive input collection with custom prompts
|
|
35
|
+
*/
|
|
36
|
+
async promptForInput(field, question, defaultValue = '', required = false) {
|
|
37
|
+
if (!this.interactive) {
|
|
38
|
+
return defaultValue;
|
|
39
|
+
}
|
|
40
|
+
let answer;
|
|
41
|
+
do {
|
|
42
|
+
answer = await this.promptHandler.prompt(question);
|
|
43
|
+
if (!answer && defaultValue) {
|
|
44
|
+
answer = defaultValue;
|
|
45
|
+
}
|
|
46
|
+
if (required && !answer) {
|
|
47
|
+
console.log('This field is required. Please provide a value.');
|
|
48
|
+
}
|
|
49
|
+
} while (required && !answer);
|
|
50
|
+
return answer;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Cleanup resources
|
|
55
|
+
*/
|
|
56
|
+
async cleanup() {
|
|
57
|
+
await this.promptHandler.close();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation Handler Module
|
|
3
|
+
* Focused module for service validation and health checks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs/promises';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
export class ValidationHandler {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.strict = options.strict || false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Validate complete service configuration
|
|
15
|
+
*/
|
|
16
|
+
async validateService(servicePath) {
|
|
17
|
+
const issues = [];
|
|
18
|
+
|
|
19
|
+
// Check for required files
|
|
20
|
+
const requiredFiles = ['package.json', 'src/config/domains.js', 'src/worker/index.js', 'wrangler.toml'];
|
|
21
|
+
for (const file of requiredFiles) {
|
|
22
|
+
const filePath = path.join(servicePath, file);
|
|
23
|
+
try {
|
|
24
|
+
await fs.access(filePath);
|
|
25
|
+
} catch {
|
|
26
|
+
issues.push(`Missing required file: ${file}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Validate package.json
|
|
31
|
+
const packageValidation = await this.validatePackageJson(servicePath);
|
|
32
|
+
issues.push(...packageValidation.issues);
|
|
33
|
+
|
|
34
|
+
// Validate domain configuration
|
|
35
|
+
const domainValidation = await this.validateDomainConfig(servicePath);
|
|
36
|
+
issues.push(...domainValidation.issues);
|
|
37
|
+
|
|
38
|
+
// Validate wrangler configuration
|
|
39
|
+
const wranglerValidation = await this.validateWranglerConfig(servicePath);
|
|
40
|
+
issues.push(...wranglerValidation.issues);
|
|
41
|
+
return {
|
|
42
|
+
valid: issues.length === 0,
|
|
43
|
+
issues,
|
|
44
|
+
servicePath
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Validate package.json structure
|
|
50
|
+
*/
|
|
51
|
+
async validatePackageJson(servicePath) {
|
|
52
|
+
const issues = [];
|
|
53
|
+
const packageJsonPath = path.join(servicePath, 'package.json');
|
|
54
|
+
try {
|
|
55
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
|
56
|
+
if (!packageJson.name) {
|
|
57
|
+
issues.push('package.json: Missing name field');
|
|
58
|
+
}
|
|
59
|
+
if (!packageJson.version) {
|
|
60
|
+
issues.push('package.json: Missing version field');
|
|
61
|
+
}
|
|
62
|
+
if (!packageJson.type || packageJson.type !== 'module') {
|
|
63
|
+
issues.push('package.json: Should use "type": "module" for ES modules');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check for required dependencies
|
|
67
|
+
const requiredDeps = ['@tamyla/clodo-framework'];
|
|
68
|
+
const deps = {
|
|
69
|
+
...packageJson.dependencies,
|
|
70
|
+
...packageJson.devDependencies
|
|
71
|
+
};
|
|
72
|
+
requiredDeps.forEach(dep => {
|
|
73
|
+
if (!deps[dep]) {
|
|
74
|
+
issues.push(`package.json: Missing required dependency: ${dep}`);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
} catch (error) {
|
|
78
|
+
issues.push(`package.json: Invalid JSON format - ${error.message}`);
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
issues
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Validate domain configuration
|
|
87
|
+
*/
|
|
88
|
+
async validateDomainConfig(servicePath) {
|
|
89
|
+
const issues = [];
|
|
90
|
+
const domainConfigPath = path.join(servicePath, 'src/config/domains.js');
|
|
91
|
+
try {
|
|
92
|
+
const domainConfig = await fs.readFile(domainConfigPath, 'utf8');
|
|
93
|
+
|
|
94
|
+
// Check for Clodo Framework integration
|
|
95
|
+
if (!domainConfig.includes('createDomainConfigSchema')) {
|
|
96
|
+
issues.push('Domain configuration missing Clodo Framework integration');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check for required exports
|
|
100
|
+
if (!domainConfig.includes('export const domains')) {
|
|
101
|
+
issues.push('Domain configuration missing domains export');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Basic domain format validation
|
|
105
|
+
const domainMatches = domainConfig.match(/domain:\s*['"]([^'"]+)['"]/g);
|
|
106
|
+
if (domainMatches) {
|
|
107
|
+
domainMatches.forEach(match => {
|
|
108
|
+
const domain = match.match(/domain:\s*['"]([^'"]+)['"]/)[1];
|
|
109
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]*\.[a-zA-Z]{2,}$/.test(domain)) {
|
|
110
|
+
issues.push(`Invalid domain format: ${domain}`);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
} catch (error) {
|
|
115
|
+
issues.push(`Domain configuration: Cannot read file - ${error.message}`);
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
issues
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Validate wrangler configuration
|
|
124
|
+
*/
|
|
125
|
+
async validateWranglerConfig(servicePath) {
|
|
126
|
+
const issues = [];
|
|
127
|
+
const wranglerConfigPath = path.join(servicePath, 'wrangler.toml');
|
|
128
|
+
try {
|
|
129
|
+
const wranglerConfig = await fs.readFile(wranglerConfigPath, 'utf8');
|
|
130
|
+
|
|
131
|
+
// Check for required fields
|
|
132
|
+
if (!wranglerConfig.includes('name =')) {
|
|
133
|
+
issues.push('wrangler.toml: Missing name field');
|
|
134
|
+
}
|
|
135
|
+
if (!wranglerConfig.includes('main =')) {
|
|
136
|
+
issues.push('wrangler.toml: Missing main field');
|
|
137
|
+
}
|
|
138
|
+
if (!wranglerConfig.includes('compatibility_date =')) {
|
|
139
|
+
issues.push('wrangler.toml: Missing compatibility_date');
|
|
140
|
+
}
|
|
141
|
+
} catch (error) {
|
|
142
|
+
issues.push(`wrangler.toml: Cannot read file - ${error.message}`);
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
issues
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Run comprehensive service diagnostics
|
|
151
|
+
*/
|
|
152
|
+
async diagnoseService(servicePath) {
|
|
153
|
+
const diagnosis = {
|
|
154
|
+
errors: [],
|
|
155
|
+
warnings: [],
|
|
156
|
+
recommendations: []
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// Basic validation
|
|
160
|
+
const validation = await this.validateService(servicePath);
|
|
161
|
+
validation.issues.forEach(issue => {
|
|
162
|
+
if (issue.includes('Missing required')) {
|
|
163
|
+
diagnosis.errors.push({
|
|
164
|
+
message: issue,
|
|
165
|
+
severity: 'high',
|
|
166
|
+
suggestion: 'Run service initialization to create missing files'
|
|
167
|
+
});
|
|
168
|
+
} else if (issue.includes('Invalid')) {
|
|
169
|
+
diagnosis.errors.push({
|
|
170
|
+
message: issue,
|
|
171
|
+
severity: 'medium',
|
|
172
|
+
suggestion: 'Review and correct the configuration'
|
|
173
|
+
});
|
|
174
|
+
} else {
|
|
175
|
+
diagnosis.warnings.push({
|
|
176
|
+
message: issue,
|
|
177
|
+
suggestion: 'Consider updating for better compatibility'
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Additional recommendations
|
|
183
|
+
try {
|
|
184
|
+
const packageJson = JSON.parse(await fs.readFile(path.join(servicePath, 'package.json'), 'utf8'));
|
|
185
|
+
if (!packageJson.scripts || !packageJson.scripts.dev) {
|
|
186
|
+
diagnosis.recommendations.push('Add development scripts for easier testing');
|
|
187
|
+
}
|
|
188
|
+
if (!packageJson.scripts || !packageJson.scripts.deploy) {
|
|
189
|
+
diagnosis.recommendations.push('Add deployment scripts for easier publishing');
|
|
190
|
+
}
|
|
191
|
+
} catch (error) {
|
|
192
|
+
// Package.json validation already handled above
|
|
193
|
+
}
|
|
194
|
+
return diagnosis;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Set validation strictness
|
|
199
|
+
*/
|
|
200
|
+
setStrict(enabled) {
|
|
201
|
+
this.strict = enabled;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clodo Framework - Service Management Module
|
|
3
|
+
* Programmatic APIs for service creation and initialization
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { ServiceCreator, createService } from './ServiceCreator.js';
|
|
7
|
+
export { ServiceInitializer, initializeService } from './ServiceInitializer.js';
|