@tamyla/clodo-framework 4.0.13 → 4.0.15

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.
Files changed (82) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +7 -0
  3. package/dist/cli/commands/create.js +2 -1
  4. package/dist/index.js +5 -0
  5. package/dist/middleware/Composer.js +38 -0
  6. package/dist/middleware/Registry.js +14 -0
  7. package/dist/middleware/index.js +3 -0
  8. package/dist/middleware/shared/basicAuth.js +21 -0
  9. package/dist/middleware/shared/cors.js +28 -0
  10. package/dist/middleware/shared/index.js +3 -0
  11. package/dist/middleware/shared/logging.js +14 -0
  12. package/dist/monitoring/HealthChecker.js +286 -0
  13. package/dist/orchestration/modules/StateManager.js +11 -3
  14. package/dist/security/ConfigurationValidator.js +216 -0
  15. package/dist/service-management/GenerationEngine.js +13 -2
  16. package/dist/service-management/ServiceOrchestrator.js +6 -2
  17. package/dist/service-management/generators/BaseGenerator.js +31 -6
  18. package/dist/service-management/generators/code/ServiceMiddlewareGenerator.js +156 -10
  19. package/dist/service-management/generators/code/WorkerIndexGenerator.js +75 -9
  20. package/dist/service-management/generators/utils/FileWriter.js +13 -1
  21. package/dist/services/ServiceClient.js +239 -0
  22. package/dist/simple-api.js +32 -1
  23. package/dist/utils/CircuitBreaker.js +192 -0
  24. package/dist/utils/EnvironmentValidator.js +147 -0
  25. package/dist/utils/TemplateRuntime.js +291 -0
  26. package/dist/utils/deployment/secret-generator.js +37 -26
  27. package/dist/version/FrameworkInfo.js +104 -0
  28. package/dist/worker/integration.js +13 -1
  29. package/docs/MIDDLEWARE_MIGRATION_SUMMARY.md +121 -0
  30. package/package.json +8 -1
  31. package/scripts/DEPLOY_COMMAND_NEW.js +128 -0
  32. package/scripts/README-automated-testing-suite.md +356 -0
  33. package/scripts/README-test-clodo-deployment.md +157 -0
  34. package/scripts/README.md +50 -0
  35. package/scripts/analyze-imports.ps1 +104 -0
  36. package/scripts/analyze-mixed-code.js +163 -0
  37. package/scripts/analyze-mixed-rationale.js +149 -0
  38. package/scripts/automated-testing-suite.js +776 -0
  39. package/scripts/check-templates.js +105 -0
  40. package/scripts/debug-generate-worker.js +58 -0
  41. package/scripts/deployment/README.md +31 -0
  42. package/scripts/deployment/deploy-domain.ps1 +449 -0
  43. package/scripts/deployment/deploy-staging.js +120 -0
  44. package/scripts/deployment/validate-staging.js +166 -0
  45. package/scripts/diagnose-imports.js +362 -0
  46. package/scripts/framework-diagnostic.js +368 -0
  47. package/scripts/migration/migrate-middleware-legacy-to-contract.js +64 -0
  48. package/scripts/post-publish-test.js +663 -0
  49. package/scripts/scan-worker-issues.js +52 -0
  50. package/scripts/service-management/README.md +27 -0
  51. package/scripts/service-management/setup-interactive.ps1 +693 -0
  52. package/scripts/test-clodo-deployment.js +588 -0
  53. package/scripts/test-downstream-install.js +237 -0
  54. package/scripts/test-local-package.ps1 +126 -0
  55. package/scripts/test-local-package.sh +166 -0
  56. package/scripts/test-package.js +339 -0
  57. package/scripts/testing/README.md +49 -0
  58. package/scripts/testing/test-first.ps1 +0 -0
  59. package/scripts/testing/test-first50.ps1 +0 -0
  60. package/scripts/testing/test.ps1 +0 -0
  61. package/scripts/utilities/README.md +61 -0
  62. package/scripts/utilities/check-bin.js +8 -0
  63. package/scripts/utilities/check-bundle.js +23 -0
  64. package/scripts/utilities/check-dist-imports.js +65 -0
  65. package/scripts/utilities/check-import-paths.js +191 -0
  66. package/scripts/utilities/cleanup-cli.js +159 -0
  67. package/scripts/utilities/deployment-helpers.ps1 +199 -0
  68. package/scripts/utilities/fix-dist-imports.js +135 -0
  69. package/scripts/utilities/generate-secrets.js +159 -0
  70. package/scripts/utilities/safe-push.ps1 +51 -0
  71. package/scripts/utilities/setup-helpers.ps1 +206 -0
  72. package/scripts/utilities/test-packaged-artifact.js +92 -0
  73. package/scripts/utilities/validate-dist-imports.js +189 -0
  74. package/scripts/utilities/validate-schema.js +102 -0
  75. package/scripts/verify-exports.js +193 -0
  76. package/scripts/verify-persistence-config.js +45 -0
  77. package/scripts/verify-worker-safety.js +73 -0
  78. package/templates/generic/src/config/.config-is-sample +1 -0
  79. package/templates/static-site/.env.example +1 -1
  80. package/templates/static-site/src/config/.config-is-sample +1 -0
  81. package/templates/static-site/src/config/domains.js +3 -0
  82. package/types/middleware.d.ts +1 -0
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Template Runtime Coordination
3
+ * Provides consistent code generation helpers for templates
4
+ */
5
+
6
+ /**
7
+ * Template Runtime for consistent code generation
8
+ */
9
+ export class TemplateRuntime {
10
+ /**
11
+ * Generate consistent binding objects for templates
12
+ * @param {Object} features - Enabled features configuration
13
+ * @returns {Object} Binding configuration
14
+ */
15
+ static generateBindings(features) {
16
+ const bindings = {};
17
+
18
+ // D1 Database bindings
19
+ if (features.d1) {
20
+ bindings.DATABASE = {
21
+ type: 'd1',
22
+ id: 'DB'
23
+ };
24
+ }
25
+
26
+ // KV Namespace bindings
27
+ if (features.kv) {
28
+ bindings.KV_CACHE = {
29
+ type: 'kv_namespace',
30
+ id: 'CACHE'
31
+ };
32
+ }
33
+
34
+ // R2 Bucket bindings
35
+ if (features.r2) {
36
+ bindings.R2_STORAGE = {
37
+ type: 'r2_bucket',
38
+ id: 'STORAGE'
39
+ };
40
+ }
41
+
42
+ // Durable Object bindings
43
+ if (features.durableObjects) {
44
+ bindings.DURABLE_OBJECT = {
45
+ type: 'durable_object',
46
+ className: 'MyDurableObject'
47
+ };
48
+ }
49
+
50
+ // Queue bindings
51
+ if (features.queue) {
52
+ bindings.QUEUE = {
53
+ type: 'queue',
54
+ queueName: 'my-queue'
55
+ };
56
+ }
57
+
58
+ // Email bindings
59
+ if (features.email) {
60
+ bindings.EMAIL = {
61
+ type: 'email'
62
+ };
63
+ }
64
+ return bindings;
65
+ }
66
+
67
+ /**
68
+ * Generate consistent import statements for templates
69
+ * @param {Object} features - Enabled features configuration
70
+ * @returns {string[]} Array of import statements
71
+ */
72
+ static generateImports(features) {
73
+ const imports = [];
74
+
75
+ // Framework imports
76
+ imports.push("import { initializeService } from '@tamyla/clodo-framework/worker';");
77
+
78
+ // Feature-specific imports
79
+ if (features.d1) {
80
+ imports.push("import { D1Database } from '@cloudflare/workers-types';");
81
+ }
82
+ if (features.kv) {
83
+ imports.push("import { KVNamespace } from '@cloudflare/workers-types';");
84
+ }
85
+ if (features.r2) {
86
+ imports.push("import { R2Bucket } from '@cloudflare/workers-types';");
87
+ }
88
+ if (features.durableObjects) {
89
+ imports.push("import { DurableObject } from '@cloudflare/workers-types';");
90
+ }
91
+ if (features.queue) {
92
+ imports.push("import { Queue } from '@cloudflare/workers-types';");
93
+ }
94
+ if (features.email) {
95
+ imports.push("import { EmailMessage } from '@cloudflare/workers-types';");
96
+ }
97
+
98
+ // Utility imports
99
+ if (features.logging) {
100
+ imports.push("import { Logger } from '@tamyla/clodo-framework/utils';");
101
+ }
102
+ if (features.validation) {
103
+ imports.push("import { validateRequest } from '@tamyla/clodo-framework/utils';");
104
+ }
105
+ return imports;
106
+ }
107
+
108
+ /**
109
+ * Generate consistent route handlers for templates
110
+ * @param {Object} routes - Route configuration
111
+ * @param {Object} features - Enabled features
112
+ * @returns {string} Generated route handler code
113
+ */
114
+ static generateRouteHandlers(routes, features) {
115
+ let code = '';
116
+
117
+ // Generate route handling logic
118
+ code += 'export async function handleRequest(request, env, ctx) {\n';
119
+ code += ' const url = new URL(request.url);\n';
120
+ code += ' const method = request.method;\n\n';
121
+
122
+ // Add route matching
123
+ for (const [path, config] of Object.entries(routes)) {
124
+ code += ` if (url.pathname === '${path}') {\n`;
125
+
126
+ // Method-specific handling
127
+ if (config.methods && Array.isArray(config.methods)) {
128
+ code += ` if (${config.methods.map(m => `method === '${m}'`).join(' || ')}) {\n`;
129
+ }
130
+
131
+ // Feature guards
132
+ if (config.features) {
133
+ for (const feature of config.features) {
134
+ code += ` // Check ${feature} feature\n`;
135
+ code += ` if (!env.FEATURES?.${feature}) {\n`;
136
+ code += ` return new Response('Feature not enabled', { status: 403 });\n`;
137
+ code += ` }\n`;
138
+ }
139
+ }
140
+
141
+ // Handler logic
142
+ const handlerName = this.capitalize(path.split('/').filter(Boolean).pop());
143
+ code += ` return await handle${handlerName}(request, env, ctx);\n`;
144
+ if (config.methods && Array.isArray(config.methods)) {
145
+ code += ` }\n`;
146
+ }
147
+ code += ` }\n\n`;
148
+ }
149
+ code += ' return new Response(\'Not Found\', { status: 404 });\n';
150
+ code += '}\n\n';
151
+
152
+ // Generate individual handlers
153
+ for (const [path] of Object.entries(routes)) {
154
+ const handlerName = this.capitalize(path.split('/').filter(Boolean).pop());
155
+ code += `async function handle${handlerName}(request, env, ctx) {\n`;
156
+ code += ` // TODO: Implement ${handlerName} handler\n`;
157
+ code += ` return new Response('Hello from ${handlerName}');\n`;
158
+ code += `}\n\n`;
159
+ }
160
+ return code;
161
+ }
162
+
163
+ /**
164
+ * Generate consistent middleware chain for templates
165
+ * @param {string[]} middleware - List of middleware names
166
+ * @returns {string} Generated middleware chain code
167
+ */
168
+ static generateMiddlewareChain(middleware) {
169
+ let code = '';
170
+ code += 'export function createMiddlewareChain() {\n';
171
+ code += ' const middlewares = [\n';
172
+ for (const mw of middleware) {
173
+ code += ` ${mw},\n`;
174
+ }
175
+ code += ' ];\n\n';
176
+ code += ' return (handler) => {\n';
177
+ code += ' return middlewares.reduceRight((acc, mw) => mw(acc), handler);\n';
178
+ code += ' };\n';
179
+ code += '}\n\n';
180
+ return code;
181
+ }
182
+
183
+ /**
184
+ * Generate consistent error handling for templates
185
+ * @param {Object} features - Enabled features
186
+ * @returns {string} Generated error handling code
187
+ */
188
+ static generateErrorHandling(features) {
189
+ let code = '';
190
+ code += 'export function createErrorHandler() {\n';
191
+ code += ' return async (error, request, env, ctx) => {\n';
192
+ code += ' console.error(\'Request error:\', error);\n\n';
193
+ if (features.logging) {
194
+ code += ' // Log error details\n';
195
+ code += ' await logError(error, request);\n\n';
196
+ }
197
+ code += ' // Return appropriate error response\n';
198
+ code += ' if (error.status) {\n';
199
+ code += ' return new Response(JSON.stringify({\n';
200
+ code += ' error: error.message,\n';
201
+ code += ' status: error.status\n';
202
+ code += ' }), {\n';
203
+ code += ' status: error.status,\n';
204
+ code += ' headers: { \'Content-Type\': \'application/json\' }\n';
205
+ code += ' });\n';
206
+ code += ' }\n\n';
207
+ code += ' return new Response(JSON.stringify({\n';
208
+ code += ' error: \'Internal Server Error\',\n';
209
+ code += ' status: 500\n';
210
+ code += ' }), {\n';
211
+ code += ' status: 500,\n';
212
+ code += ' headers: { \'Content-Type\': \'application/json\' }\n';
213
+ code += ' });\n';
214
+ code += ' };\n';
215
+ code += '}\n\n';
216
+ return code;
217
+ }
218
+
219
+ /**
220
+ * Generate consistent health check endpoint for templates
221
+ * @param {Object} serviceInfo - Service information
222
+ * @returns {string} Generated health check code
223
+ */
224
+ static generateHealthCheck(serviceInfo) {
225
+ let code = '';
226
+ code += 'export async function handleHealthCheck(request, env, ctx) {\n';
227
+ code += ' const health = {\n';
228
+ code += ' status: \'healthy\',\n';
229
+ code += ` service: '${serviceInfo.name || 'unknown'}',\n`;
230
+ code += ` version: '${serviceInfo.version || '1.0.0'}',\n`;
231
+ code += ` type: '${serviceInfo.type || 'generic'}',\n`;
232
+ code += ' timestamp: new Date().toISOString(),\n';
233
+ code += ' checks: {}\n';
234
+ code += ' };\n\n';
235
+
236
+ // Add database health checks
237
+ if (serviceInfo.features?.d1) {
238
+ code += ' try {\n';
239
+ code += ' // Check D1 database connectivity\n';
240
+ code += ' await env.DATABASE.prepare(\'SELECT 1\').first();\n';
241
+ code += ' health.checks.database = { status: \'healthy\' };\n';
242
+ code += ' } catch (error) {\n';
243
+ code += ' health.checks.database = { status: \'unhealthy\', error: error.message };\n';
244
+ code += ' health.status = \'unhealthy\';\n';
245
+ code += ' }\n\n';
246
+ }
247
+
248
+ // Add KV health checks
249
+ if (serviceInfo.features?.kv) {
250
+ code += ' try {\n';
251
+ code += ' // Check KV connectivity\n';
252
+ code += ' await env.KV_CACHE.put(\'health-check\', \'ok\', { expirationTtl: 60 });\n';
253
+ code += ' health.checks.kv = { status: \'healthy\' };\n';
254
+ code += ' } catch (error) {\n';
255
+ code += ' health.checks.kv = { status: \'unhealthy\', error: error.message };\n';
256
+ code += ' health.status = \'unhealthy\';\n';
257
+ code += ' }\n\n';
258
+ }
259
+
260
+ // Add R2 health checks
261
+ if (serviceInfo.features?.r2) {
262
+ code += ' try {\n';
263
+ code += ' // Check R2 bucket connectivity\n';
264
+ code += ' await env.R2_STORAGE.head(\'health-check.txt\');\n';
265
+ code += ' health.checks.r2 = { status: \'healthy\' };\n';
266
+ code += ' } catch (error) {\n';
267
+ code += ' health.checks.r2 = { status: \'unhealthy\', error: error.message };\n';
268
+ code += ' health.status = \'unhealthy\';\n';
269
+ code += ' }\n\n';
270
+ }
271
+ code += ' const statusCode = health.status === \'healthy\' ? 200 : 503;\n';
272
+ code += ' return new Response(JSON.stringify(health), {\n';
273
+ code += ' status: statusCode,\n';
274
+ code += ' headers: {\n';
275
+ code += ' \'Content-Type\': \'application/json\',\n';
276
+ code += ' \'Cache-Control\': \'no-cache\'\n';
277
+ code += ' }\n';
278
+ code += ' });\n';
279
+ code += '}\n\n';
280
+ return code;
281
+ }
282
+
283
+ /**
284
+ * Utility function to capitalize strings
285
+ * @param {string} str - String to capitalize
286
+ * @returns {string} Capitalized string
287
+ */
288
+ static capitalize(str) {
289
+ return str.charAt(0).toUpperCase() + str.slice(1);
290
+ }
291
+ }
@@ -23,15 +23,9 @@ const moduleFileManager = new FileManager({
23
23
  enableCache: true
24
24
  });
