@tamyla/clodo-framework 4.0.13 → 4.0.14

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 (62) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +7 -0
  3. package/dist/cli/commands/create.js +2 -1
  4. package/dist/middleware/Composer.js +38 -0
  5. package/dist/middleware/Registry.js +14 -0
  6. package/dist/middleware/index.js +3 -0
  7. package/dist/middleware/shared/basicAuth.js +21 -0
  8. package/dist/middleware/shared/cors.js +28 -0
  9. package/dist/middleware/shared/index.js +3 -0
  10. package/dist/middleware/shared/logging.js +14 -0
  11. package/dist/service-management/GenerationEngine.js +13 -2
  12. package/dist/service-management/ServiceOrchestrator.js +6 -2
  13. package/dist/service-management/generators/code/ServiceMiddlewareGenerator.js +156 -10
  14. package/dist/service-management/generators/code/WorkerIndexGenerator.js +75 -9
  15. package/dist/simple-api.js +32 -1
  16. package/docs/MIDDLEWARE_MIGRATION_SUMMARY.md +121 -0
  17. package/package.json +4 -1
  18. package/scripts/DEPLOY_COMMAND_NEW.js +128 -0
  19. package/scripts/README-automated-testing-suite.md +356 -0
  20. package/scripts/README-test-clodo-deployment.md +157 -0
  21. package/scripts/README.md +50 -0
  22. package/scripts/analyze-imports.ps1 +104 -0
  23. package/scripts/analyze-mixed-code.js +163 -0
  24. package/scripts/analyze-mixed-rationale.js +149 -0
  25. package/scripts/automated-testing-suite.js +776 -0
  26. package/scripts/deployment/README.md +31 -0
  27. package/scripts/deployment/deploy-domain.ps1 +449 -0
  28. package/scripts/deployment/deploy-staging.js +120 -0
  29. package/scripts/deployment/validate-staging.js +166 -0
  30. package/scripts/diagnose-imports.js +362 -0
  31. package/scripts/framework-diagnostic.js +368 -0
  32. package/scripts/migration/migrate-middleware-legacy-to-contract.js +47 -0
  33. package/scripts/post-publish-test.js +663 -0
  34. package/scripts/scan-worker-issues.js +52 -0
  35. package/scripts/service-management/README.md +27 -0
  36. package/scripts/service-management/setup-interactive.ps1 +693 -0
  37. package/scripts/test-clodo-deployment.js +588 -0
  38. package/scripts/test-downstream-install.js +237 -0
  39. package/scripts/test-local-package.ps1 +126 -0
  40. package/scripts/test-local-package.sh +166 -0
  41. package/scripts/test-package.js +339 -0
  42. package/scripts/testing/README.md +49 -0
  43. package/scripts/testing/test-first.ps1 +0 -0
  44. package/scripts/testing/test-first50.ps1 +0 -0
  45. package/scripts/testing/test.ps1 +0 -0
  46. package/scripts/utilities/README.md +61 -0
  47. package/scripts/utilities/check-bin.js +8 -0
  48. package/scripts/utilities/check-bundle.js +23 -0
  49. package/scripts/utilities/check-dist-imports.js +65 -0
  50. package/scripts/utilities/check-import-paths.js +191 -0
  51. package/scripts/utilities/cleanup-cli.js +159 -0
  52. package/scripts/utilities/deployment-helpers.ps1 +199 -0
  53. package/scripts/utilities/fix-dist-imports.js +135 -0
  54. package/scripts/utilities/generate-secrets.js +159 -0
  55. package/scripts/utilities/safe-push.ps1 +51 -0
  56. package/scripts/utilities/setup-helpers.ps1 +206 -0
  57. package/scripts/utilities/test-packaged-artifact.js +92 -0
  58. package/scripts/utilities/validate-dist-imports.js +189 -0
  59. package/scripts/utilities/validate-schema.js +102 -0
  60. package/scripts/verify-exports.js +193 -0
  61. package/scripts/verify-worker-safety.js +73 -0
  62. package/types/middleware.d.ts +1 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## [4.0.14](https://github.com/tamylaa/clodo-framework/compare/v4.0.13...v4.0.14) (2026-01-19)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * middleware packaging, generators, tests, and packaged-artifact smoke test ([b001434](https://github.com/tamylaa/clodo-framework/commit/b001434697da2145e8778b5c0afd02275cd1604e))
