@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 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
+ }