@tamyla/clodo-framework 3.1.23 → 3.1.25

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 (49) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cli/clodo-simple.js +111 -0
  3. package/dist/cli/commands/assess.js +29 -20
  4. package/dist/cli/commands/create.js +22 -31
  5. package/dist/cli/commands/deploy.js +20 -333
  6. package/dist/cli/commands/diagnose.js +26 -26
  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 +46 -19
  12. package/dist/cli/commands/validate.js +24 -34
  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/config/manifest-loader.js +3 -3
  23. package/dist/lib/shared/deployment/credential-collector.js +1 -1
  24. package/dist/lib/shared/deployment/index.js +2 -2
  25. package/dist/lib/shared/deployment/rollback-manager.js +1 -1
  26. package/dist/lib/shared/deployment/utilities/d1-error-recovery.js +1 -1
  27. package/dist/lib/shared/deployment/validator.js +1 -1
  28. package/dist/lib/shared/deployment/workflows/interactive-database-workflow.js +1 -1
  29. package/dist/lib/shared/monitoring/health-checker.js +2 -2
  30. package/dist/lib/shared/routing/domain-router.js +1 -1
  31. package/dist/lib/shared/utils/config-loader.js +47 -11
  32. package/dist/lib/shared/utils/service-config-manager.js +227 -0
  33. package/dist/lib/shared/validation/ValidationRegistry.js +1 -1
  34. package/dist/orchestration/cross-domain-coordinator.js +5 -5
  35. package/dist/orchestration/multi-domain-orchestrator.js +51 -0
  36. package/dist/security/index.js +2 -2
  37. package/dist/service-management/ConfirmationEngine.js +1 -1
  38. package/dist/service-management/ErrorTracker.js +1 -1
  39. package/dist/service-management/InputCollector.js +1 -1
  40. package/dist/service-management/ServiceOrchestrator.js +120 -2
  41. package/dist/service-management/generators/testing/UnitTestsGenerator.js +4 -4
  42. package/dist/service-management/handlers/ValidationHandler.js +22 -9
  43. package/dist/simple-api.js +94 -0
  44. package/dist/utils/cloudflare/ops.js +1 -1
  45. package/dist/utils/file-manager.js +1 -1
  46. package/dist/utils/formatters.js +1 -1
  47. package/dist/utils/logger.js +1 -1
  48. package/dist/worker/integration.js +1 -1
  49. package/package.json +2 -1
@@ -1,49 +1,39 @@
1
- /**
2
- * Validate Command - Validate an existing service configuration
3
- */
4
-
5
1
  import chalk from 'chalk';
