@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 +14 -0
- package/dist/lib/shared/cloudflare/ops.js +22 -2
- package/dist/lib/shared/deployment/workflows/interactive-database-workflow.js +35 -3
- package/dist/lib/shared/deployment/workflows/interactive-deployment-coordinator.js +11 -5
- package/dist/lib/shared/deployment/workflows/interactive-domain-info-gatherer.js +10 -1
- package/dist/lib/shared/deployment/workflows/interactive-secret-workflow.js +15 -1
- package/dist/utils/cloudflare/api.js +16 -0
- package/package.json +2 -1
- package/dist/worker-entry.js +0 -28
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
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|
package/dist/worker-entry.js
DELETED
|
@@ -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
|
-
};
|