@tamyla/clodo-framework 3.0.12 → 3.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.
- package/CHANGELOG.md +17 -0
- package/bin/clodo-service.js +479 -185
- package/bin/shared/security/secret-generator.js +7 -2
- package/dist/config/customers.js +8 -2
- package/dist/config/domains.js +8 -2
- package/dist/config/features.js +7 -2
- package/dist/index.js +8 -0
- package/dist/orchestration/multi-domain-orchestrator.js +66 -45
- package/dist/service-management/ConfirmationEngine.js +4 -4
- package/dist/service-management/GenerationEngine.js +23 -14
- package/dist/service-management/InputCollector.js +7 -2
- package/dist/service-management/ServiceCreator.js +9 -4
- package/dist/service-management/ServiceInitializer.js +14 -5
- package/dist/service-management/index.js +8 -1
- package/dist/shared/security/secret-generator.js +7 -2
- package/dist/utils/config/unified-config-manager.js +6 -2
- package/dist/worker/integration.js +8 -2
- package/package.json +5 -3
- package/templates/generic/src/config/domains.js +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
## [3.0.14](https://github.com/tamylaa/clodo-framework/compare/v3.0.13...v3.0.14) (2025-10-20)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* add test environment compatibility fixes for ES modules ([71a033d](https://github.com/tamylaa/clodo-framework/commit/71a033d5d959480879c1a4771005553116e8671f))
|
|
7
|
+
* ignore deployment artifacts in deployments/ directory ([768f453](https://github.com/tamylaa/clodo-framework/commit/768f453cbabb984509613a382d86875c1309bcd2))
|
|
8
|
+
* resolve circular dependency in FeatureFlagManager ([d04956f](https://github.com/tamylaa/clodo-framework/commit/d04956f83527d3236fce2fe9750f71cdf0ba8d8e))
|
|
9
|
+
* resolve ESLint errors and update test expectations ([b1188be](https://github.com/tamylaa/clodo-framework/commit/b1188be66d3723bfff62431f6e0eabec685b0111))
|
|
10
|
+
* revert Jest config to working configuration that passes tests ([35a2846](https://github.com/tamylaa/clodo-framework/commit/35a28466c8a48c78011c128f89695399e8500341))
|
|
11
|
+
* update Jest config to properly handle ES modules ([1a951c7](https://github.com/tamylaa/clodo-framework/commit/1a951c7ae147ddb8bef0ec1508c858ac044423b7))
|
|
12
|
+
* update package-lock.json to sync with package.json dependencies ([a3ef884](https://github.com/tamylaa/clodo-framework/commit/a3ef8844cba2b5657a3493b8339c98049a57dd72))
|
|
13
|
+
* update package.json semantic release config to support main branch ([a06b2e8](https://github.com/tamylaa/clodo-framework/commit/a06b2e899ceb7699b67292157991d64c88dedae3))
|
|
14
|
+
* update semantic release config to support main branch ([4102e6c](https://github.com/tamylaa/clodo-framework/commit/4102e6c4cb4f52d065750239c2a0c5a89b733ac0))
|
|
15
|
+
* update version to 3.0.13 to sync with release history ([f9bb4d4](https://github.com/tamylaa/clodo-framework/commit/f9bb4d463a3884b82e972962e5fcd21a272c3acb))
|
|
16
|
+
* use os.tmpdir() for test paths to fix CI permission errors ([022c771](https://github.com/tamylaa/clodo-framework/commit/022c771994635002410c27d8eb7ec8f0614acf6f))
|
|
17
|
+
|
|
1
18
|
## [3.0.12](https://github.com/tamylaa/clodo-framework/compare/v3.0.11...v3.0.12) (2025-10-14)
|
|
2
19
|
|
|
3
20
|
|
package/bin/clodo-service.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Clodo Framework - Unified Three-Tier Service Management CLI
|
|
@@ -18,6 +18,8 @@ import chalk from 'chalk';
|
|
|
18
18
|
import { join } from 'path';
|
|
19
19
|
import { ServiceOrchestrator } from '../dist/service-management/ServiceOrchestrator.js';
|
|
20
20
|
import { InputCollector } from '../dist/service-management/InputCollector.js';
|
|
21
|
+
import { readFileSync, existsSync } from 'fs';
|
|
22
|
+
import { resolve } from 'path';
|
|
21
23
|
|
|
22
24
|
const program = new Command();
|
|
23
25
|
|
|
@@ -26,6 +28,117 @@ program
|
|
|
26
28
|
.description('Unified conversational CLI for Clodo Framework service lifecycle management')
|
|
27
29
|
.version('1.0.0');
|
|
28
30
|
|
|
31
|
+
// Helper function to load JSON configuration file
|
|
32
|
+
function loadJsonConfig(configPath) {
|
|
33
|
+
try {
|
|
34
|
+
const fullPath = resolve(configPath);
|
|
35
|
+
if (!existsSync(fullPath)) {
|
|
36
|
+
throw new Error(`Configuration file not found: ${fullPath}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const content = readFileSync(fullPath, 'utf8');
|
|
40
|
+
const config = JSON.parse(content);
|
|
41
|
+
|
|
42
|
+
// Validate required fields
|
|
43
|
+
const required = ['customer', 'environment', 'domainName', 'cloudflareToken'];
|
|
44
|
+
const missing = required.filter(field => !config[field]);
|
|
45
|
+
|
|
46
|
+
if (missing.length > 0) {
|
|
47
|
+
throw new Error(`Missing required configuration fields: ${missing.join(', ')}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(chalk.green(`✅ Loaded configuration from: ${fullPath}`));
|
|
51
|
+
return config;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (error instanceof SyntaxError) {
|
|
54
|
+
throw new Error(`Invalid JSON in configuration file: ${error.message}`);
|
|
55
|
+
}
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Simple progress indicator for deployment steps
|
|
61
|
+
function showProgress(message, duration = 2000) {
|
|
62
|
+
return new Promise(resolve => {
|
|
63
|
+
process.stdout.write(chalk.cyan(`⏳ ${message}...`));
|
|
64
|
+
|
|
65
|
+
const spinner = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
66
|
+
let i = 0;
|
|
67
|
+
|
|
68
|
+
const interval = setInterval(() => {
|
|
69
|
+
process.stdout.write(`\r${chalk.cyan(spinner[i])} ${message}...`);
|
|
70
|
+
i = (i + 1) % spinner.length;
|
|
71
|
+
}, 100);
|
|
72
|
+
|
|
73
|
+
setTimeout(() => {
|
|
74
|
+
clearInterval(interval);
|
|
75
|
+
process.stdout.write(`\r${chalk.green('✅')} ${message}... Done!\n`);
|
|
76
|
+
resolve();
|
|
77
|
+
}, duration);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Early validation function to check prerequisites before deployment
|
|
82
|
+
async function validateDeploymentPrerequisites(coreInputs, options) {
|
|
83
|
+
const issues = [];
|
|
84
|
+
|
|
85
|
+
console.log(chalk.cyan('\n🔍 Pre-deployment Validation'));
|
|
86
|
+
console.log(chalk.gray('─'.repeat(40)));
|
|
87
|
+
|
|
88
|
+
// Check required fields
|
|
89
|
+
if (!coreInputs.customer) {
|
|
90
|
+
issues.push('Customer name is required');
|
|
91
|
+
}
|
|
92
|
+
if (!coreInputs.environment) {
|
|
93
|
+
issues.push('Environment is required');
|
|
94
|
+
}
|
|
95
|
+
if (!coreInputs.domainName) {
|
|
96
|
+
issues.push('Domain name is required');
|
|
97
|
+
}
|
|
98
|
+
if (!coreInputs.cloudflareToken) {
|
|
99
|
+
issues.push('Cloudflare API token is required');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Check Cloudflare token format (basic validation)
|
|
103
|
+
if (coreInputs.cloudflareToken && !coreInputs.cloudflareToken.startsWith('CLOUDFLARE_API_TOKEN=')) {
|
|
104
|
+
if (coreInputs.cloudflareToken.length < 40) {
|
|
105
|
+
issues.push('Cloudflare API token appears to be invalid (too short)');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Check if service path exists
|
|
110
|
+
if (options.servicePath && options.servicePath !== '.') {
|
|
111
|
+
const { existsSync } = await import('fs');
|
|
112
|
+
if (!existsSync(options.servicePath)) {
|
|
113
|
+
issues.push(`Service path does not exist: ${options.servicePath}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Check for wrangler.toml if not dry run
|
|
118
|
+
if (!options.dryRun) {
|
|
119
|
+
const { existsSync } = await import('fs');
|
|
120
|
+
const { join } = await import('path');
|
|
121
|
+
const wranglerPath = join(options.servicePath || '.', 'wrangler.toml');
|
|
122
|
+
if (!existsSync(wranglerPath)) {
|
|
123
|
+
issues.push('wrangler.toml not found in service directory');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Report issues
|
|
128
|
+
if (issues.length > 0) {
|
|
129
|
+
console.log(chalk.red('\n❌ Validation Failed:'));
|
|
130
|
+
issues.forEach(issue => {
|
|
131
|
+
console.log(chalk.red(` • ${issue}`));
|
|
132
|
+
});
|
|
133
|
+
console.log(chalk.gray('\n─'.repeat(40)));
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log(chalk.green('✅ All prerequisites validated'));
|
|
138
|
+
console.log(chalk.gray('─'.repeat(40)));
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
29
142
|
// Main interactive command
|
|
30
143
|
program
|
|
31
144
|
.command('create')
|
|
@@ -414,6 +527,86 @@ program
|
|
|
414
527
|
}
|
|
415
528
|
});
|
|
416
529
|
|
|
530
|
+
// Professional Edition: assess command (from clodo-orchestration)
|
|
531
|
+
program
|
|
532
|
+
.command('assess [service-path]')
|
|
533
|
+
.description('Run intelligent capability assessment (requires @tamyla/clodo-orchestration)')
|
|
534
|
+
.option('--export <file>', 'Export assessment results to JSON file')
|
|
535
|
+
.option('--domain <domain>', 'Domain name for assessment')
|
|
536
|
+
.option('--service-type <type>', 'Service type for assessment')
|
|
537
|
+
.option('--token <token>', 'Cloudflare API token')
|
|
538
|
+
.action(async (servicePath, options) => {
|
|
539
|
+
try {
|
|
540
|
+
// Try to load professional orchestration package
|
|
541
|
+
let orchestrationModule;
|
|
542
|
+
try {
|
|
543
|
+
orchestrationModule = await import('@tamyla/clodo-orchestration');
|
|
544
|
+
} catch (err) {
|
|
545
|
+
console.error(chalk.red('❌ clodo-orchestration package not found'));
|
|
546
|
+
console.error(chalk.yellow('💡 Install with: npm install @tamyla/clodo-orchestration'));
|
|
547
|
+
process.exit(1);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const {
|
|
551
|
+
CapabilityAssessmentEngine,
|
|
552
|
+
ServiceAutoDiscovery,
|
|
553
|
+
runAssessmentWorkflow
|
|
554
|
+
} = orchestrationModule;
|
|
555
|
+
|
|
556
|
+
const targetPath = servicePath || process.cwd();
|
|
557
|
+
console.log(chalk.cyan('\n🧠 Professional Capability Assessment'));
|
|
558
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
559
|
+
console.log(chalk.white(`Service Path: ${targetPath}`));
|
|
560
|
+
|
|
561
|
+
if (options.domain) {
|
|
562
|
+
console.log(chalk.white(`Domain: ${options.domain}`));
|
|
563
|
+
}
|
|
564
|
+
if (options.serviceType) {
|
|
565
|
+
console.log(chalk.white(`Service Type: ${options.serviceType}`));
|
|
566
|
+
}
|
|
567
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
568
|
+
|
|
569
|
+
// Use the assessment workflow
|
|
570
|
+
const assessment = await runAssessmentWorkflow({
|
|
571
|
+
servicePath: targetPath,
|
|
572
|
+
domain: options.domain,
|
|
573
|
+
serviceType: options.serviceType,
|
|
574
|
+
token: options.token || process.env.CLOUDFLARE_API_TOKEN
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
// Display results
|
|
578
|
+
console.log(chalk.cyan('\n✅ Assessment Results'));
|
|
579
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
580
|
+
console.log(chalk.white(`Service Type: ${assessment.mergedInputs?.serviceType || assessment.serviceType || 'Not determined'}`));
|
|
581
|
+
console.log(chalk.white(`Confidence: ${assessment.confidence}%`));
|
|
582
|
+
|
|
583
|
+
if (assessment.gapAnalysis?.missing) {
|
|
584
|
+
console.log(chalk.white(`Missing Capabilities: ${assessment.gapAnalysis.missing.length}`));
|
|
585
|
+
if (assessment.gapAnalysis.missing.length > 0) {
|
|
586
|
+
console.log(chalk.yellow('\n⚠️ Missing:'));
|
|
587
|
+
assessment.gapAnalysis.missing.forEach(gap => {
|
|
588
|
+
console.log(chalk.yellow(` • ${gap.capability}: ${gap.reason || 'Not available'}`));
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
594
|
+
|
|
595
|
+
// Export results if requested
|
|
596
|
+
if (options.export) {
|
|
597
|
+
require('fs').writeFileSync(options.export, JSON.stringify(assessment, null, 2));
|
|
598
|
+
console.log(chalk.green(`\n📄 Results exported to: ${options.export}`));
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
} catch (error) {
|
|
602
|
+
console.error(chalk.red(`Assessment failed: ${error.message}`));
|
|
603
|
+
if (process.env.DEBUG) {
|
|
604
|
+
console.error(chalk.gray(error.stack));
|
|
605
|
+
}
|
|
606
|
+
process.exit(1);
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
|
|
417
610
|
// Deploy command - using existing InputCollector + CustomerConfigLoader (reusable pattern)
|
|
418
611
|
program
|
|
419
612
|
.command('deploy')
|
|
@@ -422,6 +615,10 @@ program
|
|
|
422
615
|
.option('-e, --env <environment>', 'Target environment (development, staging, production)')
|
|
423
616
|
.option('-i, --interactive', 'Interactive mode (review confirmations)', true)
|
|
424
617
|
.option('--non-interactive', 'Non-interactive mode (use stored config)')
|
|
618
|
+
.option('--config-file <file>', 'Load configuration from JSON file')
|
|
619
|
+
.option('--defaults', 'Use default values where possible (non-interactive)')
|
|
620
|
+
.option('--quiet', 'Quiet mode - minimal output for CI/CD')
|
|
621
|
+
.option('--json-output', 'Output results as JSON for scripting')
|
|
425
622
|
.option('--dry-run', 'Simulate deployment without making changes')
|
|
426
623
|
.option('--domain <domain>', 'Domain name (overrides stored config)')
|
|
427
624
|
.option('--service-path <path>', 'Path to service directory', '.')
|
|
@@ -436,7 +633,9 @@ program
|
|
|
436
633
|
console.log(chalk.cyan('\n🚀 Clodo Framework Deployment'));
|
|
437
634
|
console.log(chalk.white('Using Three-Tier Input Architecture\n'));
|
|
438
635
|
|
|
439
|
-
const isInteractive = options.interactive && !options.nonInteractive;
|
|
636
|
+
const isInteractive = options.interactive && !options.nonInteractive && !options.configFile && !options.defaults;
|
|
637
|
+
const isQuiet = options.quiet;
|
|
638
|
+
const jsonOutput = options.jsonOutput;
|
|
440
639
|
const configManager = new UnifiedConfigManager({
|
|
441
640
|
configDir: join(process.cwd(), 'config', 'customers')
|
|
442
641
|
});
|
|
@@ -446,216 +645,285 @@ program
|
|
|
446
645
|
let coreInputs = {};
|
|
447
646
|
let source = 'interactive';
|
|
448
647
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
648
|
+
// Interactive mode: run input collector
|
|
649
|
+
if (isInteractive) {
|
|
650
|
+
await inputCollector.collect();
|
|
651
|
+
} else {
|
|
652
|
+
// Non-interactive mode: load from config file or stored config
|
|
653
|
+
|
|
654
|
+
// Load from JSON config file if specified
|
|
655
|
+
if (options.configFile) {
|
|
656
|
+
coreInputs = loadJsonConfig(options.configFile);
|
|
657
|
+
source = 'config-file';
|
|
658
|
+
} else if (options.customer && options.env) {
|
|
659
|
+
// Check UnifiedConfigManager for existing config
|
|
660
|
+
if (configManager.configExists(options.customer, options.env)) {
|
|
661
|
+
console.log(chalk.green(`✅ Found existing configuration for ${options.customer}/${options.env}\n`));
|
|
662
|
+
configManager.displayCustomerConfig(options.customer, options.env);
|
|
663
|
+
|
|
460
664
|
// Non-interactive: auto-load the config
|
|
461
|
-
storedConfig = configManager.loadCustomerConfig(options.customer, options.env);
|
|
665
|
+
const storedConfig = configManager.loadCustomerConfig(options.customer, options.env);
|
|
462
666
|
if (storedConfig) {
|
|
463
667
|
coreInputs = storedConfig;
|
|
464
668
|
source = 'stored-config';
|
|
465
669
|
}
|
|
466
670
|
} else {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
671
|
+
console.log(chalk.yellow(`⚠️ No configuration found for ${options.customer}/${options.env}`));
|
|
672
|
+
console.log(chalk.white('Collecting inputs interactively...\n'));
|
|
673
|
+
}
|
|
674
|
+
} else {
|
|
675
|
+
console.log(chalk.yellow('⚠️ Customer and environment not specified'));
|
|
676
|
+
console.log(chalk.white('Use --customer and --env options, or run in interactive mode'));
|
|
677
|
+
process.exit(1);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Collect inputs if we don't have them from stored config
|
|
681
|
+
if (!coreInputs.cloudflareAccountId) {
|
|
682
|
+
console.log(chalk.cyan('📊 Tier 1: Core Input Collection\n'));
|
|
683
|
+
|
|
684
|
+
// Collect basic info with smart customer selection
|
|
685
|
+
let customer = options.customer;
|
|
686
|
+
if (!customer) {
|
|
687
|
+
const customers = configManager.listCustomers();
|
|
688
|
+
if (customers.length > 0) {
|
|
689
|
+
const selection = await inputCollector.question('Select customer (enter number or name): ');
|
|
690
|
+
|
|
691
|
+
// Try to parse as number first
|
|
692
|
+
const num = parseInt(selection);
|
|
693
|
+
if (!isNaN(num) && num >= 1 && num <= customers.length) {
|
|
694
|
+
customer = customers[num - 1];
|
|
695
|
+
console.log(chalk.green(`✓ Selected: ${customer}\n`));
|
|
696
|
+
} else if (customers.includes(selection)) {
|
|
697
|
+
customer = selection;
|
|
698
|
+
console.log(chalk.green(`✓ Selected: ${customer}\n`));
|
|
699
|
+
} else {
|
|
700
|
+
// New customer name
|
|
701
|
+
customer = selection;
|
|
702
|
+
console.log(chalk.yellow(`⚠️ Creating new customer: ${customer}\n`));
|
|
475
703
|
}
|
|
476
704
|
} else {
|
|
477
|
-
|
|
705
|
+
customer = await inputCollector.question('Customer name: ');
|
|
478
706
|
}
|
|
479
707
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
708
|
+
const environment = options.env || await inputCollector.collectEnvironment();
|
|
709
|
+
const serviceName = await inputCollector.collectServiceName();
|
|
710
|
+
const serviceType = await inputCollector.collectServiceType();
|
|
711
|
+
|
|
712
|
+
// Collect Cloudflare token
|
|
713
|
+
const cloudflareToken = process.env.CLOUDFLARE_API_TOKEN || await inputCollector.collectCloudflareToken();
|
|
714
|
+
|
|
715
|
+
// Use CloudflareAPI for automatic domain discovery
|
|
716
|
+
console.log(chalk.cyan('⏳ Fetching Cloudflare configuration...'));
|
|
717
|
+
const cloudflareConfig = await inputCollector.collectCloudflareConfigWithDiscovery(
|
|
718
|
+
cloudflareToken,
|
|
719
|
+
options.domain
|
|
720
|
+
);
|
|
721
|
+
|
|
722
|
+
// Combine all inputs
|
|
723
|
+
coreInputs = {
|
|
724
|
+
customer,
|
|
725
|
+
environment,
|
|
726
|
+
serviceName,
|
|
727
|
+
serviceType,
|
|
728
|
+
domainName: cloudflareConfig.domainName,
|
|
729
|
+
cloudflareToken,
|
|
730
|
+
cloudflareAccountId: cloudflareConfig.accountId,
|
|
731
|
+
cloudflareZoneId: cloudflareConfig.zoneId
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
source = 'interactive';
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Allow domain override
|
|
738
|
+
if (options.domain) {
|
|
739
|
+
coreInputs.domainName = options.domain;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// Early validation before proceeding
|
|
743
|
+
const isValid = await validateDeploymentPrerequisites(coreInputs, options);
|
|
744
|
+
if (!isValid) {
|
|
745
|
+
console.log(chalk.red('\n❌ Deployment cancelled due to validation errors.'));
|
|
746
|
+
console.log(chalk.cyan('💡 Fix the issues above and try again.'));
|
|
747
|
+
process.exit(1);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Tier 2: Generate smart confirmations using existing ConfirmationHandler
|
|
751
|
+
// Note: ConfirmationEngine prints its own header
|
|
752
|
+
const confirmations = await confirmationHandler.generateAndConfirm(coreInputs);
|
|
753
|
+
|
|
754
|
+
// Show deployment summary
|
|
755
|
+
console.log(chalk.cyan('\n📊 Deployment Summary'));
|
|
756
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
757
|
+
|
|
758
|
+
console.log(chalk.white(`Source: ${source}`));
|
|
759
|
+
console.log(chalk.white(`Customer: ${coreInputs.customer}`));
|
|
760
|
+
console.log(chalk.white(`Environment: ${coreInputs.environment}`));
|
|
761
|
+
console.log(chalk.white(`Domain: ${coreInputs.domainName}`));
|
|
762
|
+
console.log(chalk.white(`Account ID: ${coreInputs.cloudflareAccountId ? coreInputs.cloudflareAccountId.substring(0, 8) + '...' : 'N/A'}`));
|
|
763
|
+
console.log(chalk.white(`Zone ID: ${coreInputs.cloudflareZoneId ? coreInputs.cloudflareZoneId.substring(0, 8) + '...' : 'N/A'}`));
|
|
764
|
+
|
|
765
|
+
if (confirmations.workerName) {
|
|
766
|
+
console.log(chalk.white(`Worker: ${confirmations.workerName}`));
|
|
767
|
+
}
|
|
768
|
+
if (confirmations.databaseName) {
|
|
769
|
+
console.log(chalk.white(`Database: ${confirmations.databaseName}`));
|
|
483
770
|
}
|
|
484
771
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
console.log(chalk.green('\n✅ Using stored configuration\n'));
|
|
772
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
773
|
+
|
|
774
|
+
if (options.dryRun) {
|
|
775
|
+
console.log(chalk.yellow('\n🔍 DRY RUN MODE - No changes will be made\n'));
|
|
490
776
|
}
|
|
491
|
-
|
|
492
|
-
//
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
777
|
+
|
|
778
|
+
// Tier 3: Execute deployment
|
|
779
|
+
console.log(chalk.cyan('\n⚙️ Tier 3: Automated Deployment\n'));
|
|
780
|
+
|
|
781
|
+
// OPTIONAL: Try to load professional orchestration package if available
|
|
782
|
+
let professionalOrchestration = null;
|
|
783
|
+
try {
|
|
784
|
+
const { runAssessmentWorkflow } = await import('@tamyla/clodo-orchestration');
|
|
785
|
+
professionalOrchestration = { runAssessmentWorkflow };
|
|
786
|
+
console.log(chalk.cyan('📊 Professional Edition (clodo-orchestration) detected'));
|
|
787
|
+
} catch (err) {
|
|
788
|
+
// clodo-orchestration not installed, continue with standard assessment
|
|
789
|
+
if (process.env.DEBUG) {
|
|
790
|
+
console.log(chalk.gray(` ℹ️ clodo-orchestration not available: ${err.message}`));
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// INTEGRATION: Add intelligent service discovery and assessment
|
|
795
|
+
console.log(chalk.cyan('🧠 Performing Intelligent Service Assessment...'));
|
|
796
|
+
const { CapabilityAssessmentEngine } = await import('../dist/service-management/CapabilityAssessmentEngine.js');
|
|
797
|
+
const assessor = new CapabilityAssessmentEngine(options.servicePath || process.cwd());
|
|
798
|
+
|
|
799
|
+
try {
|
|
800
|
+
const assessment = await assessor.assessCapabilities({
|
|
801
|
+
serviceName: coreInputs.serviceName,
|
|
802
|
+
serviceType: coreInputs.serviceType,
|
|
803
|
+
environment: coreInputs.environment,
|
|
804
|
+
domainName: coreInputs.domainName
|
|
498
805
|
});
|
|
806
|
+
|
|
807
|
+
// Display assessment results
|
|
808
|
+
console.log(chalk.green('✅ Assessment Complete'));
|
|
809
|
+
console.log(chalk.white(` Service Type: ${assessment.mergedInputs.serviceType || 'Not determined'}`));
|
|
810
|
+
console.log(chalk.white(` Confidence: ${assessment.confidence}%`));
|
|
811
|
+
console.log(chalk.white(` Missing Capabilities: ${assessment.gapAnalysis.missing.length}`));
|
|
812
|
+
|
|
813
|
+
// Show any blocking issues
|
|
814
|
+
const blockingIssues = assessment.gapAnalysis.missing.filter(gap => gap.priority === 'blocked');
|
|
815
|
+
if (blockingIssues.length > 0) {
|
|
816
|
+
console.log(chalk.yellow('\n⚠️ Permission-limited capabilities detected:'));
|
|
817
|
+
blockingIssues.forEach(issue => {
|
|
818
|
+
console.log(chalk.yellow(` - ${issue.capability}: ${issue.reason}`));
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
console.log('');
|
|
823
|
+
|
|
824
|
+
} catch (assessmentError) {
|
|
825
|
+
console.log(chalk.yellow('⚠️ Assessment failed, proceeding with deployment:'), assessmentError.message);
|
|
499
826
|
console.log('');
|
|
500
827
|
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// Collect inputs if we don't have them from stored config
|
|
504
|
-
if (!coreInputs.cloudflareAccountId) {
|
|
505
|
-
console.log(chalk.cyan('📊 Tier 1: Core Input Collection\n'));
|
|
506
828
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
829
|
+
const orchestrator = new MultiDomainOrchestrator({
|
|
830
|
+
domains: [coreInputs.domainName],
|
|
831
|
+
environment: coreInputs.environment,
|
|
832
|
+
dryRun: options.dryRun,
|
|
833
|
+
servicePath: options.servicePath,
|
|
834
|
+
cloudflareToken: coreInputs.cloudflareToken,
|
|
835
|
+
cloudflareAccountId: coreInputs.cloudflareAccountId
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
// Show progress for initialization
|
|
839
|
+
await showProgress('Initializing deployment orchestrator', 1500);
|
|
840
|
+
await orchestrator.initialize();
|
|
841
|
+
|
|
842
|
+
console.log(chalk.cyan('🚀 Starting deployment...\n'));
|
|
843
|
+
|
|
844
|
+
// Show progress during deployment
|
|
845
|
+
const deployPromise = orchestrator.deploySingleDomain(coreInputs.domainName, {
|
|
846
|
+
...coreInputs,
|
|
847
|
+
...confirmations,
|
|
848
|
+
servicePath: options.servicePath
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
// Show deployment progress
|
|
852
|
+
const progressMessages = [
|
|
853
|
+
'Preparing deployment configuration',
|
|
854
|
+
'Setting up Cloudflare resources',
|
|
855
|
+
'Deploying worker script',
|
|
856
|
+
'Configuring database connections',
|
|
857
|
+
'Setting up domain routing',
|
|
858
|
+
'Running final health checks'
|
|
859
|
+
];
|
|
860
|
+
|
|
861
|
+
let progressIndex = 0;
|
|
862
|
+
const progressInterval = setInterval(() => {
|
|
863
|
+
if (progressIndex < progressMessages.length) {
|
|
864
|
+
console.log(chalk.gray(` ${progressMessages[progressIndex]}`));
|
|
865
|
+
progressIndex++;
|
|
529
866
|
}
|
|
867
|
+
}, 2000);
|
|
868
|
+
|
|
869
|
+
const result = await deployPromise;
|
|
870
|
+
clearInterval(progressInterval);
|
|
871
|
+
|
|
872
|
+
// Fill in remaining progress messages quickly
|
|
873
|
+
while (progressIndex < progressMessages.length) {
|
|
874
|
+
console.log(chalk.gray(` ${progressMessages[progressIndex]}`));
|
|
875
|
+
progressIndex++;
|
|
530
876
|
}
|
|
531
|
-
const environment = options.env || await inputCollector.collectEnvironment();
|
|
532
|
-
const serviceName = await inputCollector.collectServiceName();
|
|
533
|
-
const serviceType = await inputCollector.collectServiceType();
|
|
534
877
|
|
|
535
|
-
//
|
|
536
|
-
|
|
878
|
+
// Display results with cleaner formatting
|
|
879
|
+
console.log(chalk.green('\n✅ Deployment Completed Successfully!'));
|
|
880
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
537
881
|
|
|
538
|
-
//
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
options.domain
|
|
543
|
-
);
|
|
882
|
+
// Show key information prominently
|
|
883
|
+
if (result.url || confirmations.deploymentUrl) {
|
|
884
|
+
console.log(chalk.white(`🌐 Service URL: ${chalk.bold(result.url || confirmations.deploymentUrl)}`));
|
|
885
|
+
}
|
|
544
886
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
environment,
|
|
549
|
-
serviceName,
|
|
550
|
-
serviceType,
|
|
551
|
-
domainName: cloudflareConfig.domainName,
|
|
552
|
-
cloudflareToken,
|
|
553
|
-
cloudflareAccountId: cloudflareConfig.accountId,
|
|
554
|
-
cloudflareZoneId: cloudflareConfig.zoneId
|
|
555
|
-
};
|
|
887
|
+
console.log(chalk.white(`👤 Customer: ${coreInputs.customer}`));
|
|
888
|
+
console.log(chalk.white(`🏭 Environment: ${coreInputs.environment}`));
|
|
889
|
+
console.log(chalk.white(`🔧 Worker: ${confirmations.workerName || 'N/A'}`));
|
|
556
890
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
// Tier 2: Generate smart confirmations using existing ConfirmationHandler
|
|
566
|
-
// Note: ConfirmationEngine prints its own header
|
|
567
|
-
const confirmations = await confirmationHandler.generateAndConfirm(coreInputs);
|
|
568
|
-
|
|
569
|
-
// Show deployment summary
|
|
570
|
-
console.log(chalk.cyan('\n📊 Deployment Summary'));
|
|
571
|
-
console.log(chalk.gray('─'.repeat(60)));
|
|
572
|
-
|
|
573
|
-
console.log(chalk.white(`Source: ${source}`));
|
|
574
|
-
console.log(chalk.white(`Customer: ${coreInputs.customer}`));
|
|
575
|
-
console.log(chalk.white(`Environment: ${coreInputs.environment}`));
|
|
576
|
-
console.log(chalk.white(`Domain: ${coreInputs.domainName}`));
|
|
577
|
-
console.log(chalk.white(`Account ID: ${coreInputs.cloudflareAccountId?.substring(0, 8)}...`));
|
|
578
|
-
console.log(chalk.white(`Zone ID: ${coreInputs.cloudflareZoneId?.substring(0, 8)}...`));
|
|
579
|
-
|
|
580
|
-
if (confirmations.workerName) {
|
|
581
|
-
console.log(chalk.white(`Worker: ${confirmations.workerName}`));
|
|
582
|
-
}
|
|
583
|
-
if (confirmations.databaseName) {
|
|
584
|
-
console.log(chalk.white(`Database: ${confirmations.databaseName}`));
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
console.log(chalk.gray('─'.repeat(60)));
|
|
588
|
-
|
|
589
|
-
if (options.dryRun) {
|
|
590
|
-
console.log(chalk.yellow('\n🔍 DRY RUN MODE - No changes will be made\n'));
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
// Tier 3: Execute deployment
|
|
594
|
-
console.log(chalk.cyan('\n⚙️ Tier 3: Automated Deployment\n'));
|
|
595
|
-
|
|
596
|
-
const orchestrator = new MultiDomainOrchestrator({
|
|
597
|
-
domains: [coreInputs.domainName],
|
|
598
|
-
environment: coreInputs.environment,
|
|
599
|
-
dryRun: options.dryRun,
|
|
600
|
-
servicePath: options.servicePath,
|
|
601
|
-
cloudflareToken: coreInputs.cloudflareToken,
|
|
602
|
-
cloudflareAccountId: coreInputs.cloudflareAccountId
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
await orchestrator.initialize();
|
|
606
|
-
|
|
607
|
-
console.log(chalk.cyan('🚀 Starting deployment...\n'));
|
|
608
|
-
|
|
609
|
-
const result = await orchestrator.deploySingleDomain(coreInputs.domainName, {
|
|
610
|
-
...coreInputs,
|
|
611
|
-
...confirmations,
|
|
612
|
-
servicePath: options.servicePath
|
|
613
|
-
});
|
|
614
|
-
|
|
615
|
-
// Display results
|
|
616
|
-
console.log(chalk.green('\n✅ Deployment Completed Successfully!'));
|
|
617
|
-
console.log(chalk.gray('─'.repeat(60)));
|
|
618
|
-
console.log(chalk.white(` Worker: ${confirmations.workerName || 'N/A'}`));
|
|
619
|
-
console.log(chalk.white(` URL: ${result.url || confirmations.deploymentUrl || 'N/A'}`));
|
|
620
|
-
console.log(chalk.white(` Status: ${result.status || 'deployed'}`));
|
|
621
|
-
|
|
622
|
-
if (result.health) {
|
|
623
|
-
console.log(chalk.white(` Health: ${result.health}`));
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
console.log(chalk.gray('─'.repeat(60)));
|
|
627
|
-
|
|
628
|
-
// Save deployment configuration for future reuse
|
|
629
|
-
try {
|
|
630
|
-
console.log(chalk.cyan('\n💾 Saving deployment configuration...'));
|
|
891
|
+
if (result.health) {
|
|
892
|
+
const healthStatus = result.health.toLowerCase().includes('ok') || result.health.toLowerCase().includes('healthy')
|
|
893
|
+
? chalk.green('✅ Healthy')
|
|
894
|
+
: chalk.yellow('⚠️ Check required');
|
|
895
|
+
console.log(chalk.white(`💚 Health: ${healthStatus}`));
|
|
896
|
+
}
|
|
631
897
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
898
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
899
|
+
|
|
900
|
+
// Save deployment configuration for future reuse
|
|
901
|
+
try {
|
|
902
|
+
console.log(chalk.cyan('\n💾 Saving deployment configuration...'));
|
|
903
|
+
|
|
904
|
+
const configFile = await configManager.saveCustomerConfig(
|
|
905
|
+
coreInputs.customer,
|
|
906
|
+
coreInputs.environment,
|
|
907
|
+
{
|
|
908
|
+
coreInputs,
|
|
909
|
+
confirmations,
|
|
910
|
+
result
|
|
911
|
+
}
|
|
912
|
+
);
|
|
913
|
+
|
|
914
|
+
console.log(chalk.green(' ✅ Configuration saved for future deployments'));
|
|
915
|
+
console.log(chalk.gray(` 📄 File: ${configFile}`));
|
|
916
|
+
} catch (saveError) {
|
|
917
|
+
console.log(chalk.yellow(` ⚠️ Could not save configuration: ${saveError.message}`));
|
|
918
|
+
console.log(chalk.gray(' Deployment succeeded, but you may need to re-enter values next time.'));
|
|
919
|
+
}
|
|
641
920
|
|
|
642
|
-
console.log(chalk.
|
|
643
|
-
console.log(chalk.
|
|
644
|
-
console.log(chalk.white(
|
|
645
|
-
|
|
646
|
-
console.log(chalk.yellow(` ⚠️ Could not save configuration: ${saveError.message}`));
|
|
647
|
-
console.log(chalk.gray(' Deployment succeeded, but you may need to re-enter values next time.'));
|
|
921
|
+
console.log(chalk.cyan('\n📋 Next Steps:'));
|
|
922
|
+
console.log(chalk.white(` • Test deployment: curl ${result.url || confirmations.deploymentUrl}/health`));
|
|
923
|
+
console.log(chalk.white(` • Monitor logs: wrangler tail ${confirmations.workerName}`));
|
|
924
|
+
console.log(chalk.white(` • View dashboard: https://dash.cloudflare.com`));
|
|
648
925
|
}
|
|
649
926
|
|
|
650
|
-
console.log(chalk.cyan('\n📋 Next Steps:'));
|
|
651
|
-
console.log(chalk.white(` • Test deployment: curl ${result.url || confirmations.deploymentUrl}/health`));
|
|
652
|
-
console.log(chalk.white(` • Monitor logs: wrangler tail ${confirmations.workerName}`));
|
|
653
|
-
console.log(chalk.white(` • View dashboard: https://dash.cloudflare.com`));
|
|
654
|
-
|
|
655
|
-
} finally {
|
|
656
|
-
inputCollector.close();
|
|
657
|
-
}
|
|
658
|
-
|
|
659
927
|
} catch (error) {
|
|
660
928
|
console.error(chalk.red(`\n❌ Deployment failed: ${error.message}`));
|
|
661
929
|
|
|
@@ -687,4 +955,30 @@ program
|
|
|
687
955
|
}
|
|
688
956
|
});
|
|
689
957
|
|
|
958
|
+
// Utility function to redact sensitive information from logs
|
|
959
|
+
function redactSensitiveInfo(text) {
|
|
960
|
+
if (typeof text !== 'string') return text;
|
|
961
|
+
|
|
962
|
+
// Patterns to redact
|
|
963
|
+
const patterns = [
|
|
964
|
+
// Cloudflare API tokens
|
|
965
|
+
[/(CLOUDFLARE_API_TOKEN=?)(\w{20,})/gi, '$1[REDACTED]'],
|
|
966
|
+
// Generic API tokens/keys
|
|
967
|
+
[/(api[_-]?token|api[_-]?key|auth[_-]?token)["']?[:=]\s*["']?([a-zA-Z0-9_-]{20,})["']?/gi, '$1: [REDACTED]'],
|
|
968
|
+
// Passwords
|
|
969
|
+
[/(password|passwd|pwd)["']?[:=]\s*["']?([^"'\s]{3,})["']?/gi, '$1: [REDACTED]'],
|
|
970
|
+
// Secrets
|
|
971
|
+
[/(secret|key)["']?[:=]\s*["']?([a-zA-Z0-9_-]{10,})["']?/gi, '$1: [REDACTED]'],
|
|
972
|
+
// Account IDs (partial redaction)
|
|
973
|
+
[/(account[_-]?id|zone[_-]?id)["']?[:=]\s*["']?([a-zA-Z0-9]{8})([a-zA-Z0-9]{24,})["']?/gi, '$1: $2[REDACTED]']
|
|
974
|
+
];
|
|
975
|
+
|
|
976
|
+
let redacted = text;
|
|
977
|
+
patterns.forEach(([pattern, replacement]) => {
|
|
978
|
+
redacted = redacted.replace(pattern, replacement);
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
return redacted;
|
|
982
|
+
}
|
|
983
|
+
|
|
690
984
|
program.parse();
|
|
@@ -17,8 +17,13 @@ import { execSync } from 'child_process';
|
|
|
17
17
|
import { fileURLToPath } from 'url';
|
|
18
18
|
|
|
19
19
|
const __dirname = (() => {
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
try {
|
|
21
|
+
const filename = fileURLToPath(import.meta.url);
|
|
22
|
+
return dirname(filename);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
// Fallback for test environments - use current working directory
|
|
25
|
+
return process.cwd();
|
|
26
|
+
}
|
|
22
27
|
})();
|
|
23
28
|
|
|
24
29
|
const SECRET_CONFIGS = {
|
package/dist/config/customers.js
CHANGED
|
@@ -3,10 +3,16 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSy
|
|
|
3
3
|
import { resolve, join } from 'path';
|
|
4
4
|
import toml from '@iarna/toml';
|
|
5
5
|
import { createDomainConfigSchema, validateDomainConfig, createDomainRegistry } from './domains.js';
|
|
6
|
-
import { createLogger } from '../utils/index.js';
|
|
7
6
|
import { getDirname } from '../utils/esm-helper.js';
|
|
8
7
|
const __dirname = getDirname(import.meta.url, 'src/config');
|
|
9
|
-
|
|
8
|
+
|
|
9
|
+
// Simple inline logger to avoid circular dependency with index.js
|
|
10
|
+
const logger = {
|
|
11
|
+
info: (message, ...args) => console.log(`[CustomerConfig] ${message}`, ...args),
|
|
12
|
+
error: (message, ...args) => console.error(`[CustomerConfig] ${message}`, ...args),
|
|
13
|
+
warn: (message, ...args) => console.warn(`[CustomerConfig] ${message}`, ...args),
|
|
14
|
+
debug: (message, ...args) => console.debug(`[CustomerConfig] ${message}`, ...args)
|
|
15
|
+
};
|
|
10
16
|
|
|
11
17
|
/**
|
|
12
18
|
* Customer Configuration Manager
|
package/dist/config/domains.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Simple inline logger to avoid circular dependency with index.js
|
|
2
|
+
import { validateRequired, deepMerge } from '../utils/index.js';
|
|
3
|
+
const logger = {
|
|
4
|
+
info: (message, ...args) => console.log(`[DomainConfig] ${message}`, ...args),
|
|
5
|
+
error: (message, ...args) => console.error(`[DomainConfig] ${message}`, ...args),
|
|
6
|
+
warn: (message, ...args) => console.warn(`[DomainConfig] ${message}`, ...args),
|
|
7
|
+
debug: (message, ...args) => console.debug(`[DomainConfig] ${message}`, ...args)
|
|
8
|
+
};
|
|
3
9
|
|
|
4
10
|
/**
|
|
5
11
|
* Creates a base domain configuration schema
|
package/dist/config/features.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
const logger =
|
|
1
|
+
// Simple inline logger to avoid circular dependency with index.js
|
|
2
|
+
const logger = {
|
|
3
|
+
info: (message, ...args) => console.log(`[FeatureFlagManager] ${message}`, ...args),
|
|
4
|
+
error: (message, ...args) => console.error(`[FeatureFlagManager] ${message}`, ...args),
|
|
5
|
+
warn: (message, ...args) => console.warn(`[FeatureFlagManager] ${message}`, ...args),
|
|
6
|
+
debug: (message, ...args) => console.debug(`[FeatureFlagManager] ${message}`, ...args)
|
|
7
|
+
};
|
|
3
8
|
|
|
4
9
|
/**
|
|
5
10
|
* Feature Flag Manager Class
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,14 @@ export { WranglerDeployer } from './deployment/wrangler-deployer.js';
|
|
|
24
24
|
// Security components
|
|
25
25
|
export * from './security/index.js';
|
|
26
26
|
|
|
27
|
+
// Service management components
|
|
28
|
+
export { ServiceCreator, createService } from './service-management/ServiceCreator.js';
|
|
29
|
+
export { ServiceOrchestrator } from './service-management/ServiceOrchestrator.js';
|
|
30
|
+
export { InputHandler } from './service-management/handlers/InputHandler.js';
|
|
31
|
+
export { ConfirmationHandler } from './service-management/handlers/ConfirmationHandler.js';
|
|
32
|
+
export { GenerationHandler } from './service-management/handlers/GenerationHandler.js';
|
|
33
|
+
export { ValidationHandler } from './service-management/handlers/ValidationHandler.js';
|
|
34
|
+
|
|
27
35
|
// Framework version info
|
|
28
36
|
export const FRAMEWORK_VERSION = '1.0.0';
|
|
29
37
|
export const FRAMEWORK_NAME = 'Clodo Framework';
|
|
@@ -597,7 +597,7 @@ export class MultiDomainOrchestrator {
|
|
|
597
597
|
}
|
|
598
598
|
|
|
599
599
|
/**
|
|
600
|
-
* Validate domain deployment with real HTTP health check
|
|
600
|
+
* Validate domain deployment with real HTTP health check (with retries)
|
|
601
601
|
*/
|
|
602
602
|
async validateDomainDeployment(domain) {
|
|
603
603
|
console.log(` ✅ Validating deployment for ${domain}`);
|
|
@@ -614,54 +614,75 @@ export class MultiDomainOrchestrator {
|
|
|
614
614
|
return true;
|
|
615
615
|
}
|
|
616
616
|
console.log(` 🔍 Running health check: ${deploymentUrl}/health`);
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
this.stateManager.logAuditEvent('HEALTH_CHECK_PASSED', domain, {
|
|
635
|
-
url: deploymentUrl,
|
|
636
|
-
status,
|
|
637
|
-
responseTime,
|
|
638
|
-
environment: this.environment
|
|
639
|
-
});
|
|
640
|
-
return true;
|
|
641
|
-
} else {
|
|
642
|
-
console.log(` ⚠️ Health check returned ${status} - deployment may have issues`);
|
|
643
|
-
this.stateManager.logAuditEvent('HEALTH_CHECK_WARNING', domain, {
|
|
644
|
-
url: deploymentUrl,
|
|
645
|
-
status,
|
|
646
|
-
responseTime,
|
|
647
|
-
environment: this.environment
|
|
617
|
+
|
|
618
|
+
// Retry logic for health checks
|
|
619
|
+
const maxRetries = 3;
|
|
620
|
+
const retryDelay = 5000; // 5 seconds between retries
|
|
621
|
+
|
|
622
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
623
|
+
try {
|
|
624
|
+
const startTime = Date.now();
|
|
625
|
+
console.log(` Attempt ${attempt}/${maxRetries}...`);
|
|
626
|
+
|
|
627
|
+
// Perform actual HTTP health check
|
|
628
|
+
const response = await fetch(`${deploymentUrl}/health`, {
|
|
629
|
+
method: 'GET',
|
|
630
|
+
headers: {
|
|
631
|
+
'User-Agent': 'Clodo-Orchestrator/2.0'
|
|
632
|
+
},
|
|
633
|
+
signal: AbortSignal.timeout(15000) // 15 second timeout
|
|
648
634
|
});
|
|
635
|
+
const responseTime = Date.now() - startTime;
|
|
636
|
+
const status = response.status;
|
|
637
|
+
if (status === 200) {
|
|
638
|
+
console.log(` ✅ Health check passed (${status}) - Response time: ${responseTime}ms`);
|
|
639
|
+
|
|
640
|
+
// Log successful health check
|
|
641
|
+
this.stateManager.logAuditEvent('HEALTH_CHECK_PASSED', domain, {
|
|
642
|
+
url: deploymentUrl,
|
|
643
|
+
status,
|
|
644
|
+
responseTime,
|
|
645
|
+
attempt,
|
|
646
|
+
environment: this.environment
|
|
647
|
+
});
|
|
648
|
+
return true;
|
|
649
|
+
} else {
|
|
650
|
+
const errorMsg = `Health check returned ${status} - deployment may have issues`;
|
|
651
|
+
console.log(` ⚠️ ${errorMsg}`);
|
|
652
|
+
this.stateManager.logAuditEvent('HEALTH_CHECK_WARNING', domain, {
|
|
653
|
+
url: deploymentUrl,
|
|
654
|
+
status,
|
|
655
|
+
responseTime,
|
|
656
|
+
attempt,
|
|
657
|
+
environment: this.environment
|
|
658
|
+
});
|
|
649
659
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
660
|
+
// Don't fail deployment for non-200 status, just warn
|
|
661
|
+
return true;
|
|
662
|
+
}
|
|
663
|
+
} catch (error) {
|
|
664
|
+
const isLastAttempt = attempt === maxRetries;
|
|
665
|
+
const errorMsg = `Health check failed: ${error.message}`;
|
|
666
|
+
if (isLastAttempt) {
|
|
667
|
+
console.log(` ❌ ${errorMsg} (final attempt)`);
|
|
668
|
+
console.log(` 💡 The service may still be deploying. Check manually: curl ${deploymentUrl}/health`);
|
|
669
|
+
this.stateManager.logAuditEvent('HEALTH_CHECK_FAILED', domain, {
|
|
670
|
+
url: deploymentUrl,
|
|
671
|
+
error: error.message,
|
|
672
|
+
attempts: maxRetries,
|
|
673
|
+
environment: this.environment
|
|
674
|
+
});
|
|
661
675
|
|
|
662
|
-
|
|
663
|
-
|
|
676
|
+
// Don't fail deployment for health check failure - it might just need time
|
|
677
|
+
return true;
|
|
678
|
+
} else {
|
|
679
|
+
console.log(` ⚠️ ${errorMsg} (attempt ${attempt}/${maxRetries})`);
|
|
680
|
+
console.log(` ⏳ Retrying in ${retryDelay / 1000} seconds...`);
|
|
681
|
+
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
|
682
|
+
}
|
|
683
|
+
}
|
|
664
684
|
}
|
|
685
|
+
return true;
|
|
665
686
|
}
|
|
666
687
|
|
|
667
688
|
/**
|
|
@@ -81,10 +81,10 @@ export class ConfirmationEngine {
|
|
|
81
81
|
version: '1.0.0',
|
|
82
82
|
// 4. Author - Default framework author
|
|
83
83
|
author: 'Clodo Framework',
|
|
84
|
-
// 5-7. URLs - Derived from domain
|
|
85
|
-
productionUrl: `https
|
|
86
|
-
stagingUrl: `https
|
|
87
|
-
developmentUrl: `https
|
|
84
|
+
// 5-7. URLs - Derived from domain and service name
|
|
85
|
+
productionUrl: `https://${serviceName}.${domainName}`,
|
|
86
|
+
stagingUrl: `https://${serviceName}-staging.${domainName}`,
|
|
87
|
+
developmentUrl: `https://${serviceName}-dev.${domainName}`,
|
|
88
88
|
// 8. Features - Based on service type
|
|
89
89
|
features: this.generateFeaturesForType(serviceType),
|
|
90
90
|
// 9. Database Name - Cloudflare D1 naming
|
|
@@ -36,8 +36,13 @@ import { fileURLToPath } from 'url';
|
|
|
36
36
|
import { dirname, join, relative } from 'path';
|
|
37
37
|
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
38
38
|
const __dirname = (() => {
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
try {
|
|
40
|
+
const filename = fileURLToPath(import.meta.url);
|
|
41
|
+
return dirname(filename);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
// Fallback for test environments - use current working directory
|
|
44
|
+
return process.cwd();
|
|
45
|
+
}
|
|
41
46
|
})();
|
|
42
47
|
export class GenerationEngine {
|
|
43
48
|
constructor(options = {}) {
|
|
@@ -148,12 +153,14 @@ export class GenerationEngine {
|
|
|
148
153
|
const wranglerConfig = serviceInitializer.generateWranglerConfig(coreInputs.serviceName, {
|
|
149
154
|
type: coreInputs.serviceType,
|
|
150
155
|
env: coreInputs.environment
|
|
151
|
-
},
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
156
|
+
}, {
|
|
157
|
+
domains: [{
|
|
158
|
+
domain: coreInputs.domainName,
|
|
159
|
+
accountId: coreInputs.cloudflareAccountId,
|
|
160
|
+
zoneId: coreInputs.cloudflareZoneId,
|
|
161
|
+
name: `${coreInputs.serviceName}-${coreInputs.environment}`
|
|
162
|
+
}]
|
|
163
|
+
});
|
|
157
164
|
|
|
158
165
|
// Write wrangler.toml
|
|
159
166
|
const wranglerPath = join(servicePath, 'wrangler.toml');
|
|
@@ -177,12 +184,14 @@ export class GenerationEngine {
|
|
|
177
184
|
const domainsContent = serviceInitializer.generateDomainsConfig(coreInputs.serviceName, {
|
|
178
185
|
type: coreInputs.serviceType,
|
|
179
186
|
env: coreInputs.environment
|
|
180
|
-
},
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
187
|
+
}, {
|
|
188
|
+
domains: [{
|
|
189
|
+
domain: coreInputs.domainName,
|
|
190
|
+
accountId: coreInputs.cloudflareAccountId,
|
|
191
|
+
zoneId: coreInputs.cloudflareZoneId,
|
|
192
|
+
name: `${coreInputs.serviceName}-${coreInputs.environment}`
|
|
193
|
+
}]
|
|
194
|
+
});
|
|
186
195
|
const domainsPath = join(servicePath, 'src', 'config', 'domains.js');
|
|
187
196
|
writeFileSync(domainsPath, domainsContent, 'utf8');
|
|
188
197
|
files.push(domainsPath);
|
|
@@ -15,6 +15,9 @@ import { createInterface } from 'readline';
|
|
|
15
15
|
import chalk from 'chalk';
|
|
16
16
|
import { validateServiceName, validateDomainName } from '../utils/validation.js';
|
|
17
17
|
import { uiStructuresLoader } from '../utils/ui-structures-loader.js';
|
|
18
|
+
|
|
19
|
+
// Assessment capabilities moved to @tamyla/clodo-orchestration (professional edition)
|
|
20
|
+
|
|
18
21
|
export class InputCollector {
|
|
19
22
|
constructor(options = {}) {
|
|
20
23
|
this.interactive = options.interactive !== false;
|
|
@@ -80,6 +83,8 @@ export class InputCollector {
|
|
|
80
83
|
source: 'user-provided',
|
|
81
84
|
required: true
|
|
82
85
|
};
|
|
86
|
+
|
|
87
|
+
// Assessment moved to professional edition (@tamyla/clodo-orchestration)
|
|
83
88
|
}
|
|
84
89
|
}
|
|
85
90
|
|
|
@@ -158,9 +163,9 @@ export class InputCollector {
|
|
|
158
163
|
case 'production-url':
|
|
159
164
|
return domainName ? `https://api.${domainName}` : '';
|
|
160
165
|
case 'staging-url':
|
|
161
|
-
return domainName ? `https
|
|
166
|
+
return domainName && serviceName ? `https://${serviceName}-staging.${domainName}` : '';
|
|
162
167
|
case 'development-url':
|
|
163
|
-
return domainName ? `https
|
|
168
|
+
return domainName && serviceName ? `https://${serviceName}-dev.${domainName}` : '';
|
|
164
169
|
case 'service-directory':
|
|
165
170
|
return serviceName ? `./services/${serviceName}` : '';
|
|
166
171
|
case 'database-name':
|
|
@@ -6,13 +6,18 @@
|
|
|
6
6
|
import { readFileSync, writeFileSync, mkdirSync, existsSync, cpSync, readdirSync, statSync } from 'fs';
|
|
7
7
|
import { join, dirname, resolve } from 'path';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
-
const __dirname = dirname(__filename);
|
|
11
|
-
const TEMPLATES_DIR = join(__dirname, '..', '..', 'templates');
|
|
12
9
|
const SERVICE_TYPES = ['data-service', 'auth-service', 'content-service', 'api-gateway', 'generic'];
|
|
13
10
|
export class ServiceCreator {
|
|
14
11
|
constructor(options = {}) {
|
|
15
|
-
|
|
12
|
+
const templatesDir = (() => {
|
|
13
|
+
try {
|
|
14
|
+
return join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'templates');
|
|
15
|
+
} catch (error) {
|
|
16
|
+
// Fallback for test environments - use current working directory
|
|
17
|
+
return join(process.cwd(), 'templates');
|
|
18
|
+
}
|
|
19
|
+
})();
|
|
20
|
+
this.templatesDir = options.templatesDir || templatesDir;
|
|
16
21
|
this.serviceTypes = options.serviceTypes || SERVICE_TYPES;
|
|
17
22
|
}
|
|
18
23
|
|
|
@@ -6,11 +6,20 @@
|
|
|
6
6
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, cpSync, readdirSync } from 'fs';
|
|
7
7
|
import { join, dirname, resolve } from 'path';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
|
|
10
|
+
// Get framework root - handle both ES module and CommonJS environments
|
|
11
|
+
const getFrameworkRoot = () => {
|
|
12
|
+
try {
|
|
13
|
+
// Try ES module approach
|
|
14
|
+
const filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const dirname_ = dirname(filename);
|
|
16
|
+
return resolve(dirname_, '..', '..');
|
|
17
|
+
} catch (error) {
|
|
18
|
+
// Fallback for test environments - use current working directory
|
|
19
|
+
return process.cwd();
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const FRAMEWORK_ROOT = getFrameworkRoot();
|
|
14
23
|
const TEMPLATES_DIR = join(FRAMEWORK_ROOT, 'templates');
|
|
15
24
|
const SERVICE_TYPES = ['generic', 'data-service', 'auth-service', 'content-service', 'api-gateway'];
|
|
16
25
|
export class ServiceInitializer {
|
|
@@ -4,4 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
export { ServiceCreator, createService } from './ServiceCreator.js';
|
|
7
|
-
export { ServiceInitializer, initializeService } from './ServiceInitializer.js';
|
|
7
|
+
export { ServiceInitializer, initializeService } from './ServiceInitializer.js';
|
|
8
|
+
|
|
9
|
+
// Assessment capabilities moved to @tamyla/clodo-orchestration
|
|
10
|
+
// - AssessmentCache
|
|
11
|
+
// - CapabilityAssessmentEngine
|
|
12
|
+
// - ServiceAutoDiscovery
|
|
13
|
+
// - Data-bridge components
|
|
14
|
+
// These are only available in the professional edition (clodo-orchestration)
|
|
@@ -16,8 +16,13 @@ import { join, dirname } from 'path';
|
|
|
16
16
|
import { execSync } from 'child_process';
|
|
17
17
|
import { fileURLToPath } from 'url';
|
|
18
18
|
const __dirname = (() => {
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
try {
|
|
20
|
+
const filename = fileURLToPath(import.meta.url);
|
|
21
|
+
return dirname(filename);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
// Fallback for test environments - use current working directory
|
|
24
|
+
return process.cwd();
|
|
25
|
+
}
|
|
21
26
|
})();
|
|
22
27
|
const SECRET_CONFIGS = {
|
|
23
28
|
'AUTH_JWT_SECRET': {
|
|
@@ -15,9 +15,13 @@
|
|
|
15
15
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
16
16
|
import { resolve, join } from 'path';
|
|
17
17
|
import { getDirname } from '../esm-helper.js';
|
|
18
|
-
import { createLogger } from '../index.js';
|
|
19
18
|
const __dirname = getDirname(import.meta.url, 'src/utils/config');
|
|
20
|
-
|
|
19
|
+
|
|
20
|
+
// Simple inline logger to avoid circular dependency with index.js
|
|
21
|
+
const logger = {
|
|
22
|
+
info: (message, ...args) => console.log(`[UnifiedConfigManager] ${message}`, ...args),
|
|
23
|
+
error: (message, ...args) => console.error(`[UnifiedConfigManager] ${message}`, ...args)
|
|
24
|
+
};
|
|
21
25
|
|
|
22
26
|
/**
|
|
23
27
|
* UnifiedConfigManager
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { featureManager, COMMON_FEATURES } from '../config/features.js';
|
|
2
2
|
import { getDomainFromEnv, createEnvironmentConfig } from '../config/domains.js';
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
|
|
4
|
+
// Simple inline logger to avoid circular dependency with index.js
|
|
5
|
+
const logger = {
|
|
6
|
+
info: (message, ...args) => console.log(`[WorkerIntegration] ${message}`, ...args),
|
|
7
|
+
error: (message, ...args) => console.error(`[WorkerIntegration] ${message}`, ...args),
|
|
8
|
+
warn: (message, ...args) => console.warn(`[WorkerIntegration] ${message}`, ...args),
|
|
9
|
+
debug: (message, ...args) => console.debug(`[WorkerIntegration] ${message}`, ...args)
|
|
10
|
+
};
|
|
5
11
|
|
|
6
12
|
/**
|
|
7
13
|
* Initializes a service with domain and feature context
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tamyla/clodo-framework",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.14",
|
|
4
4
|
"description": "Reusable framework for Clodo-style software architecture on Cloudflare Workers + D1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"clean": "rimraf dist",
|
|
75
75
|
"clean:generated": "rimraf generated",
|
|
76
76
|
"clean:all": "npm run clean && npm run clean:generated",
|
|
77
|
-
"test": "jest --passWithNoTests",
|
|
77
|
+
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --passWithNoTests",
|
|
78
78
|
"test:watch": "jest --watch",
|
|
79
79
|
"test:coverage": "jest --coverage",
|
|
80
80
|
"lint": "eslint --config ./eslint.config.js src",
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"modular"
|
|
113
113
|
],
|
|
114
114
|
"author": "Tamyla",
|
|
115
|
-
"license": "
|
|
115
|
+
"license": "MIT",
|
|
116
116
|
"dependencies": {
|
|
117
117
|
"@iarna/toml": "^2.2.5",
|
|
118
118
|
"chalk": "^5.3.0",
|
|
@@ -129,6 +129,7 @@
|
|
|
129
129
|
"@semantic-release/git": "^10.0.1",
|
|
130
130
|
"@types/node": "^20.19.19",
|
|
131
131
|
"babel-plugin-transform-import-meta": "^2.3.3",
|
|
132
|
+
"cross-env": "^10.1.0",
|
|
132
133
|
"eslint": "^8.54.0",
|
|
133
134
|
"jest": "^29.7.0",
|
|
134
135
|
"rimraf": "^5.0.10",
|
|
@@ -148,6 +149,7 @@
|
|
|
148
149
|
"homepage": "https://clodo-framework.tamyla.com",
|
|
149
150
|
"release": {
|
|
150
151
|
"branches": [
|
|
152
|
+
"main",
|
|
151
153
|
"master"
|
|
152
154
|
],
|
|
153
155
|
"plugins": [
|
|
@@ -18,9 +18,9 @@ export const domains = {
|
|
|
18
18
|
accountId: process.env.CLOUDFLARE_ACCOUNT_ID || '', // Configure in setup
|
|
19
19
|
zoneId: process.env.CLOUDFLARE_ZONE_ID || '', // Configure in setup
|
|
20
20
|
domains: {
|
|
21
|
-
production: process.env.PRODUCTION_DOMAIN || '
|
|
22
|
-
staging: process.env.STAGING_DOMAIN || 'staging
|
|
23
|
-
development: process.env.DEVELOPMENT_DOMAIN || 'dev
|
|
21
|
+
production: process.env.PRODUCTION_DOMAIN || '{{SERVICE_NAME}}.{{DOMAIN_NAME}}',
|
|
22
|
+
staging: process.env.STAGING_DOMAIN || '{{SERVICE_NAME}}-staging.{{DOMAIN_NAME}}',
|
|
23
|
+
development: process.env.DEVELOPMENT_DOMAIN || '{{SERVICE_NAME}}-dev.{{DOMAIN_NAME}}'
|
|
24
24
|
},
|
|
25
25
|
services: [
|
|
26
26
|
'{{SERVICE_NAME}}'
|