@tamyla/clodo-framework 2.0.7 ā 2.0.9
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 +191 -115
- package/README.md +78 -0
- package/bin/clodo-service.js +24 -9
- package/bin/security/security-cli.js +1 -1
- package/bin/service-management/create-service.js +1 -1
- package/bin/service-management/init-service.js +1 -1
- package/bin/shared/config/customer-cli.js +39 -150
- package/dist/config/ConfigurationManager.js +1 -1
- package/dist/config/CustomerConfigCLI.js +18 -12
- package/dist/config/customers.js +183 -7
- package/dist/deployment/wrangler-deployer.js +21 -0
- package/dist/orchestration/multi-domain-orchestrator.js +20 -15
- package/dist/service-management/InputCollector.js +93 -5
- package/dist/shared/config/customer-cli.js +42 -125
- package/dist/utils/cloudflare/api.js +295 -0
- package/dist/utils/cloudflare/index.js +79 -0
- package/dist/utils/deployment/index.js +1 -1
- package/package.json +4 -3
- package/dist/utils/deployment/interactive-prompts.js +0 -97
|
@@ -7,12 +7,33 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
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
|
+
}
|
|
10
29
|
|
|
11
|
-
|
|
12
|
-
|
|
30
|
+
// Default to current working directory if not specified
|
|
31
|
+
if (!configDir) {
|
|
32
|
+
configDir = resolve(process.cwd(), 'config');
|
|
33
|
+
}
|
|
13
34
|
|
|
14
35
|
async function main() {
|
|
15
|
-
const cli = new CustomerConfigCLI();
|
|
36
|
+
const cli = new CustomerConfigCLI({ configDir });
|
|
16
37
|
await cli.initialize();
|
|
17
38
|
|
|
18
39
|
try {
|
|
@@ -105,14 +126,20 @@ async function main() {
|
|
|
105
126
|
console.log('š Configured customers:\n');
|
|
106
127
|
listResult.customers.forEach(customer => {
|
|
107
128
|
console.log(`š¢ ${customer.name}`);
|
|
108
|
-
console.log(` Domain: ${customer.domain || 'Not
|
|
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'}`);
|
|
109
136
|
console.log(` Environments: ${customer.environments.join(', ')}`);
|
|
110
|
-
console.log(`
|
|
111
|
-
console.log(` Config: config/customers/${customer.name}/`);
|
|
137
|
+
console.log(` Config Path: config/customers/${customer.name}/`);
|
|
112
138
|
console.log('');
|
|
113
139
|
});
|
|
114
140
|
} else if (listResult.success) {
|
|
115
141
|
console.log('š No customers configured');
|
|
142
|
+
console.log('\nš” Tip: Run "clodo-customer-config create-customer <name>" to create your first customer');
|
|
116
143
|
} else {
|
|
117
144
|
console.error(`ā Failed to list customers: ${listResult.error}`);
|
|
118
145
|
process.exit(1);
|
|
@@ -121,6 +148,10 @@ async function main() {
|
|
|
121
148
|
|
|
122
149
|
default:
|
|
123
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');
|
|
124
155
|
console.log('Available commands:');
|
|
125
156
|
console.log(' create-customer <name> [domain] - Create new customer config from template');
|
|
126
157
|
console.log(' validate - Validate configuration structure');
|
|
@@ -132,9 +163,11 @@ async function main() {
|
|
|
132
163
|
console.log(' clodo-customer-config validate');
|
|
133
164
|
console.log(' clodo-customer-config show acmecorp production');
|
|
134
165
|
console.log(' clodo-customer-config list');
|
|
166
|
+
console.log(' clodo-customer-config --config-dir /path/to/service/config validate');
|
|
135
167
|
console.log('\nIntegration:');
|
|
136
168
|
console.log(' This tool integrates with Clodo Framework domain and feature flag systems.');
|
|
137
169
|
console.log(' Customer configurations are automatically registered as domains.');
|
|
170
|
+
console.log(' When run from a service directory, it uses ./config by default.');
|
|
138
171
|
break;
|
|
139
172
|
}
|
|
140
173
|
} catch (error) {
|
|
@@ -143,150 +176,6 @@ async function main() {
|
|
|
143
176
|
}
|
|
144
177
|
}
|
|
145
178
|
|
|
146
|
-
main().catch(error => {
|
|
147
|
-
console.error(`ā Unexpected error: ${error.message}`);
|
|
148
|
-
process.exit(1);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
async function handleCreateCustomer(args) {
|
|
152
|
-
const [customerName, domain] = args;
|
|
153
|
-
|
|
154
|
-
if (!customerName) {
|
|
155
|
-
console.error('Usage: customer-config create-customer <customer-name> [domain]');
|
|
156
|
-
process.exit(1);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
console.log(`šļø Creating customer configuration: ${customerName}`);
|
|
160
|
-
|
|
161
|
-
// Pass framework mode flag to skip strict validation
|
|
162
|
-
const customerInfo = await customerManager.createCustomer(customerName, domain, {
|
|
163
|
-
skipValidation: true,
|
|
164
|
-
isFrameworkMode: true
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
console.log(`\nš Customer ${customerName} configuration created successfully!`);
|
|
168
|
-
console.log(`\nš Customer Details:`);
|
|
169
|
-
console.log(` Name: ${customerInfo.name}`);
|
|
170
|
-
console.log(` Domain: ${customerInfo.domain || 'Not specified'}`);
|
|
171
|
-
console.log(` Config Path: ${customerInfo.configPath}`);
|
|
172
|
-
console.log(` Environments: ${customerInfo.environments.join(', ')}`);
|
|
173
|
-
|
|
174
|
-
console.log(`\nš Next steps:`);
|
|
175
|
-
console.log(`1. Review generated configs in: config/customers/${customerName}/`);
|
|
176
|
-
console.log(`2. Update domain-specific URLs if needed`);
|
|
177
|
-
console.log(`3. Generate production secrets: npm run security:generate-key ${customerName}`);
|
|
178
|
-
console.log(`4. Set production secrets: wrangler secret put KEY_NAME --env production`);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
async function handleValidate() {
|
|
182
|
-
console.log('š Validating customer configuration structure...\n');
|
|
183
|
-
|
|
184
|
-
const result = await customerManager.validateConfigs();
|
|
185
|
-
|
|
186
|
-
if (result.valid) {
|
|
187
|
-
console.log('ā
All customer configurations are valid');
|
|
188
|
-
} else {
|
|
189
|
-
console.log('ā Configuration validation failed');
|
|
190
|
-
result.errors.forEach(error => console.log(` - ${error}`));
|
|
191
|
-
process.exit(1);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async function handleShow(args) {
|
|
196
|
-
const [customerName, environment] = args;
|
|
197
|
-
|
|
198
|
-
if (!customerName || !environment) {
|
|
199
|
-
console.error('Usage: customer-config show <customer> <environment>');
|
|
200
|
-
process.exit(1);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const config = customerManager.showConfig(customerName, environment);
|
|
204
|
-
|
|
205
|
-
console.log(`š Effective configuration: ${customerName}/${environment}\n`);
|
|
206
|
-
|
|
207
|
-
if (config.variables.base) {
|
|
208
|
-
console.log('š Base variables:');
|
|
209
|
-
Object.entries(config.variables.base).slice(0, 10).forEach(([key, value]) => {
|
|
210
|
-
console.log(` ${key}=${value}`);
|
|
211
|
-
});
|
|
212
|
-
if (Object.keys(config.variables.base).length > 10) {
|
|
213
|
-
console.log(' ...');
|
|
214
|
-
}
|
|
215
|
-
console.log('');
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (config.variables.customer) {
|
|
219
|
-
console.log(`š Customer ${environment} variables:`);
|
|
220
|
-
Object.entries(config.variables.customer).slice(0, 15).forEach(([key, value]) => {
|
|
221
|
-
console.log(` ${key}=${value}`);
|
|
222
|
-
});
|
|
223
|
-
if (Object.keys(config.variables.customer).length > 15) {
|
|
224
|
-
console.log(' ...');
|
|
225
|
-
}
|
|
226
|
-
console.log('');
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (config.features && Object.keys(config.features).length > 0) {
|
|
230
|
-
console.log('š© Customer features:');
|
|
231
|
-
Object.entries(config.features).forEach(([feature, enabled]) => {
|
|
232
|
-
console.log(` ${feature}: ${enabled ? 'ā
' : 'ā'}`);
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
async function handleDeployCommand(args) {
|
|
238
|
-
const [customerName, environment] = args;
|
|
239
|
-
|
|
240
|
-
if (!customerName || !environment) {
|
|
241
|
-
console.error('Usage: customer-config deploy-command <customer> <environment>');
|
|
242
|
-
process.exit(1);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const deployInfo = customerManager.getDeployCommand(customerName, environment);
|
|
246
|
-
|
|
247
|
-
console.log(`š Deploy command for ${customerName}/${environment}:`);
|
|
248
|
-
console.log(` ${deployInfo.command}`);
|
|
249
|
-
console.log(`\nš” Ensure customer config is loaded: ${deployInfo.configPath}`);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
async function handleList() {
|
|
253
|
-
const customers = customerManager.listCustomers();
|
|
254
|
-
|
|
255
|
-
if (customers.length === 0) {
|
|
256
|
-
console.log('š No customers configured');
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
console.log('š Configured customers:\n');
|
|
261
|
-
|
|
262
|
-
customers.forEach(customer => {
|
|
263
|
-
console.log(`š¢ ${customer.name}`);
|
|
264
|
-
console.log(` Domain: ${customer.domain || 'Not specified'}`);
|
|
265
|
-
console.log(` Environments: ${customer.environments.join(', ')}`);
|
|
266
|
-
console.log(` Created: ${customer.createdAt}`);
|
|
267
|
-
console.log(` Config: config/customers/${customer.name}/`);
|
|
268
|
-
console.log('');
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function showHelp() {
|
|
273
|
-
console.log('Customer Configuration Management Tool\n');
|
|
274
|
-
console.log('Available commands:');
|
|
275
|
-
console.log(' create-customer <name> [domain] - Create new customer config from template');
|
|
276
|
-
console.log(' validate - Validate configuration structure');
|
|
277
|
-
console.log(' show <customer> <environment> - Show effective configuration');
|
|
278
|
-
console.log(' deploy-command <customer> <env> - Get deployment command');
|
|
279
|
-
console.log(' list - List all configured customers');
|
|
280
|
-
console.log('\nExamples:');
|
|
281
|
-
console.log(' customer-config create-customer acmecorp acmecorp.com');
|
|
282
|
-
console.log(' customer-config validate');
|
|
283
|
-
console.log(' customer-config show acmecorp production');
|
|
284
|
-
console.log(' customer-config list');
|
|
285
|
-
console.log('\nIntegration:');
|
|
286
|
-
console.log(' This tool integrates with Clodo Framework domain and feature flag systems.');
|
|
287
|
-
console.log(' Customer configurations are automatically registered as domains.');
|
|
288
|
-
}
|
|
289
|
-
|
|
290
179
|
main().catch(error => {
|
|
291
180
|
console.error(`ā Unexpected error: ${error.message}`);
|
|
292
181
|
process.exit(1);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* User input-driven configuration setup for deployment workflows
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { askChoice, askUser } from '
|
|
6
|
+
import { askChoice, askUser } from '../../bin/shared/utils/interactive-prompts.js';
|
|
7
7
|
export class InteractiveDeploymentConfigurator {
|
|
8
8
|
/**
|
|
9
9
|
* Generate configuration from user input
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
import { CustomerConfigurationManager } from '../config/customers.js';
|
|
7
7
|
export class CustomerConfigCLI {
|
|
8
|
-
constructor() {
|
|
9
|
-
this.customerManager = new CustomerConfigurationManager();
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.customerManager = new CustomerConfigurationManager(options);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -197,24 +197,30 @@ Integration:
|
|
|
197
197
|
|
|
198
198
|
// Convenience functions for direct use
|
|
199
199
|
export async function createCustomer(customerName, domain, options = {}) {
|
|
200
|
-
const
|
|
200
|
+
const {
|
|
201
|
+
configDir,
|
|
202
|
+
...createOptions
|
|
203
|
+
} = options;
|
|
204
|
+
const cli = new CustomerConfigCLI({
|
|
205
|
+
configDir
|
|
206
|
+
});
|
|
201
207
|
await cli.initialize();
|
|
202
|
-
return await cli.createCustomer(customerName, domain,
|
|
208
|
+
return await cli.createCustomer(customerName, domain, createOptions);
|
|
203
209
|
}
|
|
204
|
-
export async function validateCustomerConfigs() {
|
|
205
|
-
const cli = new CustomerConfigCLI();
|
|
210
|
+
export async function validateCustomerConfigs(options = {}) {
|
|
211
|
+
const cli = new CustomerConfigCLI(options);
|
|
206
212
|
await cli.initialize();
|
|
207
213
|
return await cli.validateConfigurations();
|
|
208
214
|
}
|
|
209
|
-
export function showCustomerConfig(customerName, environment) {
|
|
210
|
-
const cli = new CustomerConfigCLI();
|
|
215
|
+
export function showCustomerConfig(customerName, environment, options = {}) {
|
|
216
|
+
const cli = new CustomerConfigCLI(options);
|
|
211
217
|
return cli.showConfiguration(customerName, environment);
|
|
212
218
|
}
|
|
213
|
-
export function getCustomerDeployCommand(customerName, environment) {
|
|
214
|
-
const cli = new CustomerConfigCLI();
|
|
219
|
+
export function getCustomerDeployCommand(customerName, environment, options = {}) {
|
|
220
|
+
const cli = new CustomerConfigCLI(options);
|
|
215
221
|
return cli.getDeployCommand(customerName, environment);
|
|
216
222
|
}
|
|
217
|
-
export function listConfiguredCustomers() {
|
|
218
|
-
const cli = new CustomerConfigCLI();
|
|
223
|
+
export function listConfiguredCustomers(options = {}) {
|
|
224
|
+
const cli = new CustomerConfigCLI(options);
|
|
219
225
|
return cli.listCustomers();
|
|
220
226
|
}
|
package/dist/config/customers.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
2
2
|
// eslint-disable-next-line no-unused-vars
|
|
3
3
|
import { resolve, join } from 'path';
|
|
4
|
+
import toml from '@iarna/toml';
|
|
4
5
|
import { createDomainConfigSchema, validateDomainConfig, createDomainRegistry } from './domains.js';
|
|
5
6
|
import { createLogger } from '../utils/index.js';
|
|
6
7
|
import { getDirname } from '../utils/esm-helper.js';
|
|
@@ -336,6 +337,21 @@ export class CustomerConfigurationManager {
|
|
|
336
337
|
return;
|
|
337
338
|
}
|
|
338
339
|
try {
|
|
340
|
+
// First, try to read from root wrangler.toml (real deployment config)
|
|
341
|
+
const rootWranglerPath = resolve(this.configDir, '..', 'wrangler.toml');
|
|
342
|
+
let wranglerConfig = null;
|
|
343
|
+
let globalAccountId = null;
|
|
344
|
+
if (existsSync(rootWranglerPath)) {
|
|
345
|
+
try {
|
|
346
|
+
const wranglerContent = readFileSync(rootWranglerPath, 'utf8');
|
|
347
|
+
wranglerConfig = toml.parse(wranglerContent);
|
|
348
|
+
globalAccountId = wranglerConfig.account_id;
|
|
349
|
+
logger.info(`Loaded wrangler.toml with account_id: ${globalAccountId ? globalAccountId.substring(0, 8) + '...' : 'not found'}`);
|
|
350
|
+
} catch (error) {
|
|
351
|
+
logger.warn('Could not parse root wrangler.toml:', error.message);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
339
355
|
// Read customer directories
|
|
340
356
|
const customerDirs = this.getCustomerDirectories();
|
|
341
357
|
for (const customerName of customerDirs) {
|
|
@@ -343,27 +359,80 @@ export class CustomerConfigurationManager {
|
|
|
343
359
|
if (customerName === 'template') continue;
|
|
344
360
|
const customerDir = resolve(customersDir, customerName);
|
|
345
361
|
|
|
346
|
-
//
|
|
362
|
+
// Initialize customer metadata
|
|
347
363
|
const metadata = {
|
|
348
364
|
name: customerName,
|
|
349
365
|
createdAt: new Date().toISOString(),
|
|
350
|
-
|
|
351
|
-
|
|
366
|
+
environments: this.environments,
|
|
367
|
+
accountId: globalAccountId // Start with global account ID
|
|
352
368
|
};
|
|
353
369
|
|
|
354
|
-
// Try to
|
|
370
|
+
// Try to load from wrangler.toml [env.production] section
|
|
371
|
+
if (wranglerConfig && wranglerConfig.env && wranglerConfig.env.production) {
|
|
372
|
+
const prodEnv = wranglerConfig.env.production;
|
|
373
|
+
|
|
374
|
+
// Extract SERVICE_DOMAIN (maps to customer name usually)
|
|
375
|
+
if (prodEnv.vars && prodEnv.vars.SERVICE_DOMAIN) {
|
|
376
|
+
metadata.serviceDomain = prodEnv.vars.SERVICE_DOMAIN;
|
|
377
|
+
|
|
378
|
+
// If SERVICE_DOMAIN matches customer name, this is their config
|
|
379
|
+
if (prodEnv.vars.SERVICE_DOMAIN === customerName) {
|
|
380
|
+
// Extract database info
|
|
381
|
+
if (prodEnv.d1_databases && prodEnv.d1_databases.length > 0) {
|
|
382
|
+
const db = prodEnv.d1_databases[0];
|
|
383
|
+
metadata.databaseId = db.database_id;
|
|
384
|
+
metadata.databaseName = db.database_name;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Extract zone_id if present
|
|
388
|
+
if (prodEnv.zone_id) {
|
|
389
|
+
metadata.zoneId = prodEnv.zone_id;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Extract route to infer domain
|
|
393
|
+
if (prodEnv.route) {
|
|
394
|
+
// Route format: "example.com/*" or "*.example.com/*"
|
|
395
|
+
const domain = prodEnv.route.replace(/\/\*$/, '').replace(/^\*\./, '');
|
|
396
|
+
metadata.domain = domain;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Read customer-specific env file to get CUSTOMER_DOMAIN and other info
|
|
355
403
|
const prodConfigPath = resolve(customerDir, 'production.env');
|
|
356
404
|
if (existsSync(prodConfigPath)) {
|
|
357
405
|
try {
|
|
358
406
|
const prodConfig = this.parseEnvFile(prodConfigPath);
|
|
359
|
-
|
|
407
|
+
|
|
408
|
+
// Use CUSTOMER_DOMAIN (correct field name in real configs)
|
|
409
|
+
if (prodConfig.CUSTOMER_DOMAIN) {
|
|
410
|
+
metadata.customerDomain = prodConfig.CUSTOMER_DOMAIN.replace(/^https?:\/\//, '');
|
|
411
|
+
// If we didn't get domain from wrangler.toml, use this
|
|
412
|
+
if (!metadata.domain) {
|
|
413
|
+
metadata.domain = metadata.customerDomain;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Also check old DOMAIN field for backward compatibility
|
|
418
|
+
if (!metadata.domain && prodConfig.DOMAIN) {
|
|
360
419
|
metadata.domain = prodConfig.DOMAIN.replace(/^https?:\/\//, '');
|
|
361
420
|
}
|
|
421
|
+
|
|
422
|
+
// Extract customer ID
|
|
423
|
+
if (prodConfig.CUSTOMER_ID) {
|
|
424
|
+
metadata.customerId = prodConfig.CUSTOMER_ID;
|
|
425
|
+
}
|
|
362
426
|
} catch (error) {
|
|
363
|
-
logger.warn(`Could not parse
|
|
427
|
+
logger.warn(`Could not parse customer env for ${customerName}:`, error.message);
|
|
364
428
|
}
|
|
365
429
|
}
|
|
366
430
|
|
|
431
|
+
// Check if secrets exist (we can't read them, but can note they should be set)
|
|
432
|
+
// In a real system, you'd run: wrangler secret list --env production
|
|
433
|
+
// For now, we just note that secrets should be managed separately
|
|
434
|
+
metadata.hasSecrets = true; // Assume secrets are managed via wrangler secret commands
|
|
435
|
+
|
|
367
436
|
// Register customer
|
|
368
437
|
this.customers.set(customerName, metadata);
|
|
369
438
|
|
|
@@ -371,7 +440,9 @@ export class CustomerConfigurationManager {
|
|
|
371
440
|
try {
|
|
372
441
|
const domainConfig = this.createCustomerDomainConfig(customerName, metadata.domain, {
|
|
373
442
|
skipValidation: true,
|
|
374
|
-
isFrameworkMode: true
|
|
443
|
+
isFrameworkMode: true,
|
|
444
|
+
accountId: metadata.accountId,
|
|
445
|
+
zoneId: metadata.zoneId
|
|
375
446
|
});
|
|
376
447
|
this.domainRegistry.add(customerName, domainConfig);
|
|
377
448
|
} catch (error) {
|
|
@@ -423,6 +494,111 @@ export class CustomerConfigurationManager {
|
|
|
423
494
|
return variables;
|
|
424
495
|
}
|
|
425
496
|
|
|
497
|
+
/**
|
|
498
|
+
* Update wrangler.toml with new configuration
|
|
499
|
+
* @param {string} wranglerPath - Path to wrangler.toml
|
|
500
|
+
* @param {Object} updates - Configuration updates to merge
|
|
501
|
+
* @returns {boolean} Success status
|
|
502
|
+
*/
|
|
503
|
+
updateWranglerToml(wranglerPath, updates) {
|
|
504
|
+
try {
|
|
505
|
+
let existingConfig = {};
|
|
506
|
+
|
|
507
|
+
// Read existing config if file exists
|
|
508
|
+
if (existsSync(wranglerPath)) {
|
|
509
|
+
const content = readFileSync(wranglerPath, 'utf8');
|
|
510
|
+
existingConfig = toml.parse(content);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Deep merge updates into existing config
|
|
514
|
+
const mergedConfig = this.deepMergeConfig(existingConfig, updates);
|
|
515
|
+
|
|
516
|
+
// Write back to file
|
|
517
|
+
const tomlContent = toml.stringify(mergedConfig);
|
|
518
|
+
writeFileSync(wranglerPath, tomlContent, 'utf8');
|
|
519
|
+
logger.info(`Updated wrangler.toml: ${wranglerPath}`);
|
|
520
|
+
return true;
|
|
521
|
+
} catch (error) {
|
|
522
|
+
logger.error(`Failed to update wrangler.toml: ${error.message}`);
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Deep merge two configuration objects
|
|
529
|
+
* @private
|
|
530
|
+
*/
|
|
531
|
+
deepMergeConfig(target, source) {
|
|
532
|
+
const output = {
|
|
533
|
+
...target
|
|
534
|
+
};
|
|
535
|
+
for (const key in source) {
|
|
536
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
537
|
+
// Recursively merge objects
|
|
538
|
+
output[key] = this.deepMergeConfig(target[key] || {}, source[key]);
|
|
539
|
+
} else {
|
|
540
|
+
// Overwrite arrays and primitives
|
|
541
|
+
output[key] = source[key];
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return output;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Create or update environment section in wrangler.toml
|
|
549
|
+
* @param {string} wranglerPath - Path to wrangler.toml
|
|
550
|
+
* @param {string} environment - Environment name (production, staging, development)
|
|
551
|
+
* @param {Object} envConfig - Environment configuration
|
|
552
|
+
* @returns {boolean} Success status
|
|
553
|
+
*/
|
|
554
|
+
updateEnvironmentConfig(wranglerPath, environment, envConfig) {
|
|
555
|
+
const updates = {
|
|
556
|
+
env: {
|
|
557
|
+
[environment]: envConfig
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
return this.updateWranglerToml(wranglerPath, updates);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Add D1 database binding to environment
|
|
565
|
+
* @param {string} wranglerPath - Path to wrangler.toml
|
|
566
|
+
* @param {string} environment - Environment name
|
|
567
|
+
* @param {Object} databaseConfig - Database configuration
|
|
568
|
+
* @returns {boolean} Success status
|
|
569
|
+
*/
|
|
570
|
+
addD1Database(wranglerPath, environment, databaseConfig) {
|
|
571
|
+
try {
|
|
572
|
+
const content = readFileSync(wranglerPath, 'utf8');
|
|
573
|
+
const config = toml.parse(content);
|
|
574
|
+
|
|
575
|
+
// Ensure env section exists
|
|
576
|
+
if (!config.env) config.env = {};
|
|
577
|
+
if (!config.env[environment]) config.env[environment] = {};
|
|
578
|
+
|
|
579
|
+
// Ensure d1_databases array exists
|
|
580
|
+
if (!config.env[environment].d1_databases) {
|
|
581
|
+
config.env[environment].d1_databases = [];
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Add or update database
|
|
585
|
+
const existingIndex = config.env[environment].d1_databases.findIndex(db => db.binding === databaseConfig.binding);
|
|
586
|
+
if (existingIndex >= 0) {
|
|
587
|
+
config.env[environment].d1_databases[existingIndex] = databaseConfig;
|
|
588
|
+
} else {
|
|
589
|
+
config.env[environment].d1_databases.push(databaseConfig);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Write back
|
|
593
|
+
writeFileSync(wranglerPath, toml.stringify(config), 'utf8');
|
|
594
|
+
logger.info(`Added D1 database to ${environment} environment`);
|
|
595
|
+
return true;
|
|
596
|
+
} catch (error) {
|
|
597
|
+
logger.error(`Failed to add D1 database: ${error.message}`);
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
426
602
|
/**
|
|
427
603
|
* Capitalize first letter
|
|
428
604
|
*/
|
|
@@ -615,5 +615,26 @@ export class WranglerDeployer {
|
|
|
615
615
|
async checkD1DatabaseExists(nameOrId) {
|
|
616
616
|
return await this.d1Manager.checkD1DatabaseExists(nameOrId);
|
|
617
617
|
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Secret management methods
|
|
621
|
+
*
|
|
622
|
+
* NOTE: For secret management operations, use the Cloudflare ops module:
|
|
623
|
+
*
|
|
624
|
+
* import { deploySecret, listSecrets, deleteSecret } from '../bin/shared/cloudflare/ops.js';
|
|
625
|
+
*
|
|
626
|
+
* The ops.js module provides:
|
|
627
|
+
* - deploySecret(key, value, env)
|
|
628
|
+
* - listSecrets(env)
|
|
629
|
+
* - deleteSecret(key, env)
|
|
630
|
+
*
|
|
631
|
+
* With built-in:
|
|
632
|
+
* - Rate limiting
|
|
633
|
+
* - Error recovery
|
|
634
|
+
* - Production monitoring
|
|
635
|
+
* - Retry logic
|
|
636
|
+
*
|
|
637
|
+
* This avoids code duplication and ensures consistent secret handling across the framework.
|
|
638
|
+
*/
|
|
618
639
|
}
|
|
619
640
|
export default WranglerDeployer;
|
|
@@ -189,43 +189,48 @@ export class MultiDomainOrchestrator {
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
/**
|
|
192
|
-
*
|
|
193
|
-
* @deprecated Use deploymentCoordinator.initializeDomainDeployment() instead
|
|
192
|
+
* Initialize domain deployment (placeholder implementation)
|
|
194
193
|
*/
|
|
195
194
|
async initializeDomainDeployment(domain) {
|
|
196
|
-
|
|
195
|
+
// Placeholder: Add actual initialization logic here
|
|
196
|
+
console.log(` š§ Initializing deployment for ${domain}`);
|
|
197
|
+
return true;
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
/**
|
|
200
|
-
*
|
|
201
|
-
* @deprecated Use deploymentCoordinator.setupDomainDatabase() instead
|
|
201
|
+
* Setup domain database (placeholder implementation)
|
|
202
202
|
*/
|
|
203
203
|
async setupDomainDatabase(domain) {
|
|
204
|
-
|
|
204
|
+
// Placeholder: Add actual database setup logic here
|
|
205
|
+
console.log(` šļø Setting up database for ${domain}`);
|
|
206
|
+
return true;
|
|
205
207
|
}
|
|
206
208
|
|
|
207
209
|
/**
|
|
208
|
-
*
|
|
209
|
-
* @deprecated Use deploymentCoordinator.handleDomainSecrets() instead
|
|
210
|
+
* Handle domain secrets (placeholder implementation)
|
|
210
211
|
*/
|
|
211
212
|
async handleDomainSecrets(domain) {
|
|
212
|
-
|
|
213
|
+
// Placeholder: Add actual secrets handling logic here
|
|
214
|
+
console.log(` š Handling secrets for ${domain}`);
|
|
215
|
+
return true;
|
|
213
216
|
}
|
|
214
217
|
|
|
215
218
|
/**
|
|
216
|
-
*
|
|
217
|
-
* @deprecated Use deploymentCoordinator.deployDomainWorker() instead
|
|
219
|
+
* Deploy domain worker (placeholder implementation)
|
|
218
220
|
*/
|
|
219
221
|
async deployDomainWorker(domain) {
|
|
220
|
-
|
|
222
|
+
// Placeholder: Add actual worker deployment logic here
|
|
223
|
+
console.log(` š Deploying worker for ${domain}`);
|
|
224
|
+
return true;
|
|
221
225
|
}
|
|
222
226
|
|
|
223
227
|
/**
|
|
224
|
-
*
|
|
225
|
-
* @deprecated Use deploymentCoordinator.validateDomainDeployment() instead
|
|
228
|
+
* Validate domain deployment (placeholder implementation)
|
|
226
229
|
*/
|
|
227
230
|
async validateDomainDeployment(domain) {
|
|
228
|
-
|
|
231
|
+
// Placeholder: Add actual deployment validation logic here
|
|
232
|
+
console.log(` ā
Validating deployment for ${domain}`);
|
|
233
|
+
return true;
|
|
229
234
|
}
|
|
230
235
|
|
|
231
236
|
/**
|