@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
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wrangler Configuration Manager
|
|
5
|
+
* Manages wrangler.toml configuration files for Cloudflare Workers deployment
|
|
6
|
+
* Handles environment sections, D1 database bindings, and other Cloudflare resources
|
|
7
|
+
*
|
|
8
|
+
* @module WranglerConfigManager
|
|
9
|
+
*/
|
|
10
|
+
import { readFile, writeFile, access, mkdir } from 'fs/promises';
|
|
11
|
+
import { join, dirname } from 'path';
|
|
12
|
+
import { parse as parseToml, stringify as stringifyToml } from '@iarna/toml';
|
|
13
|
+
import { constants } from 'fs';
|
|
14
|
+
export class WranglerConfigManager {
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
// Handle both string path and options object
|
|
17
|
+
if (typeof options === 'string') {
|
|
18
|
+
this.configPath = options;
|
|
19
|
+
this.projectRoot = dirname(options);
|
|
20
|
+
this.dryRun = false;
|
|
21
|
+
this.verbose = false;
|
|
22
|
+
} else {
|
|
23
|
+
this.projectRoot = options.projectRoot || process.cwd();
|
|
24
|
+
this.configPath = options.configPath || join(this.projectRoot, 'wrangler.toml');
|
|
25
|
+
this.dryRun = options.dryRun || false;
|
|
26
|
+
this.verbose = options.verbose || false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Read and parse wrangler.toml file
|
|
32
|
+
* @returns {Promise<Object>} Parsed TOML configuration
|
|
33
|
+
*/
|
|
34
|
+
async readConfig() {
|
|
35
|
+
try {
|
|
36
|
+
const content = await readFile(this.configPath, 'utf-8');
|
|
37
|
+
return parseToml(content);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (error.code === 'ENOENT') {
|
|
40
|
+
// File doesn't exist - return minimal default config
|
|
41
|
+
if (this.verbose) {
|
|
42
|
+
console.log(` ℹ️ wrangler.toml not found at ${this.configPath}`);
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
name: 'worker',
|
|
46
|
+
main: 'src/index.js',
|
|
47
|
+
compatibility_date: new Date().toISOString().split('T')[0],
|
|
48
|
+
env: {}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
throw new Error(`Failed to read wrangler.toml: ${error.message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Write configuration back to wrangler.toml
|
|
57
|
+
* @param {Object} config - Configuration object to write
|
|
58
|
+
*/
|
|
59
|
+
async writeConfig(config) {
|
|
60
|
+
if (this.dryRun) {
|
|
61
|
+
console.log(` 🔍 DRY RUN: Would write wrangler.toml`);
|
|
62
|
+
console.log(stringifyToml(config));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
// Ensure directory exists
|
|
67
|
+
await mkdir(dirname(this.configPath), {
|
|
68
|
+
recursive: true
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Convert to TOML string
|
|
72
|
+
const tomlContent = stringifyToml(config);
|
|
73
|
+
|
|
74
|
+
// Write to file
|
|
75
|
+
await writeFile(this.configPath, tomlContent, 'utf-8');
|
|
76
|
+
if (this.verbose) {
|
|
77
|
+
console.log(` ✅ Updated wrangler.toml at ${this.configPath}`);
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
throw new Error(`Failed to write wrangler.toml: ${error.message}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Check if wrangler.toml exists
|
|
86
|
+
* @returns {Promise<boolean>}
|
|
87
|
+
*/
|
|
88
|
+
async exists() {
|
|
89
|
+
try {
|
|
90
|
+
await access(this.configPath, constants.F_OK);
|
|
91
|
+
return true;
|
|
92
|
+
} catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Ensure environment section exists in wrangler.toml
|
|
99
|
+
* @param {string} environment - Environment name (development, staging, production)
|
|
100
|
+
* @returns {Promise<boolean>} True if environment was added, false if already existed
|
|
101
|
+
*/
|
|
102
|
+
async ensureEnvironment(environment) {
|
|
103
|
+
const config = await this.readConfig();
|
|
104
|
+
|
|
105
|
+
// For production, check top-level only
|
|
106
|
+
if (environment === 'production') {
|
|
107
|
+
// Production config is typically at top level
|
|
108
|
+
if (!config.name) {
|
|
109
|
+
console.log(` ⚠️ wrangler.toml missing 'name' field`);
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
return false; // Already exists (implicitly)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// For non-production environments, check env.{environment} section
|
|
116
|
+
if (!config.env) {
|
|
117
|
+
config.env = {};
|
|
118
|
+
}
|
|
119
|
+
if (!config.env[environment]) {
|
|
120
|
+
console.log(` 📝 Adding [env.${environment}] section to wrangler.toml`);
|
|
121
|
+
|
|
122
|
+
// Create environment section with basic structure
|
|
123
|
+
config.env[environment] = {
|
|
124
|
+
name: config.name || 'worker'
|
|
125
|
+
// Don't add workers_dev by default - let wrangler decide
|
|
126
|
+
};
|
|
127
|
+
await this.writeConfig(config);
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
if (this.verbose) {
|
|
131
|
+
console.log(` ✓ [env.${environment}] section already exists`);
|
|
132
|
+
}
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Add D1 database binding to wrangler.toml
|
|
138
|
+
* @param {string} environment - Environment name
|
|
139
|
+
* @param {Object} databaseInfo - Database information
|
|
140
|
+
* @param {string} databaseInfo.binding - Binding name (e.g., 'DB')
|
|
141
|
+
* @param {string} databaseInfo.database_name - Database name
|
|
142
|
+
* @param {string} databaseInfo.database_id - Database ID
|
|
143
|
+
* @returns {Promise<boolean>} True if binding was added
|
|
144
|
+
*/
|
|
145
|
+
async addDatabaseBinding(environment, databaseInfo) {
|
|
146
|
+
// Support both camelCase and snake_case for flexibility
|
|
147
|
+
const binding = databaseInfo.binding || 'DB';
|
|
148
|
+
const databaseName = databaseInfo.database_name || databaseInfo.databaseName;
|
|
149
|
+
const databaseId = databaseInfo.database_id || databaseInfo.databaseId;
|
|
150
|
+
if (!databaseName || !databaseId) {
|
|
151
|
+
throw new Error('Database name and ID are required');
|
|
152
|
+
}
|
|
153
|
+
const config = await this.readConfig();
|
|
154
|
+
console.log(` 📝 Adding D1 database binding to wrangler.toml`);
|
|
155
|
+
console.log(` Environment: ${environment}`);
|
|
156
|
+
console.log(` Binding: ${binding}`);
|
|
157
|
+
console.log(` Database: ${databaseName}`);
|
|
158
|
+
console.log(` ID: ${databaseId}`);
|
|
159
|
+
const dbBinding = {
|
|
160
|
+
binding,
|
|
161
|
+
database_name: databaseName,
|
|
162
|
+
database_id: databaseId
|
|
163
|
+
};
|
|
164
|
+
if (environment === 'production') {
|
|
165
|
+
// Add to top-level d1_databases array
|
|
166
|
+
if (!config.d1_databases) {
|
|
167
|
+
config.d1_databases = [];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check if binding already exists
|
|
171
|
+
const existingIndex = config.d1_databases.findIndex(db => db.binding === binding || db.database_name === databaseName);
|
|
172
|
+
if (existingIndex >= 0) {
|
|
173
|
+
console.log(` 🔄 Updating existing database binding`);
|
|
174
|
+
config.d1_databases[existingIndex] = dbBinding;
|
|
175
|
+
} else {
|
|
176
|
+
console.log(` ➕ Adding new database binding`);
|
|
177
|
+
config.d1_databases.push(dbBinding);
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
// Add to environment-specific section
|
|
181
|
+
if (!config.env) {
|
|
182
|
+
config.env = {};
|
|
183
|
+
}
|
|
184
|
+
if (!config.env[environment]) {
|
|
185
|
+
config.env[environment] = {
|
|
186
|
+
name: config.name || 'worker'
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
if (!config.env[environment].d1_databases) {
|
|
190
|
+
config.env[environment].d1_databases = [];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Check if binding already exists
|
|
194
|
+
const existingIndex = config.env[environment].d1_databases.findIndex(db => db.binding === binding || db.database_name === databaseName);
|
|
195
|
+
if (existingIndex >= 0) {
|
|
196
|
+
console.log(` 🔄 Updating existing database binding`);
|
|
197
|
+
config.env[environment].d1_databases[existingIndex] = dbBinding;
|
|
198
|
+
} else {
|
|
199
|
+
console.log(` ➕ Adding new database binding`);
|
|
200
|
+
config.env[environment].d1_databases.push(dbBinding);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
await this.writeConfig(config);
|
|
204
|
+
console.log(` ✅ D1 database binding added successfully`);
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Remove D1 database binding from wrangler.toml
|
|
210
|
+
* @param {string} environment - Environment name
|
|
211
|
+
* @param {string} bindingOrName - Binding name or database name
|
|
212
|
+
* @returns {Promise<boolean>} True if binding was removed
|
|
213
|
+
*/
|
|
214
|
+
async removeDatabaseBinding(environment, bindingOrName) {
|
|
215
|
+
const config = await this.readConfig();
|
|
216
|
+
let removed = false;
|
|
217
|
+
if (environment === 'production') {
|
|
218
|
+
if (config.d1_databases) {
|
|
219
|
+
const initialLength = config.d1_databases.length;
|
|
220
|
+
config.d1_databases = config.d1_databases.filter(db => db.binding !== bindingOrName && db.database_name !== bindingOrName);
|
|
221
|
+
removed = config.d1_databases.length < initialLength;
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
if (config.env?.[environment]?.d1_databases) {
|
|
225
|
+
const initialLength = config.env[environment].d1_databases.length;
|
|
226
|
+
config.env[environment].d1_databases = config.env[environment].d1_databases.filter(db => db.binding !== bindingOrName && db.database_name !== bindingOrName);
|
|
227
|
+
removed = config.env[environment].d1_databases.length < initialLength;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (removed) {
|
|
231
|
+
await this.writeConfig(config);
|
|
232
|
+
console.log(` ✅ Removed database binding: ${bindingOrName}`);
|
|
233
|
+
} else {
|
|
234
|
+
console.log(` ℹ️ Database binding not found: ${bindingOrName}`);
|
|
235
|
+
}
|
|
236
|
+
return removed;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Get all database bindings for an environment
|
|
241
|
+
* @param {string} environment - Environment name
|
|
242
|
+
* @returns {Promise<Array>} Array of database bindings
|
|
243
|
+
*/
|
|
244
|
+
async getDatabaseBindings(environment) {
|
|
245
|
+
const config = await this.readConfig();
|
|
246
|
+
if (environment === 'production') {
|
|
247
|
+
return config.d1_databases || [];
|
|
248
|
+
} else {
|
|
249
|
+
return config.env?.[environment]?.d1_databases || [];
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Create minimal wrangler.toml if it doesn't exist
|
|
255
|
+
* @param {string} name - Worker name
|
|
256
|
+
* @param {string} environment - Initial environment to create (optional)
|
|
257
|
+
* @param {Object} options - Additional configuration options
|
|
258
|
+
* @returns {Promise<Object>} The created config object
|
|
259
|
+
*/
|
|
260
|
+
async createMinimalConfig(name = 'worker', environment = null, options = {}) {
|
|
261
|
+
const exists = await this.exists();
|
|
262
|
+
if (exists) {
|
|
263
|
+
console.log(` ✓ wrangler.toml already exists`);
|
|
264
|
+
return await this.readConfig();
|
|
265
|
+
}
|
|
266
|
+
console.log(` 📝 Creating minimal wrangler.toml`);
|
|
267
|
+
const today = new Date().toISOString().split('T')[0];
|
|
268
|
+
const minimalConfig = {
|
|
269
|
+
name: name,
|
|
270
|
+
main: options.main || 'src/index.js',
|
|
271
|
+
compatibility_date: options.compatibility_date || today,
|
|
272
|
+
// Environment sections will be added as needed
|
|
273
|
+
env: {}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// Add initial environment if specified
|
|
277
|
+
if (environment && environment !== 'production') {
|
|
278
|
+
minimalConfig.env[environment] = {};
|
|
279
|
+
}
|
|
280
|
+
await this.writeConfig(minimalConfig);
|
|
281
|
+
console.log(` ✅ Created wrangler.toml at ${this.configPath}`);
|
|
282
|
+
return minimalConfig;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Validate wrangler.toml configuration
|
|
287
|
+
* @returns {Promise<Object>} Validation result with errors and warnings arrays
|
|
288
|
+
*/
|
|
289
|
+
async validate() {
|
|
290
|
+
const errors = [];
|
|
291
|
+
const warnings = [];
|
|
292
|
+
|
|
293
|
+
// Check if file exists
|
|
294
|
+
const exists = await this.exists();
|
|
295
|
+
if (!exists) {
|
|
296
|
+
errors.push('wrangler.toml file not found');
|
|
297
|
+
return {
|
|
298
|
+
valid: false,
|
|
299
|
+
errors,
|
|
300
|
+
warnings
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
const config = await this.readConfig();
|
|
305
|
+
|
|
306
|
+
// Check required fields
|
|
307
|
+
if (!config.name) {
|
|
308
|
+
errors.push('Missing required field: name');
|
|
309
|
+
}
|
|
310
|
+
if (!config.main) {
|
|
311
|
+
warnings.push('Missing main field (entry point)');
|
|
312
|
+
}
|
|
313
|
+
if (!config.compatibility_date) {
|
|
314
|
+
warnings.push('Missing compatibility_date field');
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
valid: errors.length === 0,
|
|
318
|
+
errors,
|
|
319
|
+
warnings,
|
|
320
|
+
config
|
|
321
|
+
};
|
|
322
|
+
} catch (error) {
|
|
323
|
+
errors.push(`Invalid TOML syntax: ${error.message}`);
|
|
324
|
+
return {
|
|
325
|
+
valid: false,
|
|
326
|
+
errors,
|
|
327
|
+
warnings
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Display configuration summary
|
|
334
|
+
* @param {string} environment - Environment to display (optional)
|
|
335
|
+
*/
|
|
336
|
+
async displaySummary(environment = null) {
|
|
337
|
+
const config = await this.readConfig();
|
|
338
|
+
console.log(`\n📋 Wrangler Configuration Summary`);
|
|
339
|
+
console.log(` File: ${this.configPath}`);
|
|
340
|
+
console.log(` Worker Name: ${config.name || 'N/A'}`);
|
|
341
|
+
console.log(` Entry Point: ${config.main || 'N/A'}`);
|
|
342
|
+
console.log(` Compatibility: ${config.compatibility_date || 'N/A'}`);
|
|
343
|
+
if (environment) {
|
|
344
|
+
console.log(`\n Environment: ${environment}`);
|
|
345
|
+
const bindings = await this.getDatabaseBindings(environment);
|
|
346
|
+
if (bindings.length > 0) {
|
|
347
|
+
console.log(` D1 Databases (${bindings.length}):`);
|
|
348
|
+
bindings.forEach(db => {
|
|
349
|
+
console.log(` • ${db.binding}: ${db.database_name} (${db.database_id})`);
|
|
350
|
+
});
|
|
351
|
+
} else {
|
|
352
|
+
console.log(` D1 Databases: None configured`);
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
// Show all environments
|
|
356
|
+
if (config.env && Object.keys(config.env).length > 0) {
|
|
357
|
+
console.log(`\n Environments: ${Object.keys(config.env).join(', ')}`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
console.log('');
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
export default WranglerConfigManager;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tamyla/clodo-framework",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.20",
|
|
4
4
|
"description": "Reusable framework for Clodo-style software architecture on Cloudflare Workers + D1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"./handlers": "./dist/handlers/GenericRouteHandler.js",
|
|
21
21
|
"./config": "./dist/config/index.js",
|
|
22
22
|
"./config/discovery": "./dist/config/discovery/domain-discovery.js",
|
|
23
|
-
"./config/
|
|
23
|
+
"./config/customers": "./dist/config/customers.js",
|
|
24
|
+
"./utils/config": "./dist/utils/config/unified-config-manager.js",
|
|
24
25
|
"./worker": "./dist/worker/index.js",
|
|
25
26
|
"./utils": "./dist/utils/index.js",
|
|
26
27
|
"./utils/deployment": "./dist/utils/deployment/index.js",
|
|
@@ -38,15 +39,13 @@
|
|
|
38
39
|
"./service-management": "./dist/service-management/index.js",
|
|
39
40
|
"./service-management/create": "./dist/service-management/ServiceCreator.js",
|
|
40
41
|
"./service-management/init": "./dist/service-management/ServiceInitializer.js",
|
|
41
|
-
"./config/cli": "./dist/config/CustomerConfigCLI.js",
|
|
42
42
|
"./modules/security": "./dist/modules/security.js"
|
|
43
43
|
},
|
|
44
44
|
"bin": {
|
|
45
45
|
"clodo-service": "./bin/clodo-service.js",
|
|
46
46
|
"clodo-create-service": "./bin/service-management/create-service.js",
|
|
47
47
|
"clodo-init-service": "./bin/service-management/init-service.js",
|
|
48
|
-
"clodo-security": "./bin/security/security-cli.js"
|
|
49
|
-
"clodo-customer-config": "./bin/shared/config/customer-cli.js"
|
|
48
|
+
"clodo-security": "./bin/security/security-cli.js"
|
|
50
49
|
},
|
|
51
50
|
"publishConfig": {
|
|
52
51
|
"access": "public"
|
|
@@ -1,182 +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
|
-
|
|
9
|
-
import { CustomerConfigCLI } from '../../../dist/config/CustomerConfigCLI.js';
|
|
10
|
-
import { resolve } from 'path';
|
|
11
|
-
|
|
12
|
-
// Parse command line arguments
|
|
13
|
-
const argv = process.argv.slice(2);
|
|
14
|
-
let configDir = null;
|
|
15
|
-
let command = null;
|
|
16
|
-
let args = [];
|
|
17
|
-
|
|
18
|
-
// Extract --config-dir parameter if present
|
|
19
|
-
for (let i = 0; i < argv.length; i++) {
|
|
20
|
-
if (argv[i] === '--config-dir' && i + 1 < argv.length) {
|
|
21
|
-
configDir = resolve(argv[i + 1]);
|
|
22
|
-
i++; // Skip the next argument (the path)
|
|
23
|
-
} else if (!command) {
|
|
24
|
-
command = argv[i];
|
|
25
|
-
} else {
|
|
26
|
-
args.push(argv[i]);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Default to current working directory if not specified
|
|
31
|
-
if (!configDir) {
|
|
32
|
-
configDir = resolve(process.cwd(), 'config');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function main() {
|
|
36
|
-
const cli = new CustomerConfigCLI({ configDir });
|
|
37
|
-
await cli.initialize();
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
switch (command) {
|
|
41
|
-
case 'create-customer':
|
|
42
|
-
const [customerName, domain] = args;
|
|
43
|
-
const result = await cli.createCustomer(customerName, domain);
|
|
44
|
-
if (result.success) {
|
|
45
|
-
console.log(`\n🎉 Customer ${customerName} configuration created successfully!`);
|
|
46
|
-
console.log(`\n📋 Customer Details:`);
|
|
47
|
-
console.log(` Name: ${result.customer.name}`);
|
|
48
|
-
console.log(` Domain: ${result.customer.domain || 'Not specified'}`);
|
|
49
|
-
console.log(` Config Path: ${result.customer.configPath}`);
|
|
50
|
-
console.log(` Environments: ${result.customer.environments.join(', ')}`);
|
|
51
|
-
console.log(`\n📋 Next steps:`);
|
|
52
|
-
console.log(`1. Review generated configs in: config/customers/${customerName}/`);
|
|
53
|
-
console.log(`2. Update domain-specific URLs if needed`);
|
|
54
|
-
console.log(`3. Generate production secrets: npm run security:generate-key ${customerName}`);
|
|
55
|
-
console.log(`4. Set production secrets: wrangler secret put KEY_NAME --env production`);
|
|
56
|
-
} else {
|
|
57
|
-
console.error(`❌ Failed to create customer: ${result.error}`);
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
break;
|
|
61
|
-
|
|
62
|
-
case 'validate':
|
|
63
|
-
const validateResult = await cli.validateConfigurations();
|
|
64
|
-
if (validateResult.valid) {
|
|
65
|
-
console.log('✅ All customer configurations are valid');
|
|
66
|
-
} else {
|
|
67
|
-
console.log('❌ Configuration validation failed');
|
|
68
|
-
validateResult.errors.forEach(error => console.log(` - ${error}`));
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
break;
|
|
72
|
-
|
|
73
|
-
case 'show':
|
|
74
|
-
const [customerNameShow, environment] = args;
|
|
75
|
-
const showResult = cli.showConfiguration(customerNameShow, environment);
|
|
76
|
-
if (showResult.success) {
|
|
77
|
-
console.log(`🔍 Effective configuration: ${customerNameShow}/${environment}\n`);
|
|
78
|
-
if (showResult.config.variables?.base) {
|
|
79
|
-
console.log('📋 Base variables:');
|
|
80
|
-
Object.entries(showResult.config.variables.base).slice(0, 10).forEach(([key, value]) => {
|
|
81
|
-
console.log(` ${key}=${value}`);
|
|
82
|
-
});
|
|
83
|
-
if (Object.keys(showResult.config.variables.base).length > 10) {
|
|
84
|
-
console.log(' ...');
|
|
85
|
-
}
|
|
86
|
-
console.log('');
|
|
87
|
-
}
|
|
88
|
-
if (showResult.config.variables?.customer) {
|
|
89
|
-
console.log(`📋 Customer ${environment} variables:`);
|
|
90
|
-
Object.entries(showResult.config.variables.customer).slice(0, 15).forEach(([key, value]) => {
|
|
91
|
-
console.log(` ${key}=${value}`);
|
|
92
|
-
});
|
|
93
|
-
if (Object.keys(showResult.config.variables.customer).length > 15) {
|
|
94
|
-
console.log(' ...');
|
|
95
|
-
}
|
|
96
|
-
console.log('');
|
|
97
|
-
}
|
|
98
|
-
if (showResult.config.features && Object.keys(showResult.config.features).length > 0) {
|
|
99
|
-
console.log('🚩 Customer features:');
|
|
100
|
-
Object.entries(showResult.config.features).forEach(([feature, enabled]) => {
|
|
101
|
-
console.log(` ${feature}: ${enabled ? '✅' : '❌'}`);
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
} else {
|
|
105
|
-
console.error(`❌ Failed to show configuration: ${showResult.error}`);
|
|
106
|
-
process.exit(1);
|
|
107
|
-
}
|
|
108
|
-
break;
|
|
109
|
-
|
|
110
|
-
case 'deploy-command':
|
|
111
|
-
const [customerNameDeploy, environmentDeploy] = args;
|
|
112
|
-
const deployResult = cli.getDeployCommand(customerNameDeploy, environmentDeploy);
|
|
113
|
-
if (deployResult.success) {
|
|
114
|
-
console.log(`📋 Deploy command for ${customerNameDeploy}/${environmentDeploy}:`);
|
|
115
|
-
console.log(` ${deployResult.command}`);
|
|
116
|
-
console.log(`\n💡 Ensure customer config is loaded: ${deployResult.configPath}`);
|
|
117
|
-
} else {
|
|
118
|
-
console.error(`❌ Failed to get deploy command: ${deployResult.error}`);
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
break;
|
|
122
|
-
|
|
123
|
-
case 'list':
|
|
124
|
-
const listResult = cli.listCustomers();
|
|
125
|
-
if (listResult.success && listResult.customers.length > 0) {
|
|
126
|
-
console.log('📋 Configured customers:\n');
|
|
127
|
-
listResult.customers.forEach(customer => {
|
|
128
|
-
console.log(`🏢 ${customer.name}`);
|
|
129
|
-
console.log(` Domain: ${customer.customerDomain || customer.domain || 'Not configured'}`);
|
|
130
|
-
console.log(` Account ID: ${customer.accountId ? `${customer.accountId.substring(0, 8)}...${customer.accountId.substring(24)}` : 'Not configured'}`);
|
|
131
|
-
console.log(` Zone ID: ${customer.zoneId ? `${customer.zoneId.substring(0, 8)}...` : 'Not configured'}`);
|
|
132
|
-
if (customer.databaseId) {
|
|
133
|
-
console.log(` Database: ${customer.databaseName || 'Unnamed'} (${customer.databaseId.substring(0, 8)}...)`);
|
|
134
|
-
}
|
|
135
|
-
console.log(` Secrets: ${customer.hasSecrets ? '✅ Managed via wrangler secret commands' : '❌ Not configured'}`);
|
|
136
|
-
console.log(` Environments: ${customer.environments.join(', ')}`);
|
|
137
|
-
console.log(` Config Path: config/customers/${customer.name}/`);
|
|
138
|
-
console.log('');
|
|
139
|
-
});
|
|
140
|
-
} else if (listResult.success) {
|
|
141
|
-
console.log('📋 No customers configured');
|
|
142
|
-
console.log('\n💡 Tip: Run "clodo-customer-config create-customer <name>" to create your first customer');
|
|
143
|
-
} else {
|
|
144
|
-
console.error(`❌ Failed to list customers: ${listResult.error}`);
|
|
145
|
-
process.exit(1);
|
|
146
|
-
}
|
|
147
|
-
break;
|
|
148
|
-
|
|
149
|
-
default:
|
|
150
|
-
console.log('Customer Configuration Management Tool\n');
|
|
151
|
-
console.log('Usage:');
|
|
152
|
-
console.log(' clodo-customer-config [--config-dir <path>] <command> [args]\n');
|
|
153
|
-
console.log('Options:');
|
|
154
|
-
console.log(' --config-dir <path> - Path to config directory (default: ./config)\n');
|
|
155
|
-
console.log('Available commands:');
|
|
156
|
-
console.log(' create-customer <name> [domain] - Create new customer config from template');
|
|
157
|
-
console.log(' validate - Validate configuration structure');
|
|
158
|
-
console.log(' show <customer> <environment> - Show effective configuration');
|
|
159
|
-
console.log(' deploy-command <customer> <env> - Get deployment command');
|
|
160
|
-
console.log(' list - List all configured customers');
|
|
161
|
-
console.log('\nExamples:');
|
|
162
|
-
console.log(' clodo-customer-config create-customer acmecorp acmecorp.com');
|
|
163
|
-
console.log(' clodo-customer-config validate');
|
|
164
|
-
console.log(' clodo-customer-config show acmecorp production');
|
|
165
|
-
console.log(' clodo-customer-config list');
|
|
166
|
-
console.log(' clodo-customer-config --config-dir /path/to/service/config validate');
|
|
167
|
-
console.log('\nIntegration:');
|
|
168
|
-
console.log(' This tool integrates with Clodo Framework domain and feature flag systems.');
|
|
169
|
-
console.log(' Customer configurations are automatically registered as domains.');
|
|
170
|
-
console.log(' When run from a service directory, it uses ./config by default.');
|
|
171
|
-
break;
|
|
172
|
-
}
|
|
173
|
-
} catch (error) {
|
|
174
|
-
console.error(`❌ Error: ${error.message}`);
|
|
175
|
-
process.exit(1);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
main().catch(error => {
|
|
180
|
-
console.error(`❌ Unexpected error: ${error.message}`);
|
|
181
|
-
process.exit(1);
|
|
182
|
-
});
|