@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.
- package/CHANGELOG.md +15 -0
- package/dist/cli/clodo-simple.js +111 -0
- package/dist/cli/commands/assess.js +29 -20
- package/dist/cli/commands/create.js +22 -31
- package/dist/cli/commands/deploy.js +20 -333
- package/dist/cli/commands/diagnose.js +26 -26
- package/dist/cli/commands/helpers/deployment-verification.js +2 -2
- package/dist/cli/commands/helpers/error-recovery.js +1 -1
- package/dist/cli/commands/helpers/resource-detection.js +1 -1
- package/dist/cli/commands/init-config.js +1 -1
- package/dist/cli/commands/update.js +46 -19
- package/dist/cli/commands/validate.js +24 -34
- package/dist/cli/security-cli.js +1 -1
- package/dist/deployment/wrangler-deployer.js +1 -1
- package/dist/index.js +5 -2
- package/dist/lib/deployment/modules/DeploymentOrchestrator.js +2 -2
- package/dist/lib/deployment/modules/EnvironmentManager.js +2 -2
- package/dist/lib/shared/cloudflare/domain-manager.js +1 -1
- package/dist/lib/shared/cloudflare/ops.js +4 -4
- package/dist/lib/shared/config/command-config-manager.js +1 -1
- package/dist/lib/shared/config/index.js +1 -1
- package/dist/lib/shared/config/manifest-loader.js +3 -3
- package/dist/lib/shared/deployment/credential-collector.js +1 -1
- package/dist/lib/shared/deployment/index.js +2 -2
- package/dist/lib/shared/deployment/rollback-manager.js +1 -1
- package/dist/lib/shared/deployment/utilities/d1-error-recovery.js +1 -1
- package/dist/lib/shared/deployment/validator.js +1 -1
- package/dist/lib/shared/deployment/workflows/interactive-database-workflow.js +1 -1
- package/dist/lib/shared/monitoring/health-checker.js +2 -2
- package/dist/lib/shared/routing/domain-router.js +1 -1
- package/dist/lib/shared/utils/config-loader.js +47 -11
- package/dist/lib/shared/utils/service-config-manager.js +227 -0
- package/dist/lib/shared/validation/ValidationRegistry.js +1 -1
- package/dist/orchestration/cross-domain-coordinator.js +5 -5
- package/dist/orchestration/multi-domain-orchestrator.js +51 -0
- package/dist/security/index.js +2 -2
- package/dist/service-management/ConfirmationEngine.js +1 -1
- package/dist/service-management/ErrorTracker.js +1 -1
- package/dist/service-management/InputCollector.js +1 -1
- package/dist/service-management/ServiceOrchestrator.js +120 -2
- package/dist/service-management/generators/testing/UnitTestsGenerator.js +4 -4
- package/dist/service-management/handlers/ValidationHandler.js +22 -9
- package/dist/simple-api.js +94 -0
- package/dist/utils/cloudflare/ops.js +1 -1
- package/dist/utils/file-manager.js +1 -1
- package/dist/utils/formatters.js +1 -1
- package/dist/utils/logger.js +1 -1
- package/dist/worker/integration.js +1 -1
- 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 {
|
|
7
|
-
import { StandardOptions } from '
|
|
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('--
|
|
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('
|
|
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
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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.
|
|
39
|
-
output.success(
|
|
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(
|
|
42
|
-
|
|
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('
|
|
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
|
}
|
package/dist/cli/security-cli.js
CHANGED
|
@@ -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 '
|
|
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
|
|
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
|
|
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 '
|
|
10
|
-
import { DeploymentDatabaseManager } from '
|
|
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 '
|
|
7
|
-
import { CrossDomainCoordinator } from '
|
|
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 '
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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, '
|
|
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 '
|
|
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 '
|
|
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 '
|
|
8
|
-
export { CrossDomainCoordinator } from '
|
|
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';
|
|
@@ -42,7 +42,7 @@ export class D1ErrorRecoveryManager {
|
|
|
42
42
|
// Import WranglerDeployer for D1 error handling
|
|
43
43
|
const {
|
|
44
44
|
WranglerDeployer
|
|
45
|
-
} = await import('
|
|
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, '
|
|
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 '
|
|
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('
|
|
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('
|
|
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 '
|
|
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
|
-
*
|
|
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
|
-
//
|
|
132
|
-
const merged =
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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 '
|
|
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 '
|
|
23
|
-
import { RollbackManager } from '
|
|
24
|
-
import { DomainDiscovery } from '
|
|
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 '
|
|
28
|
-
import { DeploymentAuditor } from '
|
|
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 = {}) {
|