7
+ * resolve Windows filesystem timing issues in tests ([7092ca6](https://github.com/tamylaa/clodo-framework/commit/7092ca6ffc230a19d25c96333c0313ead1739f15))
8
+
1
9
  ## [4.0.13](https://github.com/tamylaa/clodo-framework/compare/v4.0.12...v4.0.13) (2025-12-17)
2
10
 
3
11
 
@@ -173,6 +181,9 @@ Benefits:
173
181
  - Moved 13 root-level documentation files to appropriate i-docs categories
174
182
  - Root directory now contains only essential project files (config, package.json, README, etc.)
175
183
 
184
+ * **Documentation**: Added `docs/HOWTO_CONSUME_CLODO_FRAMEWORK.md` — a concise consumer guide covering CLI usage, public exports, packaging troubleshooting, and recommended consumption patterns.
185
+ * **Middleware Architecture**: Introduce contract-first middleware generation (v4.1 candidate) with `MiddlewareRegistry` and `MiddlewareComposer` to reduce duplication and improve testability. Added a migration tool `scripts/migration/migrate-middleware-legacy-to-contract.js` and a `--middleware-strategy` CLI flag to opt into `legacy` generator output.
186
+
176
187
  * **Configuration Management**: Eliminated hard-coded values from source code
177
188
  - Moved domain defaults from ServiceCreator to `validation-config.json`
178
189
  - Added configuration hierarchy: CLI option → config file → fallback default
package/README.md CHANGED
@@ -199,6 +199,13 @@ import { CloudflareAPI } from '@tamyla/clodo-framework/utils/cloudflare';
199
199
 
200
200
  If you need functionality that's currently only in `bin/`, please open an issue - we'll consider adding it to the public API.
201
201
 
202
+ ---
203
+
204
+ ## How to consume (quick)
205
+ A short guide and best practices for consuming `@tamyla/clodo-framework` are available in the docs: [docs/HOWTO_CONSUME_CLODO_FRAMEWORK.md](./docs/HOWTO_CONSUME_CLODO_FRAMEWORK.md). This file includes quickstart steps, public export guidance, CLI usage, and troubleshooting tips for packaged artifacts.
206
+
207
+ If you'd like this information added to `clodo.dev`, I can prepare a small website PR as well.
208
+
202
209
  ## Project Structure
203
210
 
204
211
  The project is organized for maximum clarity and maintainability:
@@ -9,7 +9,7 @@ import chalk from 'chalk';
9
9
  import { Clodo, ConfigLoader } from '@tamyla/clodo-framework';
10
10
  import { StandardOptions } from '../../lib/shared/utils/cli-options.js';
11
11
  export function registerCreateCommand(program) {
12
- const command = program.command('create').description('Create a new Clodo service with conversational setup').option('-n, --non-interactive', 'Run in non-interactive mode with all required parameters').option('--service-name <name>', 'Service name (required in non-interactive mode)').option('--service-type <type>', 'Service type: data-service, auth-service, content-service, api-gateway, generic', 'generic').option('--domain-name <domain>', 'Domain name (required in non-interactive mode)').option('--cloudflare-token <token>', 'Cloudflare API token (required in non-interactive mode)').option('--cloudflare-account-id <id>', 'Cloudflare account ID (required in non-interactive mode)').option('--cloudflare-zone-id <id>', 'Cloudflare zone ID (required in non-interactive mode)').option('--environment <env>', 'Target environment: development, staging, production', 'development').option('--output-path <path>', 'Output directory for generated service', '.').option('--template-path <path>', 'Path to service templates', './templates').option('--force', 'Skip confirmation prompts').option('--validate', 'Validate service after creation');
12
+ const command = program.command('create').description('Create a new Clodo service with conversational setup').option('-n, --non-interactive', 'Run in non-interactive mode with all required parameters').option('--service-name <name>', 'Service name (required in non-interactive mode)').option('--service-type <type>', 'Service type: data-service, auth-service, content-service, api-gateway, generic', 'generic').option('--domain-name <domain>', 'Domain name (required in non-interactive mode)').option('--cloudflare-token <token>', 'Cloudflare API token (required in non-interactive mode)').option('--cloudflare-account-id <id>', 'Cloudflare account ID (required in non-interactive mode)').option('--cloudflare-zone-id <id>', 'Cloudflare zone ID (required in non-interactive mode)').option('--environment <env>', 'Target environment: development, staging, production', 'development').option('--output-path <path>', 'Output directory for generated service', '.').option('--template-path <path>', 'Path to service templates', './templates').option('--middleware-strategy <strategy>', 'Middleware generation strategy: contract|legacy', 'contract').option('--force', 'Skip confirmation prompts').option('--validate', 'Validate service after creation');
13
13
 
14
14
  // Add standard options (--verbose, --quiet, --json, --no-color, --config-file)
15
15
  StandardOptions.define(command).action(async options => {
@@ -40,6 +40,7 @@ export function registerCreateCommand(program) {
40
40
  domain: mergedOptions.domainName,
41
41
  environment: mergedOptions.environment,
42
42
  outputPath: mergedOptions.outputPath,
43
+ middlewareStrategy: mergedOptions.middlewareStrategy,
43
44
  interactive: !mergedOptions.nonInteractive,
44
45
  credentials: {
45
46
  token: mergedOptions.cloudflareToken,
@@ -0,0 +1,38 @@
1
+ // Middleware composer that runs middleware in sequence and supports short-circuiting
2
+ export class MiddlewareComposer {
3
+ static compose(...middlewares) {
4
+ const chain = middlewares.filter(Boolean);
5
+ return {
6
+ async execute(request, handler) {
7
+ // Preprocess/auth/validate phases
8
+ for (const m of chain) {
9
+ if (typeof m.preprocess === 'function') {
10
+ const res = await m.preprocess(request);
11
+ if (res) return res;
12
+ }
13
+ if (typeof m.authenticate === 'function') {
14
+ const res = await m.authenticate(request);
15
+ if (res) return res;
16
+ }
17
+ if (typeof m.validate === 'function') {
18
+ const res = await m.validate(request);
19
+ if (res) return res;
20
+ }
21
+ }
22
+
23
+ // Call the final handler
24
+ let response = await handler(request);
25
+
26
+ // Postprocess in reverse order
27
+ for (const m of chain.slice().reverse()) {
28
+ if (typeof m.postprocess === 'function') {
29
+ const updated = await m.postprocess(response);
30
+ // Allow middleware to replace response
31
+ if (updated instanceof Response) response = updated;
32
+ }
33
+ }
34
+ return response;
35
+ }
36
+ };
37
+ }
38
+ }
@@ -0,0 +1,14 @@
1
+ // Registry for service-specific middleware implementations
2
+ export class MiddlewareRegistry {
3
+ static implementations = new Map();
4
+ static register(serviceName, middleware) {
5
+ if (!serviceName) throw new Error('serviceName is required');
6
+ this.implementations.set(serviceName, middleware);
7
+ }
8
+ static get(serviceName) {
9
+ return this.implementations.get(serviceName) || null;
10
+ }
11
+ static clear() {
12
+ this.implementations.clear();
13
+ }
14
+ }
@@ -0,0 +1,3 @@
1
+ export { MiddlewareRegistry } from './Registry.js';
2
+ export { MiddlewareComposer } from './Composer.js';
3
+ export * as Shared from './shared/index.js';
@@ -0,0 +1,21 @@
1
+ export function basicAuth({
2
+ realm = 'Restricted'
3
+ } = {}) {
4
+ return {
5
+ authenticate(request) {
6
+ const auth = request.headers.get('Authorization');
7
+ if (!auth) {
8
+ return new Response(JSON.stringify({
9
+ error: 'Unauthorized'
10
+ }), {
11
+ status: 401,
12
+ headers: {
13
+ 'Content-Type': 'application/json',
14
+ 'WWW-Authenticate': `Basic realm="${realm}"`
15
+ }
16
+ });
17
+ }
18
+ return null;
19
+ }
20
+ };
21
+ }
@@ -0,0 +1,28 @@
1
+ export function cors(options = {}) {
2
+ const origin = options.origin || '*';
3
+ const allowMethods = options.methods || 'GET, POST, PUT, DELETE, OPTIONS';
4
+ const allowHeaders = options.headers || 'Content-Type, Authorization';
5
+ return {
6
+ preprocess(request) {
7
+ if (request.method === 'OPTIONS') {
8
+ const headers = new Headers();
9
+ headers.set('Access-Control-Allow-Origin', origin);
10
+ headers.set('Access-Control-Allow-Methods', allowMethods);
11
+ headers.set('Access-Control-Allow-Headers', allowHeaders);
12
+ return new Response(null, {
13
+ status: 204,
14
+ headers
15
+ });
16
+ }
17
+ return null;
18
+ },
19
+ postprocess(response) {
20
+ const headers = new Headers(response.headers);
21
+ headers.set('Access-Control-Allow-Origin', origin);
22
+ return new Response(response.body, {
23
+ ...response,
24
+ headers
25
+ });
26
+ }
27
+ };
28
+ }
@@ -0,0 +1,3 @@
1
+ export { cors } from './cors.js';
2
+ export { logging } from './logging.js';
3
+ export { basicAuth } from './basicAuth.js';
@@ -0,0 +1,14 @@
1
+ export function logging(options = {}) {
2
+ const level = options.level || 'info';
3
+ return {
4
+ preprocess(request) {
5
+ try {
6
+ const path = new URL(request.url).pathname;
7
+ console.log(`[${new Date().toISOString()}] ${level.toUpperCase()} ${request.method} ${path}`);
8
+ } catch (e) {
9
+ // Best-effort logging - do not throw
10
+ }
11
+ return null;
12
+ }
13
+ };
14
+ }
@@ -52,6 +52,8 @@ export class GenerationEngine {
52
52
  this.templatesDir = options.templatesDir || join(__dirname, '..', '..', 'templates');
53
53
  this.outputDir = options.outputDir || process.cwd();
54
54
  this.force = options.force || false;
55
+ // Default middleware strategy: 'contract' (can be overridden per-generation)
56
+ this.middlewareStrategy = options.middlewareStrategy || 'contract';
55
57
 
56
58
  // Initialize generator registry for centralized management
57
59
  this.generatorRegistry = new GeneratorRegistry({
@@ -192,7 +194,7 @@ export class GenerationEngine {
192
194
  },
193
195
  generateServiceMiddleware: {
194
196
  generator: 'serviceMiddlewareGenerator',
195
- returnPath: 'src/middleware.js'
197
+ returnPath: 'src/middleware/service-middleware.js'
196
198
  },
197
199
  generateServiceUtils: {
198
200
  generator: 'serviceUtilsGenerator',
@@ -283,6 +285,8 @@ export class GenerationEngine {
283
285
  // Only create proxy method if it doesn't already exist
284
286
  if (!this[methodName]) {
285
287
  this[methodName] = async function (...args) {
288
+ // Allow per-call overrides (apply BEFORE building context so context picks it up)
289
+ if (args[0] && args[0].middlewareStrategy) this.middlewareStrategy = args[0].middlewareStrategy;
286
290
  const context = this.buildContext(methodName, ...args);
287
291
  const generator = this[config.generator];
288
292
  const result = await generator.generate(context);
@@ -315,10 +319,12 @@ export class GenerationEngine {
315
319
  }
316
320
 
317
321
  // For most methods: (coreInputs, confirmedValues, servicePath)
322
+ // Include middlewareStrategy so generators can adapt their output
318
323
  return {
319
324
  coreInputs: args[0],
320
325
  confirmedValues: args[1],
321
- servicePath: args[2]
326
+ servicePath: args[2],
327
+ middlewareStrategy: this.middlewareStrategy
322
328
  };
323
329
  }
324
330
 
@@ -334,6 +340,11 @@ export class GenerationEngine {
334
340
  outputPath: this.outputDir,
335
341
  ...options
336
342
  };
343
+
344
+ // Allow per-generation override of middleware strategy
345
+ if (config.middlewareStrategy) {
346
+ this.middlewareStrategy = config.middlewareStrategy;
347
+ }
337
348
  console.log('⚙️ Tier 3: Automated Generation');
338
349
  console.log('Generating 67+ configuration files and service components...\n');
339
350
  try {
@@ -25,6 +25,8 @@ export class ServiceOrchestrator {
25
25
  this.interactive = options.interactive !== false;
26
26
  this.outputPath = options.outputPath || '.';
27
27
  this.templatePath = options.templatePath || './templates';
28
+ // Middleware strategy for generation: 'contract' (default) or 'legacy'
29
+ this.middlewareStrategy = options.middlewareStrategy || 'contract';
28
30
 
29
31
  // Initialize modular handler components
30
32
  this.inputHandler = new InputHandler({
@@ -63,7 +65,8 @@ export class ServiceOrchestrator {
63
65
  console.log(chalk.yellow('⚙️ Tier 3: Automated Generation'));
64
66
  console.log(chalk.white('Generating 67 configuration files and service components...\n'));
65
67
  const generationResult = await this.generationHandler.generateService(coreInputs, confirmedValues, {
66
- outputPath: this.outputPath
68
+ outputPath: this.outputPath,
69
+ middlewareStrategy: this.middlewareStrategy
67
70
  });
68
71
 
69
72
  // Display results
@@ -87,7 +90,8 @@ export class ServiceOrchestrator {
87
90
 
88
91
  // Generate service using GenerationHandler
89
92
  const generationResult = await this.generationHandler.generateService(coreInputs, confirmedValues, {
90
- outputPath: this.outputPath
93
+ outputPath: this.outputPath,
94
+ middlewareStrategy: this.middlewareStrategy
91
95
  });
92
96
  console.log(chalk.green(`✓ Service "${coreInputs.serviceName}" created successfully`));
93
97
  } catch (error) {
@@ -21,8 +21,13 @@ export class ServiceMiddlewareGenerator extends BaseGenerator {
21
21
  if (!this.shouldGenerate(context)) {
22
22
  return null;
23
23
  }
24
- const middlewareContent = `/**
25
- * ${confirmedValues.displayName} - Service Middleware
24
+
25
+ // Allow legacy strategy if requested by context
26
+ const strategy = context && context.middlewareStrategy ? context.middlewareStrategy : 'contract';
27
+ let middlewareContent;
28
+ if (strategy === 'legacy') {
29
+ middlewareContent = `/**
30
+ * ${confirmedValues.displayName} - Service Middleware (LEGACY)
26
31
  *
27
32
  * Generated by Clodo Framework GenerationEngine
28
33
  * Service Type: ${coreInputs.serviceType}
@@ -47,19 +52,19 @@ export function createServiceMiddleware(serviceConfig, env) {
47
52
  headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
48
53
  }
49
54
 
50
- ${confirmedValues.features.logging ? `
55
+ ${confirmedValues.features && confirmedValues.features.logging ? `
51
56
  // Request logging
52
- console.log(\`[\${new Date().toISOString()}] \${request.method} \${request.url}\`);` : ''}
57
+ console.log('[' + new Date().toISOString() + '] ' + request.method + ' ' + request.url);` : ''}
53
58
 
54
- ${confirmedValues.features.rateLimiting ? `
59
+ ${confirmedValues.features && confirmedValues.features.rateLimiting ? `
55
60
  // Rate limiting (placeholder - implement based on requirements)
56
61
  // This would typically check request frequency and block if over limit` : ''}
57
62
 
58
- ${confirmedValues.features.authentication ? `
63
+ ${confirmedValues.features && confirmedValues.features.authentication ? `
59
64
  // Authentication middleware (placeholder)
60
65
  // This would validate JWT tokens, API keys, etc.` : ''}
61
66
 
62
- ${confirmedValues.features.authorization ? `
67
+ ${confirmedValues.features && confirmedValues.features.authorization ? `
63
68
  // Authorization middleware (placeholder)
64
69
  // This would check user permissions and roles` : ''}
65
70
 
@@ -77,11 +82,11 @@ export function createServiceMiddleware(serviceConfig, env) {
77
82
  headers.set('X-Version', '${confirmedValues.version}');
78
83
  headers.set('X-Response-Time', Date.now().toString());
79
84
 
80
- ${confirmedValues.features.monitoring ? `
85
+ ${confirmedValues.features && confirmedValues.features.monitoring ? `
81
86
  // Response monitoring
82
- console.log(\`Response: \${response.status} (\${Date.now()}ms)\`);` : ''}
87
+ console.log('Response: ' + response.status + ' (' + Date.now() + 'ms)');` : ''}
83
88
 
84
- ${confirmedValues.features.caching ? `
89
+ ${confirmedValues.features && confirmedValues.features.caching ? `
85
90
  // Cache headers (placeholder - implement based on content type)
86
91
  if (response.status === 200) {
87
92
  headers.set('Cache-Control', 'public, max-age=300'); // 5 minutes
@@ -95,6 +100,35 @@ export function createServiceMiddleware(serviceConfig, env) {
95
100
  };
96
101
  }
97
102
  `;
103
+ } else {
104
+ middlewareContent = `/**
105
+ * ${confirmedValues.displayName} - Service Middleware
106
+ *
107
+ * Generated by Clodo Framework GenerationEngine
108
+ * Service Type: ${coreInputs.serviceType}
109
+ */
110
+
111
+ // Service middleware contract skeleton - minimal and opt-in
112
+ export default class ${confirmedValues.packageName || confirmedValues.serviceName ? confirmedValues.serviceName : 'Service'}Middleware {
113
+ // Implement only the hooks you need
114
+ async preprocess(request) { return null; }
115
+ async authenticate(request) { return null; }
116
+ async validate(request) { return null; }
117
+ async postprocess(response) { return response; }
118
+ }
119
+
120
+ // Optional registration helper - Worker generator will call this to register
121
+ export function registerMiddleware(registry, serviceName) {
122
+ if (!registry || typeof registry.register !== 'function') return;
123
+ try {
124
+ registry.register(serviceName || '${coreInputs.serviceName}', new (exports.default || ${confirmedValues.packageName || confirmedValues.serviceName ? confirmedValues.serviceName : 'Service'}Middleware)());
125
+ } catch (e) {
126
+ // Non-fatal - allow services to manually register if needed
127
+ }
128
+ }
129
+
130
+ `;
131
+ }
98
132
  const filePath = join(servicePath, 'src', 'middleware', 'service-middleware.js');
99
133
 
100
134
  // Ensure directory exists
@@ -104,6 +138,118 @@ export function createServiceMiddleware(serviceConfig, env) {
104
138
  });
105
139
  writeFileSync(filePath, middlewareContent, 'utf8');
106
140
  this.logger.info(`Generated: ${filePath}`);
141
+
142
+ // Also generate a lightweight runtime helper for middleware composition and registry
143
+ const runtimeContent = `// Lightweight middleware runtime for generated services
144
+ export const MiddlewareRegistry = (() => {
145
+ const map = new Map();
146
+ return {
147
+ register(serviceName, instance) { map.set(serviceName, instance); },
148
+ get(serviceName) { return map.get(serviceName) || null; },
149
+ clear() { map.clear(); }
150
+ };
151
+ })();
152
+
153
+ export const MiddlewareComposer = {
154
+ compose(...middlewares) {
155
+ const chain = middlewares.filter(Boolean);
156
+ return {
157
+ async execute(request, handler) {
158
+ let req = request;
159
+ for (const m of chain) {
160
+ if (typeof m.preprocess === 'function') {
161
+ const res = await m.preprocess(req);
162
+ if (res) return res;
163
+ }
164
+ if (typeof m.authenticate === 'function') {
165
+ const res = await m.authenticate(req);
166
+ if (res) return res;
167
+ }
168
+ if (typeof m.validate === 'function') {
169
+ const res = await m.validate(req);
170
+ if (res) return res;
171
+ }
172
+ }
173
+ let response = await handler(req);
174
+ for (const m of chain.slice().reverse()) {
175
+ if (typeof m.postprocess === 'function') {
176
+ const updated = await m.postprocess(response);
177
+ if (updated instanceof Response) response = updated;
178
+ }
179
+ }
180
+ return response;
181
+ }
182
+ };
183
+ }
184
+ };
185
+ `;
186
+ const runtimePath = join(servicePath, 'src', 'middleware', 'runtime.js');
187
+ writeFileSync(runtimePath, runtimeContent, 'utf8');
188
+ this.logger.info(`Generated: ${runtimePath}`);
189
+
190
+ // Minimal shared implementations (copied into generated service for self-containment)
191
+ const sharedDir = join(servicePath, 'src', 'middleware', 'shared');
192
+ mkdirSync(sharedDir, {
193
+ recursive: true
194
+ });
195
+ const corsContent = `export function cors(options = {}) {
196
+ const origin = options.origin || '*';
197
+ const allowMethods = options.methods || 'GET, POST, PUT, DELETE, OPTIONS';
198
+ const allowHeaders = options.headers || 'Content-Type, Authorization';
199
+
200
+ return {
201
+ preprocess(request) {
202
+ if (request.method === 'OPTIONS') {
203
+ const headers = new Headers();
204
+ headers.set('Access-Control-Allow-Origin', origin);
205
+ headers.set('Access-Control-Allow-Methods', allowMethods);
206
+ headers.set('Access-Control-Allow-Headers', allowHeaders);
207
+ return new Response(null, { status: 204, headers });
208
+ }
209
+ return null;
210
+ },
211
+ postprocess(response) {
212
+ const headers = new Headers(response.headers);
213
+ headers.set('Access-Control-Allow-Origin', origin);
214
+ return new Response(response.body, { ...response, headers });
215
+ }
216
+ };
217
+ }
218
+ `;
219
+ const loggingContent = `export function logging(options = {}) {
220
+ const level = options.level || 'info';
221
+ return {
222
+ preprocess(request) {
223
+ try {
224
+ const path = new URL(request.url).pathname;
225
+ console.log('[' + new Date().toISOString() + '] ' + level.toUpperCase() + ' ' + request.method + ' ' + path);
226
+ } catch (e) {}
227
+ return null;
228
+ }
229
+ };
230
+ }
231
+ `;
232
+ const basicAuthContent = `export function basicAuth({ realm = 'Restricted' } = {}) {
233
+ return {
234
+ authenticate(request) {
235
+ const auth = request.headers.get('Authorization');
236
+ if (!auth) {
237
+ return new Response(JSON.stringify({ error: 'Unauthorized' }), {
238
+ status: 401,
239
+ headers: { 'Content-Type': 'application/json', 'WWW-Authenticate': 'Basic realm="' + realm + '"' }
240
+ });
241
+ }
242
+ return null;
243
+ }
244
+ };
245
+ }
246
+ `;
247
+ writeFileSync(join(sharedDir, 'cors.js'), corsContent, 'utf8');
248
+ writeFileSync(join(sharedDir, 'logging.js'), loggingContent, 'utf8');
249
+ writeFileSync(join(sharedDir, 'basicAuth.js'), basicAuthContent, 'utf8');
250
+ const indexContent = `export { cors } from './cors.js';\nexport { logging } from './logging.js';\nexport { basicAuth } from './basicAuth.js';\n`;
251
+ writeFileSync(join(sharedDir, 'index.js'), indexContent, 'utf8');
252
+ this.logger.info(`Generated shared middleware in: ${sharedDir}`);
107
253
  return filePath;
108
254
  }
109
255
 
@@ -70,7 +70,8 @@ export class WorkerIndexGenerator extends BaseGenerator {
70
70
 
71
71
  import { domains } from '../config/domains.js';
72
72
  import { createServiceHandlers } from '../handlers/service-handlers.js';
73
- import { createServiceMiddleware } from '../middleware/service-middleware.js';
73
+ import { MiddlewareRegistry, MiddlewareComposer } from '../middleware/runtime.js';
74
+ import * as Shared from '../middleware/shared/index.js';
74
75
 
75
76
  export default {
76
77
  async fetch(request, env, ctx) {
@@ -78,16 +79,81 @@ export default {
78
79
  // Get service configuration
79
80
  const serviceConfig = domains['${coreInputs.serviceName}'];
80
81
 
81
- // Apply middleware
82
- const middleware = createServiceMiddleware(serviceConfig, env);
83
- const processedRequest = await middleware.processRequest(request);
82
+ // Build shared middleware instances
83
+ const sharedMiddlewares = [
84
+ Shared.cors({ origin: serviceConfig.corsPolicy || '*' }),
85
+ Shared.logging({ level: serviceConfig.logLevel || 'info' })
86
+ ];
84
87
 
85
- // Route to appropriate handler
86
- const handlers = createServiceHandlers(serviceConfig, env);
87
- const response = await handlers.handleRequest(processedRequest, ctx);
88
+ // Lazy-load service middleware and support legacy factory compatibility
89
+ let serviceMiddlewareInstance = null;
90
+ let legacyFactory = null;
88
91
 
89
- // Apply response middleware
90
- return await middleware.processResponse(response);
92
+ try {
93
+ const mod = await import('../middleware/service-middleware.js');
94
+
95
+ if (mod?.registerMiddleware) {
96
+ // New-style registration helper
97
+ mod.registerMiddleware(MiddlewareRegistry, serviceConfig.name);
98
+ serviceMiddlewareInstance = MiddlewareRegistry.get(serviceConfig.name);
99
+ } else if (mod?.default) {
100
+ const def = mod.default;
101
+ // If the default is a class (constructor), instantiate and register
102
+ if (typeof def === 'function' && def.prototype) {
103
+ try {
104
+ const instance = new def();
105
+ MiddlewareRegistry.register(serviceConfig.name, instance);
106
+ serviceMiddlewareInstance = instance;
107
+ } catch (e) {
108
+ // ignore instantiation errors
109
+ }
110
+ } else if (typeof def === 'function') {
111
+ // Legacy factory exported as default
112
+ legacyFactory = def;
113
+ }
114
+ } else if (mod?.createServiceMiddleware) {
115
+ legacyFactory = mod.createServiceMiddleware;
116
+ }
117
+ } catch (e) {
118
+ // No service-specific middleware found - continue with shared only
119
+ }
120
+
121
+ // Compose final middleware chain
122
+ let chain;
123
+
124
+ if (legacyFactory) {
125
+ const legacyInstance = legacyFactory(serviceConfig, env);
126
+ const adapter = {
127
+ preprocess: async (req) => {
128
+ if (legacyInstance && typeof legacyInstance.processRequest === 'function') {
129
+ const processed = await legacyInstance.processRequest(req);
130
+ if (processed instanceof Response) return processed; // short-circuit
131
+ return null; // continue (legacy returns a Request)
132
+ }
133
+ return null;
134
+ },
135
+ postprocess: async (res) => {
136
+ if (legacyInstance && typeof legacyInstance.processResponse === 'function') {
137
+ const r = await legacyInstance.processResponse(res);
138
+ return r instanceof Response ? r : res;
139
+ }
140
+ return res;
141
+ }
142
+ };
143
+
144
+ chain = MiddlewareComposer.compose(...sharedMiddlewares, adapter);
145
+ } else {
146
+ const svcMw = serviceMiddlewareInstance || MiddlewareRegistry.get(serviceConfig.name);
147
+ chain = MiddlewareComposer.compose(...sharedMiddlewares, svcMw);
148
+ }
149
+
150
+ // Execute middleware chain with final handler
151
+ const response = await chain.execute(request, async (req) => {
152
+ const handlers = createServiceHandlers(serviceConfig, env);
153
+ return handlers.handleRequest(req, ctx);
154
+ });
155
+
156
+ return response;
91
157
 
92
158
  } catch (error) {
93
159
  console.error('Worker error:', error);
@@ -25,7 +25,38 @@ export class Clodo {
25
25
  * @returns {Promise<Object>} Service creation result
26
26
  */
27
27
  static async createService(options = {}) {
28
- return await ServiceOrchestrator.create(options);
28
+ // Instantiate orchestrator with provided middleware strategy and paths
29
+ const orchestrator = new ServiceOrchestrator({
30
+ interactive: options.interactive !== false,
31
+ outputPath: options.outputPath || '.',
32
+ templatePath: options.templatePath || './templates',
33
+ middlewareStrategy: options.middlewareStrategy || 'contract'
34
+ });
35
+
36
+ // If interactive mode requested (default), run interactive flow
37
+ if (options.interactive !== false) {
38
+ await orchestrator.runInteractive();
39
+ return {
40
+ success: true,
41
+ message: 'Service created (interactive)'
42
+ };
43
+ }
44
+
45
+ // Non-interactive flow: build core inputs and run non-interactive
46
+ const coreInputs = {
47
+ serviceName: options.name,
48
+ serviceType: options.type,
49
+ domainName: options.domain,
50
+ environment: options.environment || 'development',
51
+ cloudflareToken: options.credentials?.token,
52
+ cloudflareAccountId: options.credentials?.accountId,
53
+ cloudflareZoneId: options.credentials?.zoneId
54
+ };
55
+ await orchestrator.runNonInteractive(coreInputs);
56
+ return {
57
+ success: true,
58
+ message: `Service ${options.name} created`
59
+ };
29
60
  }
30
61
 
31
62
  /**