@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.
- package/CHANGELOG.md +18 -0
- package/README.md +7 -0
- package/dist/cli/commands/create.js +2 -1
- package/dist/index.js +5 -0
- package/dist/middleware/Composer.js +38 -0
- package/dist/middleware/Registry.js +14 -0
- package/dist/middleware/index.js +3 -0
- package/dist/middleware/shared/basicAuth.js +21 -0
- package/dist/middleware/shared/cors.js +28 -0
- package/dist/middleware/shared/index.js +3 -0
- package/dist/middleware/shared/logging.js +14 -0
- package/dist/monitoring/HealthChecker.js +286 -0
- package/dist/orchestration/modules/StateManager.js +11 -3
- package/dist/security/ConfigurationValidator.js +216 -0
- package/dist/service-management/GenerationEngine.js +13 -2
- package/dist/service-management/ServiceOrchestrator.js +6 -2
- package/dist/service-management/generators/BaseGenerator.js +31 -6
- package/dist/service-management/generators/code/ServiceMiddlewareGenerator.js +156 -10
- package/dist/service-management/generators/code/WorkerIndexGenerator.js +75 -9
- package/dist/service-management/generators/utils/FileWriter.js +13 -1
- package/dist/services/ServiceClient.js +239 -0
- package/dist/simple-api.js +32 -1
- package/dist/utils/CircuitBreaker.js +192 -0
- package/dist/utils/EnvironmentValidator.js +147 -0
- package/dist/utils/TemplateRuntime.js +291 -0
- package/dist/utils/deployment/secret-generator.js +37 -26
- package/dist/version/FrameworkInfo.js +104 -0
- package/dist/worker/integration.js +13 -1
- package/docs/MIDDLEWARE_MIGRATION_SUMMARY.md +121 -0
- package/package.json +8 -1
- package/scripts/DEPLOY_COMMAND_NEW.js +128 -0
- package/scripts/README-automated-testing-suite.md +356 -0
- package/scripts/README-test-clodo-deployment.md +157 -0
- package/scripts/README.md +50 -0
- package/scripts/analyze-imports.ps1 +104 -0
- package/scripts/analyze-mixed-code.js +163 -0
- package/scripts/analyze-mixed-rationale.js +149 -0
- package/scripts/automated-testing-suite.js +776 -0
- package/scripts/check-templates.js +105 -0
- package/scripts/debug-generate-worker.js +58 -0
- package/scripts/deployment/README.md +31 -0
- package/scripts/deployment/deploy-domain.ps1 +449 -0
- package/scripts/deployment/deploy-staging.js +120 -0
- package/scripts/deployment/validate-staging.js +166 -0
- package/scripts/diagnose-imports.js +362 -0
- package/scripts/framework-diagnostic.js +368 -0
- package/scripts/migration/migrate-middleware-legacy-to-contract.js +64 -0
- package/scripts/post-publish-test.js +663 -0
- package/scripts/scan-worker-issues.js +52 -0
- package/scripts/service-management/README.md +27 -0
- package/scripts/service-management/setup-interactive.ps1 +693 -0
- package/scripts/test-clodo-deployment.js +588 -0
- package/scripts/test-downstream-install.js +237 -0
- package/scripts/test-local-package.ps1 +126 -0
- package/scripts/test-local-package.sh +166 -0
- package/scripts/test-package.js +339 -0
- package/scripts/testing/README.md +49 -0
- package/scripts/testing/test-first.ps1 +0 -0
- package/scripts/testing/test-first50.ps1 +0 -0
- package/scripts/testing/test.ps1 +0 -0
- package/scripts/utilities/README.md +61 -0
- package/scripts/utilities/check-bin.js +8 -0
- package/scripts/utilities/check-bundle.js +23 -0
- package/scripts/utilities/check-dist-imports.js +65 -0
- package/scripts/utilities/check-import-paths.js +191 -0
- package/scripts/utilities/cleanup-cli.js +159 -0
- package/scripts/utilities/deployment-helpers.ps1 +199 -0
- package/scripts/utilities/fix-dist-imports.js +135 -0
- package/scripts/utilities/generate-secrets.js +159 -0
- package/scripts/utilities/safe-push.ps1 +51 -0
- package/scripts/utilities/setup-helpers.ps1 +206 -0
- package/scripts/utilities/test-packaged-artifact.js +92 -0
- package/scripts/utilities/validate-dist-imports.js +189 -0
- package/scripts/utilities/validate-schema.js +102 -0
- package/scripts/verify-exports.js +193 -0
- package/scripts/verify-persistence-config.js +45 -0
- package/scripts/verify-worker-safety.js +73 -0
- package/templates/generic/src/config/.config-is-sample +1 -0
- package/templates/static-site/.env.example +1 -1
- package/templates/static-site/src/config/.config-is-sample +1 -0
- package/templates/static-site/src/config/domains.js +3 -0
- package/types/middleware.d.ts +1 -0
|
@@ -460,6 +460,222 @@ export class ConfigurationValidator {
|
|
|
460
460
|
return config;
|
|
461
461
|
}
|
|
462
462
|
|
|
463
|
+
/**
|
|
464
|
+
* Validate service configuration consistency between manifest and wrangler config
|
|
465
|
+
* @param {string} manifestPath - Path to manifest.json
|
|
466
|
+
* @param {string} wranglerPath - Path to wrangler.toml
|
|
467
|
+
* @returns {Object} Validation result with issues array
|
|
468
|
+
*/
|
|
469
|
+
static validateServiceConfig(manifestPath, wranglerPath) {
|
|
470
|
+
const issues = [];
|
|
471
|
+
try {
|
|
472
|
+
// Read manifest.json
|
|
473
|
+
const manifestContent = readFileSync(manifestPath, 'utf8');
|
|
474
|
+
const manifest = JSON.parse(manifestContent);
|
|
475
|
+
|
|
476
|
+
// Read wrangler.toml (basic parsing - could be enhanced with a TOML parser)
|
|
477
|
+
const wranglerContent = readFileSync(wranglerPath, 'utf8');
|
|
478
|
+
const wranglerConfig = this.parseWranglerToml(wranglerContent);
|
|
479
|
+
|
|
480
|
+
// Validate D1 database consistency
|
|
481
|
+
this.validateD1Consistency(manifest, wranglerConfig, issues);
|
|
482
|
+
|
|
483
|
+
// Validate KV namespace consistency
|
|
484
|
+
this.validateKVConsistency(manifest, wranglerConfig, issues);
|
|
485
|
+
|
|
486
|
+
// Validate R2 bucket consistency
|
|
487
|
+
this.validateR2Consistency(manifest, wranglerConfig, issues);
|
|
488
|
+
|
|
489
|
+
// Validate environment variables
|
|
490
|
+
this.validateEnvironmentConsistency(manifest, wranglerConfig, issues);
|
|
491
|
+
} catch (error) {
|
|
492
|
+
issues.push({
|
|
493
|
+
type: 'error',
|
|
494
|
+
message: `Configuration validation failed: ${error.message}`,
|
|
495
|
+
severity: 'critical'
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
return {
|
|
499
|
+
valid: issues.length === 0,
|
|
500
|
+
issues: issues
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Parse wrangler.toml content (basic implementation)
|
|
506
|
+
* @param {string} content - TOML content
|
|
507
|
+
* @returns {Object} Parsed configuration
|
|
508
|
+
*/
|
|
509
|
+
static parseWranglerToml(content) {
|
|
510
|
+
const config = {
|
|
511
|
+
d1_databases: [],
|
|
512
|
+
kv_namespaces: [],
|
|
513
|
+
r2_buckets: [],
|
|
514
|
+
vars: {}
|
|
515
|
+
};
|
|
516
|
+
if (!content || !content.trim()) {
|
|
517
|
+
return config;
|
|
518
|
+
}
|
|
519
|
+
const lines = content.split('\n');
|
|
520
|
+
let currentSection = null;
|
|
521
|
+
for (const line of lines) {
|
|
522
|
+
const trimmed = line.trim();
|
|
523
|
+
|
|
524
|
+
// Section headers
|
|
525
|
+
if (trimmed.startsWith('[[')) {
|
|
526
|
+
if (trimmed.includes('d1_databases')) {
|
|
527
|
+
currentSection = 'd1_databases';
|
|
528
|
+
config.d1_databases.push({});
|
|
529
|
+
} else if (trimmed.includes('kv_namespaces')) {
|
|
530
|
+
currentSection = 'kv_namespaces';
|
|
531
|
+
config.kv_namespaces.push({});
|
|
532
|
+
} else if (trimmed.includes('r2_buckets')) {
|
|
533
|
+
currentSection = 'r2_buckets';
|
|
534
|
+
config.r2_buckets.push({});
|
|
535
|
+
}
|
|
536
|
+
} else if (trimmed.startsWith('[') && !trimmed.startsWith('[[')) {
|
|
537
|
+
currentSection = trimmed.replace(/\[|\]/g, '');
|
|
538
|
+
// Support both table and array-of-tables syntaxes by creating a container
|
|
539
|
+
if (Array.isArray(config[currentSection]) && config[currentSection].length === 0) {
|
|
540
|
+
config[currentSection].push({});
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
// Key-value pairs
|
|
544
|
+
else if (trimmed.includes('=') && currentSection) {
|
|
545
|
+
const [key, ...valueParts] = trimmed.split('=');
|
|
546
|
+
const value = valueParts.join('=').trim().replace(/"/g, '');
|
|
547
|
+
if (currentSection === 'vars') {
|
|
548
|
+
config.vars[key.trim()] = value;
|
|
549
|
+
} else if (Array.isArray(config[currentSection])) {
|
|
550
|
+
const currentItem = config[currentSection][config[currentSection].length - 1];
|
|
551
|
+
if (currentItem) {
|
|
552
|
+
currentItem[key.trim()] = value;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return config;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Validate D1 database consistency
|
|
562
|
+
*/
|
|
563
|
+
static validateD1Consistency(manifest, wranglerConfig, issues) {
|
|
564
|
+
const manifestD1 = manifest.d1 || false;
|
|
565
|
+
const wranglerD1 = wranglerConfig.d1_databases || [];
|
|
566
|
+
if (manifestD1 === true && wranglerD1.length === 0) {
|
|
567
|
+
issues.push({
|
|
568
|
+
type: 'mismatch',
|
|
569
|
+
message: 'Manifest declares D1=true but wrangler.toml has no [[d1_databases]] configured',
|
|
570
|
+
severity: 'critical',
|
|
571
|
+
manifest: {
|
|
572
|
+
d1: manifestD1
|
|
573
|
+
},
|
|
574
|
+
wrangler: {
|
|
575
|
+
d1_databases: wranglerD1
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
} else if (manifestD1 === false && wranglerD1.length > 0) {
|
|
579
|
+
issues.push({
|
|
580
|
+
type: 'mismatch',
|
|
581
|
+
message: 'Manifest declares D1=false but wrangler.toml has [[d1_databases]] configured',
|
|
582
|
+
severity: 'critical',
|
|
583
|
+
manifest: {
|
|
584
|
+
d1: manifestD1
|
|
585
|
+
},
|
|
586
|
+
wrangler: {
|
|
587
|
+
d1_databases: wranglerD1
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Validate KV namespace consistency
|
|
595
|
+
*/
|
|
596
|
+
static validateKVConsistency(manifest, wranglerConfig, issues) {
|
|
597
|
+
const manifestKV = manifest.kv || false;
|
|
598
|
+
const wranglerKV = wranglerConfig.kv_namespaces || [];
|
|
599
|
+
if (manifestKV === true && wranglerKV.length === 0) {
|
|
600
|
+
issues.push({
|
|
601
|
+
type: 'mismatch',
|
|
602
|
+
message: 'Manifest declares KV=true but wrangler.toml has no [[kv_namespaces]] configured',
|
|
603
|
+
severity: 'critical',
|
|
604
|
+
manifest: {
|
|
605
|
+
kv: manifestKV
|
|
606
|
+
},
|
|
607
|
+
wrangler: {
|
|
608
|
+
kv_namespaces: wranglerKV
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
} else if (manifestKV === false && wranglerKV.length > 0) {
|
|
612
|
+
issues.push({
|
|
613
|
+
type: 'mismatch',
|
|
614
|
+
message: 'Manifest declares KV=false but wrangler.toml has [[kv_namespaces]] configured',
|
|
615
|
+
severity: 'critical',
|
|
616
|
+
manifest: {
|
|
617
|
+
kv: manifestKV
|
|
618
|
+
},
|
|
619
|
+
wrangler: {
|
|
620
|
+
kv_namespaces: wranglerKV
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Validate R2 bucket consistency
|
|
628
|
+
*/
|
|
629
|
+
static validateR2Consistency(manifest, wranglerConfig, issues) {
|
|
630
|
+
const manifestR2 = manifest.r2 || false;
|
|
631
|
+
const wranglerR2 = wranglerConfig.r2_buckets || [];
|
|
632
|
+
if (manifestR2 === true && wranglerR2.length === 0) {
|
|
633
|
+
issues.push({
|
|
634
|
+
type: 'mismatch',
|
|
635
|
+
message: 'Manifest declares R2=true but wrangler.toml has no [[r2_buckets]] configured',
|
|
636
|
+
severity: 'critical',
|
|
637
|
+
manifest: {
|
|
638
|
+
r2: manifestR2
|
|
639
|
+
},
|
|
640
|
+
wrangler: {
|
|
641
|
+
r2_buckets: wranglerR2
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
} else if (manifestR2 === false && wranglerR2.length > 0) {
|
|
645
|
+
issues.push({
|
|
646
|
+
type: 'mismatch',
|
|
647
|
+
message: 'Manifest declares R2=false but wrangler.toml has [[r2_buckets]] configured',
|
|
648
|
+
severity: 'critical',
|
|
649
|
+
manifest: {
|
|
650
|
+
r2: manifestR2
|
|
651
|
+
},
|
|
652
|
+
wrangler: {
|
|
653
|
+
r2_buckets: wranglerR2
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Validate environment variable consistency
|
|
661
|
+
*/
|
|
662
|
+
static validateEnvironmentConsistency(manifest, wranglerConfig, issues) {
|
|
663
|
+
const manifestEnv = manifest.environment || {};
|
|
664
|
+
const wranglerEnv = wranglerConfig.vars || {};
|
|
665
|
+
|
|
666
|
+
// Check for required environment variables declared in manifest but missing in wrangler
|
|
667
|
+
for (const [key, value] of Object.entries(manifestEnv)) {
|
|
668
|
+
if (value === 'required' && !(key in wranglerEnv)) {
|
|
669
|
+
issues.push({
|
|
670
|
+
type: 'missing_env',
|
|
671
|
+
message: `Manifest declares ${key} as required but not found in wrangler.toml vars`,
|
|
672
|
+
severity: 'warning',
|
|
673
|
+
key: key
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
463
679
|
/**
|
|
464
680
|
* Log validation results
|
|
465
681
|
*/
|
|
@@ -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) {
|
|
@@ -180,6 +180,9 @@ export class BaseGenerator {
|
|
|
180
180
|
const fullPath = path.join(this.servicePath, relativePath);
|
|
181
181
|
const overwrite = options.overwrite !== false; // Default to true
|
|
182
182
|
|
|
183
|
+
// Debug: log the target file path for diagnosis
|
|
184
|
+
this.logger.info(`BaseGenerator: writing file -> ${fullPath}`);
|
|
185
|
+
|
|
183
186
|
// Check if file exists and overwrite is disabled
|
|
184
187
|
try {
|
|
185
188
|
await fs.access(fullPath);
|
|
@@ -197,13 +200,35 @@ export class BaseGenerator {
|
|
|
197
200
|
recursive: true
|
|
198
201
|
});
|
|
199
202
|
|
|
200
|
-
// Write file
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
203
|
+
// Write file with retry logic for filesystem stability
|
|
204
|
+
const maxRetries = 3;
|
|
205
|
+
let lastError;
|
|
206
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
207
|
+
try {
|
|
208
|
+
this.logger.info(`BaseGenerator: attempt ${attempt} - writing to ${fullPath}`);
|
|
209
|
+
await fs.writeFile(fullPath, content, 'utf8');
|
|
210
|
+
|
|
211
|
+
// Add a small delay to let file system settle, especially on Windows
|
|
212
|
+
if (attempt === 1) {
|
|
213
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Verify the file was written successfully by checking it exists
|
|
217
|
+
await fs.access(fullPath, fs.constants.F_OK);
|
|
218
|
+
this.logger.info(`Generated: ${relativePath}`);
|
|
219
|
+
return; // Success
|
|
220
|
+
} catch (error) {
|
|
221
|
+
lastError = error;
|
|
222
|
+
this.logger.warn(`Write attempt ${attempt}/${maxRetries} failed for ${relativePath}: ${error.message}`);
|
|
223
|
+
if (attempt < maxRetries) {
|
|
224
|
+
// Wait before retry (exponential backoff)
|
|
225
|
+
await new Promise(resolve => setTimeout(resolve, 50 * attempt));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
206
228
|
}
|
|
229
|
+
|
|
230
|
+
// If all retries failed, throw the last error
|
|
231
|
+
throw new Error(`Failed to write and verify file '${relativePath}' after ${maxRetries} attempts: ${lastError.message}`);
|
|
207
232
|
}
|
|
208
233
|
|
|
209
234
|
/**
|
|
@@ -21,8 +21,13 @@ export class ServiceMiddlewareGenerator extends BaseGenerator {
|
|
|
21
21
|
if (!this.shouldGenerate(context)) {
|
|
22
22
|
return null;
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
|
|
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(
|
|
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(
|
|
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 {
|
|
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
|
-
//
|
|
82
|
-
const
|
|
83
|
-
|
|
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
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
// Lazy-load service middleware and support legacy factory compatibility
|
|
89
|
+
let serviceMiddlewareInstance = null;
|
|
90
|
+
let legacyFactory = null;
|
|
88
91
|
|
|
89
|
-
|
|
90
|
-
|
|
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);
|