@tamyla/clodo-framework 4.4.0 → 4.4.1
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 +7 -0
- package/dist/routing/EnhancedRouter.js +63 -0
- package/dist/utils/config/environment-var-normalizer.js +233 -0
- package/docs/CHANGELOG.md +1877 -0
- package/docs/api-reference.md +153 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [4.4.1](https://github.com/tamylaa/clodo-framework/compare/v4.4.0...v4.4.1) (2026-02-06)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **routing,config:** add express-like router methods and env var standardization for v4.4.1 ([7f35681](https://github.com/tamylaa/clodo-framework/commit/7f35681ee3e19173cd41f6cc31ed1565d0aaf945))
|
|
7
|
+
|
|
1
8
|
# [4.4.0](https://github.com/tamylaa/clodo-framework/compare/v4.3.5...v4.4.0) (2026-02-05)
|
|
2
9
|
|
|
3
10
|
|
|
@@ -38,6 +38,69 @@ export class EnhancedRouter {
|
|
|
38
38
|
this.routes.set(key, handler);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Express-like convenience method: Register GET route
|
|
43
|
+
* @param {string} path - Route path
|
|
44
|
+
* @param {Function} handler - Route handler
|
|
45
|
+
*/
|
|
46
|
+
get(path, handler) {
|
|
47
|
+
return this.registerRoute('GET', path, handler);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Express-like convenience method: Register POST route
|
|
52
|
+
* @param {string} path - Route path
|
|
53
|
+
* @param {Function} handler - Route handler
|
|
54
|
+
*/
|
|
55
|
+
post(path, handler) {
|
|
56
|
+
return this.registerRoute('POST', path, handler);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Express-like convenience method: Register PUT route
|
|
61
|
+
* @param {string} path - Route path
|
|
62
|
+
* @param {Function} handler - Route handler
|
|
63
|
+
*/
|
|
64
|
+
put(path, handler) {
|
|
65
|
+
return this.registerRoute('PUT', path, handler);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Express-like convenience method: Register PATCH route
|
|
70
|
+
* @param {string} path - Route path
|
|
71
|
+
* @param {Function} handler - Route handler
|
|
72
|
+
*/
|
|
73
|
+
patch(path, handler) {
|
|
74
|
+
return this.registerRoute('PATCH', path, handler);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Express-like convenience method: Register DELETE route
|
|
79
|
+
* @param {string} path - Route path
|
|
80
|
+
* @param {Function} handler - Route handler
|
|
81
|
+
*/
|
|
82
|
+
delete(path, handler) {
|
|
83
|
+
return this.registerRoute('DELETE', path, handler);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Express-like convenience method: Register OPTIONS route
|
|
88
|
+
* @param {string} path - Route path
|
|
89
|
+
* @param {Function} handler - Route handler
|
|
90
|
+
*/
|
|
91
|
+
options(path, handler) {
|
|
92
|
+
return this.registerRoute('OPTIONS', path, handler);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Express-like convenience method: Register HEAD route
|
|
97
|
+
* @param {string} path - Route path
|
|
98
|
+
* @param {Function} handler - Route handler
|
|
99
|
+
*/
|
|
100
|
+
head(path, handler) {
|
|
101
|
+
return this.registerRoute('HEAD', path, handler);
|
|
102
|
+
}
|
|
103
|
+
|
|
41
104
|
/**
|
|
42
105
|
* Find and execute a route handler
|
|
43
106
|
* @param {string} method - HTTP method
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Environment Variable Normalizer
|
|
5
|
+
* Standardizes service environment variable configuration across multiple formats
|
|
6
|
+
*
|
|
7
|
+
* Supports three legacy formats for backward compatibility:
|
|
8
|
+
* 1. Flat structure (recommended):
|
|
9
|
+
* service.vars = { API_KEY: "value" }
|
|
10
|
+
*
|
|
11
|
+
* 2. Nested structure (deprecated):
|
|
12
|
+
* service.environment = { vars: { API_KEY: "value" }, secrets: [...] }
|
|
13
|
+
*
|
|
14
|
+
* 3. Per-environment overrides (deprecated):
|
|
15
|
+
* service.env = { production: { vars: { ... } }, staging: { vars: { ... } } }
|
|
16
|
+
*
|
|
17
|
+
* @module EnvironmentVarNormalizer
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* EnvironmentVarNormalizer
|
|
22
|
+
* Handles conversion and validation of service environment variable formats
|
|
23
|
+
*/
|
|
24
|
+
export class EnvironmentVarNormalizer {
|
|
25
|
+
/**
|
|
26
|
+
* Normalize service configuration to standard flat structure
|
|
27
|
+
* Accepts all 3 formats and converts to unified output
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} service - Service configuration object
|
|
30
|
+
* @param {Object} options - Normalization options
|
|
31
|
+
* @param {boolean} options.warnOnDeprecated - Log deprecation warnings (default: true)
|
|
32
|
+
* @param {boolean} options.throwOnConflict - Throw error if conflicting formats found (default: false)
|
|
33
|
+
* @returns {Object} Normalized service config with flat vars and secrets
|
|
34
|
+
*/
|
|
35
|
+
static normalize(service, options = {}) {
|
|
36
|
+
const {
|
|
37
|
+
warnOnDeprecated = true,
|
|
38
|
+
throwOnConflict = false
|
|
39
|
+
} = options;
|
|
40
|
+
const result = {
|
|
41
|
+
...service,
|
|
42
|
+
vars: {},
|
|
43
|
+
secrets: [],
|
|
44
|
+
_normalizationInfo: {
|
|
45
|
+
formatDetected: null,
|
|
46
|
+
deprecatedFormatsFound: [],
|
|
47
|
+
warnings: []
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Check which formats are present
|
|
52
|
+
const hasFlat = 'vars' in service;
|
|
53
|
+
const hasNested = 'environment' in service && service.environment?.vars;
|
|
54
|
+
const hasPerEnv = 'env' in service && this.hasPerEnvironmentVars(service.env);
|
|
55
|
+
|
|
56
|
+
// Track what we found
|
|
57
|
+
const formatsFound = [];
|
|
58
|
+
if (hasFlat) formatsFound.push('flat');
|
|
59
|
+
if (hasNested) formatsFound.push('nested');
|
|
60
|
+
if (hasPerEnv) formatsFound.push('per-environment');
|
|
61
|
+
|
|
62
|
+
// Handle conflicts
|
|
63
|
+
if (formatsFound.length > 1) {
|
|
64
|
+
const warning = `Service '${service.name}' uses multiple var formats: [${formatsFound.join(', ')}]. Using flat format as primary.`;
|
|
65
|
+
result._normalizationInfo.warnings.push(warning);
|
|
66
|
+
if (warnOnDeprecated) {
|
|
67
|
+
console.warn(`⚠️ ${warning}`);
|
|
68
|
+
}
|
|
69
|
+
if (throwOnConflict) {
|
|
70
|
+
throw new Error(`Configuration conflict: Service '${service.name}' has conflicting var formats. ` + `Please use only the flat format: service.vars = {...}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Extract flat format (primary)
|
|
75
|
+
if (hasFlat && service.vars && typeof service.vars === 'object') {
|
|
76
|
+
result.vars = {
|
|
77
|
+
...service.vars
|
|
78
|
+
};
|
|
79
|
+
result._normalizationInfo.formatDetected = 'flat';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Extract flat secrets
|
|
83
|
+
if (Array.isArray(service.secrets)) {
|
|
84
|
+
result.secrets = [...service.secrets];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Extract nested format (deprecated)
|
|
88
|
+
if (hasNested) {
|
|
89
|
+
result._normalizationInfo.deprecatedFormatsFound.push('nested');
|
|
90
|
+
if (warnOnDeprecated) {
|
|
91
|
+
console.warn(`⚠️ DEPRECATION: Service '${service.name}' uses nested format (service.environment.vars). ` + `This will be removed in v5.0.0. Use flat format instead: service.vars = {...}`);
|
|
92
|
+
}
|
|
93
|
+
const nestedVars = service.environment.vars || {};
|
|
94
|
+
Object.assign(result.vars, nestedVars);
|
|
95
|
+
|
|
96
|
+
// Extract secrets from nested format (only if not already set from flat)
|
|
97
|
+
if (Array.isArray(service.environment.secrets) && result.secrets.length === 0) {
|
|
98
|
+
result.secrets = [...service.environment.secrets];
|
|
99
|
+
}
|
|
100
|
+
if (!result._normalizationInfo.formatDetected) {
|
|
101
|
+
result._normalizationInfo.formatDetected = 'nested';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Extract per-environment format (deprecated)
|
|
106
|
+
if (hasPerEnv) {
|
|
107
|
+
result._normalizationInfo.deprecatedFormatsFound.push('per-environment');
|
|
108
|
+
if (warnOnDeprecated) {
|
|
109
|
+
console.warn(`⚠️ DEPRECATION: Service '${service.name}' uses per-environment format (service.env.{environment}.vars). ` + `This will be removed in v5.0.0. Use flat format instead: service.vars = {...} ` + `(Clodo will handle environment-specific overrides internally)`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Merge all environment vars
|
|
113
|
+
for (const [envName, envConfig] of Object.entries(service.env)) {
|
|
114
|
+
if (envConfig?.vars && typeof envConfig.vars === 'object') {
|
|
115
|
+
Object.assign(result.vars, envConfig.vars);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (!result._normalizationInfo.formatDetected) {
|
|
119
|
+
result._normalizationInfo.formatDetected = 'per-environment';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Remove deprecated properties from result
|
|
124
|
+
if (hasNested) {
|
|
125
|
+
delete result.environment;
|
|
126
|
+
}
|
|
127
|
+
if (hasPerEnv) {
|
|
128
|
+
// Don't delete env - it might have other valid properties like name
|
|
129
|
+
// Just remove the vars from each environment config
|
|
130
|
+
if (result.env && typeof result.env === 'object') {
|
|
131
|
+
for (const envConfig of Object.values(result.env)) {
|
|
132
|
+
if (envConfig?.vars) {
|
|
133
|
+
delete envConfig.vars;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Check if env object contains per-environment var configs
|
|
143
|
+
* @private
|
|
144
|
+
*/
|
|
145
|
+
static hasPerEnvironmentVars(env) {
|
|
146
|
+
if (!env || typeof env !== 'object') return false;
|
|
147
|
+
for (const envConfig of Object.values(env)) {
|
|
148
|
+
if (envConfig?.vars && typeof envConfig.vars === 'object') {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get deprecation timeline for the current version
|
|
157
|
+
* @param {string} currentVersion - Current framework version (e.g., "4.4.1")
|
|
158
|
+
* @returns {Object} Deprecation timeline with dates and versions
|
|
159
|
+
*/
|
|
160
|
+
static getDeprecationTimeline(currentVersion = '4.4.1') {
|
|
161
|
+
return {
|
|
162
|
+
current: {
|
|
163
|
+
version: currentVersion,
|
|
164
|
+
status: 'DEPRECATED (but supported)',
|
|
165
|
+
message: 'Nested and per-environment formats are still supported but generate warnings'
|
|
166
|
+
},
|
|
167
|
+
v4_5_0: {
|
|
168
|
+
version: '4.5.0',
|
|
169
|
+
eta: 'Q2 2026 (May 2026)',
|
|
170
|
+
status: 'WARNINGS REQUIRED',
|
|
171
|
+
message: 'All uses of deprecated formats must emit console warnings during deployment'
|
|
172
|
+
},
|
|
173
|
+
v5_0_0: {
|
|
174
|
+
version: '5.0.0',
|
|
175
|
+
eta: 'Q3 2026 (July 2026)',
|
|
176
|
+
status: 'REMOVAL',
|
|
177
|
+
message: 'Nested and per-environment formats will no longer be supported. Only flat format accepted.'
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Validate that vars follow naming conventions
|
|
184
|
+
* @param {Object} vars - Variables object
|
|
185
|
+
* @returns {Object} Validation result with issues array
|
|
186
|
+
*/
|
|
187
|
+
static validateNamingConventions(vars) {
|
|
188
|
+
const issues = [];
|
|
189
|
+
if (!vars || typeof vars !== 'object') {
|
|
190
|
+
return {
|
|
191
|
+
valid: true,
|
|
192
|
+
issues
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
196
|
+
// Check for hyphens first
|
|
197
|
+
if (key.includes('-')) {
|
|
198
|
+
issues.push({
|
|
199
|
+
key,
|
|
200
|
+
issue: 'Hyphens not allowed in variable names',
|
|
201
|
+
message: `Variable name '${key}' contains hyphens. Use underscores instead: ${key.replace(/-/g, '_')}`,
|
|
202
|
+
severity: 'error'
|
|
203
|
+
});
|
|
204
|
+
continue; // Skip other checks for this key
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Check for dots
|
|
208
|
+
if (key.includes('.')) {
|
|
209
|
+
issues.push({
|
|
210
|
+
key,
|
|
211
|
+
issue: 'Dots not allowed in variable names',
|
|
212
|
+
message: `Variable name '${key}' contains dots. Use underscores instead: ${key.replace(/\./g, '_')}`,
|
|
213
|
+
severity: 'error'
|
|
214
|
+
});
|
|
215
|
+
continue; // Skip other checks for this key
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Check for valid identifier format
|
|
219
|
+
if (!/^[A-Z_][A-Z0-9_]*$/.test(key)) {
|
|
220
|
+
issues.push({
|
|
221
|
+
key,
|
|
222
|
+
issue: 'Invalid variable name format',
|
|
223
|
+
message: `Variable name '${key}' doesn't follow SCREAMING_SNAKE_CASE convention. ` + `Use uppercase letters, numbers, and underscores only (must start with letter or underscore).`,
|
|
224
|
+
severity: 'warning'
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
valid: issues.filter(i => i.severity === 'error').length === 0,
|
|
230
|
+
issues
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|