25
25
 
26
- // Handle test environment where import.meta might be transformed
27
- try {
28
- __filename = fileURLToPath(import.meta.url);
29
- __dirname = dirname(__filename);
30
- } catch (error) {
31
- // Fallback for test environment
32
- __dirname = join(process.cwd(), 'src', 'utils', 'deployment');
33
- __filename = join(__dirname, 'secret-generator.js');
34
- }
26
+ // Set __dirname relative to current working directory for compatibility
27
+ __dirname = join(process.cwd(), 'src', 'utils', 'deployment');
28
+ __filename = join(__dirname, 'secret-generator.js');
35
29
  const SECRET_CONFIGS = {
36
30
  'AUTH_JWT_SECRET': {
37
31
  length: 64,
@@ -104,13 +98,15 @@ export class EnhancedSecretManager {
104
98
  this.environmentConfigs = new Map();
105
99
  this.rotationSchedule = new Map();
106
100
 
107
- // Paths for secret management
101
+ // Persistence opt-in: secrets are sensitive. Default storage is inside .clodo-cache and persistence is disabled
102
+ this.enablePersistence = options.enablePersistence ?? process.env.CLODO_ENABLE_PERSISTENCE === '1';
103
+ // Paths for secret management (default to cache to avoid polluting workspace root)
108
104
  this.secretPaths = {
109
- root: join(this.projectRoot, 'secrets'),
110
- distribution: join(this.projectRoot, 'secrets', 'distribution'),
111
- backups: join(this.projectRoot, 'secrets', 'backups'),
105
+ root: join(this.projectRoot, '.clodo-cache', 'secrets'),
106
+ distribution: join(this.projectRoot, '.clodo-cache', 'secrets', 'distribution'),
107
+ backups: join(this.projectRoot, '.clodo-cache', 'secrets', 'backups'),
112
108
  audit: join(this.projectRoot, 'logs', 'secrets-audit.log'),
113
- templates: join(this.projectRoot, 'secrets', 'templates')
109
+ templates: join(this.projectRoot, '.clodo-cache', 'secrets', 'templates')
114
110
  };
115
111
 
116
112
  // Supported output formats
@@ -158,16 +154,21 @@ export class EnhancedSecretManager {
158
154
  console.log('');
159
155
  }
160
156
 
161
- // Create directories
162
- Object.values(this.secretPaths).forEach(path => {
163
- if (!path.endsWith('.log')) {
164
- this.ensureDirectory(path);
165
- } else {
166
- // For log files, ensure parent directory exists
167
- const logDir = dirname(path);
168
- this.ensureDirectory(logDir);
169
- }
170
- });
157
+ // Create directories only when persistence is explicitly enabled (prevents accidental creation of secrets in workspaces)
158
+ const shouldCreateDirs = this.enablePersistence && !isTestEnv && !this.dryRun;
159
+ if (shouldCreateDirs) {
160
+ Object.values(this.secretPaths).forEach(path => {
161
+ if (!path.endsWith('.log')) {
162
+ this.ensureDirectory(path);
163
+ } else {
164
+ // For log files, ensure parent directory exists
165
+ const logDir = dirname(path);
166
+ this.ensureDirectory(logDir);
167
+ }
168
+ });
169
+ } else {
170
+ console.log(' ℹ️ Secrets persistence disabled or dry-run/test env: skipping secrets directory creation');
171
+ }
171
172
  this.logSecretEvent('MANAGER_INITIALIZED', 'SYSTEM', {
172
173
  formats: Object.keys(this.outputFormats),
173
174
  mode: this.dryRun ? 'DRY_RUN' : 'LIVE'
@@ -698,6 +699,10 @@ ${Object.entries(SECRET_CONFIGS).map(([key, config]) => `- **${key}**: ${config.
698
699
  }
699
700
  async saveDomainSecrets(domain, environment, secretData) {
700
701
  const filename = join(this.secretPaths.root, `${domain}-${environment}-secrets.json`);
702
+ if (!this.enablePersistence) {
703
+ console.log(` ℹ️ Persistence disabled: not saving secrets to disk for ${domain} (${environment})`);
704
+ return null;
705
+ }
701
706
  if (this.dryRun) {
702
707
  console.log(` 🔍 DRY RUN: Would save secrets to ${filename}`);
703
708
  return filename;
@@ -753,7 +758,12 @@ export function generateSingleSecret(length = 32) {
753
758
  return randomBytes(length / 2).toString('hex');
754
759
  }
755
760
  export function saveSecrets(domain, environment, secrets, additionalData = {}) {
756
- const secretsDir = 'secrets';
761
+ const secretsDir = process.env.CLODO_SECRETS_DIR || '.clodo-cache/secrets';
762
+ const persistenceEnabled = process.env.CLODO_ENABLE_PERSISTENCE === '1';
763
+ if (!persistenceEnabled) {
764
+ console.log(' ℹ️ Secrets persistence disabled: saveSecrets will not write files unless CLODO_ENABLE_PERSISTENCE=1 is set.');
765
+ return null;
766
+ }
757
767
  if (!moduleFileManager.exists(secretsDir)) {
758
768
  moduleFileManager.ensureDir(secretsDir);
759
769
  }
@@ -798,7 +808,8 @@ export function loadSecrets(domain) {
798
808
  }
799
809
  }
800
810
  export function distributeSecrets(domain, secrets) {
801
- const distDir = join('secrets', 'distribution', domain);
811
+ const distBase = process.env.CLODO_SECRETS_DIR || '.clodo-cache/secrets';
812
+ const distDir = join(distBase, 'distribution', domain);
802
813
  moduleFileManager.ensureDir(distDir);
803
814
  const files = {};
804
815
 
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Framework Info Module
3
+ * Provides version detection and framework information
4
+ */
5
+
6
+ import { readFileSync } from 'fs';
7
+ import { resolve, dirname } from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+
12
+ /**
13
+ * Framework Information and Version Detection
14
+ */
15
+ export class FrameworkInfo {
16
+ /**
17
+ * Get the current framework version from the consuming service's package.json
18
+ * @param {string} serviceRoot - Root directory of the consuming service (optional)
19
+ * @returns {string} Framework version or 'unknown' if not found
20
+ */
21
+ static getVersion(serviceRoot = null) {
22
+ try {
23
+ // Try to find package.json in the consuming service
24
+ const searchPaths = [serviceRoot, process.cwd(), resolve(process.cwd(), '..'), resolve(process.cwd(), '../..')].filter(Boolean);
25
+ for (const searchPath of searchPaths) {
26
+ try {
27
+ const packagePath = resolve(searchPath, 'package.json');
28
+ const packageContent = readFileSync(packagePath, 'utf8');
29
+ const packageJson = JSON.parse(packageContent);
30
+
31
+ // Check for the framework dependency
32
+ const frameworkDep = packageJson.dependencies?.['@tamyla/clodo-framework'] || packageJson.devDependencies?.['@tamyla/clodo-framework'];
33
+ if (frameworkDep) {
34
+ // Extract version from semver range (e.g., "^4.0.11" -> "4.0.11", ">=4.0.0" -> "4.0.0")
35
+ const version = frameworkDep.replace(/^[^\d]*/, '').split(/[\s-]/)[0];
36
+ return version || frameworkDep;
37
+ }
38
+ } catch (error) {
39
+ // Continue searching other paths
40
+ continue;
41
+ }
42
+ }
43
+
44
+ // Fallback: try to get version from the framework's own package.json
45
+ try {
46
+ const frameworkPackagePath = resolve(__dirname, '../../package.json');
47
+ const frameworkPackage = JSON.parse(readFileSync(frameworkPackagePath, 'utf8'));
48
+ return frameworkPackage.version || 'unknown';
49
+ } catch (error) {
50
+ return 'unknown';
51
+ }
52
+ } catch (error) {
53
+ console.warn('Framework version detection failed:', error.message);
54
+ return 'unknown';
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Get detailed framework information
60
+ * @param {string} serviceRoot - Root directory of the consuming service (optional)
61
+ * @returns {Object} Framework information object
62
+ */
63
+ static getInfo(serviceRoot = null) {
64
+ const version = this.getVersion(serviceRoot);
65
+ return {
66
+ name: '@tamyla/clodo-framework',
67
+ version: version,
68
+ detected: version !== 'unknown',
69
+ timestamp: new Date().toISOString(),
70
+ environment: {
71
+ node: process.version,
72
+ platform: process.platform,
73
+ arch: process.arch
74
+ }
75
+ };
76
+ }
77
+
78
+ /**
79
+ * Check if the current version meets minimum requirements
80
+ * @param {string} minVersion - Minimum required version
81
+ * @param {string} serviceRoot - Root directory of the consuming service (optional)
82
+ * @returns {boolean} True if version meets requirements
83
+ */
84
+ static meetsMinimumVersion(minVersion, serviceRoot = null) {
85
+ const currentVersion = this.getVersion(serviceRoot);
86
+ if (currentVersion === 'unknown') {
87
+ return false;
88
+ }
89
+ try {
90
+ const current = currentVersion.split('.').map(Number);
91
+ const minimum = minVersion.split('.').map(Number);
92
+ for (let i = 0; i < Math.max(current.length, minimum.length); i++) {
93
+ const currentPart = current[i] || 0;
94
+ const minimumPart = minimum[i] || 0;
95
+ if (currentPart > minimumPart) return true;
96
+ if (currentPart < minimumPart) return false;
97
+ }
98
+ return true; // versions are equal
99
+ } catch (error) {
100
+ console.warn('Version comparison failed:', error.message);
101
+ return false;
102
+ }
103
+ }
104
+ }
@@ -3,6 +3,13 @@ import { getDomainFromEnv, createEnvironmentConfig } from '../config/domains.js'
3
3
  // Import COMMON_FEATURES from worker-safe constants (no Node.js dependencies)
4
4
  import { COMMON_FEATURES } from './features.js';
5
5
 
6
+ // Import new framework components (worker-safe only)
7
+ import { FrameworkInfo } from '../version/FrameworkInfo.js';
8
+ import { EnvironmentValidator } from '../utils/EnvironmentValidator.js';
9
+ import { ServiceClient } from '../services/ServiceClient.js';
10
+ import { HealthChecker } from '../monitoring/HealthChecker.js';
11
+ // Note: ConfigurationValidator requires Node.js APIs, so it's not included in worker runtime
12
+
6
13
  // Re-export COMMON_FEATURES for use in worker templates
7
14
  export { COMMON_FEATURES };
8
15
 
@@ -58,7 +65,12 @@ export const initializeService = (env, domainConfigs = {}) => {
58
65
  env,
59
66
  isProduction: environment === 'production',
60
67
  isStaging: environment === 'staging',
61
- isDevelopment: environment === 'development'
68
+ isDevelopment: environment === 'development',
69
+ // Enhanced framework features (worker-safe only)
70
+ serviceClient: new ServiceClient(),
71
+ healthChecker: new HealthChecker(),
72
+ environmentValidator: EnvironmentValidator,
73
+ frameworkInfo: FrameworkInfo.getInfo()
62
74
  };
63
75
  logger.info(`Service initialized: ${domainConfig.name} (${environment})`);
64
76
  logger.debug(`Enabled features: ${serviceContext.features.join(', ')}`);
@@ -0,0 +1,121 @@
1
+ # Middleware Architecture Migration Summary (v4.1)
2
+
3
+ ## 📌 Purpose
4
+ This document captures the design decisions, implementation details, assumptions, tests, migration steps, and next actions for the contract-first middleware migration (v4.1): moving from per-service concrete middleware implementations (`createServiceMiddleware`) to lightweight middleware contracts with a shared runtime (Registry + Composer).
5
+
6
+ ---
7
+
8
+ ## 🔧 What was implemented
9
+ - Default approach: **contract-first** middleware generation with opt-in `legacy` strategy.
10
+ - New runtime primitives (framework-level):
11
+ - `src/middleware/Registry.js` - central registry API used in tests and tooling.
12
+ - `src/middleware/Composer.js` - middleware composer that executes middleware chains with short-circuiting and post-processing.
13
+ - `src/middleware/types.d.ts` - TypeScript types describing `IServiceMiddleware` and `IMiddlewareChain`.
14
+ - `src/middleware/shared/` - framework shared middleware: `cors`, `logging`, `basicAuth` (exports in `index.js`).
15
+ - Generator changes:
16
+ - `ServiceMiddlewareGenerator` now emits either:
17
+ - A minimal contract class + `registerMiddleware` helper (contract strategy), or
18
+ - A legacy `createServiceMiddleware` factory (legacy strategy).
19
+ - Generator also emits a self-contained `src/middleware/runtime.js` and a `src/middleware/shared/` folder when generating services (for self-containment).
20
+ - Worker entry updates:
21
+ - `WorkerIndexGenerator` updated to import middleware runtime and compose pipeline using `MiddlewareComposer` + `MiddlewareRegistry`.
22
+ - Worker fetch runtime supports both contract registration and legacy factory via an adapter.
23
+ - Migration tools & tests:
24
+ - `scripts/migration/migrate-middleware-legacy-to-contract.js` (basic codemod to convert legacy factories to contract skeletons + `registerMiddleware`).
25
+ - Unit tests added for `Registry`, `Composer`, `shared/cors`, generator outputs, and migration script.
26
+ - CLI:
27
+ - `cli/commands/create.js` adds `--middleware-strategy <strategy>` CLI flag (default `contract`).
28
+ - Docs & changelog updated:
29
+ - `docs/HOWTO_CONSUME_CLODO_FRAMEWORK.md` updated to describe the new approach.
30
+ - `CHANGELOG.md` mentions the v4.1 middleware architecture change.
31
+
32
+ ---
33
+
34
+ ## ✅ Files added / modified (high-level)
35
+ - Added:
36
+ - `src/middleware/Registry.js`
37
+ - `src/middleware/Composer.js`
38
+ - `src/middleware/types.d.ts`
39
+ - `src/middleware/shared/{cors.js,logging.js,basicAuth.js,index.js}`
40
+ - `scripts/migration/migrate-middleware-legacy-to-contract.js`
41
+ - `docs/MIDDLEWARE_MIGRATION_SUMMARY.md` (this file)
42
+ - Tests: `test/utils/middleware/{Registry,Composer,cors}.test.js`, generator tests and migration test
43
+ - Modified (generators & templates):
44
+ - `src/service-management/generators/code/ServiceMiddlewareGenerator.js` (now emits contract or legacy output and service-local runtime/shared stubs)
45
+ - `src/service-management/generators/code/WorkerIndexGenerator.js` (loads middleware using dynamic import and composes pipeline; includes legacy adapter)
46
+ - `src/service-management/GenerationEngine.js` (passes `middlewareStrategy` through context)
47
+ - `src/service-management/ServiceOrchestrator.js` & `src/simple-api.js` (accept middleware strategy)
48
+ - `cli/commands/create.js` (new `--middleware-strategy` option)
49
+
50
+ ---
51
+
52
+ ## 🧭 Key assumptions made
53
+ 1. Default strategy is `contract` (opt-in `legacy`).
54
+ 2. Generated services are self-contained by default (runtime + shared stubs added into generated service) to work standalone.
55
+ 3. Legacy factory shape expected to return object with `processRequest(request)` and `processResponse(response)` methods.
56
+ 4. Runtime dynamic import is acceptable in Worker code (`await import('../middleware/service-middleware.js')`).
57
+ 5. TypeScript users will later add TS skeletons or `.d.ts` files if they require compile-time checks.
58
+
59
+ If any of these assumptions are not desirable for your environment, they require small changes (see "Nuances and review points").
60
+
61
+ ---
62
+
63
+ ## ⚠️ Nuances & review points (actionable)
64
+ - Registry duplication:
65
+ - There are both framework-level `Registry.js` and a service-local `runtime.js` (generated by the generator). Decide whether you want a single shared runtime or per-service runtime. If you prefer a single runtime, update the generator to import the framework runtime rather than generate a local copy.
66
+ - Bundler behavior & dynamic imports:
67
+ - The dynamic import pattern must be tested with your bundler (esbuild/wrangler). Ensure bundler includes generated middleware files in the built artifact.
68
+ - Adapter coverage:
69
+ - The legacy adapter covers the common `createServiceMiddleware` factory shape. If your legacy services used other shapes, extend the adapter logic accordingly.
70
+ - TypeScript support:
71
+ - Generated skeletons are JavaScript; if you primarily use TypeScript, consider adding `.ts` templates and `.d.ts` exports for better DX.
72
+ - Migration tool limitations:
73
+ - The codemod is intentionally conservative and may require manual editing for complex legacy middleware logic.
74
+ - Security/logging:
75
+ - The example `logging` middleware uses `console.log` directly—avoid logging sensitive headers. Consider adding redaction configuration.
76
+
77
+ ---
78
+
79
+ ## 🔁 Migration steps (recommended)
80
+ 1. Try generating a new service with default (contract) strategy to validate the new flow:
81
+ - `npx clodo-service create --middleware-strategy contract` (or omit flag — default is `contract`).
82
+ 2. To generate an old-style output for a single new service for comparison: `npx clodo-service create --middleware-strategy legacy`.
83
+ 3. For existing services using legacy middleware:
84
+ - Run `node scripts/migration/migrate-middleware-legacy-to-contract.js <servicePath> [serviceName]`.
85
+ - Review the converted skeleton and verify imports and custom logic—make adjustments as required.
86
+ 4. Update tests and CI to include a smoke test that deploys/loads the generated service (both contract and legacy flows).
87
+
88
+ ---
89
+
90
+ ## ✅ Tests & QA to add (if not already present)
91
+ - Integration tests that:
92
+ - Generate a service with `contract` strategy and verify `registerMiddleware` is invoked and runtime pipeline executes correctly.
93
+ - Generate a service with `legacy` strategy and verify the legacy adapter path executes `createServiceMiddleware`.
94
+ - Bundle-size smoke test that measures the generated artifact size between legacy and contract strategies.
95
+ - Migration tests on representative real-world legacy middleware shapes.
96
+
97
+ ---
98
+
99
+ ## 📋 Next steps & open todos
100
+ - Complete migration of tests and e2e fixtures that currently rely on legacy middleware.
101
+ - Add TypeScript skeleton templates (optional, recommended for TS-first projects).
102
+ - Decide on runtime model: service-local runtime (current) vs centralized runtime (change generator behavior if you prefer central runtime).
103
+ - Add more robust adapter patterns if you encounter edge-case legacy middleware shapes in the wild.
104
+
105
+ ---
106
+
107
+ ## 📎 Helpful quick references
108
+ - Generator: `src/service-management/generators/code/ServiceMiddlewareGenerator.js`
109
+ - Worker: `src/service-management/generators/code/WorkerIndexGenerator.js`
110
+ - Migration: `scripts/migration/migrate-middleware-legacy-to-contract.js`
111
+ - Runtime: `src/middleware/{Registry.js,Composer.js}`
112
+ - Tests: `test/utils/middleware/*`, `test/service-management/generators/*`, `test/scripts/migration/*`
113
+
114
+ ---
115
+
116
+ If you'd like, I can now:
117
+ - Finish updating tests/fixtures and add the bundle-size smoke test, or
118
+ - Add TypeScript templates for generated services, or
119
+ - Replace per-service runtime with a centralized runtime (if you prefer that model).
120
+
121
+ Pick one of the bullets above and I’ll proceed. If you'd like further edits to this summary, I can fold them into this document.