@tamyla/clodo-framework 3.1.14 → 3.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [3.1.15](https://github.com/tamylaa/clodo-framework/compare/v3.1.14...v3.1.15) (2025-10-28)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Complete comprehensive clodo-framework refactoring (Tasks 3.1-3.4) ([0949b8b](https://github.com/tamylaa/clodo-framework/commit/0949b8be5f4f6522c0e4e4050e858ce82be5d034))
7
+
1
8
  ## [3.1.14](https://github.com/tamylaa/clodo-framework/compare/v3.1.13...v3.1.14) (2025-10-27)
2
9
 
3
10
 
@@ -4,16 +4,43 @@
4
4
  */
5
5
 
6
6
  import chalk from 'chalk';
7
+ import { StandardOptions } from '../shared/utils/cli-options.js';
8
+ import { ConfigLoader } from '../shared/utils/config-loader.js';
7
9
  export function registerAssessCommand(program) {
8
- program.command('assess [service-path]').description('Run intelligent capability assessment (requires @tamyla/clodo-orchestration)').option('--export <file>', 'Export assessment results to JSON file').option('--domain <domain>', 'Domain name for assessment').option('--service-type <type>', 'Service type for assessment').option('--token <token>', 'Cloudflare API token').action(async (servicePath, options) => {
10
+ const command = program.command('assess [service-path]').description('Run intelligent capability assessment (requires @tamyla/clodo-orchestration)').option('--export <file>', 'Export assessment results to JSON file').option('--domain <domain>', 'Domain name for assessment').option('--service-type <type>', 'Service type for assessment').option('--token <token>', 'Cloudflare API token');
11
+
12
+ // Add standard options (--verbose, --quiet, --json, --no-color, --config-file)
13
+ StandardOptions.define(command).action(async (servicePath, options) => {
9
14
  try {
15
+ const output = new (await import('../shared/utils/output-formatter.js')).OutputFormatter(options);
16
+ const configLoader = new ConfigLoader({
17
+ verbose: options.verbose,
18
+ quiet: options.quiet,
19
+ json: options.json
20
+ });
21
+
22
+ // Load config from file if specified
23
+ let configFileData = {};
24
+ if (options.configFile) {
25
+ configFileData = configLoader.loadSafe(options.configFile, {});
26
+ if (options.verbose && !options.quiet) {
27
+ output.info(`Loaded configuration from: ${options.configFile}`);
28
+ }
29
+ }
30
+
31
+ // Substitute environment variables
32
+ configFileData = configLoader.substituteEnvironmentVariables(configFileData);
33
+
34
+ // Merge config file defaults with CLI options (CLI takes precedence)
35
+ const mergedOptions = configLoader.merge(configFileData, options);
36
+
10
37
  // Try to load professional orchestration package
11
38
  let orchestrationModule;
12
39
  try {
13
40
  orchestrationModule = await import('@tamyla/clodo-orchestration');
14
41
  } catch (err) {
15
- console.error(chalk.red('āŒ clodo-orchestration package not found'));
16
- console.error(chalk.yellow('šŸ’” Install with: npm install @tamyla/clodo-orchestration'));
42
+ output.error('āŒ clodo-orchestration package not found');
43
+ output.info('šŸ’” Install with: npm install @tamyla/clodo-orchestration');
17
44
  process.exit(1);
18
45
  }
19
46
  const {
@@ -22,53 +49,41 @@ export function registerAssessCommand(program) {
22
49
  runAssessmentWorkflow
23
50
  } = orchestrationModule;
24
51
  const targetPath = servicePath || process.cwd();
25
- console.log(chalk.cyan('\n🧠 Professional Capability Assessment'));
26
- console.log(chalk.gray('─'.repeat(60)));
27
- console.log(chalk.white(`Service Path: ${targetPath}`));
28
- if (options.domain) {
29
- console.log(chalk.white(`Domain: ${options.domain}`));
30
- }
31
- if (options.serviceType) {
32
- console.log(chalk.white(`Service Type: ${options.serviceType}`));
33
- }
34
- console.log(chalk.gray('─'.repeat(60)));
52
+ output.section('Professional Capability Assessment');
53
+ output.list([`Service Path: ${targetPath}`, mergedOptions.domain ? `Domain: ${mergedOptions.domain}` : null, mergedOptions.serviceType ? `Service Type: ${mergedOptions.serviceType}` : null].filter(Boolean));
35
54
 
36
55
  // Use the assessment workflow
37
56
  const assessment = await runAssessmentWorkflow({
38
57
  servicePath: targetPath,
39
- domain: options.domain,
40
- serviceType: options.serviceType,
41
- token: options.token || process.env.CLOUDFLARE_API_TOKEN
58
+ domain: mergedOptions.domain,
59
+ serviceType: mergedOptions.serviceType,
60
+ token: mergedOptions.token || process.env.CLOUDFLARE_API_TOKEN
42
61
  });
43
62
 
44
63
  // Display results
45
- console.log(chalk.cyan('\nāœ… Assessment Results'));
46
- console.log(chalk.gray('─'.repeat(60)));
47
- console.log(chalk.white(`Service Type: ${assessment.mergedInputs?.serviceType || assessment.serviceType || 'Not determined'}`));
48
- console.log(chalk.white(`Confidence: ${assessment.confidence}%`));
64
+ output.section('āœ… Assessment Results');
65
+ output.list([`Service Type: ${assessment.mergedInputs?.serviceType || assessment.serviceType || 'Not determined'}`, `Confidence: ${assessment.confidence}%`]);
49
66
  if (assessment.gapAnalysis?.missing) {
50
- console.log(chalk.white(`Missing Capabilities: ${assessment.gapAnalysis.missing.length}`));
51
67
  if (assessment.gapAnalysis.missing.length > 0) {
52
- console.log(chalk.yellow('\nāš ļø Missing:'));
53
- assessment.gapAnalysis.missing.forEach(gap => {
54
- console.log(chalk.yellow(` • ${gap.capability}: ${gap.reason || 'Not available'}`));
55
- });
68
+ output.warning('āš ļø Missing Capabilities:');
69
+ const missingItems = assessment.gapAnalysis.missing.map(gap => `${gap.capability}: ${gap.reason || 'Not available'}`);
70
+ output.list(missingItems);
56
71
  }
57
72
  }
58
- console.log(chalk.gray('─'.repeat(60)));
59
73
 
60
74
  // Export results if requested
61
- if (options.export) {
75
+ if (mergedOptions.export) {
62
76
  const {
63
77
  writeFileSync
64
78
  } = await import('fs');
65
- writeFileSync(options.export, JSON.stringify(assessment, null, 2));
66
- console.log(chalk.green(`\nšŸ“„ Results exported to: ${options.export}`));
79
+ writeFileSync(mergedOptions.export, JSON.stringify(assessment, null, 2));
80
+ output.success(`šŸ“„ Results exported to: ${mergedOptions.export}`);
67
81
  }
68
82
  } catch (error) {
69
- console.error(chalk.red(`Assessment failed: ${error.message}`));
83
+ const output = new (await import('../shared/utils/output-formatter.js')).OutputFormatter(options || {});
84
+ output.error(`Assessment failed: ${error.message}`);
70
85
  if (process.env.DEBUG) {
71
- console.error(chalk.gray(error.stack));
86
+ output.debug(error.stack);
72
87
  }
73
88
  process.exit(1);
74
89
  }
@@ -7,48 +7,69 @@
7
7
 
8
8
  import chalk from 'chalk';
9
9
  import { ServiceOrchestrator } from "../../service-management/ServiceOrchestrator.js";
10
+ import { StandardOptions } from '../shared/utils/cli-options.js';
11
+ import { ConfigLoader } from '../shared/utils/config-loader.js';
10
12
  export function registerCreateCommand(program) {
11
- program.command('create').description('Create a new Clodo service with conversational setup').option('-n, --non-interactive', 'Run in non-interactive mode with all required parameters').option('--service-name <name>', 'Service name (required in non-interactive mode)').option('--service-type <type>', 'Service type: data-service, auth-service, content-service, api-gateway, generic', 'generic').option('--domain-name <domain>', 'Domain name (required in non-interactive mode)').option('--cloudflare-token <token>', 'Cloudflare API token (required in non-interactive mode)').option('--cloudflare-account-id <id>', 'Cloudflare account ID (required in non-interactive mode)').option('--cloudflare-zone-id <id>', 'Cloudflare zone ID (required in non-interactive mode)').option('--environment <env>', 'Target environment: development, staging, production', 'development').option('--output-path <path>', 'Output directory for generated service', '.').option('--template-path <path>', 'Path to service templates', './templates').action(async options => {
13
+ const command = program.command('create').description('Create a new Clodo service with conversational setup').option('-n, --non-interactive', 'Run in non-interactive mode with all required parameters').option('--service-name <name>', 'Service name (required in non-interactive mode)').option('--service-type <type>', 'Service type: data-service, auth-service, content-service, api-gateway, generic', 'generic').option('--domain-name <domain>', 'Domain name (required in non-interactive mode)').option('--cloudflare-token <token>', 'Cloudflare API token (required in non-interactive mode)').option('--cloudflare-account-id <id>', 'Cloudflare account ID (required in non-interactive mode)').option('--cloudflare-zone-id <id>', 'Cloudflare zone ID (required in non-interactive mode)').option('--environment <env>', 'Target environment: development, staging, production', 'development').option('--output-path <path>', 'Output directory for generated service', '.').option('--template-path <path>', 'Path to service templates', './templates').option('--force', 'Skip confirmation prompts').option('--validate', 'Validate service after creation');
14
+
15
+ // Add standard options (--verbose, --quiet, --json, --no-color, --config-file)
16
+ StandardOptions.define(command).action(async options => {
12
17
  try {
18
+ const output = new (await import('../shared/utils/output-formatter.js')).OutputFormatter(options);
19
+ const configLoader = new ConfigLoader({
20
+ verbose: options.verbose,
21
+ quiet: options.quiet,
22
+ json: options.json
23
+ });
24
+
25
+ // Load config from file if specified
26
+ let configFileData = {};
27
+ if (options.configFile) {
28
+ configFileData = configLoader.loadSafe(options.configFile, {});
29
+ if (options.verbose && !options.quiet) {
30
+ output.info(`Loaded configuration from: ${options.configFile}`);
31
+ }
32
+ }
33
+
34
+ // Merge config file defaults with CLI options (CLI takes precedence)
35
+ const mergedOptions = configLoader.merge(configFileData, options);
13
36
  const orchestrator = new ServiceOrchestrator({
14
- interactive: !options.nonInteractive,
15
- outputPath: options.outputPath,
16
- templatePath: options.templatePath
37
+ interactive: !mergedOptions.nonInteractive,
38
+ outputPath: mergedOptions.outputPath,
39
+ templatePath: mergedOptions.templatePath
17
40
  });
18
- if (options.nonInteractive) {
41
+ if (mergedOptions.nonInteractive) {
19
42
  // Validate required parameters for non-interactive mode
20
43
  const required = ['serviceName', 'domainName', 'cloudflareToken', 'cloudflareAccountId', 'cloudflareZoneId'];
21
- const missing = required.filter(key => !options[key]);
44
+ const missing = required.filter(key => !mergedOptions[key]);
22
45
  if (missing.length > 0) {
23
- console.error(chalk.red(`Missing required parameters: ${missing.join(', ')}`));
24
- console.error(chalk.yellow('Use --help for parameter details'));
46
+ output.error(`Missing required parameters: ${missing.join(', ')}`);
47
+ output.info('Use --help for parameter details');
25
48
  process.exit(1);
26
49
  }
27
50
 
28
- // Convert CLI options to core inputs
51
+ // Convert merged options to core inputs
29
52
  const coreInputs = {
30
- serviceName: options.serviceName,
31
- serviceType: options.serviceType,
32
- domainName: options.domainName,
33
- cloudflareToken: options.cloudflareToken,
34
- cloudflareAccountId: options.cloudflareAccountId,
35
- cloudflareZoneId: options.cloudflareZoneId,
36
- environment: options.environment
53
+ serviceName: mergedOptions.serviceName,
54
+ serviceType: mergedOptions.serviceType,
55
+ domainName: mergedOptions.domainName,
56
+ cloudflareToken: mergedOptions.cloudflareToken,
57
+ cloudflareAccountId: mergedOptions.cloudflareAccountId,
58
+ cloudflareZoneId: mergedOptions.cloudflareZoneId,
59
+ environment: mergedOptions.environment
37
60
  };
38
61
  await orchestrator.runNonInteractive(coreInputs);
39
62
  } else {
40
63
  await orchestrator.runInteractive();
41
64
  }
42
- console.log(chalk.green('\nāœ“ Service creation completed successfully!'));
43
- console.log(chalk.cyan('Next steps:'));
44
- console.log(chalk.white(' 1. cd into your new service directory'));
45
- console.log(chalk.white(' 2. Run npm install'));
46
- console.log(chalk.white(' 3. Configure additional settings in src/config/domains.js'));
47
- console.log(chalk.white(' 4. Run npm run deploy to deploy to Cloudflare'));
65
+ output.success('Service creation completed successfully!');
66
+ output.section('Next steps');
67
+ output.list(['cd into your new service directory', 'Run npm install', 'Configure additional settings in src/config/domains.js', 'Run npm run deploy to deploy to Cloudflare']);
48
68
  } catch (error) {
49
- console.error(chalk.red(`\nāœ— Service creation failed: ${error.message}`));
69
+ const output = new (await import('../shared/utils/output-formatter.js')).OutputFormatter(options || {});
70
+ output.error(`Service creation failed: ${error.message}`);
50
71
  if (error.details) {
51
- console.error(chalk.yellow(`Details: ${error.details}`));
72
+ output.warning(`Details: ${error.details}`);
52
73
  }
53
74
  process.exit(1);
54
75
  }
@@ -1,12 +1,13 @@
1
1
  /**
2
2
  * Deploy Command - Smart minimal input deployment with service detection
3
3
  *
4
- * Input Strategy: SMART MINIMAL
4
+ * Input Strategy: SMART MINIMAL WITH DOMAIN ROUTING
5
5
  * - Detects Clodo services OR legacy services (wrangler.toml)
6
6
  * - Supports multiple manifest locations
7
+ * - Uses DomainRouter for intelligent domain selection
7
8
  * - Gathers credentials smartly: env vars → flags → interactive collection with auto-fetch
8
9
  * - Validates token and fetches account ID & zone ID from Cloudflare
9
- * - Integrates with modular-enterprise-deploy.js for clean CLI-based deployment
10
+ * - REFACTORED (Task 3.2): Integrates with MultiDomainOrchestrator for full deployment orchestration
10
11
  */
11
12
 
12
13
  import chalk from 'chalk';
@@ -14,13 +15,43 @@ import { resolve } from 'path';
14
15
  import { ManifestLoader } from '../shared/config/manifest-loader.js';
15
16
  import { CloudflareServiceValidator } from '../shared/config/cloudflare-service-validator.js';
16
17
  import { DeploymentCredentialCollector } from '../shared/deployment/credential-collector.js';
18
+ import { StandardOptions } from '../shared/utils/cli-options.js';
19
+ import { ConfigLoader } from '../shared/utils/config-loader.js';
20
+ import { DomainRouter } from '../shared/routing/domain-router.js';
21
+ import { MultiDomainOrchestrator } from "../../orchestration/multi-domain-orchestrator.js";
17
22
  export function registerDeployCommand(program) {
18
- program.command('deploy').description('Deploy a Clodo service with smart credential handling').option('--token <token>', 'Cloudflare API token').option('--account-id <id>', 'Cloudflare account ID').option('--zone-id <id>', 'Cloudflare zone ID').option('--dry-run', 'Simulate deployment without making changes').option('--quiet', 'Quiet mode - minimal output').option('--service-path <path>', 'Path to service directory', '.').action(async options => {
23
+ const command = program.command('deploy').description('Deploy a Clodo service with smart credential handling and domain selection')
24
+ // Cloudflare-specific options
25
+ .option('--token <token>', 'Cloudflare API token').option('--account-id <id>', 'Cloudflare account ID').option('--zone-id <id>', 'Cloudflare zone ID').option('--domain <domain>', 'Specific domain to deploy to (otherwise prompted if multiple exist)').option('--environment <env>', 'Target environment (development, staging, production)', 'production').option('--all-domains', 'Deploy to all configured domains (ignores --domain flag)').option('--dry-run', 'Simulate deployment without making changes').option('--service-path <path>', 'Path to service directory', '.');
26
+
27
+ // Add standard options (--verbose, --quiet, --json, --no-color, --config-file)
28
+ StandardOptions.define(command).action(async options => {
19
29
  try {
20
- console.log(chalk.cyan('\nšŸš€ Clodo Service Deployment\n'));
30
+ const output = new (await import('../shared/utils/output-formatter.js')).OutputFormatter(options);
31
+ const configLoader = new ConfigLoader({
32
+ verbose: options.verbose,
33
+ quiet: options.quiet,
34
+ json: options.json
35
+ });
36
+
37
+ // Load config from file if specified
38
+ let configFileData = {};
39
+ if (options.configFile) {
40
+ configFileData = configLoader.loadSafe(options.configFile, {});
41
+ if (options.verbose && !options.quiet) {
42
+ output.info(`Loaded configuration from: ${options.configFile}`);
43
+ }
44
+ }
45
+
46
+ // Substitute environment variables in config
47
+ configFileData = configLoader.substituteEnvironmentVariables(configFileData);
48
+
49
+ // Merge config file defaults with CLI options (CLI takes precedence)
50
+ const mergedOptions = configLoader.merge(configFileData, options);
51
+ output.info('šŸš€ Clodo Service Deployment');
21
52
 
22
53
  // Step 1: Load and validate service configuration
23
- const servicePath = resolve(options.servicePath);
54
+ const servicePath = resolve(mergedOptions.servicePath || '.');
24
55
  const serviceConfig = await ManifestLoader.loadAndValidateCloudflareService(servicePath);
25
56
  if (!serviceConfig.manifest) {
26
57
  if (serviceConfig.error === 'NOT_A_CLOUDFLARE_SERVICE') {
@@ -37,7 +68,7 @@ export function registerDeployCommand(program) {
37
68
  CloudflareServiceValidator.printValidationReport(serviceConfig.validationResult.validation);
38
69
  }
39
70
  ManifestLoader.printManifestInfo(serviceConfig.manifest);
40
- console.log(chalk.gray(`Configuration loaded from: ${serviceConfig.foundAt}\n`));
71
+ output.info(`Configuration loaded from: ${serviceConfig.foundAt}`);
41
72
 
42
73
  // Step 2: Smart credential gathering with interactive collection
43
74
  // Uses DeploymentCredentialCollector which:
@@ -47,126 +78,246 @@ export function registerDeployCommand(program) {
47
78
  // - Caches credentials for future use
48
79
  const credentialCollector = new DeploymentCredentialCollector({
49
80
  servicePath: servicePath,
50
- quiet: options.quiet
81
+ quiet: mergedOptions.quiet
51
82
  });
52
83
  let credentials;
53
84
  try {
54
85
  credentials = await credentialCollector.collectCredentials({
55
- token: options.token,
56
- accountId: options.accountId,
57
- zoneId: options.zoneId
86
+ token: mergedOptions.token,
87
+ accountId: mergedOptions.accountId,
88
+ zoneId: mergedOptions.zoneId
58
89
  });
59
90
  } finally {
60
91
  credentialCollector.cleanup();
61
92
  }
62
93
 
63
- // Step 3: Extract configuration from manifest
94
+ // Step 3: Initialize DomainRouter for intelligent domain selection
95
+ // REFACTORED (Task 3.2): Use DomainRouter for domain detection and selection
96
+ console.log(chalk.cyan('\nšŸ—ŗļø Detecting available domains...\n'));
97
+ const router = new DomainRouter({
98
+ environment: mergedOptions.environment || 'production',
99
+ verbose: options.verbose,
100
+ configPath: options.configPath,
101
+ disableOrchestrator: false,
102
+ // We'll use the orchestrator for deployment
103
+ orchestratorOptions: {
104
+ dryRun: options.dryRun || false,
105
+ cloudflareToken: credentials.token,
106
+ cloudflareAccountId: credentials.accountId
107
+ }
108
+ });
109
+
110
+ // Detect domains from manifest
111
+ let detectedDomains = [];
64
112
  const manifest = serviceConfig.manifest;
65
113
  const config = manifest.deployment || manifest.configuration || {};
114
+ const domainsConfig = config.domains || [];
115
+ if (Array.isArray(domainsConfig) && domainsConfig.length > 0) {
116
+ // Handle both array of strings and array of objects
117
+ detectedDomains = domainsConfig.map(d => {
118
+ if (typeof d === 'string') return d;
119
+ if (typeof d === 'object' && d.name) return d.name;
120
+ return null;
121
+ }).filter(d => d !== null);
122
+ }
123
+
124
+ // If no domains in config, try to detect from environment
125
+ if (detectedDomains.length === 0 && manifest._source === 'cloudflare-service-detected') {
126
+ // For detected CF services, use default
127
+ detectedDomains = ['workers.cloudflare.com'];
128
+ }
129
+
130
+ // Domain selection: check CLI flag first, then prompt user
131
+ let selectedDomain = mergedOptions.domain;
132
+ if (!selectedDomain && detectedDomains.length > 0) {
133
+ if (detectedDomains.length === 1) {
134
+ // Only one domain, use it directly
135
+ selectedDomain = detectedDomains[0];
136
+ if (!options.quiet) {
137
+ console.log(chalk.green(`āœ“ Selected domain: ${selectedDomain}`));
138
+ }
139
+ } else {
140
+ // Multiple domains available - let user choose
141
+ console.log(chalk.cyan('šŸ“ Available domains:'));
142
+ detectedDomains.forEach((d, i) => {
143
+ console.log(chalk.white(` ${i + 1}) ${d}`));
144
+ });
145
+
146
+ // If running interactively, prompt user
147
+ if (process.stdin.isTTY) {
148
+ const {
149
+ createPromptModule
150
+ } = await import('inquirer');
151
+ const prompt = createPromptModule();
152
+ const response = await prompt([{
153
+ type: 'list',
154
+ name: 'selectedDomain',
155
+ message: 'Select domain to deploy to:',
156
+ choices: detectedDomains,
157
+ default: detectedDomains[0]
158
+ }]);
159
+ selectedDomain = response.selectedDomain;
160
+ } else {
161
+ // Non-interactive mode: use first domain
162
+ selectedDomain = detectedDomains[0];
163
+ console.log(chalk.yellow(`āš ļø Non-interactive mode: using first domain: ${selectedDomain}`));
164
+ }
165
+ }
166
+ }
167
+ if (!selectedDomain) {
168
+ if (!options.quiet) {
169
+ console.error(chalk.yellow('āš ļø No domain configured for deployment'));
170
+ console.error(chalk.gray('For Clodo services: add deployment.domains in clodo-service-manifest.json'));
171
+ console.error(chalk.gray('For detected CF services: define routes in wrangler.toml'));
172
+ }
173
+ process.exit(1);
174
+ }
175
+
176
+ // Step 4: Validate domain configuration
177
+ console.log(chalk.cyan('\nšŸ” Validating domain configuration...\n'));
178
+ const validation = router.validateConfiguration({
179
+ domains: [selectedDomain],
180
+ environment: mergedOptions.environment || 'production'
181
+ });
182
+ if (!validation.valid) {
183
+ console.error(chalk.red('āŒ Configuration validation failed:'));
184
+ validation.errors.forEach(err => {
185
+ console.error(chalk.yellow(` • ${err}`));
186
+ });
187
+ process.exit(1);
188
+ }
189
+ if (validation.warnings && validation.warnings.length > 0) {
190
+ console.log(chalk.yellow('āš ļø Configuration warnings:'));
191
+ validation.warnings.forEach(warn => {
192
+ console.log(chalk.gray(` • ${warn}`));
193
+ });
194
+ }
66
195
 
67
196
  // Extract service metadata
68
197
  const serviceName = manifest.serviceName || 'unknown-service';
69
198
  const serviceType = manifest.serviceType || 'generic';
70
199
 
71
- // For detected Cloudflare services, domain comes from wrangler.toml or environment
72
- // For Clodo services, use first domain if available
73
- let domain = null;
74
- const domains = config.domains || [];
75
- if (domains.length > 0) {
76
- // Clodo service with multiple domains
77
- domain = domains[0].name || domains[0];
78
- } else if (manifest._source === 'cloudflare-service-detected') {
79
- // Detected CF service - get domain from route or config
80
- // For now, use a placeholder since we don't have explicit domain routing in detected services
81
- domain = 'workers.cloudflare.com';
82
- console.log(chalk.gray('Note: Using Cloudflare Workers default domain (add routes in wrangler.toml for custom domains)'));
83
- }
84
- if (!domain && !options.quiet) {
85
- console.error(chalk.yellow('āš ļø No domain configured for deployment'));
86
- console.error(chalk.gray('For Clodo services: add deployment.domains in clodo-service-manifest.json'));
87
- console.error(chalk.gray('For detected CF services: define routes in wrangler.toml'));
88
- }
89
- console.log(chalk.cyan('šŸ“‹ Deployment Plan:'));
90
- console.log(chalk.gray('─'.repeat(50)));
91
- console.log(chalk.white(`Service: ${serviceName}`));
92
- console.log(chalk.white(`Type: ${serviceType}`));
93
- if (domain) {
94
- console.log(chalk.white(`Domain: ${domain}`));
95
- }
96
- console.log(chalk.white(`Account: ${credentials.accountId.substring(0, 8)}...`));
97
- console.log(chalk.white(`Zone: ${credentials.zoneId.substring(0, 8)}...`));
98
- console.log(chalk.gray('─'.repeat(50)));
200
+ // Display deployment plan
201
+ console.log(chalk.cyan('\nšŸ“‹ Deployment Plan:'));
202
+ console.log(chalk.gray('─'.repeat(60)));
203
+ console.log(chalk.white(`Service: ${serviceName}`));
204
+ console.log(chalk.white(`Type: ${serviceType}`));
205
+ console.log(chalk.white(`Domain: ${selectedDomain}`));
206
+ console.log(chalk.white(`Environment: ${mergedOptions.environment || 'production'}`));
207
+ console.log(chalk.white(`Account: ${credentials.accountId.substring(0, 8)}...`));
208
+ console.log(chalk.white(`Zone: ${credentials.zoneId.substring(0, 8)}...`));
209
+ console.log(chalk.white(`Deployment Mode: ${options.dryRun ? 'DRY RUN' : 'LIVE'}`));
210
+ console.log(chalk.gray('─'.repeat(60)));
99
211
  if (options.dryRun) {
100
212
  console.log(chalk.yellow('\nšŸ” DRY RUN MODE - No changes will be made\n'));
101
213
  }
102
214
 
103
- // Step 4: Load and call modular deployer
104
- console.log(chalk.cyan('\nāš™ļø Initializing deployment system...\n'));
105
- let ModularEnterpriseDeployer;
215
+ // Step 5: Initialize MultiDomainOrchestrator for deployment
216
+ // REFACTORED (Task 3.2): Direct orchestrator integration instead of external deployer
217
+ console.log(chalk.cyan('\nāš™ļø Initializing orchestration system...\n'));
218
+ const orchestrator = new MultiDomainOrchestrator({
219
+ domains: [selectedDomain],
220
+ environment: mergedOptions.environment || 'production',
221
+ dryRun: options.dryRun || false,
222
+ skipTests: false,
223
+ parallelDeployments: 1,
224
+ // Single domain in this flow
225
+ servicePath: servicePath,
226
+ cloudflareToken: credentials.token,
227
+ cloudflareAccountId: credentials.accountId,
228
+ enablePersistence: !options.dryRun,
229
+ rollbackEnabled: !options.dryRun,
230
+ verbose: options.verbose
231
+ });
106
232
  try {
107
- const module = await import('../../deployment/modular-enterprise-deploy.js');
108
- ModularEnterpriseDeployer = module.ModularEnterpriseDeployer || module.default;
233
+ await orchestrator.initialize();
109
234
  } catch (err) {
110
- console.error(chalk.red('āŒ Failed to load deployment system'));
235
+ console.error(chalk.red('āŒ Failed to initialize orchestrator'));
111
236
  console.error(chalk.yellow(`Error: ${err.message}`));
112
237
  if (process.env.DEBUG) {
113
238
  console.error(chalk.gray(err.stack));
114
239
  }
115
240
  process.exit(1);
116
241
  }
117
- if (!ModularEnterpriseDeployer) {
118
- console.error(chalk.red('āŒ ModularEnterpriseDeployer not found in deployment module'));
119
- process.exit(1);
120
- }
121
242
 
122
- // Create deployer instance with gathered credentials
123
- const deployer = new ModularEnterpriseDeployer({
124
- apiToken: credentials.token,
125
- accountId: credentials.accountId,
126
- zoneId: credentials.zoneId,
127
- domain: domain,
128
- dryRun: options.dryRun,
129
- quiet: options.quiet,
130
- servicePath: servicePath,
131
- serviceName: serviceName,
132
- serviceType: serviceType
133
- });
243
+ // Step 6: Execute deployment via orchestrator
244
+ console.log(chalk.cyan('šŸš€ Starting deployment via orchestrator...\n'));
245
+ let result;
246
+ try {
247
+ result = await orchestrator.deploySingleDomain(selectedDomain, {
248
+ manifest: manifest,
249
+ credentials: credentials,
250
+ dryRun: options.dryRun,
251
+ environment: mergedOptions.environment || 'production'
252
+ });
253
+ } catch (deployError) {
254
+ console.error(chalk.red('\nāŒ Deployment failed during orchestration\n'));
255
+ console.error(chalk.yellow(`Error: ${deployError.message}`));
134
256
 
135
- // Run deployment
136
- console.log(chalk.cyan('šŸš€ Starting deployment...\n'));
137
- const result = await deployer.run({
138
- manifest: manifest,
139
- credentials: credentials,
140
- dryRun: options.dryRun
141
- });
257
+ // Provide helpful error context
258
+ if (deployError.message.includes('credentials') || deployError.message.includes('auth')) {
259
+ console.error(chalk.yellow('\nšŸ’” Credential Issue:'));
260
+ console.error(chalk.white(' Check your API token, account ID, and zone ID'));
261
+ console.error(chalk.white(' Visit: https://dash.cloudflare.com/profile/api-tokens'));
262
+ }
263
+ if (deployError.message.includes('domain') || deployError.message.includes('zone')) {
264
+ console.error(chalk.yellow('\nļæ½ Domain Issue:'));
265
+ console.error(chalk.white(' Verify domain exists in Cloudflare'));
266
+ console.error(chalk.white(' Check API token has zone:read permissions'));
267
+ }
268
+ if (process.env.DEBUG) {
269
+ console.error(chalk.gray('\nFull Stack Trace:'));
270
+ console.error(chalk.gray(deployError.stack));
271
+ }
272
+ process.exit(1);
273
+ }
142
274
 
143
- // Display results
275
+ // Step 7: Display deployment results
144
276
  console.log(chalk.green('\nāœ… Deployment Completed Successfully!\n'));
145
- console.log(chalk.gray('─'.repeat(50)));
277
+ console.log(chalk.gray('─'.repeat(60)));
278
+
279
+ // Display results from orchestrator
146
280
  if (result.url) {
147
- console.log(chalk.white(`🌐 Service URL: ${chalk.bold(result.url)}`));
281
+ console.log(chalk.white(`🌐 Service URL: ${chalk.bold(result.url)}`));
148
282
  }
149
- console.log(chalk.white(`šŸ“¦ Service: ${serviceName}`));
150
- console.log(chalk.white(`šŸ”§ Type: ${serviceType}`));
151
- console.log(chalk.white(`šŸŒ Domain: ${domain}`));
283
+ console.log(chalk.white(`šŸ“¦ Service: ${serviceName}`));
284
+ console.log(chalk.white(`šŸ”§ Type: ${serviceType}`));
285
+ console.log(chalk.white(`šŸŒ Domain: ${selectedDomain}`));
286
+ console.log(chalk.white(`šŸŒŽ Environment: ${mergedOptions.environment || 'production'}`));
152
287
  if (result.workerId) {
153
- console.log(chalk.white(`šŸ‘¤ Worker ID: ${result.workerId}`));
288
+ console.log(chalk.white(`šŸ‘¤ Worker ID: ${result.workerId}`));
289
+ }
290
+ if (result.deploymentId) {
291
+ console.log(chalk.white(`šŸ“‹ Deployment ID: ${result.deploymentId}`));
154
292
  }
155
293
  if (result.status) {
156
294
  const statusColor = result.status.toLowerCase().includes('success') ? chalk.green : chalk.yellow;
157
- console.log(chalk.white(`šŸ“Š Status: ${statusColor(result.status)}`));
295
+ console.log(chalk.white(`šŸ“Š Status: ${statusColor(result.status)}`));
296
+ }
297
+
298
+ // Display audit information if available
299
+ if (result.auditLog) {
300
+ console.log(chalk.cyan('\nšŸ“‹ Deployment Audit:'));
301
+ console.log(chalk.gray(` Started: ${result.auditLog.startTime}`));
302
+ console.log(chalk.gray(` Completed: ${result.auditLog.endTime}`));
303
+ console.log(chalk.gray(` Duration: ${result.auditLog.duration}ms`));
158
304
  }
159
- console.log(chalk.gray('─'.repeat(50)));
305
+ console.log(chalk.gray('─'.repeat(60)));
160
306
 
161
- // Next steps
307
+ // Display next steps
162
308
  if (!options.dryRun) {
163
309
  console.log(chalk.cyan('\nšŸ’” Next Steps:'));
164
- console.log(chalk.white(' • Test deployment: curl ' + (result.url || `https://${domain}`)));
165
- console.log(chalk.white(' • View logs: wrangler tail ' + serviceName));
166
- console.log(chalk.white(' • Monitor: https://dash.cloudflare.com'));
310
+ console.log(chalk.white(` • Test deployment: curl ${result.url || `https://${selectedDomain}`}`));
311
+ console.log(chalk.white(` • View logs: wrangler tail ${serviceName}`));
312
+ console.log(chalk.white(` • Monitor: https://dash.cloudflare.com`));
313
+ console.log(chalk.white(` • Check audit logs: See deployment ID above\n`));
314
+ } else {
315
+ console.log(chalk.cyan('\nšŸ’” Dry Run Complete'));
316
+ console.log(chalk.white(` • Review the plan above`));
317
+ console.log(chalk.white(` • Remove --dry-run to execute deployment\n`));
167
318
  }
168
319
  if (process.env.DEBUG && result.details) {
169
- console.log(chalk.gray('\nšŸ“‹ Full Result:'));
320
+ console.log(chalk.gray('šŸ“‹ Full Result:'));
170
321
  console.log(chalk.gray(JSON.stringify(result, null, 2)));
171
322
  }
172
323
  } catch (error) {
@@ -176,11 +327,16 @@ export function registerDeployCommand(program) {
176
327
  console.error(chalk.white(' Check your API token, account ID, and zone ID'));
177
328
  console.error(chalk.white(' Visit: https://dash.cloudflare.com/profile/api-tokens'));
178
329
  }
179
- if (error.message.includes('domain')) {
330
+ if (error.message.includes('domain') || error.message.includes('zone')) {
180
331
  console.error(chalk.yellow('\nšŸ’” Domain Issue:'));
181
332
  console.error(chalk.white(' Verify domain exists in Cloudflare'));
182
333
  console.error(chalk.white(' Check API token has zone:read permissions'));
183
334
  }
335
+ if (error.message.includes('orchestration') || error.message.includes('initialization')) {
336
+ console.error(chalk.yellow('\nšŸ’” Orchestration Issue:'));
337
+ console.error(chalk.white(' Check MultiDomainOrchestrator configuration'));
338
+ console.error(chalk.white(' Verify all modular components loaded correctly'));
339
+ }
184
340
  if (process.env.DEBUG) {
185
341
  console.error(chalk.gray('\nFull Stack Trace:'));
186
342
  console.error(chalk.gray(error.stack));