@tamyla/clodo-framework 3.1.24 → 3.1.26

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cli/clodo-simple.js +111 -0
  3. package/dist/cli/commands/assess.js +5 -5
  4. package/dist/cli/commands/create.js +22 -31
  5. package/dist/cli/commands/deploy.js +22 -357
  6. package/dist/cli/commands/diagnose.js +5 -5
  7. package/dist/cli/commands/helpers/deployment-verification.js +2 -2
  8. package/dist/cli/commands/helpers/error-recovery.js +1 -1
  9. package/dist/cli/commands/helpers/resource-detection.js +1 -1
  10. package/dist/cli/commands/init-config.js +1 -1
  11. package/dist/cli/commands/update.js +5 -5
  12. package/dist/cli/commands/validate.js +24 -48
  13. package/dist/cli/security-cli.js +1 -1
  14. package/dist/deployment/wrangler-deployer.js +1 -1
  15. package/dist/index.js +5 -2
  16. package/dist/lib/deployment/modules/DeploymentOrchestrator.js +2 -2
  17. package/dist/lib/deployment/modules/EnvironmentManager.js +2 -2
  18. package/dist/lib/shared/cloudflare/domain-manager.js +1 -1
  19. package/dist/lib/shared/cloudflare/ops.js +4 -4
  20. package/dist/lib/shared/config/command-config-manager.js +1 -1
  21. package/dist/lib/shared/config/index.js +1 -1
  22. package/dist/lib/shared/deployment/credential-collector.js +1 -1
  23. package/dist/lib/shared/deployment/index.js +2 -2
  24. package/dist/lib/shared/deployment/rollback-manager.js +1 -1
  25. package/dist/lib/shared/deployment/utilities/d1-error-recovery.js +1 -1
  26. package/dist/lib/shared/deployment/validator.js +1 -1
  27. package/dist/lib/shared/deployment/workflows/interactive-database-workflow.js +1 -1
  28. package/dist/lib/shared/monitoring/health-checker.js +2 -2
  29. package/dist/lib/shared/routing/domain-router.js +1 -1
  30. package/dist/lib/shared/validation/ValidationRegistry.js +1 -1
  31. package/dist/orchestration/cross-domain-coordinator.js +5 -5
  32. package/dist/orchestration/multi-domain-orchestrator.js +51 -0
  33. package/dist/security/index.js +2 -2
  34. package/dist/service-management/ConfirmationEngine.js +1 -1
  35. package/dist/service-management/ErrorTracker.js +1 -1
  36. package/dist/service-management/InputCollector.js +1 -1
  37. package/dist/service-management/ServiceOrchestrator.js +77 -0
  38. package/dist/service-management/generators/testing/UnitTestsGenerator.js +4 -4
  39. package/dist/simple-api.js +94 -0
  40. package/dist/utils/cloudflare/ops.js +5 -1
  41. package/dist/utils/file-manager.js +14 -2
  42. package/dist/utils/formatters.js +5 -2
  43. package/dist/utils/logger.js +5 -2
  44. package/dist/worker/integration.js +1 -1
  45. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [3.1.26](https://github.com/tamylaa/clodo-framework/compare/v3.1.25...v3.1.26) (2025-12-02)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * correct import paths for npm package distribution - use proper depth for compiled dist/ files ([d346563](https://github.com/tamylaa/clodo-framework/commit/d3465637e02077e014661e1039421e9cd0010d97))
7
+
8
+ ## [3.1.25](https://github.com/tamylaa/clodo-framework/compare/v3.1.24...v3.1.25) (2025-12-02)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * correct all import paths and add prevention documentation ([dbd3ce6](https://github.com/tamylaa/clodo-framework/commit/dbd3ce6d6b3ceceb9aa839358923c6096c762ceb))
14
+
1
15
  ## [3.1.24](https://github.com/tamylaa/clodo-framework/compare/v3.1.23...v3.1.24) (2025-11-08)
2
16
 
3
17
 
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Simple CLI - Simplified interface for Clodo Framework
5
+ *
6
+ * A streamlined CLI that provides easy access to core framework functionality
7
+ * using the unified simple API with sensible defaults.
8
+ */
9
+ import { Command } from 'commander';
10
+ import chalk from 'chalk';
11
+ import { Clodo } from '../src/simple-api.js';
12
+
13
+ // Create program instance
14
+ const program = new Command();
15
+ program.name('clodo').description('Simple CLI for Clodo Framework - streamlined service management').version('1.0.0');
16
+
17
+ /**
18
+ * Create command - Simple service creation
19
+ */
20
+ program.command('create <name>').description('Create a new service with minimal configuration').option('-t, --type <type>', 'Service type', 'generic').option('-d, --domain <domain>', 'Domain name', 'example.com').option('-e, --env <env>', 'Environment', 'development').option('-o, --output <path>', 'Output directory', '.').option('--token <token>', 'Cloudflare API token').option('--account-id <id>', 'Cloudflare account ID').option('--zone-id <id>', 'Cloudflare zone ID').action(async (name, options) => {
21
+ try {
22
+ console.log(chalk.cyan(`šŸš€ Creating service: ${name}`));
23
+ const result = await Clodo.createService({
24
+ name,
25
+ type: options.type,
26
+ domain: options.domain,
27
+ environment: options.env,
28
+ outputPath: options.output,
29
+ credentials: {
30
+ token: options.token,
31
+ accountId: options.accountId,
32
+ zoneId: options.zoneId
33
+ }
34
+ });
35
+ console.log(chalk.green(`āœ… ${result.message}`));
36
+ console.log(chalk.white(`šŸ“ Created in: ${result.outputPath}`));
37
+ } catch (error) {
38
+ console.error(chalk.red(`āŒ ${error.message}`));
39
+ process.exit(1);
40
+ }
41
+ });
42
+
43
+ /**
44
+ * Deploy command - Simple service deployment
45
+ */
46
+ program.command('deploy').description('Deploy a service with smart configuration detection').option('-p, --path <path>', 'Service directory path', '.').option('-e, --env <env>', 'Target environment', 'production').option('-d, --domain <domain>', 'Specific domain to deploy to').option('--dry-run', 'Simulate deployment without changes').option('--token <token>', 'Cloudflare API token').option('--account-id <id>', 'Cloudflare account ID').option('--zone-id <id>', 'Cloudflare zone ID').action(async options => {
47
+ try {
48
+ console.log(chalk.cyan(`šŸš€ Deploying service...`));
49
+ const result = await Clodo.deploy({
50
+ servicePath: options.path,
51
+ environment: options.env,
52
+ domain: options.domain,
53
+ dryRun: options.dryRun,
54
+ credentials: {
55
+ token: options.token,
56
+ accountId: options.accountId,
57
+ zoneId: options.zoneId
58
+ }
59
+ });
60
+ console.log(chalk.green(`āœ… ${result.message}`));
61
+ if (result.deployedDomains) {
62
+ console.log(chalk.white(`🌐 Deployed to: ${result.deployedDomains.join(', ')}`));
63
+ }
64
+ } catch (error) {
65
+ console.error(chalk.red(`āŒ ${error.message}`));
66
+ process.exit(1);
67
+ }
68
+ });
69
+
70
+ /**
71
+ * Validate command - Simple service validation
72
+ */
73
+ program.command('validate').description('Validate a service configuration').option('-p, --path <path>', 'Service directory path', '.').option('-r, --report', 'Export validation report').action(async options => {
74
+ try {
75
+ console.log(chalk.cyan(`šŸ” Validating service...`));
76
+ const result = await Clodo.validate({
77
+ servicePath: options.path,
78
+ exportReport: options.report
79
+ });
80
+ if (result.success) {
81
+ console.log(chalk.green(`āœ… ${result.message}`));
82
+ } else {
83
+ console.log(chalk.yellow(`āš ļø ${result.message}`));
84
+ if (result.issues && result.issues.length > 0) {
85
+ console.log(chalk.white('Issues found:'));
86
+ result.issues.forEach((issue, index) => {
87
+ console.log(chalk.white(` ${index + 1}. ${issue}`));
88
+ });
89
+ }
90
+ }
91
+ } catch (error) {
92
+ console.error(chalk.red(`āŒ ${error.message}`));
93
+ process.exit(1);
94
+ }
95
+ });
96
+
97
+ /**
98
+ * Info command - Show framework information
99
+ */
100
+ program.command('info').description('Show framework information').action(() => {
101
+ const info = Clodo.getInfo();
102
+ console.log(chalk.cyan(`${info.name} v${info.version}`));
103
+ console.log(chalk.white(info.description));
104
+ console.log(chalk.white('\nFeatures:'));
105
+ info.features.forEach(feature => {
106
+ console.log(chalk.white(` • ${feature}`));
107
+ });
108
+ });
109
+
110
+ // Parse command line arguments
111
+ program.parse();
@@ -5,16 +5,16 @@
5
5
 
6
6
  import chalk from 'chalk';
7
7
  import path from 'path';
8
- import { ServiceOrchestrator } from '../service-management/ServiceOrchestrator.js';
9
- import { StandardOptions } from '../lib/shared/utils/cli-options.js';
10
- import { ServiceConfigManager } from '../lib/shared/utils/service-config-manager.js';
8
+ import { ServiceOrchestrator } from '../../src/service-management/ServiceOrchestrator.js';
9
+ import { StandardOptions } from '../../lib/shared/utils/cli-options.js';
10
+ import { ServiceConfigManager } from '../../lib/shared/utils/service-config-manager.js';
11
11
  export function registerAssessCommand(program) {
12
12
  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').option('--show-config-sources', 'Display all configuration sources and merged result');
13
13
 
14
14
  // Add standard options (--verbose, --quiet, --json, --no-color, --config-file)
15
15
  StandardOptions.define(command).action(async (servicePath, options) => {
16
16
  try {
17
- const output = new (await import('../lib/shared/utils/output-formatter.js')).OutputFormatter(options);
17
+ const output = new (await import('../../lib/shared/utils/output-formatter.js')).OutputFormatter(options);
18
18
  const configManager = new ServiceConfigManager({
19
19
  verbose: options.verbose,
20
20
  quiet: options.quiet,
@@ -111,7 +111,7 @@ export function registerAssessCommand(program) {
111
111
  output.success(`šŸ“„ Results exported to: ${mergedOptions.export}`);
112
112
  }
113
113
  } catch (error) {
114
- const output = new (await import('../lib/shared/utils/output-formatter.js')).OutputFormatter(options || {});
114
+ const output = new (await import('../../lib/shared/utils/output-formatter.js')).OutputFormatter(options || {});
115
115
  output.error(`Assessment failed: ${error.message}`);
116
116
  if (process.env.DEBUG) {
117
117
  output.debug(error.stack);
@@ -6,16 +6,16 @@
6
6
  */
7
7
 
8
8
  import chalk from 'chalk';
9
- import { ServiceOrchestrator } from '../service-management/ServiceOrchestrator.js';
10
- import { StandardOptions } from '../lib/shared/utils/cli-options.js';
11
- import { ConfigLoader } from '../lib/shared/utils/config-loader.js';
9
+ import { Clodo } from '../../src/simple-api.js';
10
+ import { StandardOptions } from '../../lib/shared/utils/cli-options.js';
11
+ import { ConfigLoader } from '../../lib/shared/utils/config-loader.js';
12
12
  export function registerCreateCommand(program) {
13
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
14
 
15
15
  // Add standard options (--verbose, --quiet, --json, --no-color, --config-file)
16
16
  StandardOptions.define(command).action(async options => {
17
17
  try {
18
- const output = new (await import('../lib/shared/utils/output-formatter.js')).OutputFormatter(options);
18
+ const output = new (await import('../../lib/shared/utils/output-formatter.js')).OutputFormatter(options);
19
19
  const configLoader = new ConfigLoader({
20
20
  verbose: options.verbose,
21
21
  quiet: options.quiet,
@@ -33,40 +33,31 @@ export function registerCreateCommand(program) {
33
33
 
34
34
  // Merge config file defaults with CLI options (CLI takes precedence)
35
35
  const mergedOptions = configLoader.merge(configFileData, options);
36
- const orchestrator = new ServiceOrchestrator({
37
- interactive: !mergedOptions.nonInteractive,
36
+
37
+ // Use simple API for service creation
38
+ const result = await Clodo.createService({
39
+ name: mergedOptions.serviceName,
40
+ type: mergedOptions.serviceType,
41
+ domain: mergedOptions.domainName,
42
+ environment: mergedOptions.environment,
38
43
  outputPath: mergedOptions.outputPath,
39
- templatePath: mergedOptions.templatePath
40
- });
41
- if (mergedOptions.nonInteractive) {
42
- // Validate required parameters for non-interactive mode
43
- const required = ['serviceName', 'domainName', 'cloudflareToken', 'cloudflareAccountId', 'cloudflareZoneId'];
44
- const missing = required.filter(key => !mergedOptions[key]);
45
- if (missing.length > 0) {
46
- output.error(`Missing required parameters: ${missing.join(', ')}`);
47
- output.info('Use --help for parameter details');
48
- process.exit(1);
44
+ interactive: !mergedOptions.nonInteractive,
45
+ credentials: {
46
+ token: mergedOptions.cloudflareToken,
47
+ accountId: mergedOptions.cloudflareAccountId,
48
+ zoneId: mergedOptions.cloudflareZoneId
49
49
  }
50
-
51
- // Convert merged options to core inputs
52
- const coreInputs = {
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
60
- };
61
- await orchestrator.runNonInteractive(coreInputs);
50
+ });
51
+ if (result.success) {
52
+ output.success(result.message);
62
53
  } else {
63
- await orchestrator.runInteractive();
54
+ output.error('Service creation failed');
55
+ process.exit(1);
64
56
  }
65
- output.success('Service creation completed successfully!');
66
57
  output.section('Next steps');
67
58
  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']);
68
59
  } catch (error) {
69
- const output = new (await import('../lib/shared/utils/output-formatter.js')).OutputFormatter(options || {});
60
+ const output = new (await import('../../lib/shared/utils/output-formatter.js')).OutputFormatter(options || {});
70
61
  output.error(`Service creation failed: ${error.message}`);
71
62
  if (error.details) {
72
63
  output.warning(`Details: ${error.details}`);
@@ -1,42 +1,16 @@
1
- /**
2
- * Deploy Command - Smart minimal input deployment with service detection
3
- *
4
- * Input Strategy: SMART MINIMAL WITH DOMAIN ROUTING
5
- * - Detects Clodo services OR legacy services (wrangler.toml)
6
- * - Supports multiple manifest locations
7
- * - Uses DomainRouter for intelligent domain selection
8
- * - Gathers credentials smartly: env vars → flags → interactive collection with auto-fetch
9
- * - Validates token and fetches account ID & zone ID from Cloudflare
10
- * - REFACTORED (Task 3.2): Integrates with MultiDomainOrchestrator for full deployment orchestration
11
- * - REFACTORED (UX): Modularized with helper functions for better maintainability
12
- */
13
-
14
1
  import chalk from 'chalk';
15
- import { resolve, join } from 'path';
16
- import { existsSync } from 'fs';
17
- import { ManifestLoader } from '../lib/shared/config/manifest-loader.js';
18
- import { CloudflareServiceValidator } from '../lib/shared/config/cloudflare-service-validator.js';
19
- import { DeploymentCredentialCollector } from '../lib/shared/deployment/credential-collector.js';
20
- import { StandardOptions } from '../lib/shared/utils/cli-options.js';
21
- import { ConfigLoader } from '../lib/shared/utils/config-loader.js';
22
- import { ServiceConfigManager } from '../lib/shared/utils/service-config-manager.js';
23
- import { DomainRouter } from '../lib/shared/routing/domain-router.js';
24
- import { MultiDomainOrchestrator } from '../orchestration/multi-domain-orchestrator.js';
25
-
26
- // Import modular helpers
27
- import { detectExistingResources, displayDeploymentPlan } from './helpers/resource-detection.js';
28
- import { confirmDeployment, displayDeploymentResults, displayNextSteps } from './helpers/deployment-ui.js';
29
- import { runPostDeploymentVerification } from './helpers/deployment-verification.js';
30
- import { handleDeploymentError } from './helpers/error-recovery.js';
2
+ import { Clodo } from '../../src/simple-api.js';
3
+ import { StandardOptions } from '../../lib/shared/utils/cli-options.js';
4
+ import { ConfigLoader } from '../../lib/shared/utils/config-loader.js';
31
5
  export function registerDeployCommand(program) {
32
6
  const command = program.command('deploy').description('Deploy a Clodo service with smart credential handling and domain selection')
33
7
  // Cloudflare-specific options
34
- .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('--development', 'Deploy to development environment (shorthand for --environment development)').option('--staging', 'Deploy to staging environment (shorthand for --environment staging)').option('--production', 'Deploy to production environment (shorthand for --environment production)').option('--all-domains', 'Deploy to all configured domains (ignores --domain flag)').option('--dry-run', 'Simulate deployment without making changes').option('-y, --yes', 'Skip confirmation prompts (for CI/CD)').option('--service-path <path>', 'Path to service directory', '.').option('--show-config-sources', 'Display all configuration sources and merged result');
8
+ .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('--development', 'Deploy to development environment (shorthand for --environment development)').option('--staging', 'Deploy to staging environment (shorthand for --environment staging)').option('--production', 'Deploy to production environment (shorthand for --environment production)').option('--dry-run', 'Simulate deployment without making changes').option('-y, --yes', 'Skip confirmation prompts (for CI/CD)').option('--service-path <path>', 'Path to service directory', '.');
35
9
 
36
10
  // Add standard options (--verbose, --quiet, --json, --no-color, --config-file)
37
11
  StandardOptions.define(command).action(async options => {
38
12
  try {
39
- const output = new (await import('../lib/shared/utils/output-formatter.js')).OutputFormatter(options);
13
+ const output = new (await import('../../lib/shared/utils/output-formatter.js')).OutputFormatter(options);
40
14
  const configLoader = new ConfigLoader({
41
15
  verbose: options.verbose,
42
16
  quiet: options.quiet,
@@ -61,342 +35,33 @@ export function registerDeployCommand(program) {
61
35
  }
62
36
  }
63
37
 
64
- // Use ServiceConfigManager for standardized config loading
65
- const configManager = new ServiceConfigManager({
66
- verbose: options.verbose,
67
- quiet: options.quiet,
68
- json: options.json,
69
- showSources: options.showConfigSources
70
- });
71
-
72
- // For deploy command, we use current directory as service path
73
- const deployServicePath = resolve(options.servicePath || '.');
74
-
75
- // Load and merge all configurations
76
- const mergedOptions = await configManager.loadServiceConfig(deployServicePath, {
77
- ...options,
78
- configFile: options.configFile // Pass through the config file option
79
- }, {
80
- token: null,
81
- accountId: null,
82
- zoneId: null,
83
- domain: null,
84
- environment: 'production',
85
- allDomains: false,
86
- dryRun: false,
87
- yes: false,
88
- servicePath: '.'
89
- });
90
- output.info('šŸš€ Clodo Service Deployment');
91
-
92
- // Step 1: Load and validate service configuration
93
- const servicePath = resolve(mergedOptions.servicePath || deployServicePath);
94
- const serviceConfig = await ManifestLoader.loadAndValidateCloudflareService(servicePath);
95
- if (!serviceConfig.manifest) {
96
- if (serviceConfig.error === 'NOT_A_CLOUDFLARE_SERVICE') {
97
- ManifestLoader.printNotCloudflareServiceError(servicePath);
98
- } else if (serviceConfig.error === 'CLOUDFLARE_SERVICE_INVALID') {
99
- // Pass false because we're validating a detected service, not a Clodo manifest
100
- ManifestLoader.printValidationErrors(serviceConfig.validationResult, false);
101
- }
102
- process.exit(1);
103
- }
104
-
105
- // Print service info and validation results
106
- if (serviceConfig.validationResult) {
107
- CloudflareServiceValidator.printValidationReport(serviceConfig.validationResult.validation);
108
- }
109
- ManifestLoader.printManifestInfo(serviceConfig.manifest);
110
- console.log(chalk.blue(`ā„¹ļø Configuration loaded from: ${serviceConfig.foundAt}`));
38
+ // Merge config file defaults with CLI options (CLI takes precedence)
39
+ const mergedOptions = configLoader.merge(configFileData, options);
111
40
 
112
- // Step 2: Smart credential gathering with interactive collection
113
- // Uses DeploymentCredentialCollector which:
114
- // - Checks flags and env vars first
115
- // - Prompts for API token if needed
116
- // - Validates token and auto-fetches account ID & zone ID
117
- // - Caches credentials for future use
118
- const credentialCollector = new DeploymentCredentialCollector({
119
- servicePath: servicePath,
120
- quiet: mergedOptions.quiet
121
- });
122
- let credentials;
123
- try {
124
- credentials = await credentialCollector.collectCredentials({
41
+ // Use simple API for deployment
42
+ const result = await Clodo.deploy({
43
+ servicePath: mergedOptions.servicePath || '.',
44
+ environment: mergedOptions.environment || 'production',
45
+ domain: mergedOptions.domain,
46
+ dryRun: mergedOptions.dryRun || false,
47
+ credentials: {
125
48
  token: mergedOptions.token,
126
49
  accountId: mergedOptions.accountId,
127
50
  zoneId: mergedOptions.zoneId
128
- });
129
- } finally {
130
- credentialCollector.cleanup();
131
- }
132
-
133
- // Step 3: Initialize DomainRouter for intelligent domain selection
134
- // REFACTORED (Task 3.2): Use DomainRouter for domain detection and selection
135
- console.log(chalk.cyan('\nšŸ—ŗļø Detecting available domains...\n'));
136
- const router = new DomainRouter({
137
- environment: mergedOptions.environment || 'production',
138
- verbose: options.verbose,
139
- configPath: options.configPath,
140
- disableOrchestrator: false,
141
- // We'll use the orchestrator for deployment
142
- orchestratorOptions: {
143
- dryRun: options.dryRun || false,
144
- cloudflareToken: credentials.token,
145
- cloudflareAccountId: credentials.accountId
146
- }
147
- });
148
-
149
- // Detect domains from manifest
150
- let detectedDomains = [];
151
- const manifest = serviceConfig.manifest;
152
- const config = manifest.deployment || manifest.configuration || {};
153
- const domainsConfig = config.domains || [];
154
- if (Array.isArray(domainsConfig) && domainsConfig.length > 0) {
155
- // Handle both array of strings and array of objects
156
- detectedDomains = domainsConfig.map(d => {
157
- if (typeof d === 'string') return d;
158
- if (typeof d === 'object' && d.name) return d.name;
159
- return null;
160
- }).filter(d => d !== null);
161
- }
162
-
163
- // If no domains in manifest but we have a selected zone from credentials, use that
164
- if (detectedDomains.length === 0 && credentials.zoneName) {
165
- detectedDomains = [credentials.zoneName];
166
- if (!options.quiet) {
167
- console.log(chalk.blue(`ā„¹ļø Using selected Cloudflare domain: ${credentials.zoneName}`));
168
- }
169
- }
170
-
171
- // If still no domains and it's a detected CF service, use default
172
- if (detectedDomains.length === 0 && manifest._source === 'cloudflare-service-detected') {
173
- // For detected CF services, use default
174
- detectedDomains = ['workers.cloudflare.com'];
175
- if (!options.quiet) {
176
- console.log(chalk.blue(`ā„¹ļø No custom domains configured, using default: workers.cloudflare.com`));
177
- }
178
- } else if (detectedDomains.length > 0 && !options.quiet && !credentials.zoneName) {
179
- console.log(chalk.blue(`ā„¹ļø Found ${detectedDomains.length} configured domain(s) in manifest`));
180
- }
181
-
182
- // Domain selection: check CLI flag first, then prompt user
183
- let selectedDomain = mergedOptions.domain;
184
- if (selectedDomain && !options.quiet) {
185
- console.log(chalk.blue(`ā„¹ļø Using domain from --domain flag: ${selectedDomain}`));
186
- }
187
- if (!selectedDomain && detectedDomains.length > 0) {
188
- if (detectedDomains.length === 1) {
189
- // Only one domain, use it directly
190
- selectedDomain = detectedDomains[0];
191
- if (!options.quiet) {
192
- console.log(chalk.green(`āœ“ Auto-selected only available domain: ${selectedDomain}`));
193
- }
194
- } else {
195
- // Multiple domains available - let user choose
196
- console.log(chalk.cyan('šŸ“ Available domains:'));
197
- detectedDomains.forEach((d, i) => {
198
- console.log(chalk.white(` ${i + 1}) ${d}`));
199
- });
200
-
201
- // If running interactively, prompt user
202
- if (process.stdin.isTTY) {
203
- const {
204
- createPromptModule
205
- } = await import('inquirer');
206
- const prompt = createPromptModule();
207
- const response = await prompt([{
208
- type: 'list',
209
- name: 'selectedDomain',
210
- message: 'Select domain to deploy to:',
211
- choices: detectedDomains,
212
- default: detectedDomains[0]
213
- }]);
214
- selectedDomain = response.selectedDomain;
215
- } else {
216
- // Non-interactive mode: use first domain
217
- selectedDomain = detectedDomains[0];
218
- console.log(chalk.yellow(`āš ļø Non-interactive mode: using first domain: ${selectedDomain}`));
219
- }
220
- }
221
- }
222
- if (!selectedDomain) {
223
- if (!options.quiet) {
224
- console.error(chalk.yellow('āš ļø No domain configured for deployment'));
225
- console.error(chalk.gray('For Clodo services: add deployment.domains in clodo-service-manifest.json'));
226
- console.error(chalk.gray('For detected CF services: define routes in wrangler.toml'));
227
51
  }
228
- process.exit(1);
229
- }
230
-
231
- // Step 4: Validate domain configuration
232
- console.log(chalk.cyan('\nšŸ” Validating domain configuration...\n'));
233
- const validation = router.validateConfiguration({
234
- domains: [selectedDomain],
235
- environment: mergedOptions.environment || 'production'
236
- });
237
- if (!validation.valid) {
238
- console.error(chalk.red('āŒ Configuration validation failed:'));
239
- validation.errors.forEach(err => {
240
- console.error(chalk.yellow(` • ${err}`));
241
- });
242
- process.exit(1);
243
- }
244
- if (validation.warnings && validation.warnings.length > 0) {
245
- console.log(chalk.yellow('āš ļø Configuration warnings:'));
246
- validation.warnings.forEach(warn => {
247
- console.log(chalk.gray(` • ${warn}`));
248
- });
249
- }
250
-
251
- // Extract service metadata
252
- const serviceName = manifest.serviceName || 'unknown-service';
253
- const serviceType = manifest.serviceType || 'generic';
254
-
255
- // Step 5: Detect existing resources and display deployment plan
256
- const {
257
- existingWorker,
258
- existingDatabases,
259
- resourceDetectionFailed
260
- } = await detectExistingResources(serviceName, manifest, credentials, output, options.verbose);
261
- displayDeploymentPlan({
262
- serviceName,
263
- serviceType,
264
- selectedDomain,
265
- environment: mergedOptions.environment,
266
- credentials,
267
- dryRun: options.dryRun,
268
- existingWorker,
269
- existingDatabases,
270
- resourceDetectionFailed,
271
- manifest
272
52
  });
273
-
274
- // Step 6: Get user confirmation
275
- const confirmed = await confirmDeployment(options);
276
- if (!confirmed) {
277
- process.exit(0);
278
- }
279
-
280
- // Step 5: Initialize MultiDomainOrchestrator for deployment
281
- // REFACTORED (Task 3.2): Direct orchestrator integration instead of external deployer
282
- console.log(chalk.cyan('\nāš™ļø Initializing orchestration system...\n'));
283
-
284
- // Determine wrangler config path based on selected domain/zone
285
- // For multi-customer deployments, use customer-specific config if available
286
- let wranglerConfigPath;
287
- const configDir = join(servicePath, 'config');
288
-
289
- // Map domain to config file (customize this mapping for your setup)
290
- if (selectedDomain === 'clodo.dev' || credentials.cloudflareSettings?.zoneName === 'clodo.dev') {
291
- // Use config/wrangler.toml for clodo.dev domain
292
- const clodoConfigPath = join(configDir, 'wrangler.toml');
293
- if (existsSync(clodoConfigPath)) {
294
- wranglerConfigPath = clodoConfigPath;
295
- console.log(chalk.green(`āœ“ Using clodo.dev config: ${clodoConfigPath}`));
296
- }
297
- }
298
- // Add more domain mappings as needed:
299
- // else if (selectedDomain === 'customer2.com') {
300
- // wranglerConfigPath = path.join(configDir, 'customers', 'customer2-wrangler.toml');
301
- // }
302
-
303
- // If no specific config found, use default (root wrangler.toml)
304
- if (!wranglerConfigPath) {
305
- console.log(chalk.yellow(`ℹ Using default wrangler.toml from service root`));
306
- }
307
- const orchestrator = new MultiDomainOrchestrator({
308
- domains: [selectedDomain],
309
- environment: mergedOptions.environment || 'production',
310
- dryRun: options.dryRun || false,
311
- skipTests: false,
312
- parallelDeployments: 1,
313
- // Single domain in this flow
314
- servicePath: servicePath,
315
- serviceName: serviceName,
316
- // Pass service name for custom domain construction
317
- wranglerConfigPath: wranglerConfigPath,
318
- // Pass custom config path if found
319
- // Use cloudflareSettings object for complete zone-specific configuration
320
- cloudflareSettings: credentials.cloudflareSettings,
321
- enablePersistence: !options.dryRun,
322
- rollbackEnabled: !options.dryRun,
323
- verbose: options.verbose
324
- });
325
- try {
326
- await orchestrator.initialize();
327
- } catch (err) {
328
- console.error(chalk.red('āŒ Failed to initialize orchestrator'));
329
- console.error(chalk.yellow(`Error: ${err.message}`));
330
- if (process.env.DEBUG) {
331
- console.error(chalk.gray(err.stack));
53
+ if (result.success) {
54
+ output.success(result.message);
55
+ if (result.deployedDomains && result.deployedDomains.length > 0) {
56
+ output.info(`Deployed to domains: ${result.deployedDomains.join(', ')}`);
332
57
  }
58
+ } else {
59
+ output.error('Deployment failed');
333
60
  process.exit(1);
334
61
  }
335
-
336
- // Step 7: Execute deployment via orchestrator
337
- console.log(chalk.cyan('šŸš€ Starting deployment via orchestrator...\n'));
338
- let result;
339
- try {
340
- result = await orchestrator.deploySingleDomain(selectedDomain, {
341
- manifest: manifest,
342
- credentials: credentials,
343
- dryRun: options.dryRun,
344
- environment: mergedOptions.environment || 'production'
345
- });
346
- } catch (deployError) {
347
- await handleDeploymentError(deployError, command, options);
348
- }
349
-
350
- // Step 8: Display deployment results
351
- displayDeploymentResults({
352
- result,
353
- serviceName,
354
- serviceType,
355
- selectedDomain,
356
- environment: mergedOptions.environment
357
- });
358
-
359
- // Step 9: Run post-deployment verification and health check
360
- await runPostDeploymentVerification(serviceName, result, credentials, {
361
- ...options,
362
- serviceName,
363
- customDomain: selectedDomain !== 'workers.cloudflare.com' ? selectedDomain : null
364
- });
365
-
366
- // Step 10: Display next steps
367
- displayNextSteps({
368
- result,
369
- selectedDomain,
370
- serviceName,
371
- dryRun: options.dryRun
372
- });
373
- if (process.env.DEBUG && result.details) {
374
- console.log(chalk.gray('šŸ“‹ Full Result:'));
375
- console.log(chalk.gray(JSON.stringify(result, null, 2)));
376
- }
377
62
  } catch (error) {
378
- console.error(chalk.red(`\nāŒ Deployment failed: ${error.message}`));
379
- if (error.message.includes('credentials') || error.message.includes('auth')) {
380
- console.error(chalk.yellow('\nšŸ’” Credential Issue:'));
381
- console.error(chalk.white(' Check your API token, account ID, and zone ID'));
382
- console.error(chalk.white(' Visit: https://dash.cloudflare.com/profile/api-tokens'));
383
- }
384
- if (error.message.includes('domain') || error.message.includes('zone')) {
385
- console.error(chalk.yellow('\nšŸ’” Domain Issue:'));
386
- console.error(chalk.white(' Verify domain exists in Cloudflare'));
387
- console.error(chalk.white(' Check API token has zone:read permissions'));
388
- }
389
- if (error.message.includes('orchestration') || error.message.includes('initialization')) {
390
- console.error(chalk.yellow('\nšŸ’” Orchestration Issue:'));
391
- console.error(chalk.white(' Check MultiDomainOrchestrator configuration'));
392
- console.error(chalk.white(' Verify all modular components loaded correctly'));
393
- }
394
- if (process.env.DEBUG) {
395
- console.error(chalk.gray('\nFull Stack Trace:'));
396
- console.error(chalk.gray(error.stack));
397
- } else {
398
- console.error(chalk.gray('Run with DEBUG=1 for full stack trace'));
399
- }
63
+ const output = new (await import('../../lib/shared/utils/output-formatter.js')).OutputFormatter(options || {});
64
+ output.error(`Deployment failed: ${error.message}`);
400
65
  process.exit(1);
401
66
  }
402
67
  });