@tamyla/clodo-framework 4.0.1 → 4.0.3

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,17 @@
1
+ ## [4.0.3](https://github.com/tamylaa/clodo-framework/compare/v4.0.2...v4.0.3) (2025-12-08)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Export CLI utilities to enable framework CLI commands ([35f1701](https://github.com/tamylaa/clodo-framework/commit/35f1701653ebe01b8fe41d184d2b88dac4c5b8d4))
7
+
8
+ ## [4.0.2](https://github.com/tamylaa/clodo-framework/compare/v4.0.1...v4.0.2) (2025-12-08)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * Pass API credentials through database creation workflow ([c245618](https://github.com/tamylaa/clodo-framework/commit/c245618290a7c71808de6d1934076bd53a0519c9))
14
+
1
15
  ## [4.0.1](https://github.com/tamylaa/clodo-framework/compare/v4.0.0...v4.0.1) (2025-12-07)
2
16
 
3
17
 
@@ -207,10 +207,30 @@ export async function deployWorker(env = 'production') {
207
207
  throw new Error(`Worker deployment failed after retries: ${error.message}`);
208
208
  }
209
209
  }
210
- export async function deploySecret(key, value, env = 'production') {
210
+ export async function deploySecret(key, value, env = 'production', options = {}) {
211
+ const {
212
+ apiToken,
213
+ accountId,
214
+ scriptName
215
+ } = options;
216
+
217
+ // Use API-based operation if credentials provided
218
+ if (apiToken && accountId && scriptName) {
219
+ const {
220
+ CloudflareAPI
221
+ } = await import('@tamyla/clodo-framework/utils/cloudflare');
222
+ const cf = new CloudflareAPI(apiToken);
223
+ await cf.putWorkerSecret(accountId, scriptName, key, {
224
+ text: value
225
+ });
226
+ return;
227
+ }
228
+
229
+ // Fallback to CLI-based operation
211
230
  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}`;
212
231
  try {
213
- await executeWithRateLimit(command, 'workers', 3); // Lower retries for secrets
232
+ // Increase retries for secrets due to higher timeout likelihood
233
+ await executeWithRateLimit(command, 'workers', 5);
214
234
  } catch (error) {
215
235
  throw new Error(`Secret deployment failed: ${error.message}`);
216
236
  }
@@ -21,9 +21,11 @@ export class InteractiveDatabaseWorkflow {
21
21
  /**
22
22
  * @param {Object} options - Configuration options
23
23
  * @param {Array} options.rollbackActions - Array to track rollback actions
24
+ * @param {boolean} options.dryRun - Dry run mode
24
25
  */
25
26
  constructor(options = {}) {
26
27
  this.rollbackActions = options.rollbackActions || [];
28
+ this.dryRun = options.dryRun || false;
27
29
  }
28
30
 
29
31
  /**
@@ -34,6 +36,8 @@ export class InteractiveDatabaseWorkflow {
34
36
  * @param {Object} options - Additional options
35
37
  * @param {string} [options.suggestedName] - Suggested database name
36
38
  * @param {boolean} [options.interactive=true] - Enable interactive prompts
39
+ * @param {string} [options.apiToken] - Cloudflare API token for API-based operations
40
+ * @param {string} [options.accountId] - Cloudflare account ID for API-based operations
37
41
  * @returns {Promise<Object>} Database configuration { name, id, created, reused }
38
42
  */
39
43
  async handleDatabaseSetup(domain, environment, options = {}) {
@@ -51,7 +55,10 @@ export class InteractiveDatabaseWorkflow {
51
55
  if (existingInfo.exists) {
52
56
  return await this.handleExistingDatabase(databaseName, existingInfo, options.interactive);
53
57
  } else {
54
- return await this.createNewDatabase(databaseName, options.interactive);
58
+ return await this.createNewDatabase(databaseName, options.interactive, {
59
+ apiToken: options.apiToken,
60
+ accountId: options.accountId
61
+ });
55
62
  }
56
63
  }
57
64
 
@@ -184,10 +191,32 @@ export class InteractiveDatabaseWorkflow {
184
191
  *
185
192
  * @param {string} name - Database name
186
193
  * @param {boolean} interactive - Enable interactive prompts
194
+ * @param {Object} credentials - Cloudflare API credentials
195
+ * @param {string} [credentials.apiToken] - API token for API-based creation
196
+ * @param {string} [credentials.accountId] - Account ID for API-based creation
187
197
  * @returns {Promise<Object>} Database configuration
188
198
  */
189
- async createNewDatabase(name, interactive = true) {
199
+ async createNewDatabase(name, interactive = true, credentials = {}) {
190
200
  console.log(`\nšŸ†• Creating new database: ${name}`);
201
+
202
+ // Check dry-run mode
203
+ if (this.dryRun) {
204
+ console.log(' šŸ” DRY RUN: Would create database but skipping actual creation');
205
+ return {
206
+ name,
207
+ id: 'dry-run-database-id',
208
+ created: false,
209
+ reused: false,
210
+ dryRun: true
211
+ };
212
+ }
213
+
214
+ // Log which method will be used
215
+ if (credentials.apiToken && credentials.accountId) {
216
+ console.log(' ā„¹ļø Using Cloudflare API for database creation');
217
+ } else {
218
+ console.log(' ā„¹ļø Using Wrangler CLI for database creation (no API credentials provided)');
219
+ }
191
220
  if (interactive) {
192
221
  const confirmCreate = await askYesNo('Proceed with database creation?', 'y');
193
222
  if (!confirmCreate) {
@@ -195,7 +224,10 @@ export class InteractiveDatabaseWorkflow {
195
224
  }
196
225
  }
197
226
  try {
198
- const databaseId = await createDatabase(name);
227
+ const databaseId = await createDatabase(name, {
228
+ apiToken: credentials.apiToken,
229
+ accountId: credentials.accountId
230
+ });
199
231
  console.log(` āœ… Database created with ID: ${databaseId}`);
200
232
 
201
233
  // Add to rollback actions
@@ -68,10 +68,13 @@ export class InteractiveDeploymentCoordinator {
68
68
  configCache: null // TODO: Add config cache if available
69
69
  }),
70
70
  databaseWorkflow: new InteractiveDatabaseWorkflow({
71
- interactive: true
71
+ interactive: true,
72
+ dryRun: this.options.dryRun
72
73
  }),
73
74
  secretWorkflow: new InteractiveSecretWorkflow({
74
- interactive: true
75
+ interactive: true,
76
+ dryRun: this.options.dryRun,
77
+ credentials: this.options.credentials
75
78
  }),
76
79
  validation: new InteractiveValidationWorkflow({
77
80
  interactive: true
@@ -157,7 +160,9 @@ export class InteractiveDeploymentCoordinator {
157
160
  console.log('šŸ—„ļø Phase 2: Configuring Database Resources');
158
161
  console.log('─'.repeat(50));
159
162
  this.deploymentState.resources.database = await this.workflows.databaseWorkflow.handleDatabaseSetup(this.deploymentState.config.domain, this.deploymentState.config.environment, {
160
- interactive: true
163
+ interactive: true,
164
+ apiToken: this.deploymentState.config.credentials?.token,
165
+ accountId: this.deploymentState.config.credentials?.accountId
161
166
  });
162
167
  console.log('āœ… Database configuration complete\n');
163
168
  }
@@ -168,9 +173,10 @@ export class InteractiveDeploymentCoordinator {
168
173
  async configureSecrets() {
169
174
  console.log('šŸ” Phase 3: Configuring Secrets & Credentials');
170
175
  console.log('─'.repeat(50));
171
- const workerName = `${this.deploymentState.config.domain}-data-service`;
176
+ const workerName = this.deploymentState.config.worker?.name || `${this.deploymentState.config.domain}-data-service`;
172
177
  this.deploymentState.resources.secrets = await this.workflows.secretWorkflow.handleSecretManagement(this.deploymentState.config.domain, this.deploymentState.config.environment, workerName, {
173
- interactive: true
178
+ interactive: true,
179
+ credentials: this.deploymentState.config.credentials
174
180
  });
175
181
  console.log('āœ… Secrets configuration complete\n');
176
182
  }
@@ -101,7 +101,16 @@ export class InteractiveDomainInfoGatherer {
101
101
  }
102
102
  const confirmWorkerConfig = await askYesNo('Is this worker configuration correct?', 'y');
103
103
  if (!confirmWorkerConfig) {
104
- config.worker.name = await askUser('Enter custom worker name', config.worker.name);
104
+ let customWorkerName = await askUser('Enter custom worker name', config.worker.name);
105
+
106
+ // Validate worker name - should not be a full URL
107
+ if (customWorkerName.includes('.') || customWorkerName.includes('/') || customWorkerName.includes('://')) {
108
+ console.log(' āš ļø Worker name should be just the name, not a full URL');
109
+ console.log(' šŸ’” Example: "my-service" not "my-service.workers.dev"');
110
+ customWorkerName = customWorkerName.split('.')[0]; // Extract just the first part
111
+ console.log(` šŸ”§ Using extracted name: ${customWorkerName}`);
112
+ }
113
+ config.worker.name = customWorkerName;
105
114
  config.worker.url = `https://${config.worker.name}.tamylatrading.workers.dev`;
106
115
  console.log(`\nāœ… Updated worker configuration`);
107
116
  }
