@cloudwerk/cli 0.10.0 → 0.12.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 +2000 -1
  2. package/package.json +6 -4
package/dist/index.js CHANGED
@@ -1148,12 +1148,2011 @@ 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
+ return extractBindingsFromConfig(envConfig);
1221
+ }
1222
+ function getEnvironments(config) {
1223
+ if (!config.env) {
1224
+ return [];
1225
+ }
1226
+ return Object.keys(config.env);
1227
+ }
1228
+ function addD1Binding(cwd, bindingName, databaseName, databaseId, env) {
1229
+ const config = readWranglerToml(cwd);
1230
+ const newBinding = {
1231
+ binding: bindingName,
1232
+ database_name: databaseName,
1233
+ database_id: databaseId
1234
+ };
1235
+ if (env) {
1236
+ if (!config.env) config.env = {};
1237
+ if (!config.env[env]) config.env[env] = {};
1238
+ if (!config.env[env].d1_databases) config.env[env].d1_databases = [];
1239
+ config.env[env].d1_databases.push(newBinding);
1240
+ } else {
1241
+ if (!config.d1_databases) config.d1_databases = [];
1242
+ config.d1_databases.push(newBinding);
1243
+ }
1244
+ writeWranglerToml(cwd, config);
1245
+ }
1246
+ function addKVBinding(cwd, bindingName, namespaceId, previewId, env) {
1247
+ const config = readWranglerToml(cwd);
1248
+ const newBinding = {
1249
+ binding: bindingName,
1250
+ id: namespaceId
1251
+ };
1252
+ if (previewId) {
1253
+ newBinding.preview_id = previewId;
1254
+ }
1255
+ if (env) {
1256
+ if (!config.env) config.env = {};
1257
+ if (!config.env[env]) config.env[env] = {};
1258
+ if (!config.env[env].kv_namespaces) config.env[env].kv_namespaces = [];
1259
+ config.env[env].kv_namespaces.push(newBinding);
1260
+ } else {
1261
+ if (!config.kv_namespaces) config.kv_namespaces = [];
1262
+ config.kv_namespaces.push(newBinding);
1263
+ }
1264
+ writeWranglerToml(cwd, config);
1265
+ }
1266
+ function addR2Binding(cwd, bindingName, bucketName, env) {
1267
+ const config = readWranglerToml(cwd);
1268
+ const newBinding = {
1269
+ binding: bindingName,
1270
+ bucket_name: bucketName
1271
+ };
1272
+ if (env) {
1273
+ if (!config.env) config.env = {};
1274
+ if (!config.env[env]) config.env[env] = {};
1275
+ if (!config.env[env].r2_buckets) config.env[env].r2_buckets = [];
1276
+ config.env[env].r2_buckets.push(newBinding);
1277
+ } else {
1278
+ if (!config.r2_buckets) config.r2_buckets = [];
1279
+ config.r2_buckets.push(newBinding);
1280
+ }
1281
+ writeWranglerToml(cwd, config);
1282
+ }
1283
+ function addQueueBinding(cwd, bindingName, queueName, env) {
1284
+ const config = readWranglerToml(cwd);
1285
+ const newBinding = {
1286
+ binding: bindingName,
1287
+ queue: queueName
1288
+ };
1289
+ if (env) {
1290
+ if (!config.env) config.env = {};
1291
+ if (!config.env[env]) config.env[env] = {};
1292
+ if (!config.env[env].queues) config.env[env].queues = {};
1293
+ if (!config.env[env].queues.producers)
1294
+ config.env[env].queues.producers = [];
1295
+ config.env[env].queues.producers.push(newBinding);
1296
+ } else {
1297
+ if (!config.queues) config.queues = {};
1298
+ if (!config.queues.producers) config.queues.producers = [];
1299
+ config.queues.producers.push(newBinding);
1300
+ }
1301
+ writeWranglerToml(cwd, config);
1302
+ }
1303
+ function addDurableObjectBinding(cwd, bindingName, className, scriptName, env) {
1304
+ const config = readWranglerToml(cwd);
1305
+ const newBinding = {
1306
+ name: bindingName,
1307
+ class_name: className
1308
+ };
1309
+ if (scriptName) {
1310
+ newBinding.script_name = scriptName;
1311
+ }
1312
+ if (env) {
1313
+ if (!config.env) config.env = {};
1314
+ if (!config.env[env]) config.env[env] = {};
1315
+ if (!config.env[env].durable_objects)
1316
+ config.env[env].durable_objects = { bindings: [] };
1317
+ config.env[env].durable_objects.bindings.push(newBinding);
1318
+ } else {
1319
+ if (!config.durable_objects) config.durable_objects = { bindings: [] };
1320
+ if (!config.durable_objects.bindings) config.durable_objects.bindings = [];
1321
+ config.durable_objects.bindings.push(newBinding);
1322
+ }
1323
+ writeWranglerToml(cwd, config);
1324
+ }
1325
+ function addSecretBinding(cwd, name, value, env) {
1326
+ const config = readWranglerToml(cwd);
1327
+ if (env) {
1328
+ if (!config.env) config.env = {};
1329
+ if (!config.env[env]) config.env[env] = {};
1330
+ if (!config.env[env].vars) config.env[env].vars = {};
1331
+ config.env[env].vars[name] = value;
1332
+ } else {
1333
+ if (!config.vars) config.vars = {};
1334
+ config.vars[name] = value;
1335
+ }
1336
+ writeWranglerToml(cwd, config);
1337
+ }
1338
+ function removeBinding(cwd, bindingName, env) {
1339
+ const config = readWranglerToml(cwd);
1340
+ let removed = false;
1341
+ const removeFromConfig = (cfg) => {
1342
+ let found = false;
1343
+ if (cfg.d1_databases) {
1344
+ const idx = cfg.d1_databases.findIndex((b) => b.binding === bindingName);
1345
+ if (idx !== -1) {
1346
+ cfg.d1_databases.splice(idx, 1);
1347
+ if (cfg.d1_databases.length === 0) delete cfg.d1_databases;
1348
+ found = true;
1349
+ }
1350
+ }
1351
+ if (cfg.kv_namespaces) {
1352
+ const idx = cfg.kv_namespaces.findIndex((b) => b.binding === bindingName);
1353
+ if (idx !== -1) {
1354
+ cfg.kv_namespaces.splice(idx, 1);
1355
+ if (cfg.kv_namespaces.length === 0) delete cfg.kv_namespaces;
1356
+ found = true;
1357
+ }
1358
+ }
1359
+ if (cfg.r2_buckets) {
1360
+ const idx = cfg.r2_buckets.findIndex((b) => b.binding === bindingName);
1361
+ if (idx !== -1) {
1362
+ cfg.r2_buckets.splice(idx, 1);
1363
+ if (cfg.r2_buckets.length === 0) delete cfg.r2_buckets;
1364
+ found = true;
1365
+ }
1366
+ }
1367
+ if (cfg.queues?.producers) {
1368
+ const idx = cfg.queues.producers.findIndex(
1369
+ (b) => b.binding === bindingName
1370
+ );
1371
+ if (idx !== -1) {
1372
+ cfg.queues.producers.splice(idx, 1);
1373
+ if (cfg.queues.producers.length === 0) delete cfg.queues.producers;
1374
+ if (!cfg.queues.producers && !cfg.queues.consumers) delete cfg.queues;
1375
+ found = true;
1376
+ }
1377
+ }
1378
+ if (cfg.durable_objects?.bindings) {
1379
+ const idx = cfg.durable_objects.bindings.findIndex(
1380
+ (b) => b.name === bindingName
1381
+ );
1382
+ if (idx !== -1) {
1383
+ cfg.durable_objects.bindings.splice(idx, 1);
1384
+ if (cfg.durable_objects.bindings.length === 0)
1385
+ delete cfg.durable_objects;
1386
+ found = true;
1387
+ }
1388
+ }
1389
+ if (cfg.services) {
1390
+ const idx = cfg.services.findIndex((b) => b.binding === bindingName);
1391
+ if (idx !== -1) {
1392
+ cfg.services.splice(idx, 1);
1393
+ if (cfg.services.length === 0) delete cfg.services;
1394
+ found = true;
1395
+ }
1396
+ }
1397
+ if (cfg.vars && bindingName in cfg.vars) {
1398
+ delete cfg.vars[bindingName];
1399
+ if (Object.keys(cfg.vars).length === 0) delete cfg.vars;
1400
+ found = true;
1401
+ }
1402
+ if (cfg.ai && cfg.ai.binding === bindingName) {
1403
+ delete cfg.ai;
1404
+ found = true;
1405
+ }
1406
+ if (cfg.vectorize) {
1407
+ const idx = cfg.vectorize.findIndex((b) => b.binding === bindingName);
1408
+ if (idx !== -1) {
1409
+ cfg.vectorize.splice(idx, 1);
1410
+ if (cfg.vectorize.length === 0) delete cfg.vectorize;
1411
+ found = true;
1412
+ }
1413
+ }
1414
+ if (cfg.hyperdrive) {
1415
+ const idx = cfg.hyperdrive.findIndex((b) => b.binding === bindingName);
1416
+ if (idx !== -1) {
1417
+ cfg.hyperdrive.splice(idx, 1);
1418
+ if (cfg.hyperdrive.length === 0) delete cfg.hyperdrive;
1419
+ found = true;
1420
+ }
1421
+ }
1422
+ return found;
1423
+ };
1424
+ if (env && config.env?.[env]) {
1425
+ removed = removeFromConfig(config.env[env]);
1426
+ } else {
1427
+ removed = removeFromConfig(config);
1428
+ }
1429
+ if (removed) {
1430
+ writeWranglerToml(cwd, config);
1431
+ }
1432
+ return removed;
1433
+ }
1434
+ function setAccountId(cwd, accountId) {
1435
+ const config = readWranglerToml(cwd);
1436
+ config.account_id = accountId;
1437
+ writeWranglerToml(cwd, config);
1438
+ }
1439
+ function bindingExists(config, bindingName, env) {
1440
+ const targetConfig = env && config.env?.[env] ? config.env[env] : config;
1441
+ const bindings2 = extractBindingsFromConfig(targetConfig);
1442
+ return bindings2.some((b) => b.name === bindingName);
1443
+ }
1444
+ function extractBindingsFromConfig(config) {
1445
+ const bindings2 = [];
1446
+ if (config.d1_databases) {
1447
+ for (const db of config.d1_databases) {
1448
+ bindings2.push({
1449
+ type: "d1",
1450
+ name: db.binding,
1451
+ resourceId: db.database_id,
1452
+ resourceName: db.database_name
1453
+ });
1454
+ }
1455
+ }
1456
+ if (config.kv_namespaces) {
1457
+ for (const kv of config.kv_namespaces) {
1458
+ bindings2.push({
1459
+ type: "kv",
1460
+ name: kv.binding,
1461
+ resourceId: kv.id
1462
+ });
1463
+ }
1464
+ }
1465
+ if (config.r2_buckets) {
1466
+ for (const r2 of config.r2_buckets) {
1467
+ bindings2.push({
1468
+ type: "r2",
1469
+ name: r2.binding,
1470
+ resourceName: r2.bucket_name
1471
+ });
1472
+ }
1473
+ }
1474
+ if (config.queues?.producers) {
1475
+ for (const queue of config.queues.producers) {
1476
+ bindings2.push({
1477
+ type: "queue",
1478
+ name: queue.binding,
1479
+ resourceName: queue.queue
1480
+ });
1481
+ }
1482
+ }
1483
+ if (config.durable_objects?.bindings) {
1484
+ for (const doBinding of config.durable_objects.bindings) {
1485
+ bindings2.push({
1486
+ type: "do",
1487
+ name: doBinding.name,
1488
+ resourceName: doBinding.class_name,
1489
+ extra: doBinding.script_name ? { script_name: doBinding.script_name } : void 0
1490
+ });
1491
+ }
1492
+ }
1493
+ if (config.services) {
1494
+ for (const service of config.services) {
1495
+ bindings2.push({
1496
+ type: "service",
1497
+ name: service.binding,
1498
+ resourceName: service.service,
1499
+ extra: service.environment ? { environment: service.environment } : void 0
1500
+ });
1501
+ }
1502
+ }
1503
+ if (config.vars) {
1504
+ for (const [name] of Object.entries(config.vars)) {
1505
+ bindings2.push({
1506
+ type: "secret",
1507
+ name
1508
+ });
1509
+ }
1510
+ }
1511
+ if (config.ai) {
1512
+ bindings2.push({
1513
+ type: "ai",
1514
+ name: config.ai.binding
1515
+ });
1516
+ }
1517
+ if (config.vectorize) {
1518
+ for (const vec of config.vectorize) {
1519
+ bindings2.push({
1520
+ type: "vectorize",
1521
+ name: vec.binding,
1522
+ resourceName: vec.index_name
1523
+ });
1524
+ }
1525
+ }
1526
+ if (config.hyperdrive) {
1527
+ for (const hd of config.hyperdrive) {
1528
+ bindings2.push({
1529
+ type: "hyperdrive",
1530
+ name: hd.binding,
1531
+ resourceId: hd.id
1532
+ });
1533
+ }
1534
+ }
1535
+ return bindings2;
1536
+ }
1537
+ function getBindingTypeName(type) {
1538
+ switch (type) {
1539
+ case "d1":
1540
+ return "D1";
1541
+ case "kv":
1542
+ return "KV";
1543
+ case "r2":
1544
+ return "R2";
1545
+ case "queue":
1546
+ return "Queue";
1547
+ case "do":
1548
+ return "Durable Object";
1549
+ case "service":
1550
+ return "Service";
1551
+ case "secret":
1552
+ return "Secret/Var";
1553
+ case "ai":
1554
+ return "AI";
1555
+ case "vectorize":
1556
+ return "Vectorize";
1557
+ case "hyperdrive":
1558
+ return "Hyperdrive";
1559
+ default:
1560
+ return type;
1561
+ }
1562
+ }
1563
+ function truncateId(id, maxLen = 12) {
1564
+ if (id.length <= maxLen) return id;
1565
+ return id.slice(0, maxLen - 3) + "...";
1566
+ }
1567
+
1568
+ // src/commands/bindings.ts
1569
+ async function bindings(options) {
1570
+ const verbose = options.verbose ?? false;
1571
+ const logger = createLogger(verbose);
1572
+ const env = options.env;
1573
+ try {
1574
+ const cwd = process.cwd();
1575
+ const wranglerPath = findWranglerToml(cwd);
1576
+ if (!wranglerPath) {
1577
+ throw new CliError(
1578
+ "wrangler.toml not found",
1579
+ "ENOENT",
1580
+ "Create a wrangler.toml file or run this command from a Cloudwerk project directory."
1581
+ );
1582
+ }
1583
+ logger.debug(`Found wrangler config: ${wranglerPath}`);
1584
+ const config = readWranglerToml(cwd);
1585
+ const projectName = config.name || path8.basename(cwd);
1586
+ const bindingsList = extractBindings(config, env);
1587
+ const environments = getEnvironments(config);
1588
+ console.log();
1589
+ const envLabel = env ? env : "production";
1590
+ console.log(
1591
+ pc4.bold(`Bindings for ${projectName}`) + pc4.dim(` (${envLabel}):`)
1592
+ );
1593
+ console.log();
1594
+ if (bindingsList.length === 0) {
1595
+ console.log(pc4.dim(" No bindings configured."));
1596
+ console.log();
1597
+ console.log(
1598
+ pc4.dim(
1599
+ ` Use 'cloudwerk bindings add' to add a new binding${env ? ` --env ${env}` : ""}.`
1600
+ )
1601
+ );
1602
+ } else {
1603
+ console.log(
1604
+ pc4.dim(" Type Binding Resource")
1605
+ );
1606
+ console.log(
1607
+ pc4.dim(" " + "\u2500".repeat(14) + " " + "\u2500".repeat(13) + " " + "\u2500".repeat(30))
1608
+ );
1609
+ for (const binding of bindingsList) {
1610
+ const typeName = getBindingTypeName(binding.type).padEnd(14);
1611
+ const bindingName = binding.name.padEnd(13);
1612
+ let resource = "";
1613
+ if (binding.resourceName && binding.resourceId) {
1614
+ resource = `${binding.resourceName} (${truncateId(binding.resourceId)})`;
1615
+ } else if (binding.resourceName) {
1616
+ resource = binding.resourceName;
1617
+ } else if (binding.resourceId) {
1618
+ resource = `(${truncateId(binding.resourceId)})`;
1619
+ } else {
1620
+ resource = pc4.dim("(configured)");
1621
+ }
1622
+ console.log(` ${pc4.cyan(typeName)} ${pc4.bold(bindingName)} ${resource}`);
1623
+ }
1624
+ }
1625
+ console.log();
1626
+ if (environments.length > 0 && !env) {
1627
+ console.log(
1628
+ pc4.dim("Environments: ") + environments.join(", ")
1629
+ );
1630
+ console.log();
1631
+ }
1632
+ const envFlag = env ? ` --env ${env}` : "";
1633
+ console.log(pc4.dim(`Use 'cloudwerk bindings add${envFlag}' to add a new binding.`));
1634
+ if (!env && environments.length > 0) {
1635
+ console.log(
1636
+ pc4.dim(`Use 'cloudwerk bindings --env <name>' to view environment bindings.`)
1637
+ );
1638
+ }
1639
+ console.log();
1640
+ } catch (error) {
1641
+ handleCommandError(error, verbose);
1642
+ }
1643
+ }
1644
+
1645
+ // src/commands/bindings/add.ts
1646
+ import { spawn as spawn2 } from "child_process";
1647
+ import pc5 from "picocolors";
1648
+ import { select, input, confirm } from "@inquirer/prompts";
1649
+
1650
+ // src/utils/env-types.ts
1651
+ import * as fs8 from "fs";
1652
+ import * as path9 from "path";
1653
+ var TYPE_MAPPINGS = {
1654
+ d1: "D1Database",
1655
+ kv: "KVNamespace",
1656
+ r2: "R2Bucket",
1657
+ queue: "Queue",
1658
+ do: "DurableObjectNamespace",
1659
+ service: "Fetcher",
1660
+ secret: "string",
1661
+ ai: "Ai",
1662
+ vectorize: "VectorizeIndex",
1663
+ hyperdrive: "Hyperdrive"
1664
+ };
1665
+ function getTypeForBinding(type) {
1666
+ return TYPE_MAPPINGS[type] || "unknown";
1667
+ }
1668
+ function generateEnvTypes(cwd, bindings2, options = {}) {
1669
+ const outputPath = options.outputPath || path9.join(cwd, "env.d.ts");
1670
+ const includeTimestamp = options.includeTimestamp ?? true;
1671
+ const bindingsByType = /* @__PURE__ */ new Map();
1672
+ for (const binding of bindings2) {
1673
+ const existing = bindingsByType.get(binding.type) || [];
1674
+ existing.push(binding);
1675
+ bindingsByType.set(binding.type, existing);
1676
+ }
1677
+ const lines = [];
1678
+ lines.push("// Auto-generated by cloudwerk bindings - DO NOT EDIT");
1679
+ if (includeTimestamp) {
1680
+ lines.push(`// Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
1681
+ }
1682
+ lines.push("");
1683
+ lines.push("interface CloudflareBindings {");
1684
+ const typeOrder = [
1685
+ "d1",
1686
+ "kv",
1687
+ "r2",
1688
+ "queue",
1689
+ "do",
1690
+ "service",
1691
+ "ai",
1692
+ "vectorize",
1693
+ "hyperdrive",
1694
+ "secret"
1695
+ ];
1696
+ const resultBindings = [];
1697
+ let firstSection = true;
1698
+ for (const type of typeOrder) {
1699
+ const typeBindings = bindingsByType.get(type);
1700
+ if (!typeBindings || typeBindings.length === 0) continue;
1701
+ const tsType = getTypeForBinding(type);
1702
+ const sectionName = getSectionName(type);
1703
+ if (!firstSection) {
1704
+ lines.push("");
1705
+ }
1706
+ lines.push(` // ${sectionName}`);
1707
+ firstSection = false;
1708
+ for (const binding of typeBindings) {
1709
+ lines.push(` ${binding.name}: ${tsType}`);
1710
+ resultBindings.push({ name: binding.name, type: tsType });
1711
+ }
1712
+ }
1713
+ lines.push("}");
1714
+ lines.push("");
1715
+ lines.push("// Re-export for convenience");
1716
+ lines.push("type Env = CloudflareBindings");
1717
+ lines.push("export type { Env, CloudflareBindings }");
1718
+ lines.push("");
1719
+ fs8.writeFileSync(outputPath, lines.join("\n"), "utf-8");
1720
+ return {
1721
+ outputPath,
1722
+ bindingCount: bindings2.length,
1723
+ bindings: resultBindings
1724
+ };
1725
+ }
1726
+ function getSectionName(type) {
1727
+ switch (type) {
1728
+ case "d1":
1729
+ return "D1 Databases";
1730
+ case "kv":
1731
+ return "KV Namespaces";
1732
+ case "r2":
1733
+ return "R2 Buckets";
1734
+ case "queue":
1735
+ return "Queues";
1736
+ case "do":
1737
+ return "Durable Objects";
1738
+ case "service":
1739
+ return "Services";
1740
+ case "secret":
1741
+ return "Environment Variables";
1742
+ case "ai":
1743
+ return "AI";
1744
+ case "vectorize":
1745
+ return "Vectorize Indexes";
1746
+ case "hyperdrive":
1747
+ return "Hyperdrive";
1748
+ default:
1749
+ return "Other";
1750
+ }
1751
+ }
1752
+
1753
+ // src/commands/bindings/add.ts
1754
+ async function bindingsAdd(bindingType, options) {
1755
+ const verbose = options.verbose ?? false;
1756
+ const logger = createLogger(verbose);
1757
+ const env = options.env;
1758
+ try {
1759
+ const cwd = process.cwd();
1760
+ const wranglerPath = findWranglerToml(cwd);
1761
+ if (!wranglerPath) {
1762
+ throw new CliError(
1763
+ "wrangler.toml not found",
1764
+ "ENOENT",
1765
+ "Create a wrangler.toml file or run this command from a Cloudwerk project directory."
1766
+ );
1767
+ }
1768
+ logger.debug(`Found wrangler config: ${wranglerPath}`);
1769
+ const type = bindingType || await promptForBindingType();
1770
+ const envLabel = env ? ` (${env})` : "";
1771
+ console.log();
1772
+ logger.info(`Adding ${type.toUpperCase()} binding${envLabel}...`);
1773
+ switch (type.toLowerCase()) {
1774
+ case "d1":
1775
+ await addD1(cwd, options);
1776
+ break;
1777
+ case "kv":
1778
+ await addKV(cwd, options);
1779
+ break;
1780
+ case "r2":
1781
+ await addR2(cwd, options);
1782
+ break;
1783
+ case "queue":
1784
+ await addQueue(cwd, options);
1785
+ break;
1786
+ case "do":
1787
+ await addDurableObject(cwd, options);
1788
+ break;
1789
+ case "secret":
1790
+ await addSecret(cwd, options);
1791
+ break;
1792
+ default:
1793
+ throw new CliError(
1794
+ `Unknown binding type: ${type}`,
1795
+ "EINVAL",
1796
+ "Valid types are: d1, kv, r2, queue, do, secret"
1797
+ );
1798
+ }
1799
+ if (!options.skipTypes) {
1800
+ await regenerateTypes(cwd);
1801
+ }
1802
+ console.log();
1803
+ logger.success("Binding added successfully!");
1804
+ console.log();
1805
+ } catch (error) {
1806
+ handleCommandError(error, verbose);
1807
+ }
1808
+ }
1809
+ async function promptForBindingType() {
1810
+ return select({
1811
+ message: "What type of binding do you want to add?",
1812
+ choices: [
1813
+ { name: "D1 Database", value: "d1" },
1814
+ { name: "KV Namespace", value: "kv" },
1815
+ { name: "R2 Bucket", value: "r2" },
1816
+ { name: "Queue", value: "queue" },
1817
+ { name: "Durable Object", value: "do" },
1818
+ { name: "Secret / Environment Variable", value: "secret" }
1819
+ ]
1820
+ });
1821
+ }
1822
+ async function addD1(cwd, options) {
1823
+ const config = readWranglerToml(cwd);
1824
+ const env = options.env;
1825
+ const bindingName = await input({
1826
+ message: "Binding name (e.g., DB):",
1827
+ validate: (value) => {
1828
+ if (!value.trim()) return "Binding name is required";
1829
+ if (!/^[A-Z_][A-Z0-9_]*$/i.test(value))
1830
+ return "Binding name must be alphanumeric with underscores";
1831
+ if (bindingExists(config, value, env))
1832
+ return `Binding "${value}" already exists`;
1833
+ return true;
1834
+ }
1835
+ });
1836
+ const databaseName = await input({
1837
+ message: "Database name:",
1838
+ default: `${config.name || "my-app"}${env ? `-${env}` : ""}-db`,
1839
+ validate: (value) => {
1840
+ if (!value.trim()) return "Database name is required";
1841
+ return true;
1842
+ }
1843
+ });
1844
+ const createNew = await confirm({
1845
+ message: "Create a new D1 database?",
1846
+ default: true
1847
+ });
1848
+ let databaseId;
1849
+ if (createNew) {
1850
+ console.log();
1851
+ console.log(pc5.dim(`Creating D1 database "${databaseName}"...`));
1852
+ const result = await runWranglerCommand(["d1", "create", databaseName]);
1853
+ const idMatch = result.match(/database_id\s*=\s*"([^"]+)"/);
1854
+ if (!idMatch) {
1855
+ throw new CliError(
1856
+ "Failed to parse database ID from wrangler output",
1857
+ "EPARSE",
1858
+ "Try creating the database manually with: wrangler d1 create " + databaseName
1859
+ );
1860
+ }
1861
+ databaseId = idMatch[1];
1862
+ console.log(pc5.green("\u2713") + ` Created database: ${databaseId}`);
1863
+ } else {
1864
+ const databases = await listD1Databases();
1865
+ if (databases.length === 0) {
1866
+ throw new CliError(
1867
+ "No D1 databases found",
1868
+ "ENOENT",
1869
+ "Create a database first with: wrangler d1 create <name>"
1870
+ );
1871
+ }
1872
+ const selected = await select({
1873
+ message: "Select an existing database:",
1874
+ choices: databases.map((db) => ({
1875
+ name: `${db.name} (${db.uuid.slice(0, 8)}...)`,
1876
+ value: db.uuid
1877
+ }))
1878
+ });
1879
+ databaseId = selected;
1880
+ }
1881
+ addD1Binding(cwd, bindingName, databaseName, databaseId, env);
1882
+ console.log(pc5.green("\u2713") + " Updated wrangler.toml");
1883
+ showTypeHint(bindingName, "d1");
1884
+ }
1885
+ async function listD1Databases() {
1886
+ const output = await runWranglerCommand(["d1", "list", "--json"]);
1887
+ return JSON.parse(output);
1888
+ }
1889
+ async function addKV(cwd, options) {
1890
+ const config = readWranglerToml(cwd);
1891
+ const env = options.env;
1892
+ const bindingName = await input({
1893
+ message: "Binding name (e.g., CACHE):",
1894
+ validate: (value) => {
1895
+ if (!value.trim()) return "Binding name is required";
1896
+ if (!/^[A-Z_][A-Z0-9_]*$/i.test(value))
1897
+ return "Binding name must be alphanumeric with underscores";
1898
+ if (bindingExists(config, value, env))
1899
+ return `Binding "${value}" already exists`;
1900
+ return true;
1901
+ }
1902
+ });
1903
+ const createNew = await confirm({
1904
+ message: "Create a new KV namespace?",
1905
+ default: true
1906
+ });
1907
+ let namespaceId;
1908
+ if (createNew) {
1909
+ console.log();
1910
+ console.log(pc5.dim(`Creating KV namespace "${bindingName}"...`));
1911
+ const result = await runWranglerCommand([
1912
+ "kv",
1913
+ "namespace",
1914
+ "create",
1915
+ bindingName
1916
+ ]);
1917
+ const idMatch = result.match(/id\s*=\s*"([^"]+)"/);
1918
+ if (!idMatch) {
1919
+ throw new CliError(
1920
+ "Failed to parse namespace ID from wrangler output",
1921
+ "EPARSE",
1922
+ "Try creating the namespace manually with: wrangler kv namespace create " + bindingName
1923
+ );
1924
+ }
1925
+ namespaceId = idMatch[1];
1926
+ console.log(pc5.green("\u2713") + ` Created namespace: ${namespaceId}`);
1927
+ } else {
1928
+ const namespaces = await listKVNamespaces();
1929
+ if (namespaces.length === 0) {
1930
+ throw new CliError(
1931
+ "No KV namespaces found",
1932
+ "ENOENT",
1933
+ "Create a namespace first with: wrangler kv namespace create <name>"
1934
+ );
1935
+ }
1936
+ const selected = await select({
1937
+ message: "Select an existing namespace:",
1938
+ choices: namespaces.map((ns) => ({
1939
+ name: `${ns.title} (${ns.id.slice(0, 8)}...)`,
1940
+ value: ns.id
1941
+ }))
1942
+ });
1943
+ namespaceId = selected;
1944
+ }
1945
+ addKVBinding(cwd, bindingName, namespaceId, void 0, env);
1946
+ console.log(pc5.green("\u2713") + " Updated wrangler.toml");
1947
+ showTypeHint(bindingName, "kv");
1948
+ }
1949
+ async function listKVNamespaces() {
1950
+ const output = await runWranglerCommand(["kv", "namespace", "list", "--json"]);
1951
+ return JSON.parse(output);
1952
+ }
1953
+ async function addR2(cwd, options) {
1954
+ const config = readWranglerToml(cwd);
1955
+ const env = options.env;
1956
+ const bindingName = await input({
1957
+ message: "Binding name (e.g., STORAGE):",
1958
+ validate: (value) => {
1959
+ if (!value.trim()) return "Binding name is required";
1960
+ if (!/^[A-Z_][A-Z0-9_]*$/i.test(value))
1961
+ return "Binding name must be alphanumeric with underscores";
1962
+ if (bindingExists(config, value, env))
1963
+ return `Binding "${value}" already exists`;
1964
+ return true;
1965
+ }
1966
+ });
1967
+ const createNew = await confirm({
1968
+ message: "Create a new R2 bucket?",
1969
+ default: true
1970
+ });
1971
+ let bucketName;
1972
+ if (createNew) {
1973
+ bucketName = await input({
1974
+ message: "Bucket name:",
1975
+ default: `${config.name || "my-app"}${env ? `-${env}` : ""}-bucket`,
1976
+ validate: (value) => {
1977
+ if (!value.trim()) return "Bucket name is required";
1978
+ return true;
1979
+ }
1980
+ });
1981
+ console.log();
1982
+ console.log(pc5.dim(`Creating R2 bucket "${bucketName}"...`));
1983
+ await runWranglerCommand(["r2", "bucket", "create", bucketName]);
1984
+ console.log(pc5.green("\u2713") + ` Created bucket: ${bucketName}`);
1985
+ } else {
1986
+ const buckets = await listR2Buckets();
1987
+ if (buckets.length === 0) {
1988
+ throw new CliError(
1989
+ "No R2 buckets found",
1990
+ "ENOENT",
1991
+ "Create a bucket first with: wrangler r2 bucket create <name>"
1992
+ );
1993
+ }
1994
+ const selected = await select({
1995
+ message: "Select an existing bucket:",
1996
+ choices: buckets.map((bucket) => ({
1997
+ name: bucket.name,
1998
+ value: bucket.name
1999
+ }))
2000
+ });
2001
+ bucketName = selected;
2002
+ }
2003
+ addR2Binding(cwd, bindingName, bucketName, env);
2004
+ console.log(pc5.green("\u2713") + " Updated wrangler.toml");
2005
+ showTypeHint(bindingName, "r2");
2006
+ }
2007
+ async function listR2Buckets() {
2008
+ const output = await runWranglerCommand(["r2", "bucket", "list", "--json"]);
2009
+ return JSON.parse(output);
2010
+ }
2011
+ async function addQueue(cwd, options) {
2012
+ const config = readWranglerToml(cwd);
2013
+ const env = options.env;
2014
+ const bindingName = await input({
2015
+ message: "Binding name (e.g., JOBS):",
2016
+ validate: (value) => {
2017
+ if (!value.trim()) return "Binding name is required";
2018
+ if (!/^[A-Z_][A-Z0-9_]*$/i.test(value))
2019
+ return "Binding name must be alphanumeric with underscores";
2020
+ if (bindingExists(config, value, env))
2021
+ return `Binding "${value}" already exists`;
2022
+ return true;
2023
+ }
2024
+ });
2025
+ const createNew = await confirm({
2026
+ message: "Create a new queue?",
2027
+ default: true
2028
+ });
2029
+ let queueName;
2030
+ if (createNew) {
2031
+ queueName = await input({
2032
+ message: "Queue name:",
2033
+ default: `${config.name || "my-app"}${env ? `-${env}` : ""}-queue`,
2034
+ validate: (value) => {
2035
+ if (!value.trim()) return "Queue name is required";
2036
+ return true;
2037
+ }
2038
+ });
2039
+ console.log();
2040
+ console.log(pc5.dim(`Creating queue "${queueName}"...`));
2041
+ await runWranglerCommand(["queues", "create", queueName]);
2042
+ console.log(pc5.green("\u2713") + ` Created queue: ${queueName}`);
2043
+ } else {
2044
+ const queues = await listQueues();
2045
+ if (queues.length === 0) {
2046
+ throw new CliError(
2047
+ "No queues found",
2048
+ "ENOENT",
2049
+ "Create a queue first with: wrangler queues create <name>"
2050
+ );
2051
+ }
2052
+ const selected = await select({
2053
+ message: "Select an existing queue:",
2054
+ choices: queues.map((queue) => ({
2055
+ name: queue.queue_name,
2056
+ value: queue.queue_name
2057
+ }))
2058
+ });
2059
+ queueName = selected;
2060
+ }
2061
+ addQueueBinding(cwd, bindingName, queueName, env);
2062
+ console.log(pc5.green("\u2713") + " Updated wrangler.toml");
2063
+ showTypeHint(bindingName, "queue");
2064
+ }
2065
+ async function listQueues() {
2066
+ const output = await runWranglerCommand(["queues", "list", "--json"]);
2067
+ return JSON.parse(output);
2068
+ }
2069
+ async function addDurableObject(cwd, options) {
2070
+ const config = readWranglerToml(cwd);
2071
+ const env = options.env;
2072
+ const bindingName = await input({
2073
+ message: "Binding name (e.g., COUNTER):",
2074
+ validate: (value) => {
2075
+ if (!value.trim()) return "Binding name is required";
2076
+ if (!/^[A-Z_][A-Z0-9_]*$/i.test(value))
2077
+ return "Binding name must be alphanumeric with underscores";
2078
+ if (bindingExists(config, value, env))
2079
+ return `Binding "${value}" already exists`;
2080
+ return true;
2081
+ }
2082
+ });
2083
+ const className = await input({
2084
+ message: "Durable Object class name:",
2085
+ default: bindingName.split("_").map((s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase()).join(""),
2086
+ validate: (value) => {
2087
+ if (!value.trim()) return "Class name is required";
2088
+ return true;
2089
+ }
2090
+ });
2091
+ const isExternal = await confirm({
2092
+ message: "Is this a Durable Object from another Worker?",
2093
+ default: false
2094
+ });
2095
+ let scriptName;
2096
+ if (isExternal) {
2097
+ scriptName = await input({
2098
+ message: "Worker script name:",
2099
+ validate: (value) => {
2100
+ if (!value.trim()) return "Script name is required";
2101
+ return true;
2102
+ }
2103
+ });
2104
+ }
2105
+ addDurableObjectBinding(cwd, bindingName, className, scriptName, env);
2106
+ console.log(pc5.green("\u2713") + " Updated wrangler.toml");
2107
+ showTypeHint(bindingName, "do");
2108
+ if (!isExternal) {
2109
+ console.log();
2110
+ console.log(pc5.dim("Remember to export your Durable Object class:"));
2111
+ console.log(pc5.dim(` export class ${className} extends DurableObject { ... }`));
2112
+ }
2113
+ }
2114
+ async function addSecret(cwd, options) {
2115
+ const config = readWranglerToml(cwd);
2116
+ const env = options.env;
2117
+ const varName = await input({
2118
+ message: "Variable name (e.g., API_KEY):",
2119
+ validate: (value) => {
2120
+ if (!value.trim()) return "Variable name is required";
2121
+ if (!/^[A-Z_][A-Z0-9_]*$/i.test(value))
2122
+ return "Variable name must be alphanumeric with underscores";
2123
+ if (bindingExists(config, value, env))
2124
+ return `Variable "${value}" already exists`;
2125
+ return true;
2126
+ }
2127
+ });
2128
+ const isSecret = await confirm({
2129
+ message: "Is this a secret (sensitive value)?",
2130
+ default: true
2131
+ });
2132
+ if (isSecret) {
2133
+ console.log();
2134
+ console.log(pc5.dim("Enter the secret value (will be hidden):"));
2135
+ const varValue = await input({
2136
+ message: "Secret value:"
2137
+ });
2138
+ console.log();
2139
+ console.log(pc5.dim(`Setting secret "${varName}"...`));
2140
+ const args = ["secret", "put", varName];
2141
+ if (env) args.push("--env", env);
2142
+ await runWranglerCommandWithInput(args, varValue);
2143
+ console.log(pc5.green("\u2713") + ` Secret "${varName}" set`);
2144
+ console.log();
2145
+ console.log(
2146
+ pc5.dim("Note: Secrets are stored securely and not written to wrangler.toml.")
2147
+ );
2148
+ } else {
2149
+ const varValue = await input({
2150
+ message: "Variable value:",
2151
+ validate: (value) => {
2152
+ if (!value.trim()) return "Value is required";
2153
+ return true;
2154
+ }
2155
+ });
2156
+ addSecretBinding(cwd, varName, varValue, env);
2157
+ console.log(pc5.green("\u2713") + " Updated wrangler.toml");
2158
+ }
2159
+ showTypeHint(varName, "secret");
2160
+ }
2161
+ async function regenerateTypes(cwd) {
2162
+ const config = readWranglerToml(cwd);
2163
+ const bindings2 = extractBindings(config);
2164
+ if (bindings2.length === 0) {
2165
+ return;
2166
+ }
2167
+ const result = generateEnvTypes(cwd, bindings2);
2168
+ console.log(pc5.green("\u2713") + ` Updated env.d.ts with ${result.bindingCount} binding(s)`);
2169
+ }
2170
+ function showTypeHint(bindingName, type) {
2171
+ const tsType = getTypeForBinding(type);
2172
+ console.log();
2173
+ console.log(pc5.dim(`TypeScript type: ${bindingName}: ${tsType}`));
2174
+ }
2175
+ function parseAccountsFromError(errorMessage) {
2176
+ const accounts = [];
2177
+ const accountRegex = /`([^`]+)`:\s*`([a-f0-9]+)`/g;
2178
+ let match;
2179
+ while ((match = accountRegex.exec(errorMessage)) !== null) {
2180
+ accounts.push({
2181
+ name: match[1],
2182
+ id: match[2]
2183
+ });
2184
+ }
2185
+ return accounts;
2186
+ }
2187
+ function isMultiAccountError(errorMessage) {
2188
+ return errorMessage.includes("More than one account available");
2189
+ }
2190
+ async function promptForAccount(accounts) {
2191
+ console.log();
2192
+ const selected = await select({
2193
+ message: "Multiple Cloudflare accounts available. Which account do you want to use?",
2194
+ choices: accounts.map((account) => ({
2195
+ name: `${account.name} (${account.id.slice(0, 8)}...)`,
2196
+ value: account.id
2197
+ }))
2198
+ });
2199
+ return selected;
2200
+ }
2201
+ async function handleWranglerResult(result, retryFn, cwd) {
2202
+ if (result.error && isMultiAccountError(result.stderr)) {
2203
+ const accounts = parseAccountsFromError(result.stderr);
2204
+ if (accounts.length > 0) {
2205
+ const selectedAccountId = await promptForAccount(accounts);
2206
+ const projectCwd = cwd || process.cwd();
2207
+ setAccountId(projectCwd, selectedAccountId);
2208
+ console.log(pc5.green("\u2713") + " Set account_id in wrangler.toml");
2209
+ console.log();
2210
+ const retryResult = await retryFn();
2211
+ if (retryResult.error) {
2212
+ throw new Error(retryResult.stderr || `wrangler exited with code ${retryResult.code}`);
2213
+ }
2214
+ return retryResult.stdout;
2215
+ }
2216
+ }
2217
+ if (result.error) {
2218
+ throw new Error(result.stderr || `wrangler exited with code ${result.code}`);
2219
+ }
2220
+ return result.stdout;
2221
+ }
2222
+ async function runWranglerCommand(args, cwd) {
2223
+ const result = await runWranglerCommandRaw(args);
2224
+ return handleWranglerResult(result, () => runWranglerCommandRaw(args), cwd);
2225
+ }
2226
+ function runWranglerCommandRaw(args) {
2227
+ return new Promise((resolve4) => {
2228
+ let stdout = "";
2229
+ let stderr = "";
2230
+ const child = spawn2("npx", ["wrangler", ...args], {
2231
+ stdio: ["pipe", "pipe", "pipe"]
2232
+ });
2233
+ child.stdout?.on("data", (data) => {
2234
+ stdout += data.toString();
2235
+ });
2236
+ child.stderr?.on("data", (data) => {
2237
+ stderr += data.toString();
2238
+ });
2239
+ child.on("error", (error) => {
2240
+ resolve4({ stdout, stderr: error.message, code: null, error: true });
2241
+ });
2242
+ child.on("close", (code) => {
2243
+ resolve4({ stdout, stderr, code, error: code !== 0 });
2244
+ });
2245
+ });
2246
+ }
2247
+ async function runWranglerCommandWithInput(args, inputData, cwd) {
2248
+ const result = await runWranglerCommandWithInputRaw(args, inputData);
2249
+ return handleWranglerResult(
2250
+ result,
2251
+ () => runWranglerCommandWithInputRaw(args, inputData),
2252
+ cwd
2253
+ );
2254
+ }
2255
+ function runWranglerCommandWithInputRaw(args, inputData) {
2256
+ return new Promise((resolve4) => {
2257
+ let stdout = "";
2258
+ let stderr = "";
2259
+ const child = spawn2("npx", ["wrangler", ...args], {
2260
+ stdio: ["pipe", "pipe", "pipe"]
2261
+ });
2262
+ child.stdout?.on("data", (data) => {
2263
+ stdout += data.toString();
2264
+ });
2265
+ child.stderr?.on("data", (data) => {
2266
+ stderr += data.toString();
2267
+ });
2268
+ child.on("error", (error) => {
2269
+ resolve4({ stdout, stderr: error.message, code: null, error: true });
2270
+ });
2271
+ child.on("close", (code) => {
2272
+ resolve4({ stdout, stderr, code, error: code !== 0 });
2273
+ });
2274
+ child.stdin?.write(inputData);
2275
+ child.stdin?.end();
2276
+ });
2277
+ }
2278
+
2279
+ // src/commands/bindings/remove.ts
2280
+ import pc6 from "picocolors";
2281
+ import { confirm as confirm2, select as select2 } from "@inquirer/prompts";
2282
+ async function bindingsRemove(bindingName, options) {
2283
+ const verbose = options.verbose ?? false;
2284
+ const logger = createLogger(verbose);
2285
+ const env = options.env;
2286
+ try {
2287
+ const cwd = process.cwd();
2288
+ const wranglerPath = findWranglerToml(cwd);
2289
+ if (!wranglerPath) {
2290
+ throw new CliError(
2291
+ "wrangler.toml not found",
2292
+ "ENOENT",
2293
+ "Create a wrangler.toml file or run this command from a Cloudwerk project directory."
2294
+ );
2295
+ }
2296
+ logger.debug(`Found wrangler config: ${wranglerPath}`);
2297
+ const config = readWranglerToml(cwd);
2298
+ const bindings2 = extractBindings(config, env);
2299
+ if (bindings2.length === 0) {
2300
+ const envLabel = env ? ` in ${env}` : "";
2301
+ throw new CliError(
2302
+ `No bindings found${envLabel}`,
2303
+ "ENOENT",
2304
+ `Use 'cloudwerk bindings add' to add a binding first.`
2305
+ );
2306
+ }
2307
+ let targetBinding = bindingName;
2308
+ if (!targetBinding) {
2309
+ targetBinding = await select2({
2310
+ message: "Select a binding to remove:",
2311
+ choices: bindings2.map((b) => ({
2312
+ name: `${b.name} (${getBindingTypeName(b.type)})`,
2313
+ value: b.name
2314
+ }))
2315
+ });
2316
+ }
2317
+ const binding = bindings2.find((b) => b.name === targetBinding);
2318
+ if (!binding) {
2319
+ const envLabel = env ? ` in ${env}` : "";
2320
+ throw new CliError(
2321
+ `Binding "${targetBinding}" not found${envLabel}`,
2322
+ "ENOENT",
2323
+ `Use 'cloudwerk bindings' to see available bindings.`
2324
+ );
2325
+ }
2326
+ if (!options.force) {
2327
+ const confirmed = await confirm2({
2328
+ message: `Remove binding "${targetBinding}" (${getBindingTypeName(binding.type)})?`,
2329
+ default: false
2330
+ });
2331
+ if (!confirmed) {
2332
+ console.log(pc6.dim("Cancelled."));
2333
+ return;
2334
+ }
2335
+ }
2336
+ console.log();
2337
+ const removed = removeBinding(cwd, targetBinding, env);
2338
+ if (!removed) {
2339
+ throw new CliError(
2340
+ `Failed to remove binding "${targetBinding}"`,
2341
+ "EREMOVE",
2342
+ "The binding may have already been removed."
2343
+ );
2344
+ }
2345
+ console.log(pc6.green("\u2713") + ` Removed binding "${targetBinding}" from wrangler.toml`);
2346
+ if (!options.skipTypes) {
2347
+ const updatedConfig = readWranglerToml(cwd);
2348
+ const updatedBindings = extractBindings(updatedConfig);
2349
+ if (updatedBindings.length > 0) {
2350
+ const result = generateEnvTypes(cwd, updatedBindings);
2351
+ console.log(
2352
+ pc6.green("\u2713") + ` Updated env.d.ts with ${result.bindingCount} binding(s)`
2353
+ );
2354
+ } else {
2355
+ console.log(
2356
+ pc6.dim("Note: No bindings remain. Consider removing env.d.ts manually.")
2357
+ );
2358
+ }
2359
+ }
2360
+ console.log();
2361
+ logger.success("Binding removed successfully!");
2362
+ console.log();
2363
+ console.log(
2364
+ pc6.dim(
2365
+ "Note: The Cloudflare resource itself was not deleted. Use wrangler to delete the resource if needed."
2366
+ )
2367
+ );
2368
+ console.log();
2369
+ } catch (error) {
2370
+ handleCommandError(error, verbose);
2371
+ }
2372
+ }
2373
+
2374
+ // src/commands/bindings/update.ts
2375
+ import { spawn as spawn3 } from "child_process";
2376
+ import pc7 from "picocolors";
2377
+ import { input as input2, select as select3 } from "@inquirer/prompts";
2378
+ async function bindingsUpdate(bindingName, options) {
2379
+ const verbose = options.verbose ?? false;
2380
+ const logger = createLogger(verbose);
2381
+ const env = options.env;
2382
+ try {
2383
+ const cwd = process.cwd();
2384
+ const wranglerPath = findWranglerToml(cwd);
2385
+ if (!wranglerPath) {
2386
+ throw new CliError(
2387
+ "wrangler.toml not found",
2388
+ "ENOENT",
2389
+ "Create a wrangler.toml file or run this command from a Cloudwerk project directory."
2390
+ );
2391
+ }
2392
+ logger.debug(`Found wrangler config: ${wranglerPath}`);
2393
+ const config = readWranglerToml(cwd);
2394
+ const bindings2 = extractBindings(config, env);
2395
+ if (bindings2.length === 0) {
2396
+ const envLabel = env ? ` in ${env}` : "";
2397
+ throw new CliError(
2398
+ `No bindings found${envLabel}`,
2399
+ "ENOENT",
2400
+ `Use 'cloudwerk bindings add' to add a binding first.`
2401
+ );
2402
+ }
2403
+ let targetBinding = bindingName;
2404
+ if (!targetBinding) {
2405
+ targetBinding = await select3({
2406
+ message: "Select a binding to update:",
2407
+ choices: bindings2.map((b) => ({
2408
+ name: `${b.name} (${getBindingTypeName(b.type)})`,
2409
+ value: b.name
2410
+ }))
2411
+ });
2412
+ }
2413
+ const binding = bindings2.find((b) => b.name === targetBinding);
2414
+ if (!binding) {
2415
+ const envLabel = env ? ` in ${env}` : "";
2416
+ throw new CliError(
2417
+ `Binding "${targetBinding}" not found${envLabel}`,
2418
+ "ENOENT",
2419
+ `Use 'cloudwerk bindings' to see available bindings.`
2420
+ );
2421
+ }
2422
+ console.log();
2423
+ logger.info(
2424
+ `Updating ${getBindingTypeName(binding.type)} binding "${targetBinding}"...`
2425
+ );
2426
+ const updated = await updateBinding(cwd, config, binding, env);
2427
+ if (!updated) {
2428
+ console.log(pc7.dim("No changes made."));
2429
+ return;
2430
+ }
2431
+ if (!options.skipTypes) {
2432
+ const updatedConfig = readWranglerToml(cwd);
2433
+ const updatedBindings = extractBindings(updatedConfig);
2434
+ if (updatedBindings.length > 0) {
2435
+ const result = generateEnvTypes(cwd, updatedBindings);
2436
+ console.log(
2437
+ pc7.green("\u2713") + ` Updated env.d.ts with ${result.bindingCount} binding(s)`
2438
+ );
2439
+ }
2440
+ }
2441
+ console.log();
2442
+ logger.success("Binding updated successfully!");
2443
+ console.log();
2444
+ } catch (error) {
2445
+ handleCommandError(error, verbose);
2446
+ }
2447
+ }
2448
+ async function updateBinding(cwd, config, binding, env) {
2449
+ const updateOptions = getUpdateOptions(binding);
2450
+ if (updateOptions.length === 0) {
2451
+ console.log(pc7.dim("This binding type has no updatable fields."));
2452
+ return false;
2453
+ }
2454
+ const field = await select3({
2455
+ message: "What do you want to update?",
2456
+ choices: updateOptions
2457
+ });
2458
+ const targetConfig = env ? config.env?.[env] ?? config : config;
2459
+ switch (binding.type) {
2460
+ case "d1":
2461
+ return updateD1(cwd, config, targetConfig, binding.name, field);
2462
+ case "kv":
2463
+ return updateKV(cwd, config, targetConfig, binding.name, field);
2464
+ case "r2":
2465
+ return updateR2(cwd, config, targetConfig, binding.name, field);
2466
+ case "queue":
2467
+ return updateQueue(cwd, config, targetConfig, binding.name, field);
2468
+ case "do":
2469
+ return updateDurableObject(cwd, config, targetConfig, binding.name, field);
2470
+ case "secret":
2471
+ return updateSecret(cwd, config, targetConfig, binding.name, field, env);
2472
+ default:
2473
+ console.log(pc7.dim(`Updating ${binding.type} bindings is not yet supported.`));
2474
+ return false;
2475
+ }
2476
+ }
2477
+ function getUpdateOptions(binding) {
2478
+ switch (binding.type) {
2479
+ case "d1":
2480
+ return [
2481
+ { name: "Binding name", value: "name" },
2482
+ { name: "Database name", value: "database_name" },
2483
+ { name: "Database ID", value: "database_id" }
2484
+ ];
2485
+ case "kv":
2486
+ return [
2487
+ { name: "Binding name", value: "name" },
2488
+ { name: "Namespace ID", value: "id" },
2489
+ { name: "Preview ID", value: "preview_id" }
2490
+ ];
2491
+ case "r2":
2492
+ return [
2493
+ { name: "Binding name", value: "name" },
2494
+ { name: "Bucket name", value: "bucket_name" }
2495
+ ];
2496
+ case "queue":
2497
+ return [
2498
+ { name: "Binding name", value: "name" },
2499
+ { name: "Queue name", value: "queue" }
2500
+ ];
2501
+ case "do":
2502
+ return [
2503
+ { name: "Binding name", value: "name" },
2504
+ { name: "Class name", value: "class_name" },
2505
+ { name: "Script name", value: "script_name" }
2506
+ ];
2507
+ case "secret":
2508
+ return [
2509
+ { name: "Variable name", value: "name" },
2510
+ { name: "Value", value: "value" }
2511
+ ];
2512
+ default:
2513
+ return [];
2514
+ }
2515
+ }
2516
+ async function updateD1(cwd, config, targetConfig, bindingName, field) {
2517
+ const bindings2 = targetConfig.d1_databases;
2518
+ if (!bindings2) return false;
2519
+ const binding = bindings2.find((b) => b.binding === bindingName);
2520
+ if (!binding) return false;
2521
+ switch (field) {
2522
+ case "name": {
2523
+ const newName = await input2({
2524
+ message: "New binding name:",
2525
+ default: binding.binding
2526
+ });
2527
+ if (newName === binding.binding) return false;
2528
+ binding.binding = newName;
2529
+ break;
2530
+ }
2531
+ case "database_name": {
2532
+ const newName = await input2({
2533
+ message: "New database name:",
2534
+ default: binding.database_name
2535
+ });
2536
+ if (newName === binding.database_name) return false;
2537
+ binding.database_name = newName;
2538
+ break;
2539
+ }
2540
+ case "database_id": {
2541
+ const newId = await input2({
2542
+ message: "New database ID:",
2543
+ default: binding.database_id
2544
+ });
2545
+ if (newId === binding.database_id) return false;
2546
+ binding.database_id = newId;
2547
+ break;
2548
+ }
2549
+ }
2550
+ writeWranglerToml(cwd, config);
2551
+ console.log(pc7.green("\u2713") + " Updated wrangler.toml");
2552
+ return true;
2553
+ }
2554
+ async function updateKV(cwd, config, targetConfig, bindingName, field) {
2555
+ const bindings2 = targetConfig.kv_namespaces;
2556
+ if (!bindings2) return false;
2557
+ const binding = bindings2.find((b) => b.binding === bindingName);
2558
+ if (!binding) return false;
2559
+ switch (field) {
2560
+ case "name": {
2561
+ const newName = await input2({
2562
+ message: "New binding name:",
2563
+ default: binding.binding
2564
+ });
2565
+ if (newName === binding.binding) return false;
2566
+ binding.binding = newName;
2567
+ break;
2568
+ }
2569
+ case "id": {
2570
+ const newId = await input2({
2571
+ message: "New namespace ID:",
2572
+ default: binding.id
2573
+ });
2574
+ if (newId === binding.id) return false;
2575
+ binding.id = newId;
2576
+ break;
2577
+ }
2578
+ case "preview_id": {
2579
+ const newId = await input2({
2580
+ message: "New preview ID (leave empty to remove):",
2581
+ default: binding.preview_id || ""
2582
+ });
2583
+ if (newId === (binding.preview_id || "")) return false;
2584
+ if (newId) {
2585
+ binding.preview_id = newId;
2586
+ } else {
2587
+ delete binding.preview_id;
2588
+ }
2589
+ break;
2590
+ }
2591
+ }
2592
+ writeWranglerToml(cwd, config);
2593
+ console.log(pc7.green("\u2713") + " Updated wrangler.toml");
2594
+ return true;
2595
+ }
2596
+ async function updateR2(cwd, config, targetConfig, bindingName, field) {
2597
+ const bindings2 = targetConfig.r2_buckets;
2598
+ if (!bindings2) return false;
2599
+ const binding = bindings2.find((b) => b.binding === bindingName);
2600
+ if (!binding) return false;
2601
+ switch (field) {
2602
+ case "name": {
2603
+ const newName = await input2({
2604
+ message: "New binding name:",
2605
+ default: binding.binding
2606
+ });
2607
+ if (newName === binding.binding) return false;
2608
+ binding.binding = newName;
2609
+ break;
2610
+ }
2611
+ case "bucket_name": {
2612
+ const newName = await input2({
2613
+ message: "New bucket name:",
2614
+ default: binding.bucket_name
2615
+ });
2616
+ if (newName === binding.bucket_name) return false;
2617
+ binding.bucket_name = newName;
2618
+ break;
2619
+ }
2620
+ }
2621
+ writeWranglerToml(cwd, config);
2622
+ console.log(pc7.green("\u2713") + " Updated wrangler.toml");
2623
+ return true;
2624
+ }
2625
+ async function updateQueue(cwd, config, targetConfig, bindingName, field) {
2626
+ const bindings2 = targetConfig.queues?.producers;
2627
+ if (!bindings2) return false;
2628
+ const binding = bindings2.find((b) => b.binding === bindingName);
2629
+ if (!binding) return false;
2630
+ switch (field) {
2631
+ case "name": {
2632
+ const newName = await input2({
2633
+ message: "New binding name:",
2634
+ default: binding.binding
2635
+ });
2636
+ if (newName === binding.binding) return false;
2637
+ binding.binding = newName;
2638
+ break;
2639
+ }
2640
+ case "queue": {
2641
+ const newName = await input2({
2642
+ message: "New queue name:",
2643
+ default: binding.queue
2644
+ });
2645
+ if (newName === binding.queue) return false;
2646
+ binding.queue = newName;
2647
+ break;
2648
+ }
2649
+ }
2650
+ writeWranglerToml(cwd, config);
2651
+ console.log(pc7.green("\u2713") + " Updated wrangler.toml");
2652
+ return true;
2653
+ }
2654
+ async function updateDurableObject(cwd, config, targetConfig, bindingName, field) {
2655
+ const bindings2 = targetConfig.durable_objects?.bindings;
2656
+ if (!bindings2) return false;
2657
+ const binding = bindings2.find((b) => b.name === bindingName);
2658
+ if (!binding) return false;
2659
+ switch (field) {
2660
+ case "name": {
2661
+ const newName = await input2({
2662
+ message: "New binding name:",
2663
+ default: binding.name
2664
+ });
2665
+ if (newName === binding.name) return false;
2666
+ binding.name = newName;
2667
+ break;
2668
+ }
2669
+ case "class_name": {
2670
+ const newName = await input2({
2671
+ message: "New class name:",
2672
+ default: binding.class_name
2673
+ });
2674
+ if (newName === binding.class_name) return false;
2675
+ binding.class_name = newName;
2676
+ break;
2677
+ }
2678
+ case "script_name": {
2679
+ const newName = await input2({
2680
+ message: "New script name (leave empty to remove):",
2681
+ default: binding.script_name || ""
2682
+ });
2683
+ if (newName === (binding.script_name || "")) return false;
2684
+ if (newName) {
2685
+ binding.script_name = newName;
2686
+ } else {
2687
+ delete binding.script_name;
2688
+ }
2689
+ break;
2690
+ }
2691
+ }
2692
+ writeWranglerToml(cwd, config);
2693
+ console.log(pc7.green("\u2713") + " Updated wrangler.toml");
2694
+ return true;
2695
+ }
2696
+ async function updateSecret(cwd, config, targetConfig, varName, field, env) {
2697
+ const vars = targetConfig.vars;
2698
+ const isInToml = vars && varName in vars;
2699
+ if (!isInToml) {
2700
+ if (field === "name") {
2701
+ console.log();
2702
+ console.log(
2703
+ pc7.yellow("\u26A0") + " Secrets managed by wrangler cannot be renamed directly."
2704
+ );
2705
+ console.log(
2706
+ pc7.dim(
2707
+ " To rename, delete the old secret and create a new one with:"
2708
+ )
2709
+ );
2710
+ console.log(pc7.dim(` wrangler secret delete ${varName}`));
2711
+ console.log(pc7.dim(` wrangler secret put <new-name>`));
2712
+ return false;
2713
+ }
2714
+ console.log();
2715
+ console.log(
2716
+ pc7.dim("This is a secret managed by wrangler. Enter the new value:")
2717
+ );
2718
+ const newValue = await input2({
2719
+ message: "New secret value:"
2720
+ });
2721
+ if (!newValue.trim()) {
2722
+ console.log(pc7.dim("No value provided."));
2723
+ return false;
2724
+ }
2725
+ const args = ["secret", "put", varName];
2726
+ if (env) args.push("--env", env);
2727
+ console.log();
2728
+ console.log(pc7.dim(`Updating secret "${varName}"...`));
2729
+ await runWranglerCommandWithInput2(args, newValue);
2730
+ console.log(pc7.green("\u2713") + ` Secret "${varName}" updated`);
2731
+ return true;
2732
+ }
2733
+ switch (field) {
2734
+ case "name": {
2735
+ const newName = await input2({
2736
+ message: "New variable name:",
2737
+ default: varName
2738
+ });
2739
+ if (newName === varName) return false;
2740
+ const value = vars[varName];
2741
+ delete vars[varName];
2742
+ vars[newName] = value;
2743
+ break;
2744
+ }
2745
+ case "value": {
2746
+ const newValue = await input2({
2747
+ message: "New value:",
2748
+ default: vars[varName]
2749
+ });
2750
+ if (newValue === vars[varName]) return false;
2751
+ vars[varName] = newValue;
2752
+ break;
2753
+ }
2754
+ }
2755
+ writeWranglerToml(cwd, config);
2756
+ console.log(pc7.green("\u2713") + " Updated wrangler.toml");
2757
+ return true;
2758
+ }
2759
+ function runWranglerCommandWithInput2(args, inputData) {
2760
+ return new Promise((resolve4, reject) => {
2761
+ let stdout = "";
2762
+ let stderr = "";
2763
+ const child = spawn3("npx", ["wrangler", ...args], {
2764
+ stdio: ["pipe", "pipe", "pipe"]
2765
+ });
2766
+ child.stdout?.on("data", (data) => {
2767
+ stdout += data.toString();
2768
+ });
2769
+ child.stderr?.on("data", (data) => {
2770
+ stderr += data.toString();
2771
+ });
2772
+ child.on("error", (error) => {
2773
+ reject(error);
2774
+ });
2775
+ child.on("close", (code) => {
2776
+ if (code !== 0) {
2777
+ reject(new Error(stderr || `wrangler exited with code ${code}`));
2778
+ } else {
2779
+ resolve4(stdout);
2780
+ }
2781
+ });
2782
+ child.stdin?.write(inputData);
2783
+ child.stdin?.end();
2784
+ });
2785
+ }
2786
+
2787
+ // src/commands/bindings/generate-types.ts
2788
+ import pc8 from "picocolors";
2789
+
2790
+ // src/utils/type-generator.ts
2791
+ import * as fs9 from "fs";
2792
+ import * as path10 from "path";
2793
+ var CLOUDWERK_TYPES_DIR = ".cloudwerk/types";
2794
+ var BINDINGS_DTS = "bindings.d.ts";
2795
+ var CONTEXT_DTS = "context.d.ts";
2796
+ function generateCloudwerkTypes(cwd, bindings2, options = {}) {
2797
+ const includeTimestamp = options.includeTimestamp ?? true;
2798
+ const typesDir = path10.join(cwd, CLOUDWERK_TYPES_DIR);
2799
+ fs9.mkdirSync(typesDir, { recursive: true });
2800
+ const bindingsPath = path10.join(typesDir, BINDINGS_DTS);
2801
+ const bindingsContent = generateBindingsDts(bindings2, includeTimestamp);
2802
+ fs9.writeFileSync(bindingsPath, bindingsContent, "utf-8");
2803
+ const contextPath = path10.join(typesDir, CONTEXT_DTS);
2804
+ const contextContent = generateContextDts(bindings2, includeTimestamp);
2805
+ fs9.writeFileSync(contextPath, contextContent, "utf-8");
2806
+ const bindingInfo = bindings2.map((b) => ({
2807
+ name: b.name,
2808
+ type: getTypeForBinding(b.type)
2809
+ }));
2810
+ return {
2811
+ typesDir,
2812
+ files: {
2813
+ bindings: bindingsPath,
2814
+ context: contextPath
2815
+ },
2816
+ bindingCount: bindings2.length,
2817
+ bindings: bindingInfo
2818
+ };
2819
+ }
2820
+ function generateBindingsDts(bindings2, includeTimestamp) {
2821
+ const lines = [];
2822
+ lines.push("// Auto-generated by cloudwerk bindings - DO NOT EDIT");
2823
+ if (includeTimestamp) {
2824
+ lines.push(`// Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
2825
+ }
2826
+ lines.push("//");
2827
+ lines.push("// This file provides type information for @cloudwerk/core/bindings");
2828
+ lines.push('// Add ".cloudwerk/types" to your tsconfig.json include array');
2829
+ lines.push("");
2830
+ lines.push("declare module '@cloudwerk/core/bindings' {");
2831
+ const bindingsByType = groupBindingsByType(bindings2);
2832
+ const typeOrder = [
2833
+ "d1",
2834
+ "kv",
2835
+ "r2",
2836
+ "queue",
2837
+ "do",
2838
+ "service",
2839
+ "ai",
2840
+ "vectorize",
2841
+ "hyperdrive",
2842
+ "secret"
2843
+ ];
2844
+ let firstSection = true;
2845
+ for (const type of typeOrder) {
2846
+ const typeBindings = bindingsByType.get(type);
2847
+ if (!typeBindings || typeBindings.length === 0) continue;
2848
+ const tsType = getTypeForBinding(type);
2849
+ const sectionName = getSectionName2(type);
2850
+ if (!firstSection) {
2851
+ lines.push("");
2852
+ }
2853
+ lines.push(` // ${sectionName}`);
2854
+ firstSection = false;
2855
+ for (const binding of typeBindings) {
2856
+ lines.push(` export const ${binding.name}: ${tsType}`);
2857
+ }
2858
+ }
2859
+ lines.push("");
2860
+ lines.push(" // Bindings proxy object (for dynamic access)");
2861
+ lines.push(" export const bindings: Record<string, unknown>");
2862
+ lines.push("");
2863
+ lines.push(" // Helper functions");
2864
+ lines.push(" export function getBinding<T = unknown>(name: string): T");
2865
+ lines.push(" export function hasBinding(name: string): boolean");
2866
+ lines.push(" export function getBindingNames(): string[]");
2867
+ lines.push("}");
2868
+ lines.push("");
2869
+ return lines.join("\n");
2870
+ }
2871
+ function generateContextDts(bindings2, includeTimestamp) {
2872
+ const lines = [];
2873
+ lines.push("// Auto-generated by cloudwerk bindings - DO NOT EDIT");
2874
+ if (includeTimestamp) {
2875
+ lines.push(`// Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
2876
+ }
2877
+ lines.push("//");
2878
+ lines.push("// This file provides type information for @cloudwerk/core/context");
2879
+ lines.push('// Add ".cloudwerk/types" to your tsconfig.json include array');
2880
+ lines.push("");
2881
+ lines.push("interface CloudwerkEnv {");
2882
+ const bindingsByType = groupBindingsByType(bindings2);
2883
+ const typeOrder = [
2884
+ "d1",
2885
+ "kv",
2886
+ "r2",
2887
+ "queue",
2888
+ "do",
2889
+ "service",
2890
+ "ai",
2891
+ "vectorize",
2892
+ "hyperdrive",
2893
+ "secret"
2894
+ ];
2895
+ let firstSection = true;
2896
+ for (const type of typeOrder) {
2897
+ const typeBindings = bindingsByType.get(type);
2898
+ if (!typeBindings || typeBindings.length === 0) continue;
2899
+ const tsType = getTypeForBinding(type);
2900
+ const sectionName = getSectionName2(type);
2901
+ if (!firstSection) {
2902
+ lines.push("");
2903
+ }
2904
+ lines.push(` // ${sectionName}`);
2905
+ firstSection = false;
2906
+ for (const binding of typeBindings) {
2907
+ lines.push(` ${binding.name}: ${tsType}`);
2908
+ }
2909
+ }
2910
+ lines.push("}");
2911
+ lines.push("");
2912
+ lines.push("declare module '@cloudwerk/core/context' {");
2913
+ lines.push(" // Route parameters proxy");
2914
+ lines.push(" export const params: Record<string, string>");
2915
+ lines.push("");
2916
+ lines.push(" // Current request proxy");
2917
+ lines.push(" export const request: Request");
2918
+ lines.push("");
2919
+ lines.push(" // Environment bindings proxy (typed from wrangler.toml)");
2920
+ lines.push(" export const env: CloudwerkEnv");
2921
+ lines.push("");
2922
+ lines.push(" // Cloudflare execution context proxy");
2923
+ lines.push(" export const executionCtx: {");
2924
+ lines.push(" waitUntil(promise: Promise<unknown>): void");
2925
+ lines.push(" passThroughOnException(): void");
2926
+ lines.push(" }");
2927
+ lines.push("");
2928
+ lines.push(" // Helper functions");
2929
+ lines.push(" export function getRequestId(): string");
2930
+ lines.push(" export function get<T>(key: string): T | undefined");
2931
+ lines.push(" export function set<T>(key: string, value: T): void");
2932
+ lines.push("}");
2933
+ lines.push("");
2934
+ return lines.join("\n");
2935
+ }
2936
+ function groupBindingsByType(bindings2) {
2937
+ const grouped = /* @__PURE__ */ new Map();
2938
+ for (const binding of bindings2) {
2939
+ const existing = grouped.get(binding.type) || [];
2940
+ existing.push(binding);
2941
+ grouped.set(binding.type, existing);
2942
+ }
2943
+ return grouped;
2944
+ }
2945
+ function getSectionName2(type) {
2946
+ switch (type) {
2947
+ case "d1":
2948
+ return "D1 Databases";
2949
+ case "kv":
2950
+ return "KV Namespaces";
2951
+ case "r2":
2952
+ return "R2 Buckets";
2953
+ case "queue":
2954
+ return "Queues";
2955
+ case "do":
2956
+ return "Durable Objects";
2957
+ case "service":
2958
+ return "Services";
2959
+ case "secret":
2960
+ return "Environment Variables";
2961
+ case "ai":
2962
+ return "AI";
2963
+ case "vectorize":
2964
+ return "Vectorize Indexes";
2965
+ case "hyperdrive":
2966
+ return "Hyperdrive";
2967
+ default:
2968
+ return "Other";
2969
+ }
2970
+ }
2971
+
2972
+ // src/utils/tsconfig-updater.ts
2973
+ import * as fs10 from "fs";
2974
+ import * as path11 from "path";
2975
+ var CLOUDWERK_TYPES_INCLUDE = ".cloudwerk/types/**/*";
2976
+ var BINDINGS_PATH_KEY = "@cloudwerk/core/bindings";
2977
+ var CONTEXT_PATH_KEY = "@cloudwerk/core/context";
2978
+ function updateTsConfigPaths(cwd) {
2979
+ const tsconfigPath = path11.join(cwd, "tsconfig.json");
2980
+ const changes = {
2981
+ addedIncludes: [],
2982
+ addedPaths: [],
2983
+ setBaseUrl: false
2984
+ };
2985
+ if (!fs10.existsSync(tsconfigPath)) {
2986
+ const newConfig = {
2987
+ compilerOptions: {
2988
+ baseUrl: ".",
2989
+ paths: {
2990
+ [BINDINGS_PATH_KEY]: ["./.cloudwerk/types/bindings.d.ts"],
2991
+ [CONTEXT_PATH_KEY]: ["./.cloudwerk/types/context.d.ts"]
2992
+ }
2993
+ },
2994
+ include: [CLOUDWERK_TYPES_INCLUDE]
2995
+ };
2996
+ fs10.writeFileSync(tsconfigPath, JSON.stringify(newConfig, null, 2) + "\n", "utf-8");
2997
+ return {
2998
+ tsconfigPath,
2999
+ modified: true,
3000
+ changes: {
3001
+ addedIncludes: [CLOUDWERK_TYPES_INCLUDE],
3002
+ addedPaths: [BINDINGS_PATH_KEY, CONTEXT_PATH_KEY],
3003
+ setBaseUrl: true
3004
+ }
3005
+ };
3006
+ }
3007
+ const content = fs10.readFileSync(tsconfigPath, "utf-8");
3008
+ let config;
3009
+ try {
3010
+ config = JSON.parse(content);
3011
+ } catch {
3012
+ throw new Error(
3013
+ `Invalid JSON in tsconfig.json. Please fix the syntax and try again.`
3014
+ );
3015
+ }
3016
+ let modified = false;
3017
+ if (!config.compilerOptions) {
3018
+ config.compilerOptions = {};
3019
+ }
3020
+ if (!config.compilerOptions.baseUrl) {
3021
+ config.compilerOptions.baseUrl = ".";
3022
+ changes.setBaseUrl = true;
3023
+ modified = true;
3024
+ }
3025
+ if (!config.compilerOptions.paths) {
3026
+ config.compilerOptions.paths = {};
3027
+ }
3028
+ if (!config.compilerOptions.paths[BINDINGS_PATH_KEY]) {
3029
+ config.compilerOptions.paths[BINDINGS_PATH_KEY] = [
3030
+ "./.cloudwerk/types/bindings.d.ts"
3031
+ ];
3032
+ changes.addedPaths.push(BINDINGS_PATH_KEY);
3033
+ modified = true;
3034
+ }
3035
+ if (!config.compilerOptions.paths[CONTEXT_PATH_KEY]) {
3036
+ config.compilerOptions.paths[CONTEXT_PATH_KEY] = [
3037
+ "./.cloudwerk/types/context.d.ts"
3038
+ ];
3039
+ changes.addedPaths.push(CONTEXT_PATH_KEY);
3040
+ modified = true;
3041
+ }
3042
+ if (!config.include) {
3043
+ config.include = [];
3044
+ }
3045
+ if (!config.include.includes(CLOUDWERK_TYPES_INCLUDE)) {
3046
+ config.include.unshift(CLOUDWERK_TYPES_INCLUDE);
3047
+ changes.addedIncludes.push(CLOUDWERK_TYPES_INCLUDE);
3048
+ modified = true;
3049
+ }
3050
+ if (modified) {
3051
+ const newContent = JSON.stringify(config, null, 2) + "\n";
3052
+ fs10.writeFileSync(tsconfigPath, newContent, "utf-8");
3053
+ }
3054
+ return {
3055
+ tsconfigPath,
3056
+ modified,
3057
+ changes
3058
+ };
3059
+ }
3060
+
3061
+ // src/commands/bindings/generate-types.ts
3062
+ async function bindingsGenerateTypes(options) {
3063
+ const verbose = options.verbose ?? false;
3064
+ const logger = createLogger(verbose);
3065
+ try {
3066
+ const cwd = process.cwd();
3067
+ const wranglerPath = findWranglerToml(cwd);
3068
+ if (!wranglerPath) {
3069
+ throw new CliError(
3070
+ "wrangler.toml not found",
3071
+ "ENOENT",
3072
+ "Create a wrangler.toml file or run this command from a Cloudwerk project directory."
3073
+ );
3074
+ }
3075
+ logger.debug(`Found wrangler config: ${wranglerPath}`);
3076
+ const config = readWranglerToml(cwd);
3077
+ const bindings2 = extractBindings(config);
3078
+ if (bindings2.length === 0) {
3079
+ console.log();
3080
+ console.log(pc8.yellow("No bindings found in wrangler.toml."));
3081
+ console.log();
3082
+ console.log(
3083
+ pc8.dim(`Use 'cloudwerk bindings add' to add a binding first.`)
3084
+ );
3085
+ console.log();
3086
+ return;
3087
+ }
3088
+ console.log();
3089
+ logger.info("Generating TypeScript types...");
3090
+ const result = generateEnvTypes(cwd, bindings2);
3091
+ console.log();
3092
+ console.log(
3093
+ pc8.green("\u2713") + ` Updated ${pc8.bold("env.d.ts")} with ${result.bindingCount} binding(s):`
3094
+ );
3095
+ console.log();
3096
+ for (const binding of result.bindings) {
3097
+ console.log(` ${pc8.cyan(binding.name)}: ${pc8.dim(binding.type)}`);
3098
+ }
3099
+ logger.debug("Generating .cloudwerk/types/...");
3100
+ const cloudwerkResult = generateCloudwerkTypes(cwd, bindings2);
3101
+ console.log();
3102
+ console.log(
3103
+ pc8.green("\u2713") + ` Generated ${pc8.bold(".cloudwerk/types/")} for importable bindings:`
3104
+ );
3105
+ console.log(` ${pc8.dim(cloudwerkResult.files.bindings)}`);
3106
+ console.log(` ${pc8.dim(cloudwerkResult.files.context)}`);
3107
+ logger.debug("Updating tsconfig.json...");
3108
+ const tsconfigResult = updateTsConfigPaths(cwd);
3109
+ if (tsconfigResult.modified) {
3110
+ console.log();
3111
+ console.log(
3112
+ pc8.green("\u2713") + ` Updated ${pc8.bold("tsconfig.json")}:`
3113
+ );
3114
+ if (tsconfigResult.changes.setBaseUrl) {
3115
+ console.log(` ${pc8.dim('Added baseUrl: "."')}`);
3116
+ }
3117
+ for (const pathKey of tsconfigResult.changes.addedPaths) {
3118
+ console.log(` ${pc8.dim(`Added paths: "${pathKey}"`)}`);
3119
+ }
3120
+ for (const include of tsconfigResult.changes.addedIncludes) {
3121
+ console.log(` ${pc8.dim(`Added include: "${include}"`)}`);
3122
+ }
3123
+ } else {
3124
+ logger.debug("tsconfig.json already configured");
3125
+ }
3126
+ console.log();
3127
+ logger.success("Types generated successfully!");
3128
+ console.log();
3129
+ console.log(pc8.bold("Usage:"));
3130
+ console.log();
3131
+ console.log(pc8.dim(" // Import bindings directly (new)"));
3132
+ console.log(pc8.cyan(` import { ${bindings2[0]?.name || "DB"} } from '@cloudwerk/core/bindings'`));
3133
+ console.log();
3134
+ console.log(pc8.dim(" // Import context helpers (new)"));
3135
+ console.log(pc8.cyan(` import { params, request, get } from '@cloudwerk/core/context'`));
3136
+ console.log();
3137
+ console.log(pc8.dim(" // Or use getContext() (existing)"));
3138
+ console.log(pc8.cyan(` import { getContext } from '@cloudwerk/core'`));
3139
+ console.log();
3140
+ } catch (error) {
3141
+ handleCommandError(error, verbose);
3142
+ }
3143
+ }
3144
+
1151
3145
  // src/index.ts
1152
- program.name("cloudwerk").description("Cloudwerk CLI - Build and deploy full-stack apps to Cloudflare").version(VERSION);
3146
+ program.name("cloudwerk").description("Cloudwerk CLI - Build and deploy full-stack apps to Cloudflare").version(VERSION).enablePositionalOptions();
1153
3147
  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);
