@tamyla/clodo-framework 4.5.0 → 4.6.0

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.
@@ -7,6 +7,8 @@ import fs from 'fs/promises';
7
7
  import path from 'path';
8
8
  import { FrameworkConfig } from '../../utils/framework-config.js';
9
9
  import { ConfigurationValidator } from '../../security/ConfigurationValidator.js';
10
+ import { SecretsManager } from '../../security/SecretsManager.js';
11
+ import { ConfigSchemaValidator } from '../../validation/ConfigSchemaValidator.js';
10
12
  export class ValidationHandler {
11
13
  constructor(options = {}) {
12
14
  this.strict = options.strict || false;
@@ -62,6 +64,38 @@ export class ValidationHandler {
62
64
  return this.validationConfig;
63
65
  }
64
66
 
67
+ /**
68
+ * Check if a version is sufficient (greater than or equal to minimum)
69
+ */
70
+ isVersionSufficient(currentVersion, minVersion) {
71
+ const current = currentVersion.replace(/^v/, '').split('.').map(Number);
72
+ const min = minVersion.split('.').map(Number);
73
+ for (let i = 0; i < Math.max(current.length, min.length); i++) {
74
+ const c = current[i] || 0;
75
+ const m = min[i] || 0;
76
+ if (c > m) return true;
77
+ if (c < m) return false;
78
+ }
79
+ return true;
80
+ }
81
+
82
+ /**
83
+ * Generate fix suggestions from validation issues
84
+ */
85
+ generateFixSuggestions(issues) {
86
+ const suggestions = [];
87
+ issues.forEach(issue => {
88
+ if (issue.includes('Missing required field: main')) {
89
+ suggestions.push('Add main field to package.json');
90
+ } else if (issue.includes('Missing required dependency:')) {
91
+ suggestions.push(issue); // Pass the full issue text for dependency extraction
92
+ } else if (issue.includes('Should use "type": "module"')) {
93
+ suggestions.push('Set package.json type to module');
94
+ }
95
+ });
96
+ return suggestions;
97
+ }
98
+
65
99
  /**
66
100
  * Validate complete service configuration
67
101
  */
@@ -284,4 +318,666 @@ export class ValidationHandler {
284
318
  setStrict(enabled) {
285
319
  this.strict = enabled;
286
320
  }
321
+
322
+ /**
323
+ * Run comprehensive doctor checks for preflight diagnostics
324
+ * @param {Object} options - Options for doctor run
325
+ * @param {boolean} options.json - Output in JSON format
326
+ * @param {boolean} options.fix - Attempt to fix issues
327
+ * @param {boolean} options.strict - Strict mode (fail on warnings)
328
+ * @param {string} options.servicePath - Path to service directory
329
+ * @returns {Object} Doctor results
330
+ */
331
+ async runDoctor(options = {}) {
332
+ const {
333
+ json = false,
334
+ fix = false,
335
+ strict = this.strict,
336
+ servicePath = process.cwd()
337
+ } = options;
338
+ const results = {
339
+ timestamp: new Date().toISOString(),
340
+ servicePath,
341
+ checks: [],
342
+ summary: {
343
+ total: 0,
344
+ passed: 0,
345
+ warnings: 0,
346
+ errors: 0,
347
+ critical: 0
348
+ },
349
+ fixSuggestions: [],
350
+ fixesApplied: [],
351
+ exitCode: 0
352
+ };
353
+
354
+ // Load validation config
355
+ const config = this.loadValidationConfig(servicePath);
356
+
357
+ // Run environment checks
358
+ const envCheck = await this.checkEnvironment();
359
+ results.checks.push(envCheck);
360
+ this.updateSummary(results, envCheck);
361
+
362
+ // Run config presence checks
363
+ const configCheck = await this.checkConfigPresence(servicePath, config);
364
+ results.checks.push(configCheck);
365
+ this.updateSummary(results, configCheck);
366
+
367
+ // Run service validation
368
+ const validationCheck = await this.validateService(servicePath);
369
+ const fixSuggestions = this.generateFixSuggestions(validationCheck.issues || []);
370
+ results.checks.push({
371
+ name: 'service-validation',
372
+ status: validationCheck.valid ? 'passed' : 'failed',
373
+ severity: validationCheck.valid ? 'info' : 'error',
374
+ message: validationCheck.valid ? 'Service validation passed' : 'Service validation failed',
375
+ details: validationCheck.issues || [],
376
+ fixSuggestions
377
+ });
378
+ this.updateSummary(results, results.checks[results.checks.length - 1]);
379
+
380
+ // Run token scope check
381
+ const tokenCheck = await this.checkTokenScopes();
382
+ results.checks.push(tokenCheck);
383
+ this.updateSummary(results, tokenCheck);
384
+
385
+ // Run secrets baseline check
386
+ const secretsCheck = await this.checkSecretsBaseline(servicePath);
387
+ results.checks.push(secretsCheck);
388
+ this.updateSummary(results, secretsCheck);
389
+
390
+ // Run config schema validation check
391
+ const configSchemaCheck = await this.checkConfigSchemas(servicePath);
392
+ results.checks.push(configSchemaCheck);
393
+ this.updateSummary(results, configSchemaCheck);
394
+
395
+ // Apply fixes if requested
396
+ if (fix && results.summary.errors > 0) {
397
+ console.log('🔧 Attempting to fix detected issues...');
398
+ const fixesApplied = await this.applyFixes(results, servicePath);
399
+ results.fixesApplied = fixesApplied;
400
+
401
+ // Re-run checks after fixes
402
+ if (fixesApplied.length > 0) {
403
+ console.log(`✅ Applied ${fixesApplied.length} fixes. Re-running checks...`);
404
+ return this.runDoctor({
405
+ ...options,
406
+ fix: false
407
+ }); // Re-run without fix to see results
408
+ }
409
+ }
410
+
411
+ // Determine exit code
412
+ results.exitCode = results.summary.errors > 0 || strict && results.summary.warnings > 0 ? 1 : 0;
413
+
414
+ // Collect fix suggestions
415
+ results.checks.forEach(check => {
416
+ if (check.fixSuggestions && check.fixSuggestions.length > 0) {
417
+ results.fixSuggestions.push(...check.fixSuggestions);
418
+ }
419
+ });
420
+ return results;
421
+ }
422
+
423
+ /**
424
+ * Update summary counts based on check result
425
+ */
426
+ updateSummary(results, check) {
427
+ results.summary.total++;
428
+ if (check.status === 'passed') {
429
+ results.summary.passed++;
430
+ } else if (check.severity === 'warning') {
431
+ results.summary.warnings++;
432
+ } else if (check.severity === 'error') {
433
+ results.summary.errors++;
434
+ } else if (check.severity === 'critical') {
435
+ results.summary.critical++;
436
+ }
437
+ }
438
+
439
+ /**
440
+ * Check environment prerequisites
441
+ */
442
+ async checkEnvironment() {
443
+ const check = {
444
+ name: 'environment',
445
+ status: 'passed',
446
+ severity: 'info',
447
+ message: 'Environment checks passed',
448
+ details: [],
449
+ fixSuggestions: []
450
+ };
451
+
452
+ // Check Node.js version
453
+ const nodeVersion = process.version;
454
+ const minVersion = '18.0.0';
455
+ if (!this.isVersionSufficient(nodeVersion, minVersion)) {
456
+ check.status = 'failed';
457
+ check.severity = 'error';
458
+ check.details.push(`Node.js version ${nodeVersion} is below minimum ${minVersion}`);
459
+ check.fixSuggestions.push('Upgrade Node.js to version 18 or higher');
460
+ }
461
+
462
+ // Check if wrangler is available
463
+ try {
464
+ const {
465
+ execSync
466
+ } = await import('child_process');
467
+ execSync('wrangler --version', {
468
+ stdio: 'pipe'
469
+ });
470
+ check.details.push('Wrangler CLI is available');
471
+ } catch (error) {
472
+ check.status = 'failed';
473
+ check.severity = 'error';
474
+ check.details.push('Wrangler CLI is not installed or not in PATH');
475
+ check.fixSuggestions.push('Install Wrangler CLI: npm install -g wrangler');
476
+ }
477
+ return check;
478
+ }
479
+
480
+ /**
481
+ * Check config file presence
482
+ */
483
+ async checkConfigPresence(servicePath, config) {
484
+ const check = {
485
+ name: 'config-presence',
486
+ status: 'passed',
487
+ severity: 'info',
488
+ message: 'Configuration files present',
489
+ details: [],
490
+ fixSuggestions: []
491
+ };
492
+ const requiredFiles = config.requiredFiles || [];
493
+ for (const file of requiredFiles) {
494
+ try {
495
+ await fs.access(path.join(servicePath, file));
496
+ check.details.push(`✓ ${file}`);
497
+ } catch (error) {
498
+ check.status = 'failed';
499
+ check.severity = 'error';
500
+ check.details.push(`✗ ${file} is missing`);
501
+ check.fixSuggestions.push(`Create ${file} or ensure it exists`);
502
+ }
503
+ }
504
+ return check;
505
+ }
506
+
507
+ /**
508
+ * Check Cloudflare token scopes
509
+ */
510
+ async checkTokenScopes() {
511
+ const check = {
512
+ name: 'token-scopes',
513
+ status: 'passed',
514
+ severity: 'info',
515
+ message: 'Cloudflare token scopes validated',
516
+ details: [],
517
+ fixSuggestions: []
518
+ };
519
+
520
+ // Get Cloudflare token from environment
521
+ const token = process.env.CLOUDFLARE_API_TOKEN || process.env.CF_API_TOKEN;
522
+ if (!token) {
523
+ check.status = 'failed';
524
+ check.severity = 'error';
525
+ check.details.push('No Cloudflare API token found in environment variables');
526
+ check.fixSuggestions.push('Set CLOUDFLARE_API_TOKEN or CF_API_TOKEN environment variable');
527
+ check.fixSuggestions.push('Generate a token at: https://dash.cloudflare.com/profile/api-tokens');
528
+ return check;
529
+ }
530
+ try {
531
+ // Check token permissions by making API calls
532
+ const permissions = await this.validateCloudflareToken(token);
533
+ if (!permissions.hasWorkersEdit) {
534
+ check.status = 'failed';
535
+ check.severity = 'error';
536
+ check.details.push('Token missing required "Account:Workers:Edit" permission');
537
+ check.fixSuggestions.push('Add "Account:Workers:Edit" permission to your API token');
538
+ } else {
539
+ check.details.push('✓ Account:Workers:Edit permission present');
540
+ }
541
+ if (!permissions.hasWorkersRead) {
542
+ check.status = 'warning';
543
+ check.severity = 'warning';
544
+ check.details.push('Token missing "Account:Workers:Read" permission (recommended)');
545
+ check.fixSuggestions.push('Consider adding "Account:Workers:Read" permission for better diagnostics');
546
+ } else {
547
+ check.details.push('✓ Account:Workers:Read permission present');
548
+ }
549
+ if (!permissions.hasZoneRead) {
550
+ check.status = 'warning';
551
+ check.severity = 'warning';
552
+ check.details.push('Token missing "Zone:Read" permission (recommended for domain validation)');
553
+ check.fixSuggestions.push('Consider adding "Zone:Read" permission for domain validation');
554
+ } else {
555
+ check.details.push('✓ Zone:Read permission present');
556
+ }
557
+ } catch (error) {
558
+ check.status = 'failed';
559
+ check.severity = 'error';
560
+ check.details.push(`Token validation failed: ${error.message}`);
561
+ check.fixSuggestions.push('Verify your API token is valid and has network access');
562
+ check.fixSuggestions.push('Check token permissions at: https://dash.cloudflare.com/profile/api-tokens');
563
+ }
564
+ return check;
565
+ }
566
+
567
+ /**
568
+ * Validate Cloudflare token by testing API access
569
+ */
570
+ async validateCloudflareToken(token) {
571
+ const permissions = {
572
+ hasWorkersEdit: false,
573
+ hasWorkersRead: false,
574
+ hasZoneRead: false
575
+ };
576
+ try {
577
+ // Test Workers:Edit permission by trying to list workers
578
+ const workersResponse = await fetch('https://api.cloudflare.com/client/v4/accounts', {
579
+ method: 'GET',
580
+ headers: {
581
+ 'Authorization': `Bearer ${token}`,
582
+ 'Content-Type': 'application/json'
583
+ }
584
+ });
585
+ if (workersResponse.ok) {
586
+ const data = await workersResponse.json();
587
+ if (data.success && data.result && data.result.length > 0) {
588
+ permissions.hasWorkersRead = true;
589
+ permissions.hasWorkersEdit = true; // If we can read accounts, we likely can edit workers
590
+ }
591
+ }
592
+
593
+ // Test Zone:Read permission
594
+ const zonesResponse = await fetch('https://api.cloudflare.com/client/v4/zones', {
595
+ method: 'GET',
596
+ headers: {
597
+ 'Authorization': `Bearer ${token}`,
598
+ 'Content-Type': 'application/json'
599
+ }
600
+ });
601
+ if (zonesResponse.ok) {
602
+ permissions.hasZoneRead = true;
603
+ }
604
+ } catch (error) {
605
+ // If network fails, we can't validate but don't fail completely
606
+ console.warn('Network error during token validation:', error.message);
607
+ }
608
+ return permissions;
609
+ }
610
+
611
+ /**
612
+ * Check secrets baseline
613
+ */
614
+ async checkSecretsBaseline(servicePath) {
615
+ const check = {
616
+ name: 'secrets-baseline',
617
+ status: 'passed',
618
+ severity: 'info',
619
+ message: 'Secrets baseline validation passed',
620
+ details: [],
621
+ fixSuggestions: []
622
+ };
623
+ try {
624
+ // Scan for potential secrets
625
+ const secretsFound = await this.scanForSecrets(servicePath);
626
+ if (secretsFound.length > 0) {
627
+ // Check against baseline
628
+ const baselineSecrets = await this.loadSecretsBaseline(servicePath);
629
+ const newSecrets = secretsFound.filter(secret => !baselineSecrets.some(baseline => baseline.file === secret.file && baseline.line === secret.line && baseline.pattern === secret.pattern));
630
+ if (newSecrets.length > 0) {
631
+ check.status = 'failed';
632
+ check.severity = 'critical';
633
+ check.message = `Found ${newSecrets.length} potential secrets not in baseline`;
634
+ check.details.push(...newSecrets.map(secret => `${secret.file}:${secret.line} - ${secret.pattern}: ${secret.match}`));
635
+ check.fixSuggestions.push('Review the secrets above and ensure they are not sensitive');
636
+ check.fixSuggestions.push('If safe, add to .secrets.baseline file');
637
+ check.fixSuggestions.push('Run: clodo secrets baseline update');
638
+ } else {
639
+ check.details.push(`✓ All ${secretsFound.length} secrets are in baseline`);
640
+ }
641
+ } else {
642
+ check.details.push('✓ No potential secrets found');
643
+ }
644
+ } catch (error) {
645
+ check.status = 'warning';
646
+ check.severity = 'warning';
647
+ check.details.push(`Secrets scanning failed: ${error.message}`);
648
+ check.fixSuggestions.push('Ensure file permissions allow reading source files');
649
+ }
650
+ return check;
651
+ }
652
+
653
+ /**
654
+ * Scan for potential secrets in the codebase
655
+ * Delegates to SecretsManager for consistent scanning logic
656
+ */
657
+ async scanForSecrets(servicePath) {
658
+ const mgr = new SecretsManager();
659
+ return mgr.scan(servicePath);
660
+ }
661
+
662
+ /**
663
+ * Get list of files to scan for secrets
664
+ * Delegates to SecretsManager
665
+ */
666
+ async getFilesToScan(servicePath) {
667
+ const mgr = new SecretsManager();
668
+ return mgr.getFilesToScan(servicePath);
669
+ }
670
+
671
+ /**
672
+ * Load secrets baseline file
673
+ * Delegates to SecretsManager
674
+ */
675
+ async loadSecretsBaseline(servicePath) {
676
+ const mgr = new SecretsManager();
677
+ return mgr.loadBaseline(servicePath);
678
+ }
679
+
680
+ /**
681
+ * Check config files in the service directory against their schemas
682
+ * Validates any config JSON files found (clodo-*.json pattern)
683
+ */
684
+ async checkConfigSchemas(servicePath) {
685
+ const check = {
686
+ name: 'config-schemas',
687
+ status: 'passed',
688
+ severity: 'info',
689
+ message: 'Config schema validation passed',
690
+ details: [],
691
+ fixSuggestions: []
692
+ };
693
+ try {
694
+ const validator = new ConfigSchemaValidator();
695
+ const configPairs = [{
696
+ glob: 'clodo-create.json',
697
+ type: 'create'
698
+ }, {
699
+ glob: 'clodo-deploy.json',
700
+ type: 'deploy'
701
+ }, {
702
+ glob: 'clodo-validate.json',
703
+ type: 'validate'
704
+ }, {
705
+ glob: 'clodo-update.json',
706
+ type: 'update'
707
+ }];
708
+ let filesChecked = 0;
709
+ let errors = 0;
710
+ for (const {
711
+ glob,
712
+ type
713
+ } of configPairs) {
714
+ // Check in service root and config/ subdirectory
715
+ const candidates = [path.join(servicePath, glob), path.join(servicePath, 'config', glob)];
716
+ for (const filePath of candidates) {
717
+ try {
718
+ await fs.access(filePath);
719
+ } catch {
720
+ continue; // File doesn't exist — skip
721
+ }
722
+ filesChecked++;
723
+ const result = validator.validateConfigFile(filePath, type);
724
+ if (!result.valid) {
725
+ errors++;
726
+ check.details.push(`✗ ${path.relative(servicePath, filePath)}: ${result.errors.length} error(s)`);
727
+ for (const err of result.errors) {
728
+ check.details.push(` ${err.field}: ${err.message}`);
729
+ }
730
+ } else {
731
+ check.details.push(`✓ ${path.relative(servicePath, filePath)}: valid (${result.fieldCount} fields)`);
732
+ }
733
+
734
+ // Report warnings
735
+ for (const warn of result.warnings) {
736
+ check.details.push(` ⚠ ${warn.field}: ${warn.message}`);
737
+ }
738
+ }
739
+ }
740
+ if (filesChecked === 0) {
741
+ check.details.push('No config files found to validate');
742
+ check.details.push('Config files follow the pattern: clodo-{create|deploy|validate|update}.json');
743
+ } else if (errors > 0) {
744
+ check.status = 'failed';
745
+ check.severity = 'warning';
746
+ check.message = `${errors} config file(s) have schema validation errors`;
747
+ check.fixSuggestions.push('Review the config errors above and fix invalid fields');
748
+ check.fixSuggestions.push('Run: clodo config-schema validate <file> for details');
749
+ check.fixSuggestions.push('Run: clodo config-schema show <type> for schema reference');
750
+ } else {
751
+ check.message = `${filesChecked} config file(s) validated successfully`;
752
+ }
753
+ } catch (error) {
754
+ check.status = 'warning';
755
+ check.severity = 'warning';
756
+ check.details.push(`Config schema validation failed: ${error.message}`);
757
+ }
758
+ return check;
759
+ }
760
+
761
+ /**
762
+ * Apply automatic fixes for detected issues
763
+ */
764
+ async applyFixes(results, servicePath) {
765
+ const fixesApplied = [];
766
+ for (const check of results.checks) {
767
+ if (check.status === 'failed' && check.fixSuggestions) {
768
+ for (const suggestion of check.fixSuggestions) {
769
+ try {
770
+ const fixResult = await this.applySpecificFix(check.name, suggestion, servicePath);
771
+ if (fixResult) {
772
+ fixesApplied.push(`${check.name}: ${fixResult}`);
773
+ console.log(` ✅ ${fixResult}`);
774
+ }
775
+ } catch (error) {
776
+ console.log(` ❌ Failed to apply fix: ${error.message}`);
777
+ }
778
+ }
779
+ }
780
+ }
781
+ return fixesApplied;
782
+ }
783
+
784
+ /**
785
+ * Apply a specific fix based on check name and suggestion
786
+ */
787
+ async applySpecificFix(checkName, suggestion, servicePath) {
788
+ switch (checkName) {
789
+ case 'environment':
790
+ if (suggestion.includes('wrangler')) {
791
+ return await this.fixInstallWrangler();
792
+ }
793
+ break;
794
+ case 'service-validation':
795
+ if (suggestion.includes('dependency')) {
796
+ return await this.fixMissingDependency(suggestion, servicePath);
797
+ }
798
+ if (suggestion.includes('Add main field')) {
799
+ return await this.fixAddMainField(servicePath);
800
+ }
801
+ if (suggestion.includes('Set package.json type to module')) {
802
+ return await this.fixSetModuleType(servicePath);
803
+ }
804
+ if (suggestion.includes('domain')) {
805
+ return await this.fixDomainConfiguration(servicePath);
806
+ }
807
+ break;
808
+ case 'config-presence':
809
+ if (suggestion.includes('Create')) {
810
+ const fileName = suggestion.match(/Create (\S+)/)?.[1];
811
+ if (fileName) {
812
+ return await this.fixCreateConfigFile(fileName, servicePath);
813
+ }
814
+ }
815
+ break;
816
+ }
817
+ return null;
818
+ }
819
+
820
+ /**
821
+ * Fix: Install wrangler CLI
822
+ */
823
+ async fixInstallWrangler() {
824
+ try {
825
+ const {
826
+ execSync
827
+ } = await import('child_process');
828
+ console.log(' 📦 Installing wrangler CLI globally...');
829
+ execSync('npm install -g wrangler', {
830
+ stdio: 'inherit'
831
+ });
832
+ return 'Installed wrangler CLI globally';
833
+ } catch (error) {
834
+ throw new Error(`Failed to install wrangler: ${error.message}`);
835
+ }
836
+ }
837
+
838
+ /**
839
+ * Fix: Add missing dependency to package.json
840
+ */
841
+ async fixMissingDependency(suggestion, servicePath) {
842
+ const depMatch = suggestion.match(/Missing required dependency: (\S+)/);
843
+ if (!depMatch) return null;
844
+ const dependency = depMatch[1];
845
+ const packageJsonPath = path.join(servicePath, 'package.json');
846
+ try {
847
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
848
+ if (!packageJson.dependencies) {
849
+ packageJson.dependencies = {};
850
+ }
851
+
852
+ // Add the framework dependency with latest version
853
+ packageJson.dependencies[dependency] = '^4.5.0';
854
+ await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
855
+ return `Added ${dependency} to package.json dependencies`;
856
+ } catch (error) {
857
+ throw new Error(`Failed to add dependency: ${error.message}`);
858
+ }
859
+ }
860
+
861
+ /**
862
+ * Fix: Add main field to package.json
863
+ */
864
+ async fixAddMainField(servicePath) {
865
+ const packageJsonPath = path.join(servicePath, 'package.json');
866
+ try {
867
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
868
+ packageJson.main = 'src/worker/index.js';
869
+ await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
870
+ return 'Added main field to package.json';
871
+ } catch (error) {
872
+ throw new Error(`Failed to add main field: ${error.message}`);
873
+ }
874
+ }
875
+
876
+ /**
877
+ * Fix: Set package.json type to module
878
+ */
879
+ async fixSetModuleType(servicePath) {
880
+ const packageJsonPath = path.join(servicePath, 'package.json');
881
+ try {
882
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
883
+ packageJson.type = 'module';
884
+ await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
885
+ return 'Set package.json type to module';
886
+ } catch (error) {
887
+ throw new Error(`Failed to set module type: ${error.message}`);
888
+ }
889
+ }
890
+
891
+ /**
892
+ * Fix: Create basic domain configuration
893
+ */
894
+ async fixDomainConfiguration(servicePath) {
895
+ const domainConfigPath = path.join(servicePath, 'src/config/domains.js');
896
+ const dirPath = path.dirname(domainConfigPath);
897
+ try {
898
+ // Ensure directory exists
899
+ await fs.mkdir(dirPath, {
900
+ recursive: true
901
+ });
902
+ const basicDomainConfig = `/**
903
+ * Domain Configuration
904
+ *
905
+ * Configure domains and routing for your Clodo service
906
+ */
907
+
908
+ import { createDomainConfigSchema } from '@tamyla/clodo-framework';
909
+
910
+ export const domains = createDomainConfigSchema({
911
+ // Default domain configuration
912
+ default: {
913
+ name: 'example.com',
914
+ routes: [
915
+ {
916
+ pattern: '/',
917
+ handler: 'index'
918
+ }
919
+ ]
920
+ }
921
+ });
922
+ `;
923
+ await fs.writeFile(domainConfigPath, basicDomainConfig);
924
+ return 'Created basic domain configuration file';
925
+ } catch (error) {
926
+ throw new Error(`Failed to create domain config: ${error.message}`);
927
+ }
928
+ }
929
+
930
+ /**
931
+ * Fix: Create basic configuration file
932
+ */
933
+ async fixCreateConfigFile(fileName, servicePath) {
934
+ const filePath = path.join(servicePath, fileName);
935
+ const dirPath = path.dirname(filePath);
936
+ try {
937
+ // Ensure directory exists
938
+ await fs.mkdir(dirPath, {
939
+ recursive: true
940
+ });
941
+ let content = '';
942
+ switch (fileName) {
943
+ case 'wrangler.toml':
944
+ content = `name = "my-clodo-service"
945
+ main = "src/worker/index.js"
946
+ compatibility_date = "${new Date().toISOString().split('T')[0]}"
947
+
948
+ [vars]
949
+ NODE_ENV = "production"
950
+ `;
951
+ break;
952
+ case 'src/worker/index.js':
953
+ content = `/**
954
+ * Main Worker Entry Point
955
+ */
956
+
957
+ import { createServiceRouter } from '@tamyla/clodo-framework';
958
+
959
+ export default {
960
+ async fetch(request, env, ctx) {
961
+ const router = createServiceRouter({
962
+ // Configure your service routes here
963
+ });
964
+
965
+ return router.handle(request, env, ctx);
966
+ }
967
+ };
968
+ `;
969
+ break;
970
+ case 'src/config/domains.js':
971
+ return await this.fixDomainConfiguration(servicePath);
972
+ default:
973
+ content = `# ${fileName}
974
+ # Basic configuration file created by clodo doctor --fix
975
+ `;
976
+ }
977
+ await fs.writeFile(filePath, content);
978
+ return `Created ${fileName} with basic configuration`;
979
+ } catch (error) {
980
+ throw new Error(`Failed to create ${fileName}: ${error.message}`);
981
+ }
982
+ }
287
983
  }