6
- import { ServiceOrchestrator } from '../service-management/ServiceOrchestrator.js';
7
- import { StandardOptions } from '../lib/shared/utils/cli-options.js';
8
- import { ConfigLoader } from '../lib/shared/utils/config-loader.js';
2
+ import { Clodo } from '../../src/simple-api.js';
3
+ import { StandardOptions } from '../../lib/shared/utils/cli-options.js';
9
4
  export function registerValidateCommand(program) {
10
- const command = program.command('validate <service-path>').description('Validate an existing service configuration').option('--deep-scan', 'Run comprehensive validation checks').option('--export-report <file>', 'Export validation report to JSON file');
5
+ const command = program.command('validate <service-path>').description('Validate an existing service configuration').option('--export-report <file>', 'Export validation report to JSON file');
11
6
 
12
7
  // Add standard options (--verbose, --quiet, --json, --no-color, --config-file)
13
8
  StandardOptions.define(command).action(async (servicePath, options) => {
14
9
  try {
15
- const output = new (await import('../lib/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
- });
10
+ const output = new (await import('../../lib/shared/utils/output-formatter.js')).OutputFormatter(options);
21
11
 
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
- // Merge config file defaults with CLI options (CLI takes precedence)
32
- const mergedOptions = configLoader.merge(configFileData, options);
33
- const orchestrator = new ServiceOrchestrator();
34
- const result = await orchestrator.validateService(servicePath, {
35
- deepScan: mergedOptions.deepScan,
36
- exportReport: mergedOptions.exportReport
12
+ // Use simple API for validation
13
+ const result = await Clodo.validate({
14
+ servicePath: servicePath || '.',
15
+ exportReport: options.exportReport
37
16
  });
38
- if (result.valid) {
39
- output.success('Service configuration is valid');
17
+ if (result.success) {
18
+ output.success(result.message);
19
+ if (result.issues && result.issues.length > 0) {
20
+ output.warning(`Found ${result.issues.length} issues:`);
21
+ result.issues.forEach(issue => {
22
+ output.info(` - ${issue}`);
23
+ });
24
+ }
40
25
  } else {
41
- output.error('Service configuration has issues:');
42
- output.list(result.issues || []);
26
+ output.error(result.message);
27
+ if (result.issues && result.issues.length > 0) {
28
+ output.info('Issues found:');
29
+ result.issues.forEach(issue => {
30
+ output.info(` - ${issue}`);
31
+ });
32
+ }
43
33
  process.exit(1);
44
34
  }
45
35
  } catch (error) {
46
- const output = new (await import('../lib/shared/utils/output-formatter.js')).OutputFormatter(options || {});
36
+ const output = new (await import('../../lib/shared/utils/output-formatter.js')).OutputFormatter(options || {});
47
37
  output.error(`Validation failed: ${error.message}`);
48
38
  process.exit(1);
49
39
  }
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { SecurityCLI } from '../security/SecurityCLI.js';
2
+ import { SecurityCLI } from '../src/security/SecurityCLI.js';
3
3
  const command = process.argv[2];
4
4
  const args = process.argv.slice(3);
5
5
  async function main() {
@@ -2,7 +2,7 @@ import { spawn } from 'child_process';
2
2
  import { execSync } from 'child_process';
3
3
  import fs from 'fs';
4
4
  import path from 'path';
5
- import { WranglerD1Manager } from '../lib/database/wrangler-d1-manager.js';
5
+ import { WranglerD1Manager } from '../../lib/database/wrangler-d1-manager.js';
6
6
 
7
7
  /**
8
8
  * WranglerDeployer - Executes actual Cloudflare Workers deployments using wrangler CLI
package/dist/index.js CHANGED
@@ -6,10 +6,13 @@ export * from './worker/index.js';
6
6
  export * from './utils/index.js';
7
7
  export * from './orchestration/index.js';
8
8
 
9
+ // Simple API - Recommended for most users
10
+ export { default as Clodo, createService, deploy, validate, initialize, getInfo } from './simple-api.js';
11
+
9
12
  // Core framework classes and utilities
10
13
  export { FeatureFlagManager } from './config/features.js';
11
14
  export { createDomainConfigSchema, validateDomainConfig, createDefaultDomainConfig } from './utils/domain-config.js';
12
- export { initializeService, createFeatureGuard } from './worker/integration.js';
15
+ export { initializeService } from './worker/integration.js';
13
16
 
14
17
  // Core data and schema components
15
18
  export * from './services/GenericDataService.js';
@@ -25,7 +28,7 @@ export { WranglerDeployer } from './deployment/wrangler-deployer.js';
25
28
  export * from './security/index.js';
26
29
 
27
30
  // Service management components
28
- export { ServiceCreator, createService } from './service-management/ServiceCreator.js';
31
+ export { ServiceCreator } from './service-management/ServiceCreator.js';
29
32
  export { ServiceOrchestrator } from './service-management/ServiceOrchestrator.js';
30
33
  export { InputHandler } from './service-management/handlers/InputHandler.js';
31
34
  export { ConfirmationHandler } from './service-management/handlers/ConfirmationHandler.js';
@@ -6,8 +6,8 @@
6
6
 
7
7
  import { readFileSync, writeFileSync } from 'fs';
8
8
  import { deploySecret, runMigrations, checkHealth } from '../../shared/cloudflare/ops.js';
9
- import { WranglerDeployer } from '../deployment/wrangler-deployer.js';
10
- import { DeploymentDatabaseManager } from '../database/deployment-db-manager.js';
9
+ import { WranglerDeployer } from '../../deployment/wrangler-deployer.js';
10
+ import { DeploymentDatabaseManager } from '../../database/deployment-db-manager.js';
11
11
  import { DeploymentConfiguration } from './DeploymentConfiguration.js';
12
12
 
13
13
  /**
@@ -3,8 +3,8 @@
3
3
  * Handles environment configuration, domain mapping, deployment mode selection, and cross-domain coordination
4
4
  */
5
5
 
6
- import { MultiDomainOrchestrator } from '../../orchestration/multi-domain-orchestrator.js';
7
- import { CrossDomainCoordinator } from '../../orchestration/cross-domain-coordinator.js';
6
+ import { MultiDomainOrchestrator } from '../../../src/orchestration/multi-domain-orchestrator.js';
7
+ import { CrossDomainCoordinator } from '../../../src/orchestration/cross-domain-coordinator.js';
8
8
  import { DomainDiscovery } from '../../shared/cloudflare/domain-discovery.js';
9
9
  import { askChoice, askUser, askYesNo, DeploymentInteractiveUtils } from '../../shared/utils/interactive-utils.js';
10
10
  export class EnvironmentManager {
@@ -15,7 +15,7 @@ import { promisify } from 'util';
15
15
  import { exec } from 'child_process';
16
16
  import { askChoice, askYesNo } from '../utils/interactive-prompts.js';
17
17
  import { DomainDiscovery } from './domain-discovery.js';
18
- import { MultiDomainOrchestrator } from '../../orchestration/multi-domain-orchestrator.js';
18
+ import { MultiDomainOrchestrator } from '../../../src/orchestration/multi-domain-orchestrator.js';
19
19
  import { getCommandConfig } from '../config/command-config-manager.js';
20
20
  import { CloudflareTokenManager } from '../security/api-token-manager.js';
21
21
  const execAsync = promisify(exec);
@@ -242,7 +242,7 @@ export async function listDatabases(options = {}) {
242
242
  if (apiToken && accountId) {
243
243
  const {
244
244
  CloudflareAPI
245
- } = await import('../../utils/cloudflare/api.js');
245
+ } = await import('../../../src/utils/cloudflare/api.js');
246
246
  const cf = new CloudflareAPI(apiToken);
247
247
  return await cf.listD1Databases(accountId);
248
248
  }
@@ -267,7 +267,7 @@ export async function databaseExists(databaseName, options = {}) {
267
267
  if (apiToken && accountId) {
268
268
  const {
269
269
  CloudflareAPI
270
- } = await import('../../utils/cloudflare/api.js');
270
+ } = await import('../../../src/utils/cloudflare/api.js');
271
271
  const cf = new CloudflareAPI(apiToken);
272
272
  return await cf.d1DatabaseExists(accountId, databaseName);
273
273
  }
@@ -290,7 +290,7 @@ export async function createDatabase(name, options = {}) {
290
290
  if (apiToken && accountId) {
291
291
  const {
292
292
  CloudflareAPI
293
- } = await import('../../utils/cloudflare/api.js');
293
+ } = await import('../../../src/utils/cloudflare/api.js');
294
294
  const cf = new CloudflareAPI(apiToken);
295
295
  const result = await cf.createD1Database(accountId, name);
296
296
  return result.uuid; // Return UUID to match CLI behavior
@@ -405,7 +405,7 @@ export async function getDatabaseId(databaseName, options = {}) {
405
405
  if (apiToken && accountId) {
406
406
  const {
407
407
  CloudflareAPI
408
- } = await import('../../utils/cloudflare/api.js');
408
+ } = await import('../../../src/utils/cloudflare/api.js');
409
409
  const cf = new CloudflareAPI(apiToken);
410
410
  const db = await cf.getD1Database(accountId, databaseName);
411
411
  return db?.uuid || null;
@@ -14,7 +14,7 @@ import { fileURLToPath } from 'url';
14
14
  const __filename = fileURLToPath(import.meta.url);
15
15
  const __dirname = dirname(__filename);
16
16
  // Navigate up to framework root, then to config directory
17
- const FRAMEWORK_CONFIG_PATH = join(__dirname, '../../config/validation-config.json');
17
+ const FRAMEWORK_CONFIG_PATH = join(__dirname, '../../../config/validation-config.json');
18
18
  export class CommandConfigManager {
19
19
  constructor(configPath = null) {
20
20
  this.configPath = configPath || join(process.cwd(), 'validation-config.json');
@@ -7,7 +7,7 @@
7
7
  export { ConfigCache } from './cache.js';
8
8
  export { ConfigManager } from './manager.js';
9
9
  export { CommandConfigManager } from './command-config-manager.js';
10
- export { CustomerConfigurationManager } from '../../config/customers.js';
10
+ export { CustomerConfigurationManager } from '../../../config/customers.js';
11
11
 
12
12
  // Phase 3.2 consolidated configuration management
13
13
  export { ConfigurationManager, configManager, isFeatureEnabled, getEnabledFeatures, withFeature, FEATURES, COMMON_FEATURES } from './ConfigurationManager.js';
@@ -276,13 +276,13 @@ export class ManifestLoader {
276
276
  */
277
277
  static printManifestInfo(manifest) {
278
278
  console.log(chalk.cyan('\n📋 Service Configuration:\n'));
279
- console.log(chalk.white(`Service Name: ${chalk.bold(manifest.serviceName)}`));
280
- console.log(chalk.white(`Service Type: ${chalk.bold(manifest.serviceType)}`));
279
+ console.log(chalk.white(`Service Name: ${chalk.bold(manifest.service?.name || manifest.serviceName || 'unknown')}`));
280
+ console.log(chalk.white(`Service Type: ${chalk.bold(manifest.service?.type || manifest.serviceType || 'unknown')}`));
281
281
  console.log(chalk.white(`Source: ${chalk.gray(manifest._source)}`));
282
282
  if (manifest._legacyNote) {
283
283
  console.log(chalk.yellow(`\nℹ️ ${manifest._legacyNote}`));
284
284
  }
285
- if (manifest.deployment.domains) {
285
+ if (manifest.deployment && manifest.deployment.domains) {
286
286
  console.log(chalk.white(`\nDomains:`));
287
287
  manifest.deployment.domains.forEach(domain => {
288
288
  console.log(chalk.gray(` - ${domain.name} (${domain.environment})`));
@@ -11,7 +11,7 @@
11
11
  import chalk from 'chalk';
12
12
  import { askUser, askPassword, askChoice, closePrompts } from '../utils/interactive-prompts.js';
13
13
  import { ApiTokenManager } from '../security/api-token-manager.js';
14
- import { CloudflareAPI } from '../../utils/cloudflare/api.js';
14
+ import { CloudflareAPI } from '../../../src/utils/cloudflare/api.js';
15
15
  export class DeploymentCredentialCollector {
16
16
  constructor(options = {}) {
17
17
  this.servicePath = options.servicePath || '.';
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  export { DeploymentValidator } from './validator.js';
7
- export { MultiDomainOrchestrator } from '../orchestration/multi-domain-orchestrator.js';
8
- export { CrossDomainCoordinator } from '../orchestration/cross-domain-coordinator.js';
7
+ export { MultiDomainOrchestrator } from '../../orchestration/multi-domain-orchestrator.js';
8
+ export { CrossDomainCoordinator } from '../../orchestration/cross-domain-coordinator.js';
9
9
  export { DeploymentAuditor } from './auditor.js';
10
10
  export { RollbackManager } from './rollback-manager.js';
@@ -4,4 +4,4 @@
4
4
  * Re-exports the RollbackManager for deployment operations
5
5
  */
6
6
 
7
- export { RollbackManager } from '../../deployment/rollback-manager.js';
7
+ export { RollbackManager } from '../../../src/deployment/rollback-manager.js';
@@ -42,7 +42,7 @@ export class D1ErrorRecoveryManager {
42
42
  // Import WranglerDeployer for D1 error handling
43
43
  const {
44
44
  WranglerDeployer
45
- } = await import('../../deployment/wrangler-deployer.js');
45
+ } = await import('../../../src/deployment/wrangler-deployer.js');
46
46
  deployer = new WranglerDeployer({
47
47
  cwd: config.cwd || process.cwd(),
48
48
  environment: config.environment
@@ -18,7 +18,7 @@ import { getCommandConfig } from '../config/command-config-manager.js';
18
18
  const __filename = fileURLToPath(import.meta.url);
19
19
  const __dirname = dirname(__filename);
20
20
  // Navigate up to framework root, then to config directory
21
- const FRAMEWORK_CONFIG_PATH = join(__dirname, '../../config/validation-config.json');
21
+ const FRAMEWORK_CONFIG_PATH = join(__dirname, '../../../config/validation-config.json');
22
22
  const execAsync = promisify(exec);
23
23
 
24
24
  /**
@@ -7,7 +7,7 @@
7
7
  * @module interactive-database-workflow
8
8
  */
9
9
 
10
- import { askUser, askYesNo, askChoice } from '../utils/interactive-prompts.js';
10
+ import { askUser, askYesNo, askChoice } from '../../utils/interactive-prompts.js';
11
11
  import { databaseExists, createDatabase, deleteDatabase } from '../../cloudflare/ops.js';
12
12
  import { exec } from 'child_process';
13
13
  import { promisify } from 'util';
@@ -14,7 +14,7 @@ const execAsync = promisify(exec);
14
14
  // Load framework configuration
15
15
  const {
16
16
  frameworkConfig
17
- } = await import('../../utils/framework-config.js');
17
+ } = await import('../../../src/utils/framework-config.js');
18
18
  const timing = frameworkConfig.getTiming();
19
19
  function makeHttpRequest(url, method = 'GET', timeout = 5000) {
20
20
  return new Promise((resolve, reject) => {
@@ -546,7 +546,7 @@ export async function verifyWorkerDeployment(workerName, credentials, options =
546
546
  }
547
547
  const {
548
548
  CloudflareAPI
549
- } = await import('../../utils/cloudflare/api.js');
549
+ } = await import('../../../src/utils/cloudflare/api.js');
550
550
  const cfApi = new CloudflareAPI(credentials.token);
551
551
 
552
552
  // List all workers to find ours
@@ -14,7 +14,7 @@
14
14
 
15
15
  import { existsSync, readFileSync } from 'fs';
16
16
  import { resolve } from 'path';
17
- import { MultiDomainOrchestrator } from '../../orchestration/multi-domain-orchestrator.js';
17
+ import { MultiDomainOrchestrator } from '../../../src/orchestration/multi-domain-orchestrator.js';
18
18
  export class DomainRouter {
19
19
  constructor(options = {}) {
20
20
  this.configPath = options.configPath || './config/domains.json';
@@ -114,8 +114,9 @@ export class ConfigLoader {
114
114
  }
115
115
 
116
116
  /**
117
- * Merge configuration file defaults with CLI options
117
+ * Deep merge configuration file defaults with CLI options
118
118
  * CLI options take precedence over config file defaults
119
+ * Handles nested objects properly (deep merge)
119
120
  * @param {Object} configFile - Configuration loaded from file
120
121
  * @param {Object} cliOptions - Options from command line
121
122
  * @returns {Object} Merged configuration with CLI options taking precedence
@@ -128,22 +129,57 @@ export class ConfigLoader {
128
129
  return configFile || {};
129
130
  }
130
131
 
131
- // Create merged result: start with config file, override with CLI options
132
- const merged = {
133
- ...configFile
134
- };
135
- for (const [key, value] of Object.entries(cliOptions)) {
136
- // Only override with CLI option if it's explicitly provided (not undefined, null, or empty string)
137
- if (value !== undefined && value !== null && value !== '') {
138
- merged[key] = value;
139
- }
140
- }
132
+ // Start with deep copy of config file
133
+ const merged = this.deepClone(configFile);
134
+
135
+ // Deep merge CLI options on top
136
+ this.deepMergeInto(merged, cliOptions);
141
137
  if (this.verbose && !this.quiet) {
142
138
  console.log('📋 Configuration merged: CLI options override file defaults');
143
139
  }
144
140
  return merged;
145
141
  }
146
142
 
143
+ /**
144
+ * Deep clone an object
145
+ */
146
+ deepClone(obj) {
147
+ if (obj === null || typeof obj !== 'object') return obj;
148
+ if (obj instanceof Date) return new Date(obj.getTime());
149
+ if (obj instanceof Array) return obj.map(item => this.deepClone(item));
150
+ if (typeof obj === 'object') {
151
+ const cloned = {};
152
+ for (const key in obj) {
153
+ if (obj.hasOwnProperty(key)) {
154
+ cloned[key] = this.deepClone(obj[key]);
155
+ }
156
+ }
157
+ return cloned;
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Deep merge source into target (modifies target)
163
+ */
164
+ deepMergeInto(target, source) {
165
+ if (!source || typeof source !== 'object') return;
166
+ for (const [key, value] of Object.entries(source)) {
167
+ // Only override if value is explicitly provided (not undefined, null, or empty string)
168
+ if (value !== undefined && value !== null && value !== '') {
169
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
170
+ // Deep merge nested objects
171
+ if (!target[key] || typeof target[key] !== 'object' || Array.isArray(target[key])) {
172
+ target[key] = {};
173
+ }
174
+ this.deepMergeInto(target[key], value);
175
+ } else {
176
+ // Override with source value (including arrays and primitives)
177
+ target[key] = value;
178
+ }
179
+ }
180
+ }
181
+ }
182
+
147
183
  /**
148
184
  * Substitute environment variables in configuration values
149
185
  * Format: ${ENV_VAR_NAME} in strings will be replaced with environment variable values
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Service Config Manager
3
+ * Centralized, reusable configuration loading for CLI commands
4
+ *
5
+ * Features:
6
+ * - Standardized config file discovery across all commands
7
+ * - Proper error handling with user-friendly messages
8
+ * - Deep merging of nested configuration objects
9
+ * - Config validation against schemas
10
+ * - Debugging support with --show-config-sources
11
+ * - Environment variable substitution
12
+ */
13
+
14
+ import { existsSync } from 'fs';
15
+ import path from 'path';
16
+ import { ConfigLoader } from './config-loader.js';
17
+ export class ServiceConfigManager {
18
+ constructor(options = {}) {
19
+ this.configLoader = new ConfigLoader(options);
20
+ this.verbose = options.verbose || false;
21
+ this.quiet = options.quiet || false;
22
+ this.json = options.json || false;
23
+ this.showSources = options.showSources || false;
24
+ }
25
+
26
+ /**
27
+ * Load and merge configuration for a service command
28
+ * @param {string} servicePath - Path to the service directory
29
+ * @param {Object} cliOptions - CLI options from commander
30
+ * @param {Object} commandDefaults - Default options for this command
31
+ * @returns {Object} Merged configuration
32
+ */
33
+ async loadServiceConfig(servicePath, cliOptions = {}, commandDefaults = {}) {
34
+ const sources = [];
35
+
36
+ // 1. Load framework defaults (always available)
37
+ const frameworkConfig = this.loadFrameworkDefaults();
38
+ sources.push({
39
+ name: 'Framework Defaults',
40
+ path: 'config/validation-config.json',
41
+ config: frameworkConfig,
42
+ priority: 4
43
+ });
44
+
45
+ // 2. Load service-specific config (highest priority for customization)
46
+ const serviceConfig = await this.loadServiceConfigFile(servicePath);
47
+ if (serviceConfig) {
48
+ sources.push({
49
+ name: 'Service Config',
50
+ path: path.join(servicePath, 'validation-config.json'),
51
+ config: serviceConfig,
52
+ priority: 1
53
+ });
54
+ }
55
+
56
+ // 3. Load current directory config (for deploy command compatibility)
57
+ const cwdConfig = this.loadCwdConfigFile();
58
+ if (cwdConfig) {
59
+ sources.push({
60
+ name: 'Current Directory Config',
61
+ path: path.join(process.cwd(), 'validation-config.json'),
62
+ config: cwdConfig,
63
+ priority: 2
64
+ });
65
+ }
66
+
67
+ // 4. Load explicitly specified config file
68
+ let explicitConfig = {};
69
+ if (cliOptions.configFile) {
70
+ explicitConfig = this.configLoader.loadSafe(cliOptions.configFile, {});
71
+ if (Object.keys(explicitConfig).length > 0) {
72
+ sources.push({
73
+ name: 'Explicit Config File',
74
+ path: cliOptions.configFile,
75
+ config: explicitConfig,
76
+ priority: 0 // Highest priority
77
+ });
78
+ }
79
+ }
80
+
81
+ // Sort sources by priority (lower number = higher priority)
82
+ sources.sort((a, b) => a.priority - b.priority);
83
+
84
+ // Show config sources if requested
85
+ if (this.showSources) {
86
+ this.displayConfigSources(sources);
87
+ }
88
+
89
+ // Deep merge all configurations
90
+ let mergedConfig = {
91
+ ...commandDefaults
92
+ };
93
+ for (const source of sources) {
94
+ mergedConfig = this.deepMerge(mergedConfig, source.config);
95
+ }
96
+
97
+ // CLI options override everything (except when showing sources)
98
+ if (!this.showSources) {
99
+ mergedConfig = this.deepMerge(mergedConfig, cliOptions);
100
+ }
101
+
102
+ // Substitute environment variables
103
+ mergedConfig = this.configLoader.substituteEnvironmentVariables(mergedConfig);
104
+ return mergedConfig;
105
+ }
106
+
107
+ /**
108
+ * Load framework default configuration
109
+ */
110
+ loadFrameworkDefaults() {
111
+ try {
112
+ const frameworkConfigPath = path.join(process.cwd(), 'config', 'validation-config.json');
113
+ return this.configLoader.loadSafe(frameworkConfigPath, {});
114
+ } catch (error) {
115
+ // Framework config is optional, return empty object
116
+ return {};
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Load service-specific configuration file
122
+ */
123
+ async loadServiceConfigFile(servicePath) {
124
+ if (!servicePath) return null;
125
+ const configPath = path.join(servicePath, 'validation-config.json');
126
+ try {
127
+ if (!existsSync(configPath)) {
128
+ return null; // File doesn't exist, which is fine
129
+ }
130
+ const config = this.configLoader.load(configPath);
131
+ if (this.verbose && !this.quiet) {
132
+ console.log(`✅ Loaded service config: ${configPath}`);
133
+ }
134
+ return config;
135
+ } catch (error) {
136
+ // This is an actual error (file exists but can't be loaded)
137
+ if (!this.quiet) {
138
+ console.warn(`⚠️ Failed to load service config from ${configPath}: ${error.message}`);
139
+ console.warn(` Using framework defaults instead.`);
140
+ }
141
+ return null;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Load configuration from current working directory
147
+ */
148
+ loadCwdConfigFile() {
149
+ const configPath = path.join(process.cwd(), 'validation-config.json');
150
+ try {
151
+ if (!existsSync(configPath)) {
152
+ return null;
153
+ }
154
+ const config = this.configLoader.load(configPath);
155
+ if (this.verbose && !this.quiet) {
156
+ console.log(`✅ Loaded current directory config: ${configPath}`);
157
+ }
158
+ return config;
159
+ } catch (error) {
160
+ if (!this.quiet) {
161
+ console.warn(`⚠️ Failed to load current directory config from ${configPath}: ${error.message}`);
162
+ console.warn(` Using framework defaults instead.`);
163
+ }
164
+ return null;
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Deep merge two configuration objects
170
+ */
171
+ deepMerge(target, source) {
172
+ if (!source || typeof source !== 'object') return target;
173
+ if (!target || typeof target !== 'object') return source;
174
+ const result = {
175
+ ...target
176
+ };
177
+ for (const [key, value] of Object.entries(source)) {
178
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
179
+ // Deep merge nested objects
180
+ result[key] = this.deepMerge(result[key] || {}, value);
181
+ } else {
182
+ // Override with source value (including arrays and primitives)
183
+ result[key] = value;
184
+ }
185
+ }
186
+ return result;
187
+ }
188
+
189
+ /**
190
+ * Display configuration sources for debugging
191
+ */
192
+ displayConfigSources(sources) {
193
+ console.log('\n📋 Configuration Sources (in merge order):');
194
+ for (const source of sources) {
195
+ const status = Object.keys(source.config).length > 0 ? '✅ Loaded' : '❌ Empty';
196
+ console.log(` ${status} ${source.name}: ${source.path}`);
197
+ }
198
+ console.log('\n📋 Final merged configuration:');
199
+ console.log(JSON.stringify(this.deepMerge({}, ...sources.map(s => s.config)), null, 2));
200
+ console.log('');
201
+ }
202
+
203
+ /**
204
+ * Validate service path exists and is accessible
205
+ */
206
+ async validateServicePath(servicePath, orchestrator) {
207
+ if (!servicePath) {
208
+ // Try auto-detection
209
+ servicePath = await orchestrator.detectServicePath();
210
+ if (!servicePath) {
211
+ const error = new Error('No service path provided and could not auto-detect service directory');
212
+ error.suggestions = ['Ensure you are in a service directory or specify --service-path', 'Service directories must contain: package.json, src/config/domains.js, wrangler.toml', 'Try: cd /path/to/your/service && clodo-service <command>'];
213
+ throw error;
214
+ }
215
+ }
216
+
217
+ // Validate the path exists and is accessible
218
+ try {
219
+ await orchestrator.isServiceDirectory(servicePath);
220
+ } catch (error) {
221
+ const validationError = new Error(`Invalid service path: ${servicePath}`);
222
+ validationError.suggestions = ['Ensure the directory exists and is accessible', 'Service directories must contain: package.json, src/config/domains.js, wrangler.toml', `Check permissions for: ${servicePath}`];
223
+ throw validationError;
224
+ }
225
+ return servicePath;
226
+ }
227
+ }
@@ -8,7 +8,7 @@
8
8
  /**
9
9
  * Import validators from src/utils (source of truth)
10
10
  */
11
- import { validateServiceName, validateDomainName, validateCloudflareToken, validateCloudflareId, validateServiceType, validateEnvironment } from '../../utils/validation.js';
11
+ import { validateServiceName, validateDomainName, validateCloudflareToken, validateCloudflareId, validateServiceType, validateEnvironment } from '../../../src/utils/validation.js';
12
12
 
13
13
  /**
14
14
  * Validation Registry - Single source of truth for all validators
@@ -19,13 +19,13 @@
19
19
 
20
20
  import { access } from 'fs/promises';
21
21
  import { MultiDomainOrchestrator } from './multi-domain-orchestrator.js';
22
- import { DeploymentValidator } from '../lib/shared/deployment/validator.js';
23
- import { RollbackManager } from '../lib/shared/deployment/rollback-manager.js';
24
- import { DomainDiscovery } from '../lib/shared/cloudflare/domain-discovery.js';
22
+ import { DeploymentValidator } from '../../lib/shared/deployment/validator.js';
23
+ import { RollbackManager } from '../../lib/shared/deployment/rollback-manager.js';
24
+ import { DomainDiscovery } from '../../lib/shared/cloudflare/domain-discovery.js';
25
25
  import { DatabaseOrchestrator } from '../database/database-orchestrator.js';
26
26
  import { EnhancedSecretManager } from '../utils/deployment/secret-generator.js';
27
- import { ProductionTester } from '../lib/shared/production-tester/index.js';
28
- import { DeploymentAuditor } from '../lib/shared/deployment/auditor.js';
27
+ import { ProductionTester } from '../../lib/shared/production-tester/index.js';
28
+ import { DeploymentAuditor } from '../../lib/shared/deployment/auditor.js';
29
29
  import { ConfigurationCacheManager } from '../utils/deployment/config-cache.js';
30
30
  export class CrossDomainCoordinator {
31
31
  constructor(options = {}) {