1154
3148
  program.command("build [path]").description("Build project for production deployment to Cloudflare Workers").option("-o, --output <dir>", "Output directory", "./dist").option("--ssg", "Generate static pages for routes with rendering: static").option("--minify", "Minify bundles (default: true)").option("--no-minify", "Disable minification").option("--sourcemap", "Generate source maps").option("-c, --config <path>", "Path to config file").option("--verbose", "Enable verbose logging").action(build);
1155
3149
  program.command("deploy [path]").description("Deploy to Cloudflare Workers").option("-e, --env <environment>", "Environment to deploy to").option("--dry-run", "Preview deployment without executing").option("--skip-build", "Skip the build step").option("-c, --config <path>", "Path to config file").option("--verbose", "Enable verbose logging").action(deploy);
1156
3150
  var configCmd = program.command("config").description("Manage Cloudwerk configuration");
1157
3151
  configCmd.command("get <key>").description("Get a configuration value").action(configGet);
1158
3152
  configCmd.command("set <key> <value>").description("Set a configuration value").action(configSet);
3153
+ var bindingsCmd = program.command("bindings").description("Manage Cloudflare bindings (D1, KV, R2, Queues, etc.)").enablePositionalOptions().passThroughOptions().option("--verbose", "Enable verbose logging").action(bindings);
3154
+ 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);
3155
+ 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);
3156
+ 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);
3157
+ bindingsCmd.command("generate-types").description("Regenerate TypeScript type definitions").option("--verbose", "Enable verbose logging").action(bindingsGenerateTypes);
1159
3158
  program.parse();