@tamyla/clodo-framework 4.0.6 → 4.0.8

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 CHANGED
@@ -1,3 +1,20 @@
1
+ ## [4.0.8](https://github.com/tamylaa/clodo-framework/compare/v4.0.7...v4.0.8) (2025-12-09)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * respect user-provided worker and database names in deployment ([04ffc38](https://github.com/tamylaa/clodo-framework/commit/04ffc382ab910d4d260ba5c3f2e421763587948a))
7
+
8
+ ## [4.0.7](https://github.com/tamylaa/clodo-framework/compare/v4.0.6...v4.0.7) (2025-12-09)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * correct dynamic import paths in interactive-validation.js ([acbadae](https://github.com/tamylaa/clodo-framework/commit/acbadaefc10cf7efab9dd263f7a40f8db18fccb4))
14
+ * correct import path in interactive-validation.js ([8355677](https://github.com/tamylaa/clodo-framework/commit/83556777a0faa1a85766c0bf4795428d8ba807e6))
15
+ * correct remaining dynamic import paths in interactive-validation.js ([837f0a4](https://github.com/tamylaa/clodo-framework/commit/837f0a48489dbde6b644944950232ec0f424639f))
16
+ * correct Windows PowerShell environment variable handling ([f3c5354](https://github.com/tamylaa/clodo-framework/commit/f3c535449f744a2448b4c1b7d98fc62853a8eaab))
17
+
1
18
  ## [4.0.6](https://github.com/tamylaa/clodo-framework/compare/v4.0.5...v4.0.6) (2025-12-09)
2
19
 
3
20
 
@@ -171,12 +171,18 @@ export async function listWorkers(options = {}) {
171
171
 
172
172
  // Fallback to CLI-based operation
173
173
  try {
174
- const envVars = apiToken ? `CLOUDFLARE_API_TOKEN=${apiToken}` : '';
175
- const command = `${envVars} npx wrangler list`;
176
- const {
177
- stdout: list
178
- } = await executeWithRateLimit(command, 'workers');
179
- return list;
174
+ if (apiToken) {
175
+ const command = process.platform === 'win32' ? `powershell -Command "$env:CLOUDFLARE_API_TOKEN = '${apiToken}'; npx wrangler list"` : `CLOUDFLARE_API_TOKEN=${apiToken} npx wrangler list`;
176
+ const {
177
+ stdout: list
178
+ } = await executeWithRateLimit(command, 'workers');
179
+ return list;
180
+ } else {
181
+ const {
182
+ stdout: list
183
+ } = await executeWithRateLimit('npx wrangler list', 'workers');
184
+ return list;
185
+ }
180
186
  } catch (error) {
181
187
  throw new Error(`Failed to list workers: ${error.message}`);
182
188
  }
@@ -244,40 +250,40 @@ export async function deploySecret(key, value, env = 'production', options = {})
244
250
  }
245
251
 
246
252
  // Fallback to CLI-based operation with API token if available
247
- const envVars = apiToken ? `CLOUDFLARE_API_TOKEN=${apiToken}` : '';
248
- const command = process.platform === 'win32' ? `${envVars} powershell -Command "Write-Output '${value}' | npx wrangler secret put ${key} --env ${env}"` : `${envVars} echo "${value}" | npx wrangler secret put ${key} --env ${env}`;
249
- try {
250
- // Increase retries for secrets due to higher timeout likelihood
253
+ if (apiToken) {
254
+ const command = process.platform === 'win32' ? `powershell -Command "$env:CLOUDFLARE_API_TOKEN = '${apiToken}'; Write-Output '${value}' | npx wrangler secret put ${key} --env ${env}"` : `CLOUDFLARE_API_TOKEN=${apiToken} echo "${value}" | npx wrangler secret put ${key} --env ${env}`;
255
+ await executeWithRateLimit(command, 'workers', 5);
256
+ } else {
257
+ const command = process.platform === 'win32' ? `powershell -Command "Write-Output '${value}' | npx wrangler secret put ${key} --env ${env}"` : `echo "${value}" | npx wrangler secret put ${key} --env ${env}`;
251
258
  await executeWithRateLimit(command, 'workers', 5);
252
- } catch (error) {
253
- throw new Error(`Secret deployment failed: ${error.message}`);
254
259
  }
255
260
  }
256
261
  export async function deleteSecret(key, env = 'production', options = {}) {
257
262
  const {
258
263
  apiToken
259
264
  } = options;
260
- const envVars = apiToken ? `CLOUDFLARE_API_TOKEN=${apiToken}` : '';
261
- const command = `${envVars} npx wrangler secret delete ${key} --env ${env}`;
262
- try {
265
+ if (apiToken) {
266
+ const command = process.platform === 'win32' ? `powershell -Command "$env:CLOUDFLARE_API_TOKEN = '${apiToken}'; npx wrangler secret delete ${key} --env ${env}"` : `CLOUDFLARE_API_TOKEN=${apiToken} npx wrangler secret delete ${key} --env ${env}`;
263
267
  await executeWithRateLimit(command, 'workers');
264
- } catch (error) {
265
- throw new Error(`Secret deletion failed: ${error.message}`);
268
+ } else {
269
+ await executeWithRateLimit(`npx wrangler secret delete ${key} --env ${env}`, 'workers');
266
270
  }
267
271
  }
268
272
  export async function listSecrets(env = 'production', options = {}) {
269
273
  const {
270
274
  apiToken
271
275
  } = options;
272
- const envVars = apiToken ? `CLOUDFLARE_API_TOKEN=${apiToken}` : '';
273
- const command = `${envVars} npx wrangler secret list --env ${env}`;
274
- try {
276
+ if (apiToken) {
277
+ const command = process.platform === 'win32' ? `powershell -Command "$env:CLOUDFLARE_API_TOKEN = '${apiToken}'; npx wrangler secret list --env ${env}"` : `CLOUDFLARE_API_TOKEN=${apiToken} npx wrangler secret list --env ${env}`;
275
278
  const {
276
279
  stdout: list
277
280
  } = await executeWithRateLimit(command, 'workers');
278
281
  return list;
279
- } catch (error) {
280
- throw new Error(`Failed to list secrets: ${error.message}`);
282
+ } else {
283
+ const {
284
+ stdout: list
285
+ } = await executeWithRateLimit(`npx wrangler secret list --env ${env}`, 'workers');
286
+ return list;
281
287
  }
282
288
  }
283
289
  export async function listDatabases(options = {}) {
@@ -346,16 +352,26 @@ export async function createDatabase(name, options = {}) {
346
352
 
347
353
  // Fallback to CLI-based operation
348
354
  try {
349
- const envVars = apiToken ? `CLOUDFLARE_API_TOKEN=${apiToken}` : '';
350
- const command = `${envVars} npx wrangler d1 create ${name}`;
351
- const {
352
- stdout: output
353
- } = await executeWithRateLimit(command, 'd1');
354
- const idMatch = output.match(/database_id = "([^"]+)"/);
355
- if (!idMatch) {
356
- throw new Error('Could not extract database ID from creation output');
355
+ if (apiToken) {
356
+ const command = process.platform === 'win32' ? `powershell -Command "$env:CLOUDFLARE_API_TOKEN = '${apiToken}'; npx wrangler d1 create ${name}"` : `CLOUDFLARE_API_TOKEN=${apiToken} npx wrangler d1 create ${name}`;
357
+ const {
358
+ stdout: output
359
+ } = await executeWithRateLimit(command, 'd1');
360
+ const idMatch = output.match(/database_id = "([^"]+)"/);
361
+ if (!idMatch) {
362
+ throw new Error('Could not extract database ID from creation output');
363
+ }
364
+ return idMatch[1];
365
+ } else {
366
+ const {
367
+ stdout: output
368
+ } = await executeWithRateLimit(`npx wrangler d1 create ${name}`, 'd1');
369
+ const idMatch = output.match(/database_id = "([^"]+)"/);
370
+ if (!idMatch) {
371
+ throw new Error('Could not extract database ID from creation output');
372
+ }
373
+ return idMatch[1];
357
374
  }
358
- return idMatch[1];
359
375
  } catch (error) {
360
376
  throw new Error(`Database creation failed: ${error.message}`);
361
377
  }
@@ -364,12 +380,13 @@ export async function deleteDatabase(name, options = {}) {
364
380
  const {
365
381
  apiToken
366
382
  } = options;
367
- const envVars = apiToken ? `CLOUDFLARE_API_TOKEN=${apiToken}` : '';
368
- const command = `${envVars} npx wrangler d1 delete ${name} --skip-confirmation`;
369
- try {
383
+ if (apiToken) {
384
+ // For Windows PowerShell, set environment variable within the command
385
+ const command = process.platform === 'win32' ? `powershell -Command "$env:CLOUDFLARE_API_TOKEN = '${apiToken}'; npx wrangler d1 delete ${name} --skip-confirmation"` : `CLOUDFLARE_API_TOKEN=${apiToken} npx wrangler d1 delete ${name} --skip-confirmation`;
370
386
  await executeWithRateLimit(command, 'd1');
371
- } catch (error) {
372
- throw new Error(`Database deletion failed: ${error.message}`);
387
+ } else {
388
+ // No API token provided, use default wrangler auth
389
+ await executeWithRateLimit(`npx wrangler d1 delete ${name} --skip-confirmation`, 'd1');
373
390
  }
374
391
  }
375
392
  export async function runMigrations(databaseName, env = 'production', options = {}) {
@@ -380,9 +397,12 @@ export async function runMigrations(databaseName, env = 'production', options =
380
397
  try {
381
398
  await ensureMonitoringInitialized();
382
399
  const result = await errorRecovery.executeWithRecovery(async () => {
383
- const envVars = apiToken ? `CLOUDFLARE_API_TOKEN=${apiToken}` : '';
384
- const command = `${envVars} npx wrangler d1 migrations apply ${databaseName} --env ${env} --remote`;
385
- await executeWithRateLimit(command, 'd1');
400
+ if (apiToken) {
401
+ const command = process.platform === 'win32' ? `powershell -Command "$env:CLOUDFLARE_API_TOKEN = '${apiToken}'; npx wrangler d1 migrations apply ${databaseName} --env ${env} --remote"` : `CLOUDFLARE_API_TOKEN=${apiToken} npx wrangler d1 migrations apply ${databaseName} --env ${env} --remote`;
402
+ await executeWithRateLimit(command, 'd1');
403
+ } else {
404
+ await executeWithRateLimit(`npx wrangler d1 migrations apply ${databaseName} --env ${env} --remote`, 'd1');
405
+ }
386
406
  return true;
387
407
  }, {
388
408
  operationId: `runMigrations_${databaseName}_${env}`
@@ -209,11 +209,19 @@ export class InteractiveDeploymentCoordinator {
209
209
  async executeDeployment() {
210
210
  console.log('🚀 Phase 6: Executing Deployment');
211
211
  console.log('─'.repeat(50));
212
+ const workerName = this.deploymentState.config.worker?.name;
213
+ const databaseName = this.deploymentState.resources.database?.name;
214
+ console.log(` 🔍 DEBUG: workerName from state: "${workerName}"`);
215
+ console.log(` 🔍 DEBUG: databaseName from state: "${databaseName}"`);
212
216
  const result = await Clodo.deploy({
213
217
  servicePath: this.deploymentState.config.servicePath,
214
218
  environment: this.deploymentState.config.environment,
215
219
  domain: this.deploymentState.config.credentials.zoneName,
216
220
  serviceName: this.options.serviceName,
221
+ workerName: workerName,
222
+ // Pass the collected worker name
223
+ databaseName: databaseName,
224
+ // Pass the collected database name
217
225
  dryRun: this.deploymentState.config.dryRun,
218
226
  credentials: this.deploymentState.config.credentials
219
227
  });
@@ -129,7 +129,7 @@ export class InteractiveValidationWorkflow {
129
129
  console.log('===========================');
130
130
  const {
131
131
  askChoice
132
- } = await import('../utils/prompt-utils.js');
132
+ } = await import('../../utils/interactive-prompts.js');
133
133
  const choice = await askChoice('How would you like to handle the existing worker?', ['🔄 Overwrite/Update - Deploy new version (recommended)', '📝 Rename - Create with a different worker name', '🔍 Compare - Show differences before deciding', '💾 Backup & Update - Create backup before overwriting', '❌ Cancel - Stop deployment'], 0);
134
134
  switch (choice) {
135
135
  case 0:
@@ -168,7 +168,7 @@ export class InteractiveValidationWorkflow {
168
168
  async promptForNewWorkerName(currentName) {
169
169
  const {
170
170
  askUser
171
- } = await import('../utils/prompt-utils.js');
171
+ } = await import('../../utils/interactive-prompts.js');
172
172
  let newName = await askUser(`Enter new worker name (current: ${currentName})`);
173
173
  newName = newName.trim();
174
174
  if (!newName) {
@@ -36,6 +36,8 @@ export class MultiDomainOrchestrator {
36
36
  this.parallelDeployments = options.parallelDeployments || 3;
37
37
  this.servicePath = options.servicePath || process.cwd();
38
38
  this.serviceName = options.serviceName || 'data-service'; // Service name for custom domain (e.g., 'data-service', 'auth-service')
39
+ this.workerName = options.workerName; // Specific worker name to use (optional)
40
+ this.databaseName = options.databaseName; // Specific database name to use (optional)
39
41
 
40
42
  // Wrangler config path - allows using customer-specific wrangler.toml files
41
43
  // If not specified, wrangler uses the default wrangler.toml in servicePath
@@ -299,7 +301,7 @@ export class MultiDomainOrchestrator {
299
301
  console.log(` 🗄️ Setting up database for ${domain}`);
300
302
  if (this.dryRun) {
301
303
  console.log(` � DRY RUN: Would create database for ${domain}`);
302
- const databaseName = `${domain.replace(/\./g, '-')}-${this.environment}-db`;
304
+ const databaseName = this.databaseName || `${domain.replace(/\./g, '-')}-${this.environment}-db`;
303
305
  return {
304
306
  databaseName,
305
307
  databaseId: 'dry-run-id',
@@ -308,7 +310,8 @@ export class MultiDomainOrchestrator {
308
310
  }
309
311
  try {
310
312
  // Create D1 database using Cloudflare ops
311
- const databaseName = `${domain.replace(/\./g, '-')}-${this.environment}-db`;
313
+ // Use provided database name, or generate one based on domain/environment
314
+ const databaseName = this.databaseName || `${domain.replace(/\./g, '-')}-${this.environment}-db`;
312
315
 
313
316
  // Check if database already exists
314
317
  console.log(` � Checking if database exists: ${databaseName}`);
@@ -540,11 +543,13 @@ export class MultiDomainOrchestrator {
540
543
  // 2. Root wrangler.toml is ephemeral (reflects current active deployment)
541
544
  if (this.cloudflareZoneName) {
542
545
  console.log(` 🔧 Preparing customer config for zone: ${this.cloudflareZoneName}`);
546
+ console.log(` 🔍 DEBUG: this.workerName is "${this.workerName}"`);
543
547
 
544
548
  // Generate or update customer config with current deployment parameters
545
549
  const customerConfigPath = await this.wranglerConfigManager.generateCustomerConfig(this.cloudflareZoneName, {
546
550
  accountId: this.cloudflareAccountId,
547
- environment: this.environment
551
+ environment: this.environment,
552
+ workerName: this.workerName // Pass specific worker name if provided
548
553
  });
549
554
 
550
555
  // Copy customer config to root (ephemeral working copy for this deployment)
@@ -763,12 +768,14 @@ export class MultiDomainOrchestrator {
763
768
  }
764
769
 
765
770
  /**
766
- * Simple API: Deploy a service with minimal configuration
767
- * @param {Object} options - Simple deployment options
771
+ * Deploy a single service to a specific domain/environment
772
+ * @param {Object} options - Deployment options
768
773
  * @param {string} options.servicePath - Path to service directory
769
774
  * @param {string} options.environment - Target environment
770
775
  * @param {string} options.domain - Specific domain to deploy to (used as zone name and domain suffix)
771
776
  * @param {string} options.serviceName - Service name for URL generation (e.g., 'data-service', 'auth-service')
777
+ * @param {string} options.workerName - Specific worker name to use
778
+ * @param {string} options.databaseName - Specific database name to use
772
779
  * @param {boolean} options.dryRun - Simulate deployment
773
780
  * @param {Object} options.credentials - Cloudflare credentials
774
781
  * @returns {Promise<Object>} Deployment result
@@ -779,6 +786,8 @@ export class MultiDomainOrchestrator {
779
786
  environment = 'production',
780
787
  domain,
781
788
  serviceName,
789
+ workerName,
790
+ databaseName,
782
791
  dryRun = false,
783
792
  credentials = {}
784
793
  } = options;
@@ -790,6 +799,10 @@ export class MultiDomainOrchestrator {
790
799
  servicePath,
791
800
  serviceName,
792
801
  // Pass through serviceName if provided
802
+ workerName,
803
+ // Pass through workerName if provided
804
+ databaseName,
805
+ // Pass through databaseName if provided
793
806
  cloudflareToken: credentials.token,
794
807
  cloudflareAccountId: credentials.accountId,
795
808
  cloudflareZoneId: credentials.zoneId,
@@ -34,6 +34,9 @@ export class Clodo {
34
34
  * @param {string} options.servicePath - Path to service directory
35
35
  * @param {string} options.environment - Target environment
36
36
  * @param {string} options.domain - Specific domain to deploy to
37
+ * @param {string} options.serviceName - Service name for URL generation
38
+ * @param {string} options.workerName - Specific worker name to use
39
+ * @param {string} options.databaseName - Specific database name to use
37
40
  * @param {boolean} options.dryRun - Simulate deployment
38
41
  * @returns {Promise<Object>} Deployment result
39
42
  */
@@ -127,13 +127,16 @@ export class WranglerConfigManager {
127
127
  * @param {Object} params - Deployment parameters
128
128
  * @param {string} params.accountId - Cloudflare account ID
129
129
  * @param {string} params.environment - Deployment environment (production, staging, development)
130
+ * @param {string} params.workerName - Specific worker name to use (optional, overrides automatic naming)
130
131
  * @returns {Promise<string>} Path to generated/updated customer config
131
132
  */
132
133
  async generateCustomerConfig(zoneName, params = {}) {
133
134
  const {
134
135
  accountId,
135
- environment = 'production'
136
+ environment = 'production',
137
+ workerName
136
138
  } = params;
139
+ console.log(` 🔍 DEBUG: generateCustomerConfig called with workerName: ${workerName}`);
137
140
  if (!zoneName) {
138
141
  throw new Error('Zone name is required to generate customer config');
139
142
  }
@@ -186,16 +189,25 @@ export class WranglerConfigManager {
186
189
  console.log(` ✅ Set account_id to ${accountId} in customer config`);
187
190
  }
188
191
 
189
- // Update worker name based on zone
192
+ // Update worker name based on zone or provided name
190
193
  // Extract base service name and apply zone-based naming
191
194
  const customerPrefix = zoneName.split('.')[0]; // clodo.dev → clodo
192
195
 
193
196
  // Update root-level worker name
194
197
  if (config.name) {
195
- const baseName = config.name.replace(/^[^-]+-/, ''); // Remove existing prefix
196
- const newWorkerName = `${customerPrefix}-${baseName}`;
198
+ let newWorkerName;
199
+ console.log(` 🔍 DEBUG: config.name is "${config.name}", workerName param is "${workerName}"`);
200
+ if (workerName) {
201
+ // Use the provided worker name directly
202
+ newWorkerName = workerName;
203
+ console.log(` ✅ Set worker name to ${newWorkerName} in customer config (provided)`);
204
+ } else {
205
+ // Apply automatic zone-based naming
206
+ const baseName = config.name.replace(/^[^-]+-/, ''); // Remove existing prefix
207
+ newWorkerName = `${customerPrefix}-${baseName}`;
208
+ console.log(` ✅ Set worker name to ${newWorkerName} in customer config (auto-generated)`);
209
+ }
197
210
  config.name = newWorkerName;
198
- console.log(` ✅ Set worker name to ${newWorkerName} in customer config`);
199
211
  }
200
212
 
201
213
  // Update SERVICE_DOMAIN in environment variables (all environments)
@@ -227,9 +239,20 @@ export class WranglerConfigManager {
227
239
  if (config.env) {
228
240
  for (const [envName, envConfig] of Object.entries(config.env)) {
229
241
  if (envConfig.name) {
230
- const baseName = envConfig.name.replace(/^[^-]+-/, ''); // Remove existing prefix
231
- envConfig.name = `${customerPrefix}-${baseName}`;
232
- console.log(` ✅ Set [env.${envName}] worker name to ${envConfig.name}`);
242
+ let newEnvWorkerName;
243
+ if (workerName) {
244
+ // Derive environment-specific name from provided worker name
245
+ const baseName = workerName.replace(/-service$/, ''); // Remove -service suffix if present
246
+ const envSuffix = envName === 'production' ? '' : `-${envName}`;
247
+ newEnvWorkerName = `${baseName}${envSuffix}`;
248
+ console.log(` ✅ Set [env.${envName}] worker name to ${newEnvWorkerName} (derived from provided name)`);
249
+ } else {
250
+ // Apply automatic zone-based naming
251
+ const baseName = envConfig.name.replace(/^[^-]+-/, ''); // Remove existing prefix
252
+ newEnvWorkerName = `${customerPrefix}-${baseName}`;
253
+ console.log(` ✅ Set [env.${envName}] worker name to ${newEnvWorkerName} (auto-generated)`);
254
+ }
255
+ envConfig.name = newEnvWorkerName;
233
256
  }
234
257
  if (envConfig.vars) {
235
258
  updateServiceDomain(envConfig.vars);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tamyla/clodo-framework",
3
- "version": "4.0.6",
3
+ "version": "4.0.8",
4
4
  "description": "Reusable framework for Clodo-style software architecture on Cloudflare Workers + D1",
5
5
  "type": "module",
6
6
  "sideEffects": [