@@ -21,9 +21,13 @@ export class InteractiveSecretWorkflow {
21
21
  /**
22
22
  * @param {Object} options - Configuration options
23
23
  * @param {Array} options.rollbackActions - Array to track rollback actions
24
+ * @param {boolean} options.dryRun - Dry run mode
25
+ * @param {Object} options.credentials - Cloudflare credentials
24
26
  */
25
27
  constructor(options = {}) {
26
28
  this.rollbackActions = options.rollbackActions || [];
29
+ this.dryRun = options.dryRun || false;
30
+ this.credentials = options.credentials || {};
27
31
  }
28
32
 
29
33
  /**
@@ -161,6 +165,12 @@ export class InteractiveSecretWorkflow {
161
165
  */
162
166
  async deploySecrets(secrets, workerName, environment, interactive = true) {
163
167
  console.log('\nā˜ļø Deploying secrets to Cloudflare Workers...');
168
+
169
+ // Check dry-run mode
170
+ if (this.dryRun) {
171
+ console.log(` šŸ” DRY RUN: Would deploy ${Object.keys(secrets).length} secrets to worker '${workerName}' but skipping actual deployment`);
172
+ return;
173
+ }
164
174
  if (interactive) {
165
175
  const deployConfirmed = await askYesNo(`Deploy ${Object.keys(secrets).length} secrets to worker '${workerName}'?`, 'y');
166
176
  if (!deployConfirmed) {
@@ -170,7 +180,11 @@ export class InteractiveSecretWorkflow {
170
180
  for (const [key, value] of Object.entries(secrets)) {
171
181
  console.log(` šŸ”‘ Deploying ${key}...`);
172
182
  try {
173
- await deploySecret(key, value, environment);
183
+ await deploySecret(key, value, environment, {
184
+ apiToken: this.credentials?.token,
185
+ accountId: this.credentials?.accountId,
186
+ scriptName: workerName
187
+ });
174
188
  console.log(` āœ… ${key} deployed`);
175
189
 
176
190
  // Add to rollback actions
@@ -242,6 +242,22 @@ export class CloudflareAPI {
242
242
  }));
243
243
  }
244
244
 
245
+ /**
246
+ * Put a secret for a worker
247
+ * @param {string} accountId - Account ID
248
+ * @param {string} scriptName - Worker script name
249
+ * @param {string} secretName - Secret name
250
+ * @param {Object} secretValue - Secret value object with 'text' property
251
+ * @returns {Promise<Object>} API response
252
+ */
253
+ async putWorkerSecret(accountId, scriptName, secretName, secretValue) {
254
+ const data = await this.request(`/accounts/${accountId}/workers/scripts/${scriptName}/secrets/${secretName}`, {
255
+ method: 'PUT',
256
+ body: JSON.stringify(secretValue)
257
+ });
258
+ return data;
259
+ }
260
+
245
261
  /**
246
262
  * List D1 databases for an account
247
263
  * @param {string} accountId - Account ID
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tamyla/clodo-framework",
3
- "version": "4.0.1",
3
+ "version": "4.0.3",
4
4
  "description": "Reusable framework for Clodo-style software architecture on Cloudflare Workers + D1",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -100,6 +100,7 @@
100
100
  "check:bundle": "node scripts/utilities/check-bundle.js || echo 'Bundle check passed'",
101
101
  "check:imports": "node scripts/utilities/check-import-paths.js",
102
102
  "check:all": "npm run type-check && npm run test",
103
+ "diagnose": "node scripts/framework-diagnostic.js",
103
104
  "analyze:bundle": "echo 'Bundle analysis not implemented yet'",
104
105
  "docs": "echo 'JSDoc documentation generation not configured yet'",
105
106
  "validate": "npm run check:all",
@@ -1,28 +0,0 @@
1
- /**
2
- * Worker Entry Point
3
- * Minimal exports for Cloudflare Workers - excludes CLI/Node.js dependencies
4
- *
5
- * This is the entry point for wrangler.toml
6
- * Only exports worker-compatible code (no fs, path, child_process, etc.)
7
- */
8
-
9
- // Worker integration (no Node.js dependencies)
10
- export { initializeService, configManager } from './worker/integration.js';
11
-
12
- // Domain configuration (pure JS, no fs dependencies)
13
- export { getDomainFromEnv, createEnvironmentConfig } from './config/domains.js';
14
-
15
- // Framework version
16
- export const FRAMEWORK_VERSION = '1.0.0';
17
-
18
- // Default export for module worker format
19
- export default {
20
- async fetch(request, env, ctx) {
21
- return new Response('Clodo Framework Worker - Use initializeService() to set up your worker', {
22
- status: 200,
23
- headers: {
24
- 'content-type': 'text/plain'
25
- }
26
- });
27
- }
28
- };