@tamyla/clodo-framework 2.0.18 โ 2.0.20
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 +174 -88
- package/bin/clodo-service.js +31 -55
- package/dist/modules/security.js +24 -22
- package/dist/orchestration/multi-domain-orchestrator.js +33 -0
- package/dist/security/SecurityCLI.js +9 -66
- package/dist/security/index.js +7 -6
- package/dist/service-management/ServiceOrchestrator.js +14 -19
- package/dist/utils/config/unified-config-manager.js +448 -0
- package/dist/utils/deployment/index.js +1 -1
- package/dist/utils/deployment/wrangler-config-manager.js +363 -0
- package/package.json +4 -5
- package/bin/shared/config/customer-cli.js +0 -182
- package/dist/config/ConfigurationManager.js +0 -159
- package/dist/config/CustomerConfigCLI.js +0 -226
- package/dist/config/customer-config-loader.js +0 -247
- package/dist/security/DeploymentManager.js +0 -208
- package/dist/service-management/handlers/ConfigMutator.js +0 -130
- package/dist/shared/config/customer-cli.js +0 -175
- package/dist/utils/deployment/config-persistence.js +0 -347
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import { ConfigurationValidator } from './ConfigurationValidator.js';
|
|
2
|
-
import { SecretGenerator } from './SecretGenerator.js';
|
|
3
|
-
import { checkHealth } from '../utils/health-checker.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Secure Deployment Manager
|
|
7
|
-
* Manages secure deployment pipeline with security validation
|
|
8
|
-
*/
|
|
9
|
-
export class DeploymentManager {
|
|
10
|
-
/**
|
|
11
|
-
* Deploy customer configuration with security validation
|
|
12
|
-
* @param {Object} options - Deployment options
|
|
13
|
-
*/
|
|
14
|
-
static async deployWithSecurity(options) {
|
|
15
|
-
const {
|
|
16
|
-
customer,
|
|
17
|
-
environment,
|
|
18
|
-
skipValidation = false,
|
|
19
|
-
allowInsecure = false,
|
|
20
|
-
dryRun = false,
|
|
21
|
-
deploymentUrl // New parameter for real URL extraction
|
|
22
|
-
} = options;
|
|
23
|
-
console.log(`\n๐ Secure Deployment: ${customer}/${environment}`);
|
|
24
|
-
console.log('='.repeat(60));
|
|
25
|
-
try {
|
|
26
|
-
// Step 1: Validate configuration
|
|
27
|
-
if (!skipValidation) {
|
|
28
|
-
console.log('\n๐ Step 1: Configuration Validation');
|
|
29
|
-
await this.validateConfiguration(customer, environment, {
|
|
30
|
-
allowInsecure
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Step 2: Security validation
|
|
35
|
-
console.log('\n๐ Step 2: Security Validation');
|
|
36
|
-
const securityResult = ConfigurationValidator.validateConfiguration(customer, environment);
|
|
37
|
-
if (!securityResult.valid) {
|
|
38
|
-
const criticalIssues = securityResult.securityIssues.filter(issue => issue.severity === 'critical');
|
|
39
|
-
if (criticalIssues.length > 0 && !allowInsecure) {
|
|
40
|
-
console.log('\nโ DEPLOYMENT BLOCKED');
|
|
41
|
-
console.log('Critical security issues must be resolved before deployment.');
|
|
42
|
-
console.log('\nTo resolve:');
|
|
43
|
-
console.log('1. Fix the security issues listed above');
|
|
44
|
-
console.log('2. Use secure secrets management');
|
|
45
|
-
console.log('3. Generate secure keys with SecretGenerator');
|
|
46
|
-
if (dryRun) {
|
|
47
|
-
console.log('\n๐งช DRY RUN: Would have been blocked by security issues');
|
|
48
|
-
} else {
|
|
49
|
-
throw new Error('Deployment blocked due to critical security issues');
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Step 3: Pre-deployment checks
|
|
55
|
-
console.log('\nโ
Step 3: Pre-deployment Checks');
|
|
56
|
-
await this.performPreDeploymentChecks(customer, environment);
|
|
57
|
-
|
|
58
|
-
// Step 4: Deploy (or dry run)
|
|
59
|
-
if (dryRun) {
|
|
60
|
-
console.log('\n๐งช DRY RUN: Would deploy with security validation');
|
|
61
|
-
console.log(` Customer: ${customer}`);
|
|
62
|
-
console.log(` Environment: ${environment}`);
|
|
63
|
-
console.log(` Security validation: ${securityResult.valid ? 'PASSED' : 'ISSUES FOUND'}`);
|
|
64
|
-
console.log('\nโ
Dry run completed successfully');
|
|
65
|
-
} else {
|
|
66
|
-
console.log('\n๐ Step 4: Deploying');
|
|
67
|
-
const deployResult = await this.performDeployment(customer, environment);
|
|
68
|
-
console.log(` โ
Deployment URL: ${deployResult.url}`);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Step 5: Post-deployment validation (real implementation)
|
|
72
|
-
if (!dryRun && deploymentUrl) {
|
|
73
|
-
console.log('\n๐ Step 5: Post-deployment Validation');
|
|
74
|
-
await this.performPostDeploymentChecks(customer, environment, deploymentUrl);
|
|
75
|
-
}
|
|
76
|
-
console.log('\n๐ Deployment completed successfully!');
|
|
77
|
-
} catch (error) {
|
|
78
|
-
console.error(`\nโ Deployment failed: ${error.message}`);
|
|
79
|
-
throw error;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Validate configuration before deployment
|
|
85
|
-
*/
|
|
86
|
-
static async validateConfiguration(customer, environment, options = {}) {
|
|
87
|
-
const {
|
|
88
|
-
allowInsecure = false
|
|
89
|
-
} = options;
|
|
90
|
-
try {
|
|
91
|
-
// This would integrate with the framework's config builder
|
|
92
|
-
// For now, we'll assume configuration validation is handled elsewhere
|
|
93
|
-
console.log(` โ Configuration structure validated for ${customer}/${environment}`);
|
|
94
|
-
} catch (error) {
|
|
95
|
-
if (!allowInsecure) {
|
|
96
|
-
throw new Error(`Configuration validation failed: ${error.message}`);
|
|
97
|
-
}
|
|
98
|
-
console.log(` โ ๏ธ Configuration validation failed but proceeding (--allow-insecure)`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Perform pre-deployment checks
|
|
104
|
-
*/
|
|
105
|
-
// eslint-disable-next-line no-unused-vars
|
|
106
|
-
static async performPreDeploymentChecks(_customer, _environment) {
|
|
107
|
-
const checks = ['Environment variables validation', 'Required secrets presence', 'Configuration consistency', 'Resource availability'];
|
|
108
|
-
for (const check of checks) {
|
|
109
|
-
console.log(` โ ${check}`);
|
|
110
|
-
// Add actual check logic here
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Perform the actual deployment
|
|
116
|
-
*/
|
|
117
|
-
static async performDeployment(_customer, _environment) {
|
|
118
|
-
// This would integrate with the actual deployment system
|
|
119
|
-
// For now, simulate deployment
|
|
120
|
-
console.log(` ๐ Deploying ${_customer} to ${_environment} environment`);
|
|
121
|
-
|
|
122
|
-
// Simulate deployment steps
|
|
123
|
-
await this.delay(1000);
|
|
124
|
-
console.log(` โ Deployment package prepared`);
|
|
125
|
-
await this.delay(1000);
|
|
126
|
-
console.log(` โ Deploying to Cloudflare Workers`);
|
|
127
|
-
await this.delay(2000);
|
|
128
|
-
console.log(` โ Deployment completed`);
|
|
129
|
-
|
|
130
|
-
// Return deployment result with URL
|
|
131
|
-
const deploymentUrl = `https://${_customer}-${_environment}.workers.dev`;
|
|
132
|
-
return {
|
|
133
|
-
url: deploymentUrl,
|
|
134
|
-
customer: _customer,
|
|
135
|
-
environment: _environment,
|
|
136
|
-
timestamp: new Date().toISOString()
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Perform post-deployment checks (real HTTP-based validation)
|
|
142
|
-
*/
|
|
143
|
-
static async performPostDeploymentChecks(customer, environment, deploymentUrl) {
|
|
144
|
-
console.log(` ๐ Validating deployment at ${deploymentUrl}`);
|
|
145
|
-
try {
|
|
146
|
-
// Real health check using HTTP
|
|
147
|
-
const healthResult = await checkHealth(deploymentUrl);
|
|
148
|
-
if (healthResult.status !== 'ok') {
|
|
149
|
-
throw new Error(`Health check failed: ${healthResult.message}`);
|
|
150
|
-
}
|
|
151
|
-
console.log(` โ
Health check passed for ${customer}/${environment}`);
|
|
152
|
-
} catch (error) {
|
|
153
|
-
console.error(` โ Post-deployment validation failed: ${error.message}`);
|
|
154
|
-
throw error;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Generate secure deployment configuration
|
|
160
|
-
*/
|
|
161
|
-
static generateSecureConfig(customer, environment) {
|
|
162
|
-
const config = {
|
|
163
|
-
customer,
|
|
164
|
-
environment,
|
|
165
|
-
secrets: {},
|
|
166
|
-
security: {
|
|
167
|
-
validated: true,
|
|
168
|
-
timestamp: new Date().toISOString()
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
// Generate required secure keys
|
|
173
|
-
const requiredKeys = ['CONTENT_SKIMMER_API_KEY', 'LOGGER_SERVICE_API_KEY', 'AUTH_SERVICE_API_KEY', 'X_SERVICE_KEY'];
|
|
174
|
-
for (const keyName of requiredKeys) {
|
|
175
|
-
config.secrets[keyName] = SecretGenerator.generateServiceKey(keyName.replace('_API_KEY', '').toLowerCase().replace('_', '-'), environment);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Generate JWT secret
|
|
179
|
-
config.secrets['AUTH_JWT_SECRET'] = SecretGenerator.generateSecureJwtSecret();
|
|
180
|
-
return config;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Validate deployment readiness
|
|
185
|
-
*/
|
|
186
|
-
// eslint-disable-next-line no-unused-vars
|
|
187
|
-
static validateDeploymentReadiness(_customer, _environment) {
|
|
188
|
-
const issues = [];
|
|
189
|
-
|
|
190
|
-
// Check if all required secrets are available
|
|
191
|
-
// eslint-disable-next-line no-unused-vars
|
|
192
|
-
const requiredSecrets = ['CONTENT_SKIMMER_API_KEY', 'LOGGER_SERVICE_API_KEY', 'AUTH_SERVICE_API_KEY', 'X_SERVICE_KEY', 'AUTH_JWT_SECRET'];
|
|
193
|
-
|
|
194
|
-
// This would check actual secret availability
|
|
195
|
-
// For now, return empty issues array
|
|
196
|
-
return {
|
|
197
|
-
ready: issues.length === 0,
|
|
198
|
-
issues
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Utility method for delays in async operations
|
|
204
|
-
*/
|
|
205
|
-
static delay(ms) {
|
|
206
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
207
|
-
}
|
|
208
|
-
}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Configuration Mutator Module
|
|
3
|
-
* Focused module for safe configuration file mutations
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import fs from 'fs/promises';
|
|
7
|
-
import path from 'path';
|
|
8
|
-
export class ConfigMutator {
|
|
9
|
-
constructor(options = {}) {
|
|
10
|
-
this.dryRun = options.dryRun || false;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Safely update domain configuration
|
|
15
|
-
*/
|
|
16
|
-
async updateDomainConfig(servicePath, currentConfig, updates) {
|
|
17
|
-
const domainConfigPath = path.join(servicePath, 'src/config/domains.js');
|
|
18
|
-
let domainConfig = await fs.readFile(domainConfigPath, 'utf8');
|
|
19
|
-
if (updates.domainName) {
|
|
20
|
-
// Use regex-based replacement for safety
|
|
21
|
-
domainConfig = domainConfig.replace(new RegExp(`name: ['"]${this.escapeRegExp(currentConfig.domainName)}['"]`, 'g'), `name: '${updates.domainName}'`);
|
|
22
|
-
|
|
23
|
-
// Update domain URLs
|
|
24
|
-
domainConfig = domainConfig.replace(new RegExp(`https://[^'"]*${this.escapeRegExp(currentConfig.domainName)}[^'"]*`, 'g'), match => match.replace(currentConfig.domainName, updates.domainName));
|
|
25
|
-
}
|
|
26
|
-
if (!this.dryRun) {
|
|
27
|
-
await fs.writeFile(domainConfigPath, domainConfig, 'utf8');
|
|
28
|
-
}
|
|
29
|
-
return {
|
|
30
|
-
updated: true,
|
|
31
|
-
path: domainConfigPath
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Update Cloudflare configuration
|
|
37
|
-
*/
|
|
38
|
-
async updateCloudflareConfig(servicePath, currentConfig, updates) {
|
|
39
|
-
const domainConfigPath = path.join(servicePath, 'src/config/domains.js');
|
|
40
|
-
let domainConfig = await fs.readFile(domainConfigPath, 'utf8');
|
|
41
|
-
if (updates.accountId) {
|
|
42
|
-
domainConfig = domainConfig.replace(new RegExp(`accountId:\\s*['"]${this.escapeRegExp(currentConfig.cloudflareAccountId || '')}['"]`, 'g'), `accountId: '${updates.accountId}'`);
|
|
43
|
-
}
|
|
44
|
-
if (updates.zoneId) {
|
|
45
|
-
domainConfig = domainConfig.replace(new RegExp(`zoneId:\\s*['"]${this.escapeRegExp(currentConfig.cloudflareZoneId || '')}['"]`, 'g'), `zoneId: '${updates.zoneId}'`);
|
|
46
|
-
}
|
|
47
|
-
if (!this.dryRun) {
|
|
48
|
-
await fs.writeFile(domainConfigPath, domainConfig, 'utf8');
|
|
49
|
-
}
|
|
50
|
-
return {
|
|
51
|
-
updated: true,
|
|
52
|
-
path: domainConfigPath
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Update environment configuration
|
|
58
|
-
*/
|
|
59
|
-
async updateEnvironmentConfig(servicePath, currentConfig, updates) {
|
|
60
|
-
const wranglerConfigPath = path.join(servicePath, 'wrangler.toml');
|
|
61
|
-
let wranglerConfig = await fs.readFile(wranglerConfigPath, 'utf8');
|
|
62
|
-
if (updates.environment) {
|
|
63
|
-
// Update environment-specific names
|
|
64
|
-
wranglerConfig = wranglerConfig.replace(new RegExp(`name\\s*=\\s*['"].*${this.escapeRegExp(currentConfig.environment)}.*['"]`, 'g'), `name = "${currentConfig.serviceName}-${updates.environment}"`);
|
|
65
|
-
wranglerConfig = wranglerConfig.replace(new RegExp(`\\[env\\.${this.escapeRegExp(currentConfig.environment)}\\]`, 'g'), `[env.${updates.environment}]`);
|
|
66
|
-
}
|
|
67
|
-
if (!this.dryRun) {
|
|
68
|
-
await fs.writeFile(wranglerConfigPath, wranglerConfig, 'utf8');
|
|
69
|
-
}
|
|
70
|
-
return {
|
|
71
|
-
updated: true,
|
|
72
|
-
path: wranglerConfigPath
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Update feature configuration
|
|
78
|
-
*/
|
|
79
|
-
async updateFeatureConfig(servicePath, action, feature) {
|
|
80
|
-
const domainConfigPath = path.join(servicePath, 'src/config/domains.js');
|
|
81
|
-
let domainConfig = await fs.readFile(domainConfigPath, 'utf8');
|
|
82
|
-
if (action === 'add') {
|
|
83
|
-
// Add feature flag
|
|
84
|
-
const featureLine = ` ${feature}: true,`;
|
|
85
|
-
domainConfig = domainConfig.replace(/(\s+)(cors:\s*(?:true|false),?)(\n)/, `$1$2$3$1${featureLine}$3`);
|
|
86
|
-
} else if (action === 'remove') {
|
|
87
|
-
// Remove feature flag
|
|
88
|
-
const featureLinePattern = new RegExp(`\\s+${this.escapeRegExp(feature)}:\\s*(?:true|false),?\\n`, 'g');
|
|
89
|
-
domainConfig = domainConfig.replace(featureLinePattern, '');
|
|
90
|
-
}
|
|
91
|
-
if (!this.dryRun) {
|
|
92
|
-
await fs.writeFile(domainConfigPath, domainConfig, 'utf8');
|
|
93
|
-
}
|
|
94
|
-
return {
|
|
95
|
-
updated: true,
|
|
96
|
-
path: domainConfigPath
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Validate configuration file exists and is writable
|
|
102
|
-
*/
|
|
103
|
-
async validateConfigFile(filePath) {
|
|
104
|
-
try {
|
|
105
|
-
await fs.access(filePath, fs.constants.R_OK | fs.constants.W_OK);
|
|
106
|
-
return {
|
|
107
|
-
valid: true
|
|
108
|
-
};
|
|
109
|
-
} catch (error) {
|
|
110
|
-
return {
|
|
111
|
-
valid: false,
|
|
112
|
-
error: `Configuration file not accessible: ${filePath}`
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Escape special regex characters for safe replacement
|
|
119
|
-
*/
|
|
120
|
-
escapeRegExp(string) {
|
|
121
|
-
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Enable/disable dry run mode
|
|
126
|
-
*/
|
|
127
|
-
setDryRun(enabled) {
|
|
128
|
-
this.dryRun = enabled;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Customer Configuration Management CLI
|
|
5
|
-
* Manages multi-environment, multi-customer configuration structure
|
|
6
|
-
* Integrates with Clodo Framework domain and feature flag systems
|
|
7
|
-
*/
|
|
8
|
-
import { CustomerConfigCLI } from '../../../dist/config/CustomerConfigCLI.js';
|
|
9
|
-
import { resolve } from 'path';
|
|
10
|
-
|
|
11
|
-
// Parse command line arguments
|
|
12
|
-
const argv = process.argv.slice(2);
|
|
13
|
-
let configDir = null;
|
|
14
|
-
let command = null;
|
|
15
|
-
let args = [];
|
|
16
|
-
|
|
17
|
-
// Extract --config-dir parameter if present
|
|
18
|
-
for (let i = 0; i < argv.length; i++) {
|
|
19
|
-
if (argv[i] === '--config-dir' && i + 1 < argv.length) {
|
|
20
|
-
configDir = resolve(argv[i + 1]);
|
|
21
|
-
i++; // Skip the next argument (the path)
|
|
22
|
-
} else if (!command) {
|
|
23
|
-
command = argv[i];
|
|
24
|
-
} else {
|
|
25
|
-
args.push(argv[i]);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Default to current working directory if not specified
|
|
30
|
-
if (!configDir) {
|
|
31
|
-
configDir = resolve(process.cwd(), 'config');
|
|
32
|
-
}
|
|
33
|
-
async function main() {
|
|
34
|
-
const cli = new CustomerConfigCLI({
|
|
35
|
-
configDir
|
|
36
|
-
});
|
|
37
|
-
await cli.initialize();
|
|
38
|
-
try {
|
|
39
|
-
switch (command) {
|
|
40
|
-
case 'create-customer':
|
|
41
|
-
const [customerName, domain] = args;
|
|
42
|
-
const result = await cli.createCustomer(customerName, domain);
|
|
43
|
-
if (result.success) {
|
|
44
|
-
console.log(`\n๐ Customer ${customerName} configuration created successfully!`);
|
|
45
|
-
console.log(`\n๐ Customer Details:`);
|
|
46
|
-
console.log(` Name: ${result.customer.name}`);
|
|
47
|
-
console.log(` Domain: ${result.customer.domain || 'Not specified'}`);
|
|
48
|
-
console.log(` Config Path: ${result.customer.configPath}`);
|
|
49
|
-
console.log(` Environments: ${result.customer.environments.join(', ')}`);
|
|
50
|
-
console.log(`\n๐ Next steps:`);
|
|
51
|
-
console.log(`1. Review generated configs in: config/customers/${customerName}/`);
|
|
52
|
-
console.log(`2. Update domain-specific URLs if needed`);
|
|
53
|
-
console.log(`3. Generate production secrets: npm run security:generate-key ${customerName}`);
|
|
54
|
-
console.log(`4. Set production secrets: wrangler secret put KEY_NAME --env production`);
|
|
55
|
-
} else {
|
|
56
|
-
console.error(`โ Failed to create customer: ${result.error}`);
|
|
57
|
-
process.exit(1);
|
|
58
|
-
}
|
|
59
|
-
break;
|
|
60
|
-
case 'validate':
|
|
61
|
-
const validateResult = await cli.validateConfigurations();
|
|
62
|
-
if (validateResult.valid) {
|
|
63
|
-
console.log('โ
All customer configurations are valid');
|
|
64
|
-
} else {
|
|
65
|
-
console.log('โ Configuration validation failed');
|
|
66
|
-
validateResult.errors.forEach(error => console.log(` - ${error}`));
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
|
-
break;
|
|
70
|
-
case 'show':
|
|
71
|
-
const [customerNameShow, environment] = args;
|
|
72
|
-
const showResult = cli.showConfiguration(customerNameShow, environment);
|
|
73
|
-
if (showResult.success) {
|
|
74
|
-
console.log(`๐ Effective configuration: ${customerNameShow}/${environment}\n`);
|
|
75
|
-
if (showResult.config.variables?.base) {
|
|
76
|
-
console.log('๐ Base variables:');
|
|
77
|
-
Object.entries(showResult.config.variables.base).slice(0, 10).forEach(([key, value]) => {
|
|
78
|
-
console.log(` ${key}=${value}`);
|
|
79
|
-
});
|
|
80
|
-
if (Object.keys(showResult.config.variables.base).length > 10) {
|
|
81
|
-
console.log(' ...');
|
|
82
|
-
}
|
|
83
|
-
console.log('');
|
|
84
|
-
}
|
|
85
|
-
if (showResult.config.variables?.customer) {
|
|
86
|
-
console.log(`๐ Customer ${environment} variables:`);
|
|
87
|
-
Object.entries(showResult.config.variables.customer).slice(0, 15).forEach(([key, value]) => {
|
|
88
|
-
console.log(` ${key}=${value}`);
|
|
89
|
-
});
|
|
90
|
-
if (Object.keys(showResult.config.variables.customer).length > 15) {
|
|
91
|
-
console.log(' ...');
|
|
92
|
-
}
|
|
93
|
-
console.log('');
|
|
94
|
-
}
|
|
95
|
-
if (showResult.config.features && Object.keys(showResult.config.features).length > 0) {
|
|
96
|
-
console.log('๐ฉ Customer features:');
|
|
97
|
-
Object.entries(showResult.config.features).forEach(([feature, enabled]) => {
|
|
98
|
-
console.log(` ${feature}: ${enabled ? 'โ
' : 'โ'}`);
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
} else {
|
|
102
|
-
console.error(`โ Failed to show configuration: ${showResult.error}`);
|
|
103
|
-
process.exit(1);
|
|
104
|
-
}
|
|
105
|
-
break;
|
|
106
|
-
case 'deploy-command':
|
|
107
|
-
const [customerNameDeploy, environmentDeploy] = args;
|
|
108
|
-
const deployResult = cli.getDeployCommand(customerNameDeploy, environmentDeploy);
|
|
109
|
-
if (deployResult.success) {
|
|
110
|
-
console.log(`๐ Deploy command for ${customerNameDeploy}/${environmentDeploy}:`);
|
|
111
|
-
console.log(` ${deployResult.command}`);
|
|
112
|
-
console.log(`\n๐ก Ensure customer config is loaded: ${deployResult.configPath}`);
|
|
113
|
-
} else {
|
|
114
|
-
console.error(`โ Failed to get deploy command: ${deployResult.error}`);
|
|
115
|
-
process.exit(1);
|
|
116
|
-
}
|
|
117
|
-
break;
|
|
118
|
-
case 'list':
|
|
119
|
-
const listResult = cli.listCustomers();
|
|
120
|
-
if (listResult.success && listResult.customers.length > 0) {
|
|
121
|
-
console.log('๐ Configured customers:\n');
|
|
122
|
-
listResult.customers.forEach(customer => {
|
|
123
|
-
console.log(`๐ข ${customer.name}`);
|
|
124
|
-
console.log(` Domain: ${customer.customerDomain || customer.domain || 'Not configured'}`);
|
|
125
|
-
console.log(` Account ID: ${customer.accountId ? `${customer.accountId.substring(0, 8)}...${customer.accountId.substring(24)}` : 'Not configured'}`);
|
|
126
|
-
console.log(` Zone ID: ${customer.zoneId ? `${customer.zoneId.substring(0, 8)}...` : 'Not configured'}`);
|
|
127
|
-
if (customer.databaseId) {
|
|
128
|
-
console.log(` Database: ${customer.databaseName || 'Unnamed'} (${customer.databaseId.substring(0, 8)}...)`);
|
|
129
|
-
}
|
|
130
|
-
console.log(` Secrets: ${customer.hasSecrets ? 'โ
Managed via wrangler secret commands' : 'โ Not configured'}`);
|
|
131
|
-
console.log(` Environments: ${customer.environments.join(', ')}`);
|
|
132
|
-
console.log(` Config Path: config/customers/${customer.name}/`);
|
|
133
|
-
console.log('');
|
|
134
|
-
});
|
|
135
|
-
} else if (listResult.success) {
|
|
136
|
-
console.log('๐ No customers configured');
|
|
137
|
-
console.log('\n๐ก Tip: Run "clodo-customer-config create-customer <name>" to create your first customer');
|
|
138
|
-
} else {
|
|
139
|
-
console.error(`โ Failed to list customers: ${listResult.error}`);
|
|
140
|
-
process.exit(1);
|
|
141
|
-
}
|
|
142
|
-
break;
|
|
143
|
-
default:
|
|
144
|
-
console.log('Customer Configuration Management Tool\n');
|
|
145
|
-
console.log('Usage:');
|
|
146
|
-
console.log(' clodo-customer-config [--config-dir <path>] <command> [args]\n');
|
|
147
|
-
console.log('Options:');
|
|
148
|
-
console.log(' --config-dir <path> - Path to config directory (default: ./config)\n');
|
|
149
|
-
console.log('Available commands:');
|
|
150
|
-
console.log(' create-customer <name> [domain] - Create new customer config from template');
|
|
151
|
-
console.log(' validate - Validate configuration structure');
|
|
152
|
-
console.log(' show <customer> <environment> - Show effective configuration');
|
|
153
|
-
console.log(' deploy-command <customer> <env> - Get deployment command');
|
|
154
|
-
console.log(' list - List all configured customers');
|
|
155
|
-
console.log('\nExamples:');
|
|
156
|
-
console.log(' clodo-customer-config create-customer acmecorp acmecorp.com');
|
|
157
|
-
console.log(' clodo-customer-config validate');
|
|
158
|
-
console.log(' clodo-customer-config show acmecorp production');
|
|
159
|
-
console.log(' clodo-customer-config list');
|
|
160
|
-
console.log(' clodo-customer-config --config-dir /path/to/service/config validate');
|
|
161
|
-
console.log('\nIntegration:');
|
|
162
|
-
console.log(' This tool integrates with Clodo Framework domain and feature flag systems.');
|
|
163
|
-
console.log(' Customer configurations are automatically registered as domains.');
|
|
164
|
-
console.log(' When run from a service directory, it uses ./config by default.');
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
167
|
-
} catch (error) {
|
|
168
|
-
console.error(`โ Error: ${error.message}`);
|
|
169
|
-
process.exit(1);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
main().catch(error => {
|
|
173
|
-
console.error(`โ Unexpected error: ${error.message}`);
|
|
174
|
-
process.exit(1);
|
|
175
|
-
});
|