@cloudwerk/cli 0.10.0 → 0.11.0

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.
Files changed (2) hide show
  1. package/dist/index.js +1635 -0
  2. package/package.json +6 -4
package/dist/index.js CHANGED
@@ -1148,6 +1148,1636 @@ async function askConfirmation(question, defaultValue) {
1148
1148
  });
1149
1149
  }
1150
1150
 
1151
+ // src/commands/bindings.ts
1152
+ import * as path8 from "path";
1153
+ import pc4 from "picocolors";
1154
+
1155
+ // src/utils/command-error-handler.ts
1156
+ import pc3 from "picocolors";
1157
+ function handleCommandError(error, verbose = false) {
1158
+ if (error instanceof CliError) {
1159
+ printError(error.message, error.suggestion);
1160
+ process.exit(1);
1161
+ }
1162
+ if (error instanceof Error && (error.message.includes("User force closed") || error.name === "ExitPromptError")) {
1163
+ console.log();
1164
+ console.log(pc3.dim("Cancelled."));
1165
+ process.exit(0);
1166
+ }
1167
+ if (error instanceof Error) {
1168
+ printError(error.message);
1169
+ if (verbose && error.stack) {
1170
+ console.log(error.stack);
1171
+ }
1172
+ process.exit(1);
1173
+ }
1174
+ printError(String(error));
1175
+ process.exit(1);
1176
+ }
1177
+
1178
+ // src/utils/wrangler-toml.ts
1179
+ import * as fs7 from "fs";
1180
+ import * as path7 from "path";
1181
+ import TOML from "@iarna/toml";
1182
+ function findWranglerToml(cwd) {
1183
+ const wranglerPath = path7.join(cwd, "wrangler.toml");
1184
+ if (fs7.existsSync(wranglerPath)) {
1185
+ return wranglerPath;
1186
+ }
1187
+ const wranglerJsonPath = path7.join(cwd, "wrangler.json");
1188
+ if (fs7.existsSync(wranglerJsonPath)) {
1189
+ return wranglerJsonPath;
1190
+ }
1191
+ return null;
1192
+ }
1193
+ function readWranglerToml(cwd) {
1194
+ const configPath = findWranglerToml(cwd);
1195
+ if (!configPath) {
1196
+ return {};
1197
+ }
1198
+ const content = fs7.readFileSync(configPath, "utf-8");
1199
+ if (configPath.endsWith(".json")) {
1200
+ return JSON.parse(content);
1201
+ }
1202
+ return TOML.parse(content);
1203
+ }
1204
+ function writeWranglerToml(cwd, config) {
1205
+ const configPath = path7.join(cwd, "wrangler.toml");
1206
+ const content = TOML.stringify(config);
1207
+ fs7.writeFileSync(configPath, content, "utf-8");
1208
+ }
1209
+ function getEnvConfig(config, env) {
1210
+ if (!env || !config.env?.[env]) {
1211
+ return config;
1212
+ }
1213
+ return {
1214
+ ...config,
1215
+ ...config.env[env]
1216
+ };
1217
+ }
1218
+ function extractBindings(config, env) {
1219
+ const envConfig = getEnvConfig(config, env);
1220
+ const bindings2 = [];
1221
+ if (envConfig.d1_databases) {
1222
+ for (const db of envConfig.d1_databases) {
1223
+ bindings2.push({
1224
+ type: "d1",
1225
+ name: db.binding,
1226
+ resourceId: db.database_id,
1227
+ resourceName: db.database_name
1228
+ });
1229
+ }
1230
+ }
1231
+ if (envConfig.kv_namespaces) {
1232
+ for (const kv of envConfig.kv_namespaces) {
1233
+ bindings2.push({
1234
+ type: "kv",
1235
+ name: kv.binding,
1236
+ resourceId: kv.id
1237
+ });
1238
+ }
1239
+ }
1240
+ if (envConfig.r2_buckets) {
1241
+ for (const r2 of envConfig.r2_buckets) {
1242
+ bindings2.push({
1243
+ type: "r2",
1244
+ name: r2.binding,
1245
+ resourceName: r2.bucket_name
1246
+ });
1247
+ }
1248
+ }
1249
+ if (envConfig.queues?.producers) {
1250
+ for (const queue of envConfig.queues.producers) {
1251
+ bindings2.push({
1252
+ type: "queue",
1253
+ name: queue.binding,
1254
+ resourceName: queue.queue
1255
+ });
1256
+ }
1257
+ }
1258
+ if (envConfig.durable_objects?.bindings) {
1259
+ for (const doBinding of envConfig.durable_objects.bindings) {
1260
+ bindings2.push({
1261
+ type: "do",
1262
+ name: doBinding.name,
1263
+ resourceName: doBinding.class_name,
1264
+ extra: doBinding.script_name ? { script_name: doBinding.script_name } : void 0
1265
+ });
1266
+ }
1267
+ }
1268
+ if (envConfig.services) {
1269
+ for (const service of envConfig.services) {
1270
+ bindings2.push({
1271
+ type: "service",
1272
+ name: service.binding,
1273
+ resourceName: service.service,
1274
+ extra: service.environment ? { environment: service.environment } : void 0
1275
+ });
1276
+ }
1277
+ }
1278
+ if (envConfig.vars) {
1279
+ for (const [name] of Object.entries(envConfig.vars)) {
1280
+ bindings2.push({
1281
+ type: "secret",
1282
+ name
1283
+ });
1284
+ }
1285
+ }
1286
+ if (envConfig.ai) {
1287
+ bindings2.push({
1288
+ type: "ai",
1289
+ name: envConfig.ai.binding
1290
+ });
1291
+ }
1292
+ if (envConfig.vectorize) {
1293
+ for (const vec of envConfig.vectorize) {
1294
+ bindings2.push({
1295
+ type: "vectorize",
1296
+ name: vec.binding,
1297
+ resourceName: vec.index_name
1298
+ });
1299
+ }
1300
+ }
1301
+ if (envConfig.hyperdrive) {
1302
+ for (const hd of envConfig.hyperdrive) {
1303
+ bindings2.push({
1304
+ type: "hyperdrive",
1305
+ name: hd.binding,
1306
+ resourceId: hd.id
1307
+ });
1308
+ }
1309
+ }
1310
+ return bindings2;
1311
+ }
1312
+ function getEnvironments(config) {
1313
+ if (!config.env) {
1314
+ return [];
1315
+ }
1316
+ return Object.keys(config.env);
1317
+ }
1318
+ function addD1Binding(cwd, bindingName, databaseName, databaseId, env) {
1319
+ const config = readWranglerToml(cwd);
1320
+ const newBinding = {
1321
+ binding: bindingName,
1322
+ database_name: databaseName,
1323
+ database_id: databaseId
1324
+ };
1325
+ if (env) {
1326
+ if (!config.env) config.env = {};
1327
+ if (!config.env[env]) config.env[env] = {};
1328
+ if (!config.env[env].d1_databases) config.env[env].d1_databases = [];
1329
+ config.env[env].d1_databases.push(newBinding);
1330
+ } else {
1331
+ if (!config.d1_databases) config.d1_databases = [];
1332
+ config.d1_databases.push(newBinding);
1333
+ }
1334
+ writeWranglerToml(cwd, config);
1335
+ }
1336
+ function addKVBinding(cwd, bindingName, namespaceId, previewId, env) {
1337
+ const config = readWranglerToml(cwd);
1338
+ const newBinding = {
1339
+ binding: bindingName,
1340
+ id: namespaceId
1341
+ };
1342
+ if (previewId) {
1343
+ newBinding.preview_id = previewId;
1344
+ }
1345
+ if (env) {
1346
+ if (!config.env) config.env = {};
1347
+ if (!config.env[env]) config.env[env] = {};
1348
+ if (!config.env[env].kv_namespaces) config.env[env].kv_namespaces = [];
1349
+ config.env[env].kv_namespaces.push(newBinding);
1350
+ } else {
1351
+ if (!config.kv_namespaces) config.kv_namespaces = [];
1352
+ config.kv_namespaces.push(newBinding);
1353
+ }
1354
+ writeWranglerToml(cwd, config);
1355
+ }
1356
+ function addR2Binding(cwd, bindingName, bucketName, env) {
1357
+ const config = readWranglerToml(cwd);
1358
+ const newBinding = {
1359
+ binding: bindingName,
1360
+ bucket_name: bucketName
1361
+ };
1362
+ if (env) {
1363
+ if (!config.env) config.env = {};
1364
+ if (!config.env[env]) config.env[env] = {};
1365
+ if (!config.env[env].r2_buckets) config.env[env].r2_buckets = [];
1366
+ config.env[env].r2_buckets.push(newBinding);
1367
+ } else {
1368
+ if (!config.r2_buckets) config.r2_buckets = [];
1369
+ config.r2_buckets.push(newBinding);
1370
+ }
1371
+ writeWranglerToml(cwd, config);
1372
+ }
1373
+ function addQueueBinding(cwd, bindingName, queueName, env) {
1374
+ const config = readWranglerToml(cwd);
1375
+ const newBinding = {
1376
+ binding: bindingName,
1377
+ queue: queueName
1378
+ };
1379
+ if (env) {
1380
+ if (!config.env) config.env = {};
1381
+ if (!config.env[env]) config.env[env] = {};
1382
+ if (!config.env[env].queues) config.env[env].queues = {};
1383
+ if (!config.env[env].queues.producers)
1384
+ config.env[env].queues.producers = [];
1385
+ config.env[env].queues.producers.push(newBinding);
1386
+ } else {
1387
+ if (!config.queues) config.queues = {};
1388
+ if (!config.queues.producers) config.queues.producers = [];
1389
+ config.queues.producers.push(newBinding);
1390
+ }
1391
+ writeWranglerToml(cwd, config);
1392
+ }
1393
+ function addDurableObjectBinding(cwd, bindingName, className, scriptName, env) {
1394
+ const config = readWranglerToml(cwd);
1395
+ const newBinding = {
1396
+ name: bindingName,
1397
+ class_name: className
1398
+ };
1399
+ if (scriptName) {
1400
+ newBinding.script_name = scriptName;
1401
+ }
1402
+ if (env) {
1403
+ if (!config.env) config.env = {};
1404
+ if (!config.env[env]) config.env[env] = {};
1405
+ if (!config.env[env].durable_objects)
1406
+ config.env[env].durable_objects = { bindings: [] };
1407
+ config.env[env].durable_objects.bindings.push(newBinding);
1408
+ } else {
1409
+ if (!config.durable_objects) config.durable_objects = { bindings: [] };
1410
+ if (!config.durable_objects.bindings) config.durable_objects.bindings = [];
1411
+ config.durable_objects.bindings.push(newBinding);
1412
+ }
1413
+ writeWranglerToml(cwd, config);
1414
+ }
1415
+ function addSecretBinding(cwd, name, value, env) {
1416
+ const config = readWranglerToml(cwd);
1417
+ if (env) {
1418
+ if (!config.env) config.env = {};
1419
+ if (!config.env[env]) config.env[env] = {};
1420
+ if (!config.env[env].vars) config.env[env].vars = {};
1421
+ config.env[env].vars[name] = value;
1422
+ } else {
1423
+ if (!config.vars) config.vars = {};
1424
+ config.vars[name] = value;
1425
+ }
1426
+ writeWranglerToml(cwd, config);
1427
+ }
1428
+ function removeBinding(cwd, bindingName, env) {
1429
+ const config = readWranglerToml(cwd);
1430
+ let removed = false;
1431
+ const removeFromConfig = (cfg) => {
1432
+ let found = false;
1433
+ if (cfg.d1_databases) {
1434
+ const idx = cfg.d1_databases.findIndex((b) => b.binding === bindingName);
1435
+ if (idx !== -1) {
1436
+ cfg.d1_databases.splice(idx, 1);
1437
+ if (cfg.d1_databases.length === 0) delete cfg.d1_databases;
1438
+ found = true;
1439
+ }
1440
+ }
1441
+ if (cfg.kv_namespaces) {
1442
+ const idx = cfg.kv_namespaces.findIndex((b) => b.binding === bindingName);
1443
+ if (idx !== -1) {
1444
+ cfg.kv_namespaces.splice(idx, 1);
1445
+ if (cfg.kv_namespaces.length === 0) delete cfg.kv_namespaces;
1446
+ found = true;
1447
+ }
1448
+ }
1449
+ if (cfg.r2_buckets) {
1450
+ const idx = cfg.r2_buckets.findIndex((b) => b.binding === bindingName);
1451
+ if (idx !== -1) {
1452
+ cfg.r2_buckets.splice(idx, 1);
1453
+ if (cfg.r2_buckets.length === 0) delete cfg.r2_buckets;
1454
+ found = true;
1455
+ }
1456
+ }
1457
+ if (cfg.queues?.producers) {
1458
+ const idx = cfg.queues.producers.findIndex(
1459
+ (b) => b.binding === bindingName
1460
+ );
1461
+ if (idx !== -1) {
1462
+ cfg.queues.producers.splice(idx, 1);
1463
+ if (cfg.queues.producers.length === 0) delete cfg.queues.producers;
1464
+ if (!cfg.queues.producers && !cfg.queues.consumers) delete cfg.queues;
1465
+ found = true;
1466
+ }
1467
+ }
1468
+ if (cfg.durable_objects?.bindings) {
1469
+ const idx = cfg.durable_objects.bindings.findIndex(
1470
+ (b) => b.name === bindingName
1471
+ );
1472
+ if (idx !== -1) {
1473
+ cfg.durable_objects.bindings.splice(idx, 1);
1474
+ if (cfg.durable_objects.bindings.length === 0)
1475
+ delete cfg.durable_objects;
1476
+ found = true;
1477
+ }
1478
+ }
1479
+ if (cfg.services) {
1480
+ const idx = cfg.services.findIndex((b) => b.binding === bindingName);
1481
+ if (idx !== -1) {
1482
+ cfg.services.splice(idx, 1);
1483
+ if (cfg.services.length === 0) delete cfg.services;
1484
+ found = true;
1485
+ }
1486
+ }
1487
+ if (cfg.vars && bindingName in cfg.vars) {
1488
+ delete cfg.vars[bindingName];
1489
+ if (Object.keys(cfg.vars).length === 0) delete cfg.vars;
1490
+ found = true;
1491
+ }
1492
+ if (cfg.ai && cfg.ai.binding === bindingName) {
1493
+ delete cfg.ai;
1494
+ found = true;
1495
+ }
1496
+ if (cfg.vectorize) {
1497
+ const idx = cfg.vectorize.findIndex((b) => b.binding === bindingName);
1498
+ if (idx !== -1) {
1499
+ cfg.vectorize.splice(idx, 1);
1500
+ if (cfg.vectorize.length === 0) delete cfg.vectorize;
1501
+ found = true;
1502
+ }
1503
+ }
1504
+ if (cfg.hyperdrive) {
1505
+ const idx = cfg.hyperdrive.findIndex((b) => b.binding === bindingName);
1506
+ if (idx !== -1) {
1507
+ cfg.hyperdrive.splice(idx, 1);
1508
+ if (cfg.hyperdrive.length === 0) delete cfg.hyperdrive;
1509
+ found = true;
1510
+ }
1511
+ }
1512
+ return found;
1513
+ };
1514
+ if (env && config.env?.[env]) {
1515
+ removed = removeFromConfig(config.env[env]);
1516
+ } else {
1517
+ removed = removeFromConfig(config);
1518
+ }
1519
+ if (removed) {
1520
+ writeWranglerToml(cwd, config);
1521
+ }
1522
+ return removed;
1523
+ }
1524
+ function bindingExists(config, bindingName, env) {
1525
+ const bindings2 = extractBindings(config, env);
1526
+ return bindings2.some((b) => b.name === bindingName);
1527
+ }
1528
+ function getBindingTypeName(type) {
1529
+ switch (type) {
1530
+ case "d1":
1531
+ return "D1";
1532
+ case "kv":
1533
+ return "KV";
1534
+ case "r2":
1535
+ return "R2";
1536
+ case "queue":
1537
+ return "Queue";
1538
+ case "do":
1539
+ return "Durable Object";
1540
+ case "service":
1541
+ return "Service";
1542
+ case "secret":
1543
+ return "Secret/Var";
1544
+ case "ai":
1545
+ return "AI";
1546
+ case "vectorize":
1547
+ return "Vectorize";
1548
+ case "hyperdrive":
1549
+ return "Hyperdrive";
1550
+ default:
1551
+ return type;
1552
+ }
1553
+ }
1554
+ function truncateId(id, maxLen = 12) {
1555
+ if (id.length <= maxLen) return id;
1556
+ return id.slice(0, maxLen - 3) + "...";
1557
+ }
1558
+
1559
+ // src/commands/bindings.ts
1560
+ async function bindings(options) {
1561
+ const verbose = options.verbose ?? false;
1562
+ const logger = createLogger(verbose);
1563
+ const env = options.env;
1564
+ try {
1565
+ const cwd = process.cwd();
1566
+ const wranglerPath = findWranglerToml(cwd);
1567
+ if (!wranglerPath) {
1568
+ throw new CliError(
1569
+ "wrangler.toml not found",
1570
+ "ENOENT",
1571
+ "Create a wrangler.toml file or run this command from a Cloudwerk project directory."
1572
+ );
1573
+ }
1574
+ logger.debug(`Found wrangler config: ${wranglerPath}`);
1575
+ const config = readWranglerToml(cwd);
1576
+ const projectName = config.name || path8.basename(cwd);
1577
+ const bindingsList = extractBindings(config, env);
1578
+ const environments = getEnvironments(config);
1579
+ console.log();
1580
+ const envLabel = env ? env : "production";
1581
+ console.log(
1582
+ pc4.bold(`Bindings for ${projectName}`) + pc4.dim(` (${envLabel}):`)
1583
+ );
1584
+ console.log();
1585
+ if (bindingsList.length === 0) {
1586
+ console.log(pc4.dim(" No bindings configured."));
1587
+ console.log();
1588
+ console.log(
1589
+ pc4.dim(
1590
+ ` Use 'cloudwerk bindings add' to add a new binding${env ? ` --env ${env}` : ""}.`
1591
+ )
1592
+ );
1593
+ } else {
1594
+ console.log(
1595
+ pc4.dim(" Type Binding Resource")
1596
+ );
1597
+ console.log(
1598
+ pc4.dim(" " + "\u2500".repeat(14) + " " + "\u2500".repeat(13) + " " + "\u2500".repeat(30))
1599
+ );
1600
+ for (const binding of bindingsList) {
1601
+ const typeName = getBindingTypeName(binding.type).padEnd(14);
1602
+ const bindingName = binding.name.padEnd(13);
1603
+ let resource = "";
1604
+ if (binding.resourceName && binding.resourceId) {
1605
+ resource = `${binding.resourceName} (${truncateId(binding.resourceId)})`;
1606
+ } else if (binding.resourceName) {
1607
+ resource = binding.resourceName;
1608
+ } else if (binding.resourceId) {
1609
+ resource = `(${truncateId(binding.resourceId)})`;
1610
+ } else {
1611
+ resource = pc4.dim("(configured)");
1612
+ }
1613
+ console.log(` ${pc4.cyan(typeName)} ${pc4.bold(bindingName)} ${resource}`);
1614
+ }
1615
+ }
1616
+ console.log();
1617
+ if (environments.length > 0 && !env) {
1618
+ console.log(
1619
+ pc4.dim("Environments: ") + environments.join(", ")
1620
+ );
1621
+ console.log();
1622
+ }
1623
+ const envFlag = env ? ` --env ${env}` : "";
1624
+ console.log(pc4.dim(`Use 'cloudwerk bindings add${envFlag}' to add a new binding.`));
1625
+ if (!env && environments.length > 0) {
1626
+ console.log(
1627
+ pc4.dim(`Use 'cloudwerk bindings --env <name>' to view environment bindings.`)
1628
+ );
1629
+ }
1630
+ console.log();
1631
+ } catch (error) {
1632
+ handleCommandError(error, verbose);
1633
+ }
1634
+ }
1635
+
1636
+ // src/commands/bindings/add.ts
1637
+ import { spawn as spawn2 } from "child_process";
1638
+ import pc5 from "picocolors";
1639
+ import { select, input, confirm } from "@inquirer/prompts";
1640
+
1641
+ // src/utils/env-types.ts
1642
+ import * as fs8 from "fs";
1643
+ import * as path9 from "path";
1644
+ var TYPE_MAPPINGS = {
1645
+ d1: "D1Database",
1646
+ kv: "KVNamespace",
1647
+ r2: "R2Bucket",
1648
+ queue: "Queue",
1649
+ do: "DurableObjectNamespace",
1650
+ service: "Fetcher",
1651
+ secret: "string",
1652
+ ai: "Ai",
1653
+ vectorize: "VectorizeIndex",
1654
+ hyperdrive: "Hyperdrive"
1655
+ };
1656
+ function getTypeForBinding(type) {
1657
+ return TYPE_MAPPINGS[type] || "unknown";
1658
+ }
1659
+ function generateEnvTypes(cwd, bindings2, options = {}) {
1660
+ const outputPath = options.outputPath || path9.join(cwd, "env.d.ts");
1661
+ const includeTimestamp = options.includeTimestamp ?? true;
1662
+ const bindingsByType = /* @__PURE__ */ new Map();
1663
+ for (const binding of bindings2) {
1664
+ const existing = bindingsByType.get(binding.type) || [];
1665
+ existing.push(binding);
1666
+ bindingsByType.set(binding.type, existing);
1667
+ }
1668
+ const lines = [];
1669
+ lines.push("// Auto-generated by cloudwerk bindings - DO NOT EDIT");
1670
+ if (includeTimestamp) {
1671
+ lines.push(`// Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
1672
+ }
1673
+ lines.push("");
1674
+ lines.push("interface CloudflareBindings {");
1675
+ const typeOrder = [
1676
+ "d1",
1677
+ "kv",
1678
+ "r2",
1679
+ "queue",
1680
+ "do",
1681
+ "service",
1682
+ "ai",
1683
+ "vectorize",
1684
+ "hyperdrive",
1685
+ "secret"
1686
+ ];
1687
+ const resultBindings = [];
1688
+ let firstSection = true;
1689
+ for (const type of typeOrder) {
1690
+ const typeBindings = bindingsByType.get(type);
1691
+ if (!typeBindings || typeBindings.length === 0) continue;
1692
+ const tsType = getTypeForBinding(type);
1693
+ const sectionName = getSectionName(type);
1694
+ if (!firstSection) {
1695
+ lines.push("");
1696
+ }
1697
+ lines.push(` // ${sectionName}`);
1698
+ firstSection = false;
1699
+ for (const binding of typeBindings) {
1700
+ lines.push(` ${binding.name}: ${tsType}`);
1701
+ resultBindings.push({ name: binding.name, type: tsType });
1702
+ }
1703
+ }
1704
+ lines.push("}");
1705
+ lines.push("");
1706
+ lines.push("// Re-export for convenience");
1707
+ lines.push("type Env = CloudflareBindings");
1708
+ lines.push("export type { Env, CloudflareBindings }");
1709
+ lines.push("");
1710
+ fs8.writeFileSync(outputPath, lines.join("\n"), "utf-8");
1711
+ return {
1712
+ outputPath,
1713
+ bindingCount: bindings2.length,
1714
+ bindings: resultBindings
1715
+ };
1716
+ }
1717
+ function getSectionName(type) {
1718
+ switch (type) {
1719
+ case "d1":
1720
+ return "D1 Databases";
1721
+ case "kv":
1722
+ return "KV Namespaces";
1723
+ case "r2":
1724
+ return "R2 Buckets";
1725
+ case "queue":
1726
+ return "Queues";
1727
+ case "do":
1728
+ return "Durable Objects";
1729
+ case "service":
1730
+ return "Services";
1731
+ case "secret":
1732
+ return "Environment Variables";
1733
+ case "ai":
1734
+ return "AI";
1735
+ case "vectorize":
1736
+ return "Vectorize Indexes";
1737
+ case "hyperdrive":
1738
+ return "Hyperdrive";
1739
+ default:
1740
+ return "Other";
1741
+ }
1742
+ }
1743
+
1744
+ // src/commands/bindings/add.ts
1745
+ async function bindingsAdd(bindingType, options) {
1746
+ const verbose = options.verbose ?? false;
1747
+ const logger = createLogger(verbose);
1748
+ const env = options.env;
1749
+ try {
1750
+ const cwd = process.cwd();
1751
+ const wranglerPath = findWranglerToml(cwd);
1752
+ if (!wranglerPath) {
1753
+ throw new CliError(
1754
+ "wrangler.toml not found",
1755
+ "ENOENT",
1756
+ "Create a wrangler.toml file or run this command from a Cloudwerk project directory."
1757
+ );
1758
+ }
1759
+ logger.debug(`Found wrangler config: ${wranglerPath}`);
1760
+ const type = bindingType || await promptForBindingType();
1761
+ const envLabel = env ? ` (${env})` : "";
1762
+ console.log();
1763
+ logger.info(`Adding ${type.toUpperCase()} binding${envLabel}...`);
1764
+ switch (type.toLowerCase()) {
1765
+ case "d1":
1766
+ await addD1(cwd, options);
1767
+ break;
1768
+ case "kv":
1769
+ await addKV(cwd, options);
1770
+ break;
1771
+ case "r2":
1772
+ await addR2(cwd, options);
1773
+ break;
1774
+ case "queue":
1775
+ await addQueue(cwd, options);
1776
+ break;
1777
+ case "do":
1778
+ await addDurableObject(cwd, options);
1779
+ break;
1780
+ case "secret":
1781
+ await addSecret(cwd, options);
1782
+ break;
1783
+ default:
1784
+ throw new CliError(
1785
+ `Unknown binding type: ${type}`,
1786
+ "EINVAL",
1787
+ "Valid types are: d1, kv, r2, queue, do, secret"
1788
+ );
1789
+ }
1790
+ if (!options.skipTypes) {
1791
+ await regenerateTypes(cwd);
1792
+ }
1793
+ console.log();
1794
+ logger.success("Binding added successfully!");
1795
+ console.log();
1796
+ } catch (error) {
1797
+ handleCommandError(error, verbose);
1798
+ }
1799
+ }
1800
+ async function promptForBindingType() {
1801
+ return select({
1802
+ message: "What type of binding do you want to add?",
1803
+ choices: [
1804
+ { name: "D1 Database", value: "d1" },
1805
+ { name: "KV Namespace", value: "kv" },
1806
+ { name: "R2 Bucket", value: "r2" },
1807
+ { name: "Queue", value: "queue" },
1808
+ { name: "Durable Object", value: "do" },
1809
+ { name: "Secret / Environment Variable", value: "secret" }
1810
+ ]
1811
+ });
1812
+ }
1813
+ async function addD1(cwd, options) {
1814
+ const config = readWranglerToml(cwd);
1815
+ const env = options.env;
1816
+ const bindingName = await input({
1817
+ message: "Binding name (e.g., DB):",
1818
+ validate: (value) => {
1819
+ if (!value.trim()) return "Binding name is required";
1820
+ if (!/^[A-Z_][A-Z0-9_]*$/i.test(value))
1821
+ return "Binding name must be alphanumeric with underscores";
1822
+ if (bindingExists(config, value, env))
1823
+ return `Binding "${value}" already exists`;
1824
+ return true;
1825
+ }
1826
+ });
1827
+ const databaseName = await input({
1828
+ message: "Database name:",
1829
+ default: `${config.name || "my-app"}-db${env ? `-${env}` : ""}`,
1830
+ validate: (value) => {
1831
+ if (!value.trim()) return "Database name is required";
1832
+ return true;
1833
+ }
1834
+ });
1835
+ const createNew = await confirm({
1836
+ message: "Create a new D1 database?",
1837
+ default: true
1838
+ });
1839
+ let databaseId;
1840
+ if (createNew) {
1841
+ console.log();
1842
+ console.log(pc5.dim(`Creating D1 database "${databaseName}"...`));
1843
+ const result = await runWranglerCommand(["d1", "create", databaseName]);
1844
+ const idMatch = result.match(/database_id\s*=\s*"([^"]+)"/);
1845
+ if (!idMatch) {
1846
+ throw new CliError(
1847
+ "Failed to parse database ID from wrangler output",
1848
+ "EPARSE",
1849
+ "Try creating the database manually with: wrangler d1 create " + databaseName
1850
+ );
1851
+ }
1852
+ databaseId = idMatch[1];
1853
+ console.log(pc5.green("\u2713") + ` Created database: ${databaseId}`);
1854
+ } else {
1855
+ const databases = await listD1Databases();
1856
+ if (databases.length === 0) {
1857
+ throw new CliError(
1858
+ "No D1 databases found",
1859
+ "ENOENT",
1860
+ "Create a database first with: wrangler d1 create <name>"
1861
+ );
1862
+ }
1863
+ const selected = await select({
1864
+ message: "Select an existing database:",
1865
+ choices: databases.map((db) => ({
1866
+ name: `${db.name} (${db.uuid.slice(0, 8)}...)`,
1867
+ value: db.uuid
1868
+ }))
1869
+ });
1870
+ databaseId = selected;
1871
+ }
1872
+ addD1Binding(cwd, bindingName, databaseName, databaseId, env);
1873
+ console.log(pc5.green("\u2713") + " Updated wrangler.toml");
1874
+ showTypeHint(bindingName, "d1");
1875
+ }
1876
+ async function listD1Databases() {
1877
+ const output = await runWranglerCommand(["d1", "list", "--json"]);
1878
+ return JSON.parse(output);
1879
+ }
1880
+ async function addKV(cwd, options) {
1881
+ const config = readWranglerToml(cwd);
1882
+ const env = options.env;
1883
+ const bindingName = await input({
1884
+ message: "Binding name (e.g., CACHE):",
1885
+ validate: (value) => {
1886
+ if (!value.trim()) return "Binding name is required";
1887
+ if (!/^[A-Z_][A-Z0-9_]*$/i.test(value))
1888
+ return "Binding name must be alphanumeric with underscores";
1889
+ if (bindingExists(config, value, env))
1890
+ return `Binding "${value}" already exists`;
1891
+ return true;
1892
+ }
1893
+ });
1894
+ const createNew = await confirm({
1895
+ message: "Create a new KV namespace?",
1896
+ default: true
1897
+ });
1898
+ let namespaceId;
1899
+ if (createNew) {
1900
+ console.log();
1901
+ console.log(pc5.dim(`Creating KV namespace "${bindingName}"...`));
1902
+ const result = await runWranglerCommand([
1903
+ "kv",
1904
+ "namespace",
1905
+ "create",
1906
+ bindingName
1907
+ ]);
1908
+ const idMatch = result.match(/id\s*=\s*"([^"]+)"/);
1909
+ if (!idMatch) {
1910
+ throw new CliError(
1911
+ "Failed to parse namespace ID from wrangler output",
1912
+ "EPARSE",
1913
+ "Try creating the namespace manually with: wrangler kv namespace create " + bindingName
1914
+ );
1915
+ }
1916
+ namespaceId = idMatch[1];
1917
+ console.log(pc5.green("\u2713") + ` Created namespace: ${namespaceId}`);
1918
+ } else {
1919
+ const namespaces = await listKVNamespaces();
1920
+ if (namespaces.length === 0) {
1921
+ throw new CliError(
1922
+ "No KV namespaces found",
1923
+ "ENOENT",
1924
+ "Create a namespace first with: wrangler kv namespace create <name>"
1925
+ );
1926
+ }
1927
+ const selected = await select({
1928
+ message: "Select an existing namespace:",
1929
+ choices: namespaces.map((ns) => ({
1930
+ name: `${ns.title} (${ns.id.slice(0, 8)}...)`,
1931
+ value: ns.id
1932
+ }))
1933
+ });
1934
+ namespaceId = selected;
1935
+ }
1936
+ addKVBinding(cwd, bindingName, namespaceId, void 0, env);
1937
+ console.log(pc5.green("\u2713") + " Updated wrangler.toml");
1938
+ showTypeHint(bindingName, "kv");
1939
+ }
1940
+ async function listKVNamespaces() {
1941
+ const output = await runWranglerCommand(["kv", "namespace", "list", "--json"]);
1942
+ return JSON.parse(output);
1943
+ }
1944
+ async function addR2(cwd, options) {
1945
+ const config = readWranglerToml(cwd);
1946
+ const env = options.env;
1947
+ const bindingName = await input({
1948
+ message: "Binding name (e.g., STORAGE):",
1949
+ validate: (value) => {
1950
+ if (!value.trim()) return "Binding name is required";
1951
+ if (!/^[A-Z_][A-Z0-9_]*$/i.test(value))
1952
+ return "Binding name must be alphanumeric with underscores";
1953
+ if (bindingExists(config, value, env))
1954
+ return `Binding "${value}" already exists`;
1955
+ return true;
1956
+ }
1957
+ });
1958
+ const createNew = await confirm({
1959
+ message: "Create a new R2 bucket?",
1960
+ default: true
1961
+ });
1962
+ let bucketName;
1963
+ if (createNew) {
1964
+ bucketName = await input({
1965
+ message: "Bucket name:",
1966
+ default: `${config.name || "my-app"}-bucket${env ? `-${env}` : ""}`,
1967
+ validate: (value) => {
1968
+ if (!value.trim()) return "Bucket name is required";
1969
+ return true;
1970
+ }
1971
+ });
1972
+ console.log();
1973
+ console.log(pc5.dim(`Creating R2 bucket "${bucketName}"...`));
1974
+ await runWranglerCommand(["r2", "bucket", "create", bucketName]);
1975
+ console.log(pc5.green("\u2713") + ` Created bucket: ${bucketName}`);
1976
+ } else {
1977
+ const buckets = await listR2Buckets();
1978
+ if (buckets.length === 0) {
1979
+ throw new CliError(
1980
+ "No R2 buckets found",
1981
+ "ENOENT",
1982
+ "Create a bucket first with: wrangler r2 bucket create <name>"
1983
+ );
1984
+ }
1985
+ const selected = await select({
1986
+ message: "Select an existing bucket:",
1987
+ choices: buckets.map((bucket) => ({
1988
+ name: bucket.name,
1989
+ value: bucket.name
1990
+ }))
1991
+ });
1992
+ bucketName = selected;
1993
+ }
1994
+ addR2Binding(cwd, bindingName, bucketName, env);
1995
+ console.log(pc5.green("\u2713") + " Updated wrangler.toml");
1996
+ showTypeHint(bindingName, "r2");
1997
+ }
1998
+ async function listR2Buckets() {
1999
+ const output = await runWranglerCommand(["r2", "bucket", "list", "--json"]);
2000
+ return JSON.parse(output);
2001
+ }
2002
+ async function addQueue(cwd, options) {
2003
+ const config = readWranglerToml(cwd);
2004
+ const env = options.env;
2005
+ const bindingName = await input({
2006
+ message: "Binding name (e.g., JOBS):",
2007
+ validate: (value) => {
2008
+ if (!value.trim()) return "Binding name is required";
2009
+ if (!/^[A-Z_][A-Z0-9_]*$/i.test(value))
2010
+ return "Binding name must be alphanumeric with underscores";
2011
+ if (bindingExists(config, value, env))
2012
+ return `Binding "${value}" already exists`;
2013
+ return true;
2014
+ }
2015
+ });
2016
+ const createNew = await confirm({
2017
+ message: "Create a new queue?",
2018
+ default: true
2019
+ });
2020
+ let queueName;
2021
+ if (createNew) {
2022
+ queueName = await input({
2023
+ message: "Queue name:",
2024
+ default: `${config.name || "my-app"}-queue${env ? `-${env}` : ""}`,
2025
+ validate: (value) => {
2026
+ if (!value.trim()) return "Queue name is required";
2027
+ return true;
2028
+ }
2029
+ });
2030
+ console.log();
2031
+ console.log(pc5.dim(`Creating queue "${queueName}"...`));
2032
+ await runWranglerCommand(["queues", "create", queueName]);
2033
+ console.log(pc5.green("\u2713") + ` Created queue: ${queueName}`);
2034
+ } else {
2035
+ const queues = await listQueues();
2036
+ if (queues.length === 0) {
2037
+ throw new CliError(
2038
+ "No queues found",
2039
+ "ENOENT",
2040
+ "Create a queue first with: wrangler queues create <name>"
2041
+ );
2042
+ }
2043
+ const selected = await select({
2044
+ message: "Select an existing queue:",
2045
+ choices: queues.map((queue) => ({
2046
+ name: queue.queue_name,
2047
+ value: queue.queue_name
2048
+ }))
2049
+ });
2050
+ queueName = selected;
2051
+ }
2052
+ addQueueBinding(cwd, bindingName, queueName, env);
2053
+ console.log(pc5.green("\u2713") + " Updated wrangler.toml");
2054
+ showTypeHint(bindingName, "queue");
2055
+ }
2056
+ async function listQueues() {
2057
+ const output = await runWranglerCommand(["queues", "list", "--json"]);
2058
+ return JSON.parse(output);
2059
+ }
2060
+ async function addDurableObject(cwd, options) {
2061
+ const config = readWranglerToml(cwd);
2062
+ const env = options.env;
2063
+ const bindingName = await input({
2064
+ message: "Binding name (e.g., COUNTER):",
2065
+ validate: (value) => {
2066
+ if (!value.trim()) return "Binding name is required";
2067
+ if (!/^[A-Z_][A-Z0-9_]*$/i.test(value))
2068
+ return "Binding name must be alphanumeric with underscores";
2069
+ if (bindingExists(config, value, env))
2070
+ return `Binding "${value}" already exists`;
2071
+ return true;
2072
+ }
2073
+ });
2074
+ const className = await input({
2075
+ message: "Durable Object class name:",
2076
+ default: bindingName.split("_").map((s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase()).join(""),
2077
+ validate: (value) => {
2078
+ if (!value.trim()) return "Class name is required";
2079
+ return true;
2080
+ }
2081
+ });
2082
+ const isExternal = await confirm({
2083
+ message: "Is this a Durable Object from another Worker?",
2084
+ default: false
2085
+ });
2086
+ let scriptName;
2087
+ if (isExternal) {
2088
+ scriptName = await input({
2089
+ message: "Worker script name:",
2090
+ validate: (value) => {
2091
+ if (!value.trim()) return "Script name is required";
2092
+ return true;
2093
+ }
2094
+ });
2095
+ }
2096
+ addDurableObjectBinding(cwd, bindingName, className, scriptName, env);
2097
+ console.log(pc5.green("\u2713") + " Updated wrangler.toml");
2098
+ showTypeHint(bindingName, "do");
2099
+ if (!isExternal) {
2100
+ console.log();
2101
+ console.log(pc5.dim("Remember to export your Durable Object class:"));
2102
+ console.log(pc5.dim(` export class ${className} extends DurableObject { ... }`));
2103
+ }
2104
+ }
2105
+ async function addSecret(cwd, options) {
2106
+ const config = readWranglerToml(cwd);
2107
+ const env = options.env;
2108
+ const varName = await input({
2109
+ message: "Variable name (e.g., API_KEY):",
2110
+ validate: (value) => {
2111
+ if (!value.trim()) return "Variable name is required";
2112
+ if (!/^[A-Z_][A-Z0-9_]*$/i.test(value))
2113
+ return "Variable name must be alphanumeric with underscores";
2114
+ if (bindingExists(config, value, env))
2115
+ return `Variable "${value}" already exists`;
2116
+ return true;
2117
+ }
2118
+ });
2119
+ const isSecret = await confirm({
2120
+ message: "Is this a secret (sensitive value)?",
2121
+ default: true
2122
+ });
2123
+ if (isSecret) {
2124
+ console.log();
2125
+ console.log(pc5.dim("Enter the secret value (will be hidden):"));
2126
+ const varValue = await input({
2127
+ message: "Secret value:"
2128
+ });
2129
+ console.log();
2130
+ console.log(pc5.dim(`Setting secret "${varName}"...`));
2131
+ const args = ["secret", "put", varName];
2132
+ if (env) args.push("--env", env);
2133
+ await runWranglerCommandWithInput(args, varValue);
2134
+ console.log(pc5.green("\u2713") + ` Secret "${varName}" set`);
2135
+ console.log();
2136
+ console.log(
2137
+ pc5.dim("Note: Secrets are stored securely and not written to wrangler.toml.")
2138
+ );
2139
+ } else {
2140
+ const varValue = await input({
2141
+ message: "Variable value:",
2142
+ validate: (value) => {
2143
+ if (!value.trim()) return "Value is required";
2144
+ return true;
2145
+ }
2146
+ });
2147
+ addSecretBinding(cwd, varName, varValue, env);
2148
+ console.log(pc5.green("\u2713") + " Updated wrangler.toml");
2149
+ }
2150
+ showTypeHint(varName, "secret");
2151
+ }
2152
+ async function regenerateTypes(cwd) {
2153
+ const config = readWranglerToml(cwd);
2154
+ const bindings2 = extractBindings(config);
2155
+ if (bindings2.length === 0) {
2156
+ return;
2157
+ }
2158
+ const result = generateEnvTypes(cwd, bindings2);
2159
+ console.log(pc5.green("\u2713") + ` Updated env.d.ts with ${result.bindingCount} binding(s)`);
2160
+ }
2161
+ function showTypeHint(bindingName, type) {
2162
+ const tsType = getTypeForBinding(type);
2163
+ console.log();
2164
+ console.log(pc5.dim(`TypeScript type: ${bindingName}: ${tsType}`));
2165
+ }
2166
+ function runWranglerCommand(args) {
2167
+ return new Promise((resolve4, reject) => {
2168
+ let stdout = "";
2169
+ let stderr = "";
2170
+ const child = spawn2("npx", ["wrangler", ...args], {
2171
+ stdio: ["pipe", "pipe", "pipe"]
2172
+ });
2173
+ child.stdout?.on("data", (data) => {
2174
+ stdout += data.toString();
2175
+ });
2176
+ child.stderr?.on("data", (data) => {
2177
+ stderr += data.toString();
2178
+ });
2179
+ child.on("error", (error) => {
2180
+ reject(error);
2181
+ });
2182
+ child.on("close", (code) => {
2183
+ if (code !== 0) {
2184
+ reject(new Error(stderr || `wrangler exited with code ${code}`));
2185
+ } else {
2186
+ resolve4(stdout);
2187
+ }
2188
+ });
2189
+ });
2190
+ }
2191
+ function runWranglerCommandWithInput(args, inputData) {
2192
+ return new Promise((resolve4, reject) => {
2193
+ let stdout = "";
2194
+ let stderr = "";
2195
+ const child = spawn2("npx", ["wrangler", ...args], {
2196
+ stdio: ["pipe", "pipe", "pipe"]
2197
+ });
2198
+ child.stdout?.on("data", (data) => {
2199
+ stdout += data.toString();
2200
+ });
2201
+ child.stderr?.on("data", (data) => {
2202
+ stderr += data.toString();
2203
+ });
2204
+ child.on("error", (error) => {
2205
+ reject(error);
2206
+ });
2207
+ child.on("close", (code) => {
2208
+ if (code !== 0) {
2209
+ reject(new Error(stderr || `wrangler exited with code ${code}`));
2210
+ } else {
2211
+ resolve4(stdout);
2212
+ }
2213
+ });
2214
+ child.stdin?.write(inputData);
2215
+ child.stdin?.end();
2216
+ });
2217
+ }
2218
+
2219
+ // src/commands/bindings/remove.ts
2220
+ import pc6 from "picocolors";
2221
+ import { confirm as confirm2, select as select2 } from "@inquirer/prompts";
2222
+ async function bindingsRemove(bindingName, options) {
2223
+ const verbose = options.verbose ?? false;
2224
+ const logger = createLogger(verbose);
2225
+ const env = options.env;
2226
+ try {
2227
+ const cwd = process.cwd();
2228
+ const wranglerPath = findWranglerToml(cwd);
2229
+ if (!wranglerPath) {
2230
+ throw new CliError(
2231
+ "wrangler.toml not found",
2232
+ "ENOENT",
2233
+ "Create a wrangler.toml file or run this command from a Cloudwerk project directory."
2234
+ );
2235
+ }
2236
+ logger.debug(`Found wrangler config: ${wranglerPath}`);
2237
+ const config = readWranglerToml(cwd);
2238
+ const bindings2 = extractBindings(config, env);
2239
+ if (bindings2.length === 0) {
2240
+ const envLabel = env ? ` in ${env}` : "";
2241
+ throw new CliError(
2242
+ `No bindings found${envLabel}`,
2243
+ "ENOENT",
2244
+ `Use 'cloudwerk bindings add' to add a binding first.`
2245
+ );
2246
+ }
2247
+ let targetBinding = bindingName;
2248
+ if (!targetBinding) {
2249
+ targetBinding = await select2({
2250
+ message: "Select a binding to remove:",
2251
+ choices: bindings2.map((b) => ({
2252
+ name: `${b.name} (${getBindingTypeName(b.type)})`,
2253
+ value: b.name
2254
+ }))
2255
+ });
2256
+ }
2257
+ const binding = bindings2.find((b) => b.name === targetBinding);
2258
+ if (!binding) {
2259
+ const envLabel = env ? ` in ${env}` : "";
2260
+ throw new CliError(
2261
+ `Binding "${targetBinding}" not found${envLabel}`,
2262
+ "ENOENT",
2263
+ `Use 'cloudwerk bindings' to see available bindings.`
2264
+ );
2265
+ }
2266
+ if (!options.force) {
2267
+ const confirmed = await confirm2({
2268
+ message: `Remove binding "${targetBinding}" (${getBindingTypeName(binding.type)})?`,
2269
+ default: false
2270
+ });
2271
+ if (!confirmed) {
2272
+ console.log(pc6.dim("Cancelled."));
2273
+ return;
2274
+ }
2275
+ }
2276
+ console.log();
2277
+ const removed = removeBinding(cwd, targetBinding, env);
2278
+ if (!removed) {
2279
+ throw new CliError(
2280
+ `Failed to remove binding "${targetBinding}"`,
2281
+ "EREMOVE",
2282
+ "The binding may have already been removed."
2283
+ );
2284
+ }
2285
+ console.log(pc6.green("\u2713") + ` Removed binding "${targetBinding}" from wrangler.toml`);
2286
+ if (!options.skipTypes) {
2287
+ const updatedConfig = readWranglerToml(cwd);
2288
+ const updatedBindings = extractBindings(updatedConfig);
2289
+ if (updatedBindings.length > 0) {
2290
+ const result = generateEnvTypes(cwd, updatedBindings);
2291
+ console.log(
2292
+ pc6.green("\u2713") + ` Updated env.d.ts with ${result.bindingCount} binding(s)`
2293
+ );
2294
+ } else {
2295
+ console.log(
2296
+ pc6.dim("Note: No bindings remain. Consider removing env.d.ts manually.")
2297
+ );
2298
+ }
2299
+ }
2300
+ console.log();
2301
+ logger.success("Binding removed successfully!");
2302
+ console.log();
2303
+ console.log(
2304
+ pc6.dim(
2305
+ "Note: The Cloudflare resource itself was not deleted. Use wrangler to delete the resource if needed."
2306
+ )
2307
+ );
2308
+ console.log();
2309
+ } catch (error) {
2310
+ handleCommandError(error, verbose);
2311
+ }
2312
+ }
2313
+
2314
+ // src/commands/bindings/update.ts
2315
+ import { spawn as spawn3 } from "child_process";
2316
+ import pc7 from "picocolors";
2317
+ import { input as input2, select as select3 } from "@inquirer/prompts";
2318
+ async function bindingsUpdate(bindingName, options) {
2319
+ const verbose = options.verbose ?? false;
2320
+ const logger = createLogger(verbose);
2321
+ const env = options.env;
2322
+ try {
2323
+ const cwd = process.cwd();
2324
+ const wranglerPath = findWranglerToml(cwd);
2325
+ if (!wranglerPath) {
2326
+ throw new CliError(
2327
+ "wrangler.toml not found",
2328
+ "ENOENT",
2329
+ "Create a wrangler.toml file or run this command from a Cloudwerk project directory."
2330
+ );
2331
+ }
2332
+ logger.debug(`Found wrangler config: ${wranglerPath}`);
2333
+ const config = readWranglerToml(cwd);
2334
+ const bindings2 = extractBindings(config, env);
2335
+ if (bindings2.length === 0) {
2336
+ const envLabel = env ? ` in ${env}` : "";
2337
+ throw new CliError(
2338
+ `No bindings found${envLabel}`,
2339
+ "ENOENT",
2340
+ `Use 'cloudwerk bindings add' to add a binding first.`
2341
+ );
2342
+ }
2343
+ let targetBinding = bindingName;
2344
+ if (!targetBinding) {
2345
+ targetBinding = await select3({
2346
+ message: "Select a binding to update:",
2347
+ choices: bindings2.map((b) => ({
2348
+ name: `${b.name} (${getBindingTypeName(b.type)})`,
2349
+ value: b.name
2350
+ }))
2351
+ });
2352
+ }
2353
+ const binding = bindings2.find((b) => b.name === targetBinding);
2354
+ if (!binding) {
2355
+ const envLabel = env ? ` in ${env}` : "";
2356
+ throw new CliError(
2357
+ `Binding "${targetBinding}" not found${envLabel}`,
2358
+ "ENOENT",
2359
+ `Use 'cloudwerk bindings' to see available bindings.`
2360
+ );
2361
+ }
2362
+ console.log();
2363
+ logger.info(
2364
+ `Updating ${getBindingTypeName(binding.type)} binding "${targetBinding}"...`
2365
+ );
2366
+ const updated = await updateBinding(cwd, config, binding, env);
2367
+ if (!updated) {
2368
+ console.log(pc7.dim("No changes made."));
2369
+ return;
2370
+ }
2371
+ if (!options.skipTypes) {
2372
+ const updatedConfig = readWranglerToml(cwd);
2373
+ const updatedBindings = extractBindings(updatedConfig);
2374
+ if (updatedBindings.length > 0) {
2375
+ const result = generateEnvTypes(cwd, updatedBindings);
2376
+ console.log(
2377
+ pc7.green("\u2713") + ` Updated env.d.ts with ${result.bindingCount} binding(s)`
2378
+ );
2379
+ }
2380
+ }
2381
+ console.log();
2382
+ logger.success("Binding updated successfully!");
2383
+ console.log();
2384
+ } catch (error) {
2385
+ handleCommandError(error, verbose);
2386
+ }
2387
+ }
2388
+ async function updateBinding(cwd, config, binding, env) {
2389
+ const updateOptions = getUpdateOptions(binding);
2390
+ if (updateOptions.length === 0) {
2391
+ console.log(pc7.dim("This binding type has no updatable fields."));
2392
+ return false;
2393
+ }
2394
+ const field = await select3({
2395
+ message: "What do you want to update?",
2396
+ choices: updateOptions
2397
+ });
2398
+ const targetConfig = env ? config.env?.[env] ?? config : config;
2399
+ switch (binding.type) {
2400
+ case "d1":
2401
+ return updateD1(cwd, config, targetConfig, binding.name, field);
2402
+ case "kv":
2403
+ return updateKV(cwd, config, targetConfig, binding.name, field);
2404
+ case "r2":
2405
+ return updateR2(cwd, config, targetConfig, binding.name, field);
2406
+ case "queue":
2407
+ return updateQueue(cwd, config, targetConfig, binding.name, field);
2408
+ case "do":
2409
+ return updateDurableObject(cwd, config, targetConfig, binding.name, field);
2410
+ case "secret":
2411
+ return updateSecret(cwd, config, targetConfig, binding.name, field, env);
2412
+ default:
2413
+ console.log(pc7.dim(`Updating ${binding.type} bindings is not yet supported.`));
2414
+ return false;
2415
+ }
2416
+ }
2417
+ function getUpdateOptions(binding) {
2418
+ switch (binding.type) {
2419
+ case "d1":
2420
+ return [
2421
+ { name: "Binding name", value: "name" },
2422
+ { name: "Database name", value: "database_name" },
2423
+ { name: "Database ID", value: "database_id" }
2424
+ ];
2425
+ case "kv":
2426
+ return [
2427
+ { name: "Binding name", value: "name" },
2428
+ { name: "Namespace ID", value: "id" },
2429
+ { name: "Preview ID", value: "preview_id" }
2430
+ ];
2431
+ case "r2":
2432
+ return [
2433
+ { name: "Binding name", value: "name" },
2434
+ { name: "Bucket name", value: "bucket_name" }
2435
+ ];
2436
+ case "queue":
2437
+ return [
2438
+ { name: "Binding name", value: "name" },
2439
+ { name: "Queue name", value: "queue" }
2440
+ ];
2441
+ case "do":
2442
+ return [
2443
+ { name: "Binding name", value: "name" },
2444
+ { name: "Class name", value: "class_name" },
2445
+ { name: "Script name", value: "script_name" }
2446
+ ];
2447
+ case "secret":
2448
+ return [
2449
+ { name: "Variable name", value: "name" },
2450
+ { name: "Value", value: "value" }
2451
+ ];
2452
+ default:
2453
+ return [];
2454
+ }
2455
+ }
2456
+ async function updateD1(cwd, config, targetConfig, bindingName, field) {
2457
+ const bindings2 = targetConfig.d1_databases;
2458
+ if (!bindings2) return false;
2459
+ const binding = bindings2.find((b) => b.binding === bindingName);
2460
+ if (!binding) return false;
2461
+ switch (field) {
2462
+ case "name": {
2463
+ const newName = await input2({
2464
+ message: "New binding name:",
2465
+ default: binding.binding
2466
+ });
2467
+ if (newName === binding.binding) return false;
2468
+ binding.binding = newName;
2469
+ break;
2470
+ }
2471
+ case "database_name": {
2472
+ const newName = await input2({
2473
+ message: "New database name:",
2474
+ default: binding.database_name
2475
+ });
2476
+ if (newName === binding.database_name) return false;
2477
+ binding.database_name = newName;
2478
+ break;
2479
+ }
2480
+ case "database_id": {
2481
+ const newId = await input2({
2482
+ message: "New database ID:",
2483
+ default: binding.database_id
2484
+ });
2485
+ if (newId === binding.database_id) return false;
2486
+ binding.database_id = newId;
2487
+ break;
2488
+ }
2489
+ }
2490
+ writeWranglerToml(cwd, config);
2491
+ console.log(pc7.green("\u2713") + " Updated wrangler.toml");
2492
+ return true;
2493
+ }
2494
+ async function updateKV(cwd, config, targetConfig, bindingName, field) {
2495
+ const bindings2 = targetConfig.kv_namespaces;
2496
+ if (!bindings2) return false;
2497
+ const binding = bindings2.find((b) => b.binding === bindingName);
2498
+ if (!binding) return false;
2499
+ switch (field) {
2500
+ case "name": {
2501
+ const newName = await input2({
2502
+ message: "New binding name:",
2503
+ default: binding.binding
2504
+ });
2505
+ if (newName === binding.binding) return false;
2506
+ binding.binding = newName;
2507
+ break;
2508
+ }
2509
+ case "id": {
2510
+ const newId = await input2({
2511
+ message: "New namespace ID:",
2512
+ default: binding.id
2513
+ });
2514
+ if (newId === binding.id) return false;
2515
+ binding.id = newId;
2516
+ break;
2517
+ }
2518
+ case "preview_id": {
2519
+ const newId = await input2({
2520
+ message: "New preview ID (leave empty to remove):",
2521
+ default: binding.preview_id || ""
2522
+ });
2523
+ if (newId === (binding.preview_id || "")) return false;
2524
+ if (newId) {
2525
+ binding.preview_id = newId;
2526
+ } else {
2527
+ delete binding.preview_id;
2528
+ }
2529
+ break;
2530
+ }
2531
+ }
2532
+ writeWranglerToml(cwd, config);
2533
+ console.log(pc7.green("\u2713") + " Updated wrangler.toml");
2534
+ return true;
2535
+ }
2536
+ async function updateR2(cwd, config, targetConfig, bindingName, field) {
2537
+ const bindings2 = targetConfig.r2_buckets;
2538
+ if (!bindings2) return false;
2539
+ const binding = bindings2.find((b) => b.binding === bindingName);
2540
+ if (!binding) return false;
2541
+ switch (field) {
2542
+ case "name": {
2543
+ const newName = await input2({
2544
+ message: "New binding name:",
2545
+ default: binding.binding
2546
+ });
2547
+ if (newName === binding.binding) return false;
2548
+ binding.binding = newName;
2549
+ break;
2550
+ }
2551
+ case "bucket_name": {
2552
+ const newName = await input2({
2553
+ message: "New bucket name:",
2554
+ default: binding.bucket_name
2555
+ });
2556
+ if (newName === binding.bucket_name) return false;
2557
+ binding.bucket_name = newName;
2558
+ break;
2559
+ }
2560
+ }
2561
+ writeWranglerToml(cwd, config);
2562
+ console.log(pc7.green("\u2713") + " Updated wrangler.toml");
2563
+ return true;
2564
+ }
2565
+ async function updateQueue(cwd, config, targetConfig, bindingName, field) {
2566
+ const bindings2 = targetConfig.queues?.producers;
2567
+ if (!bindings2) return false;
2568
+ const binding = bindings2.find((b) => b.binding === bindingName);
2569
+ if (!binding) return false;
2570
+ switch (field) {
2571
+ case "name": {
2572
+ const newName = await input2({
2573
+ message: "New binding name:",
2574
+ default: binding.binding
2575
+ });
2576
+ if (newName === binding.binding) return false;
2577
+ binding.binding = newName;
2578
+ break;
2579
+ }
2580
+ case "queue": {
2581
+ const newName = await input2({
2582
+ message: "New queue name:",
2583
+ default: binding.queue
2584
+ });
2585
+ if (newName === binding.queue) return false;
2586
+ binding.queue = newName;
2587
+ break;
2588
+ }
2589
+ }
2590
+ writeWranglerToml(cwd, config);
2591
+ console.log(pc7.green("\u2713") + " Updated wrangler.toml");
2592
+ return true;
2593
+ }
2594
+ async function updateDurableObject(cwd, config, targetConfig, bindingName, field) {
2595
+ const bindings2 = targetConfig.durable_objects?.bindings;
2596
+ if (!bindings2) return false;
2597
+ const binding = bindings2.find((b) => b.name === bindingName);
2598
+ if (!binding) return false;
2599
+ switch (field) {
2600
+ case "name": {
2601
+ const newName = await input2({
2602
+ message: "New binding name:",
2603
+ default: binding.name
2604
+ });
2605
+ if (newName === binding.name) return false;
2606
+ binding.name = newName;
2607
+ break;
2608
+ }
2609
+ case "class_name": {
2610
+ const newName = await input2({
2611
+ message: "New class name:",
2612
+ default: binding.class_name
2613
+ });
2614
+ if (newName === binding.class_name) return false;
2615
+ binding.class_name = newName;
2616
+ break;
2617
+ }
2618
+ case "script_name": {
2619
+ const newName = await input2({
2620
+ message: "New script name (leave empty to remove):",
2621
+ default: binding.script_name || ""
2622
+ });
2623
+ if (newName === (binding.script_name || "")) return false;
2624
+ if (newName) {
2625
+ binding.script_name = newName;
2626
+ } else {
2627
+ delete binding.script_name;
2628
+ }
2629
+ break;
2630
+ }
2631
+ }
2632
+ writeWranglerToml(cwd, config);
2633
+ console.log(pc7.green("\u2713") + " Updated wrangler.toml");
2634
+ return true;
2635
+ }
2636
+ async function updateSecret(cwd, config, targetConfig, varName, field, env) {
2637
+ const vars = targetConfig.vars;
2638
+ const isInToml = vars && varName in vars;
2639
+ if (!isInToml) {
2640
+ if (field === "name") {
2641
+ console.log();
2642
+ console.log(
2643
+ pc7.yellow("\u26A0") + " Secrets managed by wrangler cannot be renamed directly."
2644
+ );
2645
+ console.log(
2646
+ pc7.dim(
2647
+ " To rename, delete the old secret and create a new one with:"
2648
+ )
2649
+ );
2650
+ console.log(pc7.dim(` wrangler secret delete ${varName}`));
2651
+ console.log(pc7.dim(` wrangler secret put <new-name>`));
2652
+ return false;
2653
+ }
2654
+ console.log();
2655
+ console.log(
2656
+ pc7.dim("This is a secret managed by wrangler. Enter the new value:")
2657
+ );
2658
+ const newValue = await input2({
2659
+ message: "New secret value:"
2660
+ });
2661
+ if (!newValue.trim()) {
2662
+ console.log(pc7.dim("No value provided."));
2663
+ return false;
2664
+ }
2665
+ const args = ["secret", "put", varName];
2666
+ if (env) args.push("--env", env);
2667
+ console.log();
2668
+ console.log(pc7.dim(`Updating secret "${varName}"...`));
2669
+ await runWranglerCommandWithInput2(args, newValue);
2670
+ console.log(pc7.green("\u2713") + ` Secret "${varName}" updated`);
2671
+ return true;
2672
+ }
2673
+ switch (field) {
2674
+ case "name": {
2675
+ const newName = await input2({
2676
+ message: "New variable name:",
2677
+ default: varName
2678
+ });
2679
+ if (newName === varName) return false;
2680
+ const value = vars[varName];
2681
+ delete vars[varName];
2682
+ vars[newName] = value;
2683
+ break;
2684
+ }
2685
+ case "value": {
2686
+ const newValue = await input2({
2687
+ message: "New value:",
2688
+ default: vars[varName]
2689
+ });
2690
+ if (newValue === vars[varName]) return false;
2691
+ vars[varName] = newValue;
2692
+ break;
2693
+ }
2694
+ }
2695
+ writeWranglerToml(cwd, config);
2696
+ console.log(pc7.green("\u2713") + " Updated wrangler.toml");
2697
+ return true;
2698
+ }
2699
+ function runWranglerCommandWithInput2(args, inputData) {
2700
+ return new Promise((resolve4, reject) => {
2701
+ let stdout = "";
2702
+ let stderr = "";
2703
+ const child = spawn3("npx", ["wrangler", ...args], {
2704
+ stdio: ["pipe", "pipe", "pipe"]
2705
+ });
2706
+ child.stdout?.on("data", (data) => {
2707
+ stdout += data.toString();
2708
+ });
2709
+ child.stderr?.on("data", (data) => {
2710
+ stderr += data.toString();
2711
+ });
2712
+ child.on("error", (error) => {
2713
+ reject(error);
2714
+ });
2715
+ child.on("close", (code) => {
2716
+ if (code !== 0) {
2717
+ reject(new Error(stderr || `wrangler exited with code ${code}`));
2718
+ } else {
2719
+ resolve4(stdout);
2720
+ }
2721
+ });
2722
+ child.stdin?.write(inputData);
2723
+ child.stdin?.end();
2724
+ });
2725
+ }
2726
+
2727
+ // src/commands/bindings/generate-types.ts
2728
+ import pc8 from "picocolors";
2729
+ async function bindingsGenerateTypes(options) {
2730
+ const verbose = options.verbose ?? false;
2731
+ const logger = createLogger(verbose);
2732
+ try {
2733
+ const cwd = process.cwd();
2734
+ const wranglerPath = findWranglerToml(cwd);
2735
+ if (!wranglerPath) {
2736
+ throw new CliError(
2737
+ "wrangler.toml not found",
2738
+ "ENOENT",
2739
+ "Create a wrangler.toml file or run this command from a Cloudwerk project directory."
2740
+ );
2741
+ }
2742
+ logger.debug(`Found wrangler config: ${wranglerPath}`);
2743
+ const config = readWranglerToml(cwd);
2744
+ const bindings2 = extractBindings(config);
2745
+ if (bindings2.length === 0) {
2746
+ console.log();
2747
+ console.log(pc8.yellow("No bindings found in wrangler.toml."));
2748
+ console.log();
2749
+ console.log(
2750
+ pc8.dim(`Use 'cloudwerk bindings add' to add a binding first.`)
2751
+ );
2752
+ console.log();
2753
+ return;
2754
+ }
2755
+ console.log();
2756
+ logger.info("Generating TypeScript types...");
2757
+ const result = generateEnvTypes(cwd, bindings2);
2758
+ console.log();
2759
+ console.log(
2760
+ pc8.green("\u2713") + ` Updated ${pc8.bold("env.d.ts")} with ${result.bindingCount} binding(s):`
2761
+ );
2762
+ console.log();
2763
+ for (const binding of result.bindings) {
2764
+ console.log(` ${pc8.cyan(binding.name)}: ${pc8.dim(binding.type)}`);
2765
+ }
2766
+ console.log();
2767
+ logger.success("Types generated successfully!");
2768
+ console.log();
2769
+ console.log(
2770
+ pc8.dim("Make sure env.d.ts is included in your tsconfig.json:")
2771
+ );
2772
+ console.log(
2773
+ pc8.dim(' "include": ["env.d.ts", "app/**/*"]')
2774
+ );
2775
+ console.log();
2776
+ } catch (error) {
2777
+ handleCommandError(error, verbose);
2778
+ }
2779
+ }
2780
+
1151
2781
  // src/index.ts
1152
2782
  program.name("cloudwerk").description("Cloudwerk CLI - Build and deploy full-stack apps to Cloudflare").version(VERSION);
1153
2783
  program.command("dev [path]").description("Start development server").option("-p, --port <number>", "Port to listen on", String(DEFAULT_PORT)).option("-H, --host <host>", "Host to bind", DEFAULT_HOST).option("-c, --config <path>", "Path to config file").option("--verbose", "Enable verbose logging").action(dev);
@@ -1156,4 +2786,9 @@ program.command("deploy [path]").description("Deploy to Cloudflare Workers").opt
1156
2786
  var configCmd = program.command("config").description("Manage Cloudwerk configuration");
1157
2787
  configCmd.command("get <key>").description("Get a configuration value").action(configGet);
1158
2788
  configCmd.command("set <key> <value>").description("Set a configuration value").action(configSet);
2789
+ var bindingsCmd = program.command("bindings").description("Manage Cloudflare bindings (D1, KV, R2, Queues, etc.)").option("-e, --env <environment>", "Environment to operate on").option("--verbose", "Enable verbose logging").action(bindings);
2790
+ bindingsCmd.command("add [type]").description("Add a new binding (d1, kv, r2, queue, do, secret)").option("-e, --env <environment>", "Environment to add binding to").option("--skip-types", "Skip TypeScript type generation").option("--verbose", "Enable verbose logging").action(bindingsAdd);
2791
+ bindingsCmd.command("remove [name]").description("Remove a binding").option("-e, --env <environment>", "Environment to remove binding from").option("-f, --force", "Skip confirmation prompt").option("--skip-types", "Skip TypeScript type generation").option("--verbose", "Enable verbose logging").action(bindingsRemove);
2792
+ bindingsCmd.command("update [name]").description("Update an existing binding").option("-e, --env <environment>", "Environment to update binding in").option("--skip-types", "Skip TypeScript type generation").option("--verbose", "Enable verbose logging").action(bindingsUpdate);
2793
+ bindingsCmd.command("generate-types").description("Regenerate TypeScript type definitions").option("--verbose", "Enable verbose logging").action(bindingsGenerateTypes);
1159
2794
  program.parse();