@tamyla/clodo-framework 3.1.4 → 3.1.8
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 +29 -0
- package/bin/clodo-service.js +29 -989
- package/bin/database/enterprise-db-manager.js +7 -5
- package/bin/security/security-cli.js +0 -0
- package/bin/service-management/create-service.js +0 -0
- package/bin/service-management/init-service.js +0 -0
- package/bin/shared/cloudflare/domain-discovery.js +11 -10
- package/bin/shared/cloudflare/ops.js +1 -1
- package/bin/shared/config/ConfigurationManager.js +539 -0
- package/bin/shared/config/index.js +13 -1
- package/bin/shared/database/connection-manager.js +2 -2
- package/bin/shared/database/orchestrator.js +5 -4
- package/bin/shared/deployment/auditor.js +9 -8
- package/bin/shared/logging/Logger.js +214 -0
- package/bin/shared/monitoring/production-monitor.js +21 -9
- package/bin/shared/utils/ErrorHandler.js +675 -0
- package/bin/shared/utils/error-recovery.js +33 -13
- package/bin/shared/utils/file-manager.js +162 -0
- package/bin/shared/utils/formatters.js +247 -0
- package/bin/shared/utils/index.js +14 -4
- package/bin/shared/validation/ValidationRegistry.js +143 -0
- package/dist/deployment/auditor.js +23 -8
- package/dist/deployment/orchestration/BaseDeploymentOrchestrator.js +426 -0
- package/dist/deployment/orchestration/EnterpriseOrchestrator.js +401 -0
- package/dist/deployment/orchestration/PortfolioOrchestrator.js +273 -0
- package/dist/deployment/orchestration/SingleServiceOrchestrator.js +231 -0
- package/dist/deployment/orchestration/UnifiedDeploymentOrchestrator.js +662 -0
- package/dist/deployment/orchestration/index.js +17 -0
- package/dist/index.js +12 -0
- package/dist/orchestration/modules/DomainResolver.js +8 -6
- package/dist/orchestration/multi-domain-orchestrator.js +13 -1
- package/dist/security/index.js +2 -2
- package/dist/service-management/ConfirmationEngine.js +8 -7
- package/dist/service-management/ErrorTracker.js +7 -2
- package/dist/service-management/InputCollector.js +31 -16
- package/dist/service-management/ServiceCreator.js +22 -7
- package/dist/service-management/ServiceInitializer.js +12 -18
- package/dist/shared/cloudflare/domain-discovery.js +11 -10
- package/dist/shared/cloudflare/ops.js +1 -1
- package/dist/shared/config/ConfigurationManager.js +519 -0
- package/dist/shared/config/index.js +5 -1
- package/dist/shared/database/connection-manager.js +2 -2
- package/dist/shared/database/orchestrator.js +13 -4
- package/dist/shared/deployment/auditor.js +23 -8
- package/dist/shared/logging/Logger.js +209 -0
- package/dist/shared/monitoring/production-monitor.js +24 -8
- package/dist/{utils → shared/utils}/ErrorHandler.js +306 -28
- package/dist/shared/utils/error-recovery.js +33 -13
- package/dist/shared/utils/file-manager.js +155 -0
- package/dist/shared/utils/formatters.js +215 -0
- package/dist/shared/utils/index.js +14 -4
- package/dist/shared/validation/ValidationRegistry.js +126 -0
- package/dist/utils/config/unified-config-manager.js +14 -12
- package/dist/utils/deployment/config-cache.js +3 -1
- package/dist/utils/deployment/secret-generator.js +32 -29
- package/dist/utils/framework-config.js +6 -3
- package/dist/utils/ui-structures-loader.js +3 -0
- package/dist/worker/integration.js +11 -1
- package/package.json +31 -3
- package/dist/config/FeatureManager.js +0 -426
- package/dist/config/features.js +0 -230
- package/dist/utils/error-recovery.js +0 -240
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Error Recovery Module
|
|
2
|
+
* Error Recovery Module - UNIFIED
|
|
3
3
|
* Implements circuit breakers, retries, and graceful degradation
|
|
4
|
+
*
|
|
5
|
+
* Consolidation: Oct 26, 2025 - Phase 3.2.3a
|
|
6
|
+
*
|
|
7
|
+
* Merged from:
|
|
8
|
+
* - src/utils/error-recovery.js (225 lines)
|
|
9
|
+
* - bin/shared/utils/error-recovery.js (225 lines - DUPLICATE)
|
|
10
|
+
*
|
|
11
|
+
* This is the canonical version. All imports should use this version.
|
|
4
12
|
*/
|
|
5
13
|
|
|
6
14
|
export class ErrorRecoveryManager {
|
|
@@ -16,17 +24,29 @@ export class ErrorRecoveryManager {
|
|
|
16
24
|
*/
|
|
17
25
|
async initialize() {
|
|
18
26
|
// Import framework config for consistent timing and retry settings
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
try {
|
|
28
|
+
const { frameworkConfig } = await import('./framework-config.js');
|
|
29
|
+
const timing = frameworkConfig.getTiming();
|
|
30
|
+
|
|
31
|
+
this.config = {
|
|
32
|
+
maxRetries: this.options.maxRetries || timing.retryAttempts,
|
|
33
|
+
retryDelay: this.options.retryDelay || timing.retryDelay,
|
|
34
|
+
circuitBreakerThreshold: this.options.circuitBreakerThreshold || timing.circuitBreakerThreshold,
|
|
35
|
+
circuitBreakerTimeout: this.options.circuitBreakerTimeout || timing.circuitBreakerTimeout,
|
|
36
|
+
gracefulDegradation: this.options.gracefulDegradation !== false,
|
|
37
|
+
...this.options
|
|
38
|
+
};
|
|
39
|
+
} catch (error) {
|
|
40
|
+
// Fallback to defaults if framework config not available
|
|
41
|
+
this.config = {
|
|
42
|
+
maxRetries: this.options.maxRetries || 3,
|
|
43
|
+
retryDelay: this.options.retryDelay || 1000,
|
|
44
|
+
circuitBreakerThreshold: this.options.circuitBreakerThreshold || 5,
|
|
45
|
+
circuitBreakerTimeout: this.options.circuitBreakerTimeout || 60000,
|
|
46
|
+
gracefulDegradation: this.options.gracefulDegradation !== false,
|
|
47
|
+
...this.options
|
|
48
|
+
};
|
|
49
|
+
}
|
|
30
50
|
}
|
|
31
51
|
|
|
32
52
|
/**
|
|
@@ -222,4 +242,4 @@ export function withRetry(fn, options = {}) {
|
|
|
222
242
|
export function withCircuitBreaker(fn, options = {}) {
|
|
223
243
|
const recovery = new ErrorRecoveryManager(options);
|
|
224
244
|
return (...args) => recovery.executeWithRecovery(() => fn(...args), options);
|
|
225
|
-
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized File Manager
|
|
3
|
+
* Replaces: 12 scattered file operation implementations
|
|
4
|
+
* Savings: 200+ lines
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
readFileSync, writeFileSync, appendFileSync,
|
|
9
|
+
existsSync, mkdirSync, statSync
|
|
10
|
+
} from 'fs';
|
|
11
|
+
import { dirname, basename } from 'path';
|
|
12
|
+
|
|
13
|
+
export class FileManager {
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.cache = new Map();
|
|
16
|
+
this.enableCache = options.enableCache !== false;
|
|
17
|
+
this.createBackups = options.createBackups !== false;
|
|
18
|
+
this.backupDir = options.backupDir || '.backups';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Read JSON configuration file
|
|
23
|
+
*/
|
|
24
|
+
readConfig(path, defaultValue = null) {
|
|
25
|
+
try {
|
|
26
|
+
if (this.enableCache && this.cache.has(path)) {
|
|
27
|
+
return this.cache.get(path);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!existsSync(path)) {
|
|
31
|
+
if (defaultValue !== null) return defaultValue;
|
|
32
|
+
throw new Error(`Configuration file not found: ${path}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const content = readFileSync(path, 'utf8');
|
|
36
|
+
const data = JSON.parse(content);
|
|
37
|
+
|
|
38
|
+
if (this.enableCache) {
|
|
39
|
+
this.cache.set(path, data);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return data;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (error.message.includes('JSON')) {
|
|
45
|
+
throw new Error(`Invalid JSON in configuration file: ${error.message}`);
|
|
46
|
+
}
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Write JSON configuration file with optional backup
|
|
53
|
+
*/
|
|
54
|
+
writeConfig(path, data, options = {}) {
|
|
55
|
+
try {
|
|
56
|
+
// Create backup if needed
|
|
57
|
+
if (this.createBackups && existsSync(path)) {
|
|
58
|
+
this.createBackup(path);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Ensure directory exists
|
|
62
|
+
this.ensureDir(dirname(path));
|
|
63
|
+
|
|
64
|
+
// Write file
|
|
65
|
+
const content = JSON.stringify(data, null, 2);
|
|
66
|
+
writeFileSync(path, content, 'utf8');
|
|
67
|
+
|
|
68
|
+
// Clear cache
|
|
69
|
+
if (this.enableCache) {
|
|
70
|
+
this.cache.delete(path);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { success: true, path };
|
|
74
|
+
} catch (error) {
|
|
75
|
+
throw new Error(`Failed to write configuration: ${error.message}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Read text file
|
|
81
|
+
*/
|
|
82
|
+
readFile(path) {
|
|
83
|
+
if (!existsSync(path)) {
|
|
84
|
+
throw new Error(`File not found: ${path}`);
|
|
85
|
+
}
|
|
86
|
+
return readFileSync(path, 'utf8');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Write text file
|
|
91
|
+
*/
|
|
92
|
+
writeFile(path, content) {
|
|
93
|
+
this.ensureDir(dirname(path));
|
|
94
|
+
writeFileSync(path, content, 'utf8');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Append to file
|
|
99
|
+
*/
|
|
100
|
+
appendFile(path, content) {
|
|
101
|
+
this.ensureDir(dirname(path));
|
|
102
|
+
appendFileSync(path, content, 'utf8');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Check if file exists
|
|
107
|
+
*/
|
|
108
|
+
exists(path) {
|
|
109
|
+
return existsSync(path);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Ensure directory exists
|
|
114
|
+
*/
|
|
115
|
+
ensureDir(dir) {
|
|
116
|
+
if (!existsSync(dir)) {
|
|
117
|
+
mkdirSync(dir, { recursive: true });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Create backup of file
|
|
123
|
+
*/
|
|
124
|
+
createBackup(path) {
|
|
125
|
+
this.ensureDir(this.backupDir);
|
|
126
|
+
|
|
127
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
128
|
+
const fileName = basename(path);
|
|
129
|
+
const backupPath = `${this.backupDir}/${fileName}.${timestamp}.bak`;
|
|
130
|
+
|
|
131
|
+
const content = readFileSync(path, 'utf8');
|
|
132
|
+
writeFileSync(backupPath, content, 'utf8');
|
|
133
|
+
|
|
134
|
+
return backupPath;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get file stats
|
|
139
|
+
*/
|
|
140
|
+
getStats(path) {
|
|
141
|
+
if (!existsSync(path)) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
return statSync(path);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Clear cache
|
|
149
|
+
*/
|
|
150
|
+
clearCache(path = null) {
|
|
151
|
+
if (path) {
|
|
152
|
+
this.cache.delete(path);
|
|
153
|
+
} else {
|
|
154
|
+
this.cache.clear();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Export singleton instance
|
|
161
|
+
*/
|
|
162
|
+
export const fileManager = new FileManager();
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Formatters for Clodo Framework
|
|
3
|
+
* Centralizes all data transformation logic
|
|
4
|
+
* Replaces: 5 scattered formatting implementations
|
|
5
|
+
* Savings: 150+ lines
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Name formatting utilities
|
|
10
|
+
*/
|
|
11
|
+
export const NameFormatters = {
|
|
12
|
+
/**
|
|
13
|
+
* Convert kebab-case to Display Case
|
|
14
|
+
* Example: 'my-service' → 'My Service'
|
|
15
|
+
*/
|
|
16
|
+
toDisplayName(kebabCase) {
|
|
17
|
+
if (!kebabCase) return '';
|
|
18
|
+
return kebabCase
|
|
19
|
+
.replace(/-/g, ' ')
|
|
20
|
+
.replace(/\b\w/g, l => l.toUpperCase());
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Convert camelCase to kebab-case
|
|
25
|
+
* Example: 'myService' → 'my-service'
|
|
26
|
+
*/
|
|
27
|
+
toKebabCase(camelCase) {
|
|
28
|
+
if (!camelCase) return '';
|
|
29
|
+
return camelCase
|
|
30
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
31
|
+
.replace(/^([A-Z])([A-Z])/g, '$1-$2')
|
|
32
|
+
.toLowerCase();
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Convert kebab-case to camelCase
|
|
37
|
+
* Example: 'my-service' → 'myService'
|
|
38
|
+
*/
|
|
39
|
+
toCamelCase(kebabCase) {
|
|
40
|
+
if (!kebabCase) return '';
|
|
41
|
+
return kebabCase
|
|
42
|
+
.split('-')
|
|
43
|
+
.map((part, i) =>
|
|
44
|
+
i === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)
|
|
45
|
+
)
|
|
46
|
+
.join('');
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Convert snake_case to camelCase
|
|
51
|
+
* Example: 'my_service' → 'myService'
|
|
52
|
+
*/
|
|
53
|
+
snakeToCamel(snakeCase) {
|
|
54
|
+
if (!snakeCase) return '';
|
|
55
|
+
return snakeCase
|
|
56
|
+
.split('_')
|
|
57
|
+
.map((part, i) =>
|
|
58
|
+
i === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)
|
|
59
|
+
)
|
|
60
|
+
.join('');
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* URL formatting utilities
|
|
66
|
+
*/
|
|
67
|
+
export const UrlFormatters = {
|
|
68
|
+
/**
|
|
69
|
+
* Build service URL
|
|
70
|
+
* Example: buildServiceUrl('api', 'example.com', 'production')
|
|
71
|
+
* → 'https://api.example.com'
|
|
72
|
+
*/
|
|
73
|
+
buildServiceUrl(serviceName, domain, environment = 'production') {
|
|
74
|
+
if (!serviceName || !domain) return '';
|
|
75
|
+
|
|
76
|
+
const prefix = environment === 'production'
|
|
77
|
+
? serviceName
|
|
78
|
+
: `${serviceName}-${environment.substring(0, 3)}`;
|
|
79
|
+
|
|
80
|
+
return `https://${prefix}.${domain}`;
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Build production URL
|
|
85
|
+
*/
|
|
86
|
+
buildProductionUrl(serviceName, domain) {
|
|
87
|
+
return this.buildServiceUrl(serviceName, domain, 'production');
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Build staging URL
|
|
92
|
+
*/
|
|
93
|
+
buildStagingUrl(serviceName, domain) {
|
|
94
|
+
return this.buildServiceUrl(serviceName, domain, 'staging');
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Build development URL
|
|
99
|
+
*/
|
|
100
|
+
buildDevUrl(serviceName, domain) {
|
|
101
|
+
return this.buildServiceUrl(serviceName, domain, 'development');
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Build API endpoint URL
|
|
106
|
+
*/
|
|
107
|
+
buildApiUrl(serviceName, domain, path = '') {
|
|
108
|
+
const baseUrl = this.buildProductionUrl(serviceName, domain);
|
|
109
|
+
return path ? `${baseUrl}${path}` : baseUrl;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Resource name formatters
|
|
115
|
+
*/
|
|
116
|
+
export const ResourceFormatters = {
|
|
117
|
+
/**
|
|
118
|
+
* Format database name
|
|
119
|
+
* Example: 'my-service' → 'my-service-db'
|
|
120
|
+
*/
|
|
121
|
+
databaseName(serviceName) {
|
|
122
|
+
if (!serviceName) return '';
|
|
123
|
+
return `${serviceName}-db`;
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Format worker name
|
|
128
|
+
* Example: 'my-service' → 'my-service-worker'
|
|
129
|
+
*/
|
|
130
|
+
workerName(serviceName) {
|
|
131
|
+
if (!serviceName) return '';
|
|
132
|
+
return `${serviceName}-worker`;
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Format service directory
|
|
137
|
+
* Example: 'my-service' → './services/my-service'
|
|
138
|
+
*/
|
|
139
|
+
serviceDirectory(serviceName) {
|
|
140
|
+
if (!serviceName) return '';
|
|
141
|
+
return `./services/${serviceName}`;
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Format configuration key
|
|
146
|
+
* Example: 'cloudflareApiToken' → 'cloudflare-api-token'
|
|
147
|
+
*/
|
|
148
|
+
configKey(camelCase) {
|
|
149
|
+
return NameFormatters.toKebabCase(camelCase);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Environment-related formatters
|
|
155
|
+
*/
|
|
156
|
+
export const EnvironmentFormatters = {
|
|
157
|
+
/**
|
|
158
|
+
* Get environment variable prefix
|
|
159
|
+
* Example: 'production' → 'PROD_'
|
|
160
|
+
*/
|
|
161
|
+
getEnvPrefix(environment) {
|
|
162
|
+
switch (environment) {
|
|
163
|
+
case 'production':
|
|
164
|
+
return 'PROD_';
|
|
165
|
+
case 'staging':
|
|
166
|
+
return 'STAGING_';
|
|
167
|
+
case 'development':
|
|
168
|
+
return 'DEV_';
|
|
169
|
+
default:
|
|
170
|
+
return 'APP_';
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get log level for environment
|
|
176
|
+
* Example: 'production' → 'warn'
|
|
177
|
+
*/
|
|
178
|
+
getLogLevel(environment) {
|
|
179
|
+
switch (environment) {
|
|
180
|
+
case 'production':
|
|
181
|
+
return 'warn';
|
|
182
|
+
case 'staging':
|
|
183
|
+
return 'info';
|
|
184
|
+
case 'development':
|
|
185
|
+
return 'debug';
|
|
186
|
+
default:
|
|
187
|
+
return 'info';
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get CORS policy for environment
|
|
193
|
+
*/
|
|
194
|
+
getCorsPolicy(domain, environment) {
|
|
195
|
+
switch (environment) {
|
|
196
|
+
case 'production':
|
|
197
|
+
return `https://${domain}`;
|
|
198
|
+
case 'staging':
|
|
199
|
+
return `https://${domain}`;
|
|
200
|
+
case 'development':
|
|
201
|
+
return '*'; // Allow all in development
|
|
202
|
+
default:
|
|
203
|
+
return '*';
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Version formatting
|
|
210
|
+
*/
|
|
211
|
+
export const VersionFormatters = {
|
|
212
|
+
/**
|
|
213
|
+
* Normalize version string
|
|
214
|
+
*/
|
|
215
|
+
normalize(version) {
|
|
216
|
+
const match = version.match(/(\d+\.\d+\.\d+)/);
|
|
217
|
+
return match ? match[1] : '1.0.0';
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Bump version
|
|
222
|
+
*/
|
|
223
|
+
bump(version, type = 'patch') {
|
|
224
|
+
const [major, minor, patch] = version.split('.').map(Number);
|
|
225
|
+
|
|
226
|
+
switch (type) {
|
|
227
|
+
case 'major':
|
|
228
|
+
return `${major + 1}.0.0`;
|
|
229
|
+
case 'minor':
|
|
230
|
+
return `${major}.${minor + 1}.0`;
|
|
231
|
+
case 'patch':
|
|
232
|
+
default:
|
|
233
|
+
return `${major}.${minor}.${patch + 1}`;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Convenience exports - combine all formatters
|
|
240
|
+
*/
|
|
241
|
+
export const Formatters = {
|
|
242
|
+
...NameFormatters,
|
|
243
|
+
...UrlFormatters,
|
|
244
|
+
...ResourceFormatters,
|
|
245
|
+
...EnvironmentFormatters,
|
|
246
|
+
...VersionFormatters
|
|
247
|
+
};
|
|
@@ -3,7 +3,17 @@
|
|
|
3
3
|
* Exports all general utility functions and managers
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
export {
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
// Interactive prompts utilities
|
|
7
|
+
export { askUser, askYesNo, askChoice, askMultiChoice, closePrompts, showProgress, askPassword } from './interactive-prompts.js';
|
|
8
|
+
|
|
9
|
+
// Error recovery and handling
|
|
10
|
+
export { ErrorRecoveryManager, withRetry, withCircuitBreaker } from './error-recovery.js';
|
|
11
|
+
|
|
12
|
+
// Graceful shutdown
|
|
13
|
+
export { GracefulShutdownManager, getShutdownManager, initializeGracefulShutdown, withGracefulShutdown } from './graceful-shutdown-manager.js';
|
|
14
|
+
|
|
15
|
+
// Rate limiting
|
|
16
|
+
export { executeWithRateLimit, queueRequest, getRateLimitStatus, clearQueues, RATE_LIMITS } from './rate-limiter.js';
|
|
17
|
+
|
|
18
|
+
// Unified error handling module (Phase 3.2.3d - consolidated from 5 sources)
|
|
19
|
+
export { default as ErrorHandler, createErrorResponse, createContextualError, createErrorHandler } from './ErrorHandler.js';
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Validation Registry
|
|
3
|
+
* Centralizes all validation logic
|
|
4
|
+
* Replaces: Fragmented validation across 3+ files
|
|
5
|
+
* Savings: 100+ lines
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Import validators from src/utils (source of truth)
|
|
10
|
+
*/
|
|
11
|
+
import {
|
|
12
|
+
validateServiceName,
|
|
13
|
+
validateDomainName,
|
|
14
|
+
validateCloudflareToken,
|
|
15
|
+
validateCloudflareId,
|
|
16
|
+
validateServiceType,
|
|
17
|
+
validateEnvironment
|
|
18
|
+
} from '../../../src/utils/validation.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Validation Registry - Single source of truth for all validators
|
|
22
|
+
*/
|
|
23
|
+
export class ValidationRegistry {
|
|
24
|
+
/**
|
|
25
|
+
* Standard validation rules
|
|
26
|
+
*/
|
|
27
|
+
static RULES = {
|
|
28
|
+
// Service configuration
|
|
29
|
+
serviceName: {
|
|
30
|
+
validator: validateServiceName,
|
|
31
|
+
message: 'Service name must be 3-50 characters, lowercase with hyphens only'
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
domainName: {
|
|
35
|
+
validator: validateDomainName,
|
|
36
|
+
message: 'Domain name must be valid (e.g., example.com)'
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
serviceType: {
|
|
40
|
+
validator: validateServiceType,
|
|
41
|
+
message: 'Service type must be one of: data-service, auth-service, content-service, api-gateway, generic'
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
environment: {
|
|
45
|
+
validator: validateEnvironment,
|
|
46
|
+
message: 'Environment must be one of: development, staging, production'
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
// Cloudflare configuration
|
|
50
|
+
cloudflareToken: {
|
|
51
|
+
validator: validateCloudflareToken,
|
|
52
|
+
message: 'Cloudflare API token must be at least 20 characters'
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
cloudflareAccountId: {
|
|
56
|
+
validator: validateCloudflareId,
|
|
57
|
+
message: 'Cloudflare Account ID must be 32 hexadecimal characters'
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
cloudflareZoneId: {
|
|
61
|
+
validator: validateCloudflareId,
|
|
62
|
+
message: 'Cloudflare Zone ID must be 32 hexadecimal characters'
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Validate a value against a registered rule
|
|
68
|
+
* Returns: { valid: boolean, message: string }
|
|
69
|
+
*/
|
|
70
|
+
static validate(ruleName, value) {
|
|
71
|
+
const rule = this.RULES[ruleName];
|
|
72
|
+
|
|
73
|
+
if (!rule) {
|
|
74
|
+
return {
|
|
75
|
+
valid: false,
|
|
76
|
+
message: `Unknown validation rule: ${ruleName}`
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const isValid = rule.validator(value);
|
|
82
|
+
return {
|
|
83
|
+
valid: isValid,
|
|
84
|
+
message: isValid ? 'Valid' : rule.message
|
|
85
|
+
};
|
|
86
|
+
} catch (error) {
|
|
87
|
+
return {
|
|
88
|
+
valid: false,
|
|
89
|
+
message: `Validation error: ${error.message}`
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Validate multiple fields
|
|
96
|
+
* Returns: { valid: boolean, errors: Map<fieldName, message> }
|
|
97
|
+
*/
|
|
98
|
+
static validateMultiple(fields) {
|
|
99
|
+
const errors = new Map();
|
|
100
|
+
|
|
101
|
+
for (const [fieldName, value] of Object.entries(fields)) {
|
|
102
|
+
const result = this.validate(fieldName, value);
|
|
103
|
+
if (!result.valid) {
|
|
104
|
+
errors.set(fieldName, result.message);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
valid: errors.size === 0,
|
|
110
|
+
errors
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Register custom validation rule
|
|
116
|
+
* Usage: ValidationRegistry.register('customRule', customValidator, 'Custom error message')
|
|
117
|
+
*/
|
|
118
|
+
static register(ruleName, validator, message = 'Invalid value') {
|
|
119
|
+
this.RULES[ruleName] = {
|
|
120
|
+
validator,
|
|
121
|
+
message
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get all registered rules
|
|
127
|
+
*/
|
|
128
|
+
static getAllRules() {
|
|
129
|
+
return Object.keys(this.RULES);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get rule details
|
|
134
|
+
*/
|
|
135
|
+
static getRule(ruleName) {
|
|
136
|
+
return this.RULES[ruleName];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Export for convenience
|
|
142
|
+
*/
|
|
143
|
+
export const validators = ValidationRegistry;
|