@tamyla/clodo-framework 3.0.7 → 3.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,11 @@
1
+ ## [3.0.8](https://github.com/tamylaa/clodo-framework/compare/v3.0.7...v3.0.8) (2025-10-14)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * add --env flag to D1 migration commands for environment-specific database configs ([ef095e3](https://github.com/tamylaa/clodo-framework/commit/ef095e3ce7d8ea10e0e45d7eba45011cfa4271db))
7
+ * implement API token authentication for D1 database operations ([d8be0a9](https://github.com/tamylaa/clodo-framework/commit/d8be0a9388bf3ed5cf433fa8b61108acd25007fd))
8
+
1
9
  ## [3.0.7](https://github.com/tamylaa/clodo-framework/compare/v3.0.6...v3.0.7) (2025-10-14)
2
10
 
3
11
 
@@ -227,7 +227,17 @@ export async function listSecrets(env = 'production') {
227
227
  }
228
228
  }
229
229
 
230
- export async function listDatabases() {
230
+ export async function listDatabases(options = {}) {
231
+ const { apiToken, accountId } = options;
232
+
233
+ // Use API-based operation if credentials provided
234
+ if (apiToken && accountId) {
235
+ const { CloudflareAPI } = await import('../../../src/utils/cloudflare/api.js');
236
+ const cf = new CloudflareAPI(apiToken);
237
+ return await cf.listD1Databases(accountId);
238
+ }
239
+
240
+ // Fallback to CLI-based operation
231
241
  try {
232
242
  const { stdout: list } = await executeWithRateLimit('npx wrangler d1 list', 'd1');
233
243
  return list;
@@ -236,7 +246,17 @@ export async function listDatabases() {
236
246
  }
237
247
  }
238
248
 
239
- export async function databaseExists(databaseName) {
249
+ export async function databaseExists(databaseName, options = {}) {
250
+ const { apiToken, accountId } = options;
251
+
252
+ // Use API-based operation if credentials provided
253
+ if (apiToken && accountId) {
254
+ const { CloudflareAPI } = await import('../../../src/utils/cloudflare/api.js');
255
+ const cf = new CloudflareAPI(apiToken);
256
+ return await cf.d1DatabaseExists(accountId, databaseName);
257
+ }
258
+
259
+ // Fallback to CLI-based operation
240
260
  try {
241
261
  const list = await listDatabases();
242
262
  return list.includes(databaseName);
@@ -245,7 +265,18 @@ export async function databaseExists(databaseName) {
245
265
  }
246
266
  }
247
267
 
248
- export async function createDatabase(name) {
268
+ export async function createDatabase(name, options = {}) {
269
+ const { apiToken, accountId } = options;
270
+
271
+ // Use API-based operation if credentials provided
272
+ if (apiToken && accountId) {
273
+ const { CloudflareAPI } = await import('../../../src/utils/cloudflare/api.js');
274
+ const cf = new CloudflareAPI(apiToken);
275
+ const result = await cf.createD1Database(accountId, name);
276
+ return result.uuid; // Return UUID to match CLI behavior
277
+ }
278
+
279
+ // Fallback to CLI-based operation
249
280
  try {
250
281
  const { stdout: output } = await executeWithRateLimit(`npx wrangler d1 create ${name}`, 'd1');
251
282
  const idMatch = output.match(/database_id = "([^"]+)"/);
@@ -319,7 +350,18 @@ export async function executeSql(databaseName, sql, env = 'production') {
319
350
  }
320
351
 
321
352
  // Get database ID from list output
322
- export async function getDatabaseId(databaseName) {
353
+ export async function getDatabaseId(databaseName, options = {}) {
354
+ const { apiToken, accountId } = options;
355
+
356
+ // Use API-based operation if credentials provided
357
+ if (apiToken && accountId) {
358
+ const { CloudflareAPI } = await import('../../../src/utils/cloudflare/api.js');
359
+ const cf = new CloudflareAPI(apiToken);
360
+ const db = await cf.getD1Database(accountId, databaseName);
361
+ return db?.uuid || null;
362
+ }
363
+
364
+ // Fallback to CLI-based operation
323
365
  try {
324
366
  const list = await listDatabases();
325
367
  const lines = list.split('\n');
@@ -650,10 +650,15 @@ export class DatabaseOrchestrator {
650
650
 
651
651
  buildMigrationCommand(databaseName, environment, isRemote) {
652
652
  // Use DATABASE name, NOT binding name
653
- // Wrangler expects: "npx wrangler d1 migrations apply database-name --local"
653
+ // Wrangler expects: "npx wrangler d1 migrations apply database-name --remote --env environment"
654
654
  // NOT: "npx wrangler d1 migrations apply binding-name --local"
655
655
  let command = `npx wrangler d1 migrations apply ${databaseName}`;
656
656
 
657
+ // Add environment flag for non-production environments
658
+ if (environment !== 'production') {
659
+ command += ` --env ${environment}`;
660
+ }
661
+
657
662
  // For remote environments, add --remote flag
658
663
  // For local development, use --local
659
664
  if (isRemote) {
@@ -35,6 +35,10 @@ export class MultiDomainOrchestrator {
35
35
  this.parallelDeployments = options.parallelDeployments || 3;
36
36
  this.servicePath = options.servicePath || process.cwd();
37
37
 
38
+ // Cloudflare credentials for API-based operations
39
+ this.cloudflareToken = options.cloudflareToken;
40
+ this.cloudflareAccountId = options.cloudflareAccountId;
41
+
38
42
  // Initialize modular components
39
43
  this.domainResolver = new DomainResolver({
40
44
  environment: this.environment,
@@ -270,19 +274,49 @@ export class MultiDomainOrchestrator {
270
274
 
271
275
  // Check if database already exists
272
276
  console.log(` � Checking if database exists: ${databaseName}`);
273
- const exists = await databaseExists(databaseName);
274
- let databaseId;
275
- let created = false;
276
- if (exists) {
277
- console.log(` ✅ Database already exists: ${databaseName}`);
278
- databaseId = await getDatabaseId(databaseName);
279
- console.log(` 📊 Existing Database ID: ${databaseId}`);
277
+ let exists,
278
+ databaseId,
279
+ created = false;
280
+
281
+ // Use API-based operations if credentials are available
282
+ if (this.cloudflareToken && this.cloudflareAccountId) {
283
+ console.log(` 🔑 Using API token authentication for account: ${this.cloudflareAccountId}`);
284
+ exists = await databaseExists(databaseName, {
285
+ apiToken: this.cloudflareToken,
286
+ accountId: this.cloudflareAccountId
287
+ });
288
+ if (exists) {
289
+ console.log(` ✅ Database already exists: ${databaseName}`);
290
+ databaseId = await getDatabaseId(databaseName, {
291
+ apiToken: this.cloudflareToken,
292
+ accountId: this.cloudflareAccountId
293
+ });
294
+ console.log(` 📊 Existing Database ID: ${databaseId}`);
295
+ } else {
296
+ console.log(` 📦 Creating database: ${databaseName}`);
297
+ databaseId = await createDatabase(databaseName, {
298
+ apiToken: this.cloudflareToken,
299
+ accountId: this.cloudflareAccountId
300
+ });
301
+ console.log(` ✅ Database created: ${databaseName}`);
302
+ console.log(` 📊 Database ID: ${databaseId}`);
303
+ created = true;
304
+ }
280
305
  } else {
281
- console.log(` 📦 Creating database: ${databaseName}`);
282
- databaseId = await createDatabase(databaseName);
283
- console.log(` ✅ Database created: ${databaseName}`);
284
- console.log(` 📊 Database ID: ${databaseId}`);
285
- created = true;
306
+ // Fallback to CLI-based operations (OAuth)
307
+ console.log(` 🔐 Using OAuth authentication (wrangler CLI)`);
308
+ exists = await databaseExists(databaseName);
309
+ if (exists) {
310
+ console.log(` ✅ Database already exists: ${databaseName}`);
311
+ databaseId = await getDatabaseId(databaseName);
312
+ console.log(` 📊 Existing Database ID: ${databaseId}`);
313
+ } else {
314
+ console.log(` 📦 Creating database: ${databaseName}`);
315
+ databaseId = await createDatabase(databaseName);
316
+ console.log(` ✅ Database created: ${databaseName}`);
317
+ console.log(` 📊 Database ID: ${databaseId}`);
318
+ created = true;
319
+ }
286
320
  }
287
321
 
288
322
  // Store database info in domain state
@@ -230,7 +230,22 @@ export async function listSecrets(env = 'production') {
230
230
  throw new Error(`Failed to list secrets: ${error.message}`);
231
231
  }
232
232
  }
233
- export async function listDatabases() {
233
+ export async function listDatabases(options = {}) {
234
+ const {
235
+ apiToken,
236
+ accountId
237
+ } = options;
238
+
239
+ // Use API-based operation if credentials provided
240
+ if (apiToken && accountId) {
241
+ const {
242
+ CloudflareAPI
243
+ } = await import('../../../src/utils/cloudflare/api.js');
244
+ const cf = new CloudflareAPI(apiToken);
245
+ return await cf.listD1Databases(accountId);
246
+ }
247
+
248
+ // Fallback to CLI-based operation
234
249
  try {
235
250
  const {
236
251
  stdout: list
@@ -240,7 +255,22 @@ export async function listDatabases() {
240
255
  throw new Error(`Failed to list databases: ${error.message}`);
241
256
  }
242
257
  }
243
- export async function databaseExists(databaseName) {
258
+ export async function databaseExists(databaseName, options = {}) {
259
+ const {
260
+ apiToken,
261
+ accountId
262
+ } = options;
263
+
264
+ // Use API-based operation if credentials provided
265
+ if (apiToken && accountId) {
266
+ const {
267
+ CloudflareAPI
268
+ } = await import('../../../src/utils/cloudflare/api.js');
269
+ const cf = new CloudflareAPI(apiToken);
270
+ return await cf.d1DatabaseExists(accountId, databaseName);
271
+ }
272
+
273
+ // Fallback to CLI-based operation
244
274
  try {
245
275
  const list = await listDatabases();
246
276
  return list.includes(databaseName);
@@ -248,7 +278,23 @@ export async function databaseExists(databaseName) {
248
278
  return false; // Assume doesn't exist if can't check
249
279
  }
250
280
  }
251
- export async function createDatabase(name) {
281
+ export async function createDatabase(name, options = {}) {
282
+ const {
283
+ apiToken,
284
+ accountId
285
+ } = options;
286
+
287
+ // Use API-based operation if credentials provided
288
+ if (apiToken && accountId) {
289
+ const {
290
+ CloudflareAPI
291
+ } = await import('../../../src/utils/cloudflare/api.js');
292
+ const cf = new CloudflareAPI(apiToken);
293
+ const result = await cf.createD1Database(accountId, name);
294
+ return result.uuid; // Return UUID to match CLI behavior
295
+ }
296
+
297
+ // Fallback to CLI-based operation
252
298
  try {
253
299
  const {
254
300
  stdout: output
@@ -347,7 +393,23 @@ export async function executeSql(databaseName, sql, env = 'production') {
347
393
  }
348
394
 
349
395
  // Get database ID from list output
350
- export async function getDatabaseId(databaseName) {
396
+ export async function getDatabaseId(databaseName, options = {}) {
397
+ const {
398
+ apiToken,
399
+ accountId
400
+ } = options;
401
+
402
+ // Use API-based operation if credentials provided
403
+ if (apiToken && accountId) {
404
+ const {
405
+ CloudflareAPI
406
+ } = await import('../../../src/utils/cloudflare/api.js');
407
+ const cf = new CloudflareAPI(apiToken);
408
+ const db = await cf.getD1Database(accountId, databaseName);
409
+ return db?.uuid || null;
410
+ }
411
+
412
+ // Fallback to CLI-based operation
351
413
  try {
352
414
  const list = await listDatabases();
353
415
  const lines = list.split('\n');
@@ -227,6 +227,57 @@ export class CloudflareAPI {
227
227
  }
228
228
  }
229
229
 
230
+ /**
231
+ * Create a D1 database
232
+ * @param {string} accountId - Account ID
233
+ * @param {string} name - Database name
234
+ * @returns {Promise<Object>} Created database info
235
+ */
236
+ async createD1Database(accountId, name) {
237
+ const data = await this.request(`/accounts/${accountId}/d1/database`, {
238
+ method: 'POST',
239
+ body: JSON.stringify({
240
+ name
241
+ })
242
+ });
243
+ return {
244
+ uuid: data.result.uuid,
245
+ name: data.result.name,
246
+ version: data.result.version,
247
+ createdAt: data.result.created_at
248
+ };
249
+ }
250
+
251
+ /**
252
+ * Check if a D1 database exists
253
+ * @param {string} accountId - Account ID
254
+ * @param {string} name - Database name
255
+ * @returns {Promise<boolean>} True if database exists
256
+ */
257
+ async d1DatabaseExists(accountId, name) {
258
+ try {
259
+ const databases = await this.listD1Databases(accountId);
260
+ return databases.some(db => db.name === name);
261
+ } catch (error) {
262
+ return false;
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Get D1 database by name
268
+ * @param {string} accountId - Account ID
269
+ * @param {string} name - Database name
270
+ * @returns {Promise<Object|null>} Database info or null if not found
271
+ */
272
+ async getD1Database(accountId, name) {
273
+ try {
274
+ const databases = await this.listD1Databases(accountId);
275
+ return databases.find(db => db.name === name) || null;
276
+ } catch (error) {
277
+ return null;
278
+ }
279
+ }
280
+
230
281
  /**
231
282
  * Helper: Get complete deployment info for a zone
232
283
  * This combines zone details with useful metadata for deployment
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tamyla/clodo-framework",
3
- "version": "3.0.7",
3
+ "version": "3.0.8",
4
4
  "description": "Reusable framework for Clodo-style software architecture on Cloudflare Workers + D1",
5
5
  "type": "module",
6
6
  "sideEffects": [