@tamyla/clodo-framework 3.0.12 → 3.0.13
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 +7 -0
- package/bin/clodo-service.js +385 -184
- package/dist/orchestration/multi-domain-orchestrator.js +66 -45
- package/dist/service-management/AssessmentCache.js +303 -0
- package/dist/service-management/CapabilityAssessmentEngine.js +902 -0
- package/dist/service-management/ConfirmationEngine.js +4 -4
- package/dist/service-management/InputCollector.js +100 -2
- package/dist/service-management/ServiceAutoDiscovery.js +745 -0
- package/dist/service-management/ServiceCreator.js +1 -4
- package/dist/service-management/ServiceOrchestrator.js +269 -1
- package/dist/service-management/index.js +4 -1
- package/dist/utils/config/unified-config-manager.js +6 -2
- package/package.json +1 -1
- package/templates/generic/src/config/domains.js +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [3.0.13](https://github.com/tamylaa/clodo-framework/compare/v3.0.12...v3.0.13) (2025-10-16)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* resolve circular dependency in unified-config-manager ([dff0841](https://github.com/tamylaa/clodo-framework/commit/dff0841c3bfc4ce0200b057c99ab76ea09641c68))
|
|
7
|
+
|
|
1
8
|
## [3.0.12](https://github.com/tamylaa/clodo-framework/compare/v3.0.11...v3.0.12) (2025-10-14)
|
|
2
9
|
|
|
3
10
|
|
package/bin/clodo-service.js
CHANGED
|
@@ -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')
|
|
@@ -422,6 +535,10 @@ program
|
|
|
422
535
|
.option('-e, --env <environment>', 'Target environment (development, staging, production)')
|
|
423
536
|
.option('-i, --interactive', 'Interactive mode (review confirmations)', true)
|
|
424
537
|
.option('--non-interactive', 'Non-interactive mode (use stored config)')
|
|
538
|
+
.option('--config-file <file>', 'Load configuration from JSON file')
|
|
539
|
+
.option('--defaults', 'Use default values where possible (non-interactive)')
|
|
540
|
+
.option('--quiet', 'Quiet mode - minimal output for CI/CD')
|
|
541
|
+
.option('--json-output', 'Output results as JSON for scripting')
|
|
425
542
|
.option('--dry-run', 'Simulate deployment without making changes')
|
|
426
543
|
.option('--domain <domain>', 'Domain name (overrides stored config)')
|
|
427
544
|
.option('--service-path <path>', 'Path to service directory', '.')
|
|
@@ -436,7 +553,9 @@ program
|
|
|
436
553
|
console.log(chalk.cyan('\n🚀 Clodo Framework Deployment'));
|
|
437
554
|
console.log(chalk.white('Using Three-Tier Input Architecture\n'));
|
|
438
555
|
|
|
439
|
-
const isInteractive = options.interactive && !options.nonInteractive;
|
|
556
|
+
const isInteractive = options.interactive && !options.nonInteractive && !options.configFile && !options.defaults;
|
|
557
|
+
const isQuiet = options.quiet;
|
|
558
|
+
const jsonOutput = options.jsonOutput;
|
|
440
559
|
const configManager = new UnifiedConfigManager({
|
|
441
560
|
configDir: join(process.cwd(), 'config', 'customers')
|
|
442
561
|
});
|
|
@@ -446,216 +565,272 @@ program
|
|
|
446
565
|
let coreInputs = {};
|
|
447
566
|
let source = 'interactive';
|
|
448
567
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
568
|
+
// Interactive mode: run input collector
|
|
569
|
+
if (isInteractive) {
|
|
570
|
+
await inputCollector.collect();
|
|
571
|
+
} else {
|
|
572
|
+
// Non-interactive mode: load from config file or stored config
|
|
573
|
+
|
|
574
|
+
// Load from JSON config file if specified
|
|
575
|
+
if (options.configFile) {
|
|
576
|
+
coreInputs = loadJsonConfig(options.configFile);
|
|
577
|
+
source = 'config-file';
|
|
578
|
+
} else if (options.customer && options.env) {
|
|
579
|
+
// Check UnifiedConfigManager for existing config
|
|
580
|
+
if (configManager.configExists(options.customer, options.env)) {
|
|
581
|
+
console.log(chalk.green(`✅ Found existing configuration for ${options.customer}/${options.env}\n`));
|
|
582
|
+
configManager.displayCustomerConfig(options.customer, options.env);
|
|
583
|
+
|
|
460
584
|
// Non-interactive: auto-load the config
|
|
461
|
-
storedConfig = configManager.loadCustomerConfig(options.customer, options.env);
|
|
585
|
+
const storedConfig = configManager.loadCustomerConfig(options.customer, options.env);
|
|
462
586
|
if (storedConfig) {
|
|
463
587
|
coreInputs = storedConfig;
|
|
464
588
|
source = 'stored-config';
|
|
465
589
|
}
|
|
466
590
|
} else {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
591
|
+
console.log(chalk.yellow(`⚠️ No configuration found for ${options.customer}/${options.env}`));
|
|
592
|
+
console.log(chalk.white('Collecting inputs interactively...\n'));
|
|
593
|
+
}
|
|
594
|
+
} else {
|
|
595
|
+
console.log(chalk.yellow('⚠️ Customer and environment not specified'));
|
|
596
|
+
console.log(chalk.white('Use --customer and --env options, or run in interactive mode'));
|
|
597
|
+
process.exit(1);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Collect inputs if we don't have them from stored config
|
|
601
|
+
if (!coreInputs.cloudflareAccountId) {
|
|
602
|
+
console.log(chalk.cyan('📊 Tier 1: Core Input Collection\n'));
|
|
603
|
+
|
|
604
|
+
// Collect basic info with smart customer selection
|
|
605
|
+
let customer = options.customer;
|
|
606
|
+
if (!customer) {
|
|
607
|
+
const customers = configManager.listCustomers();
|
|
608
|
+
if (customers.length > 0) {
|
|
609
|
+
const selection = await inputCollector.question('Select customer (enter number or name): ');
|
|
610
|
+
|
|
611
|
+
// Try to parse as number first
|
|
612
|
+
const num = parseInt(selection);
|
|
613
|
+
if (!isNaN(num) && num >= 1 && num <= customers.length) {
|
|
614
|
+
customer = customers[num - 1];
|
|
615
|
+
console.log(chalk.green(`✓ Selected: ${customer}\n`));
|
|
616
|
+
} else if (customers.includes(selection)) {
|
|
617
|
+
customer = selection;
|
|
618
|
+
console.log(chalk.green(`✓ Selected: ${customer}\n`));
|
|
619
|
+
} else {
|
|
620
|
+
// New customer name
|
|
621
|
+
customer = selection;
|
|
622
|
+
console.log(chalk.yellow(`⚠️ Creating new customer: ${customer}\n`));
|
|
475
623
|
}
|
|
476
624
|
} else {
|
|
477
|
-
|
|
625
|
+
customer = await inputCollector.question('Customer name: ');
|
|
478
626
|
}
|
|
479
627
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
628
|
+
const environment = options.env || await inputCollector.collectEnvironment();
|
|
629
|
+
const serviceName = await inputCollector.collectServiceName();
|
|
630
|
+
const serviceType = await inputCollector.collectServiceType();
|
|
631
|
+
|
|
632
|
+
// Collect Cloudflare token
|
|
633
|
+
const cloudflareToken = process.env.CLOUDFLARE_API_TOKEN || await inputCollector.collectCloudflareToken();
|
|
634
|
+
|
|
635
|
+
// Use CloudflareAPI for automatic domain discovery
|
|
636
|
+
console.log(chalk.cyan('⏳ Fetching Cloudflare configuration...'));
|
|
637
|
+
const cloudflareConfig = await inputCollector.collectCloudflareConfigWithDiscovery(
|
|
638
|
+
cloudflareToken,
|
|
639
|
+
options.domain
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
// Combine all inputs
|
|
643
|
+
coreInputs = {
|
|
644
|
+
customer,
|
|
645
|
+
environment,
|
|
646
|
+
serviceName,
|
|
647
|
+
serviceType,
|
|
648
|
+
domainName: cloudflareConfig.domainName,
|
|
649
|
+
cloudflareToken,
|
|
650
|
+
cloudflareAccountId: cloudflareConfig.accountId,
|
|
651
|
+
cloudflareZoneId: cloudflareConfig.zoneId
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
source = 'interactive';
|
|
483
655
|
}
|
|
484
656
|
|
|
485
|
-
//
|
|
486
|
-
if (
|
|
487
|
-
coreInputs =
|
|
488
|
-
source = 'stored-config';
|
|
489
|
-
console.log(chalk.green('\n✅ Using stored configuration\n'));
|
|
657
|
+
// Allow domain override
|
|
658
|
+
if (options.domain) {
|
|
659
|
+
coreInputs.domainName = options.domain;
|
|
490
660
|
}
|
|
491
|
-
|
|
492
|
-
//
|
|
493
|
-
const
|
|
494
|
-
if (
|
|
495
|
-
console.log(chalk.
|
|
496
|
-
|
|
497
|
-
|
|
661
|
+
|
|
662
|
+
// Early validation before proceeding
|
|
663
|
+
const isValid = await validateDeploymentPrerequisites(coreInputs, options);
|
|
664
|
+
if (!isValid) {
|
|
665
|
+
console.log(chalk.red('\n❌ Deployment cancelled due to validation errors.'));
|
|
666
|
+
console.log(chalk.cyan('💡 Fix the issues above and try again.'));
|
|
667
|
+
process.exit(1);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Tier 2: Generate smart confirmations using existing ConfirmationHandler
|
|
671
|
+
// Note: ConfirmationEngine prints its own header
|
|
672
|
+
const confirmations = await confirmationHandler.generateAndConfirm(coreInputs);
|
|
673
|
+
|
|
674
|
+
// Show deployment summary
|
|
675
|
+
console.log(chalk.cyan('\n📊 Deployment Summary'));
|
|
676
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
677
|
+
|
|
678
|
+
console.log(chalk.white(`Source: ${source}`));
|
|
679
|
+
console.log(chalk.white(`Customer: ${coreInputs.customer}`));
|
|
680
|
+
console.log(chalk.white(`Environment: ${coreInputs.environment}`));
|
|
681
|
+
console.log(chalk.white(`Domain: ${coreInputs.domainName}`));
|
|
682
|
+
console.log(chalk.white(`Account ID: ${coreInputs.cloudflareAccountId ? coreInputs.cloudflareAccountId.substring(0, 8) + '...' : 'N/A'}`));
|
|
683
|
+
console.log(chalk.white(`Zone ID: ${coreInputs.cloudflareZoneId ? coreInputs.cloudflareZoneId.substring(0, 8) + '...' : 'N/A'}`));
|
|
684
|
+
|
|
685
|
+
if (confirmations.workerName) {
|
|
686
|
+
console.log(chalk.white(`Worker: ${confirmations.workerName}`));
|
|
687
|
+
}
|
|
688
|
+
if (confirmations.databaseName) {
|
|
689
|
+
console.log(chalk.white(`Database: ${confirmations.databaseName}`));
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
693
|
+
|
|
694
|
+
if (options.dryRun) {
|
|
695
|
+
console.log(chalk.yellow('\n🔍 DRY RUN MODE - No changes will be made\n'));
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Tier 3: Execute deployment
|
|
699
|
+
console.log(chalk.cyan('\n⚙️ Tier 3: Automated Deployment\n'));
|
|
700
|
+
|
|
701
|
+
// INTEGRATION: Add intelligent service discovery and assessment
|
|
702
|
+
console.log(chalk.cyan('🧠 Performing Intelligent Service Assessment...'));
|
|
703
|
+
const { CapabilityAssessmentEngine } = await import('../dist/service-management/CapabilityAssessmentEngine.js');
|
|
704
|
+
const assessor = new CapabilityAssessmentEngine(options.servicePath || process.cwd());
|
|
705
|
+
|
|
706
|
+
try {
|
|
707
|
+
const assessment = await assessor.assessCapabilities({
|
|
708
|
+
serviceName: coreInputs.serviceName,
|
|
709
|
+
serviceType: coreInputs.serviceType,
|
|
710
|
+
environment: coreInputs.environment,
|
|
711
|
+
domainName: coreInputs.domainName
|
|
498
712
|
});
|
|
713
|
+
|
|
714
|
+
// Display assessment results
|
|
715
|
+
console.log(chalk.green('✅ Assessment Complete'));
|
|
716
|
+
console.log(chalk.white(` Service Type: ${assessment.mergedInputs.serviceType || 'Not determined'}`));
|
|
717
|
+
console.log(chalk.white(` Confidence: ${assessment.confidence}%`));
|
|
718
|
+
console.log(chalk.white(` Missing Capabilities: ${assessment.gapAnalysis.missing.length}`));
|
|
719
|
+
|
|
720
|
+
// Show any blocking issues
|
|
721
|
+
const blockingIssues = assessment.gapAnalysis.missing.filter(gap => gap.priority === 'blocked');
|
|
722
|
+
if (blockingIssues.length > 0) {
|
|
723
|
+
console.log(chalk.yellow('\n⚠️ Permission-limited capabilities detected:'));
|
|
724
|
+
blockingIssues.forEach(issue => {
|
|
725
|
+
console.log(chalk.yellow(` - ${issue.capability}: ${issue.reason}`));
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
console.log('');
|
|
730
|
+
|
|
731
|
+
} catch (assessmentError) {
|
|
732
|
+
console.log(chalk.yellow('⚠️ Assessment failed, proceeding with deployment:'), assessmentError.message);
|
|
499
733
|
console.log('');
|
|
500
734
|
}
|
|
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
735
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
736
|
+
const orchestrator = new MultiDomainOrchestrator({
|
|
737
|
+
domains: [coreInputs.domainName],
|
|
738
|
+
environment: coreInputs.environment,
|
|
739
|
+
dryRun: options.dryRun,
|
|
740
|
+
servicePath: options.servicePath,
|
|
741
|
+
cloudflareToken: coreInputs.cloudflareToken,
|
|
742
|
+
cloudflareAccountId: coreInputs.cloudflareAccountId
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
// Show progress for initialization
|
|
746
|
+
await showProgress('Initializing deployment orchestrator', 1500);
|
|
747
|
+
await orchestrator.initialize();
|
|
748
|
+
|
|
749
|
+
console.log(chalk.cyan('🚀 Starting deployment...\n'));
|
|
750
|
+
|
|
751
|
+
// Show progress during deployment
|
|
752
|
+
const deployPromise = orchestrator.deploySingleDomain(coreInputs.domainName, {
|
|
753
|
+
...coreInputs,
|
|
754
|
+
...confirmations,
|
|
755
|
+
servicePath: options.servicePath
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
// Show deployment progress
|
|
759
|
+
const progressMessages = [
|
|
760
|
+
'Preparing deployment configuration',
|
|
761
|
+
'Setting up Cloudflare resources',
|
|
762
|
+
'Deploying worker script',
|
|
763
|
+
'Configuring database connections',
|
|
764
|
+
'Setting up domain routing',
|
|
765
|
+
'Running final health checks'
|
|
766
|
+
];
|
|
767
|
+
|
|
768
|
+
let progressIndex = 0;
|
|
769
|
+
const progressInterval = setInterval(() => {
|
|
770
|
+
if (progressIndex < progressMessages.length) {
|
|
771
|
+
console.log(chalk.gray(` ${progressMessages[progressIndex]}`));
|
|
772
|
+
progressIndex++;
|
|
529
773
|
}
|
|
774
|
+
}, 2000);
|
|
775
|
+
|
|
776
|
+
const result = await deployPromise;
|
|
777
|
+
clearInterval(progressInterval);
|
|
778
|
+
|
|
779
|
+
// Fill in remaining progress messages quickly
|
|
780
|
+
while (progressIndex < progressMessages.length) {
|
|
781
|
+
console.log(chalk.gray(` ${progressMessages[progressIndex]}`));
|
|
782
|
+
progressIndex++;
|
|
530
783
|
}
|
|
531
|
-
const environment = options.env || await inputCollector.collectEnvironment();
|
|
532
|
-
const serviceName = await inputCollector.collectServiceName();
|
|
533
|
-
const serviceType = await inputCollector.collectServiceType();
|
|
534
784
|
|
|
535
|
-
//
|
|
536
|
-
|
|
785
|
+
// Display results with cleaner formatting
|
|
786
|
+
console.log(chalk.green('\n✅ Deployment Completed Successfully!'));
|
|
787
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
537
788
|
|
|
538
|
-
//
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
options.domain
|
|
543
|
-
);
|
|
789
|
+
// Show key information prominently
|
|
790
|
+
if (result.url || confirmations.deploymentUrl) {
|
|
791
|
+
console.log(chalk.white(`🌐 Service URL: ${chalk.bold(result.url || confirmations.deploymentUrl)}`));
|
|
792
|
+
}
|
|
544
793
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
environment,
|
|
549
|
-
serviceName,
|
|
550
|
-
serviceType,
|
|
551
|
-
domainName: cloudflareConfig.domainName,
|
|
552
|
-
cloudflareToken,
|
|
553
|
-
cloudflareAccountId: cloudflareConfig.accountId,
|
|
554
|
-
cloudflareZoneId: cloudflareConfig.zoneId
|
|
555
|
-
};
|
|
794
|
+
console.log(chalk.white(`👤 Customer: ${coreInputs.customer}`));
|
|
795
|
+
console.log(chalk.white(`🏭 Environment: ${coreInputs.environment}`));
|
|
796
|
+
console.log(chalk.white(`🔧 Worker: ${confirmations.workerName || 'N/A'}`));
|
|
556
797
|
|
|
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...'));
|
|
798
|
+
if (result.health) {
|
|
799
|
+
const healthStatus = result.health.toLowerCase().includes('ok') || result.health.toLowerCase().includes('healthy')
|
|
800
|
+
? chalk.green('✅ Healthy')
|
|
801
|
+
: chalk.yellow('⚠️ Check required');
|
|
802
|
+
console.log(chalk.white(`💚 Health: ${healthStatus}`));
|
|
803
|
+
}
|
|
631
804
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
805
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
806
|
+
|
|
807
|
+
// Save deployment configuration for future reuse
|
|
808
|
+
try {
|
|
809
|
+
console.log(chalk.cyan('\n💾 Saving deployment configuration...'));
|
|
810
|
+
|
|
811
|
+
const configFile = await configManager.saveCustomerConfig(
|
|
812
|
+
coreInputs.customer,
|
|
813
|
+
coreInputs.environment,
|
|
814
|
+
{
|
|
815
|
+
coreInputs,
|
|
816
|
+
confirmations,
|
|
817
|
+
result
|
|
818
|
+
}
|
|
819
|
+
);
|
|
820
|
+
|
|
821
|
+
console.log(chalk.green(' ✅ Configuration saved for future deployments'));
|
|
822
|
+
console.log(chalk.gray(` 📄 File: ${configFile}`));
|
|
823
|
+
} catch (saveError) {
|
|
824
|
+
console.log(chalk.yellow(` ⚠️ Could not save configuration: ${saveError.message}`));
|
|
825
|
+
console.log(chalk.gray(' Deployment succeeded, but you may need to re-enter values next time.'));
|
|
826
|
+
}
|
|
641
827
|
|
|
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.'));
|
|
828
|
+
console.log(chalk.cyan('\n📋 Next Steps:'));
|
|
829
|
+
console.log(chalk.white(` • Test deployment: curl ${result.url || confirmations.deploymentUrl}/health`));
|
|
830
|
+
console.log(chalk.white(` • Monitor logs: wrangler tail ${confirmations.workerName}`));
|
|
831
|
+
console.log(chalk.white(` • View dashboard: https://dash.cloudflare.com`));
|
|
648
832
|
}
|
|
649
833
|
|
|
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
834
|
} catch (error) {
|
|
660
835
|
console.error(chalk.red(`\n❌ Deployment failed: ${error.message}`));
|
|
661
836
|
|
|
@@ -687,4 +862,30 @@ program
|
|
|
687
862
|
}
|
|
688
863
|
});
|
|
689
864
|
|
|
865
|
+
// Utility function to redact sensitive information from logs
|
|
866
|
+
function redactSensitiveInfo(text) {
|
|
867
|
+
if (typeof text !== 'string') return text;
|
|
868
|
+
|
|
869
|
+
// Patterns to redact
|
|
870
|
+
const patterns = [
|
|
871
|
+
// Cloudflare API tokens
|
|
872
|
+
[/(CLOUDFLARE_API_TOKEN=?)(\w{20,})/gi, '$1[REDACTED]'],
|
|
873
|
+
// Generic API tokens/keys
|
|
874
|
+
[/(api[_-]?token|api[_-]?key|auth[_-]?token)["']?[:=]\s*["']?([a-zA-Z0-9_-]{20,})["']?/gi, '$1: [REDACTED]'],
|
|
875
|
+
// Passwords
|
|
876
|
+
[/(password|passwd|pwd)["']?[:=]\s*["']?([^"'\s]{3,})["']?/gi, '$1: [REDACTED]'],
|
|
877
|
+
// Secrets
|
|
878
|
+
[/(secret|key)["']?[:=]\s*["']?([a-zA-Z0-9_-]{10,})["']?/gi, '$1: [REDACTED]'],
|
|
879
|
+
// Account IDs (partial redaction)
|
|
880
|
+
[/(account[_-]?id|zone[_-]?id)["']?[:=]\s*["']?([a-zA-Z0-9]{8})([a-zA-Z0-9]{24,})["']?/gi, '$1: $2[REDACTED]']
|
|
881
|
+
];
|
|
882
|
+
|
|
883
|
+
let redacted = text;
|
|
884
|
+
patterns.forEach(([pattern, replacement]) => {
|
|
885
|
+
redacted = redacted.replace(pattern, replacement);
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
return redacted;
|
|
889
|
+
}
|
|
890
|
+
|
|
690
891
|
program.parse();
|