@cloudwerk/vite-plugin 0.4.1 → 0.5.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 +368 -19
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/plugin.ts
2
- import * as path2 from "path";
3
- import * as fs from "fs";
2
+ import * as path3 from "path";
3
+ import * as fs2 from "fs";
4
4
  import {
5
5
  scanRoutes,
6
6
  buildRouteManifest,
@@ -1037,25 +1037,358 @@ export default __WrappedComponent
1037
1037
  }
1038
1038
  }
1039
1039
 
1040
+ // src/wrangler-watcher.ts
1041
+ import * as fs from "fs";
1042
+ import * as path2 from "path";
1043
+ var TYPE_MAPPINGS = {
1044
+ d1: "D1Database",
1045
+ kv: "KVNamespace",
1046
+ r2: "R2Bucket",
1047
+ queue: "Queue",
1048
+ do: "DurableObjectNamespace",
1049
+ service: "Fetcher",
1050
+ secret: "string",
1051
+ ai: "Ai",
1052
+ vectorize: "VectorizeIndex",
1053
+ hyperdrive: "Hyperdrive"
1054
+ };
1055
+ function parseSimpleToml(content) {
1056
+ const result = {};
1057
+ const d1Matches = content.matchAll(/\[\[d1_databases\]\]\s*\n([^[]*)/g);
1058
+ for (const match of d1Matches) {
1059
+ const bindingMatch = match[1].match(/binding\s*=\s*["']([^"']+)["']/);
1060
+ if (bindingMatch) {
1061
+ if (!result.d1_databases) result.d1_databases = [];
1062
+ result.d1_databases.push({ binding: bindingMatch[1] });
1063
+ }
1064
+ }
1065
+ const kvMatches = content.matchAll(/\[\[kv_namespaces\]\]\s*\n([^[]*)/g);
1066
+ for (const match of kvMatches) {
1067
+ const bindingMatch = match[1].match(/binding\s*=\s*["']([^"']+)["']/);
1068
+ if (bindingMatch) {
1069
+ if (!result.kv_namespaces) result.kv_namespaces = [];
1070
+ result.kv_namespaces.push({ binding: bindingMatch[1] });
1071
+ }
1072
+ }
1073
+ const r2Matches = content.matchAll(/\[\[r2_buckets\]\]\s*\n([^[]*)/g);
1074
+ for (const match of r2Matches) {
1075
+ const bindingMatch = match[1].match(/binding\s*=\s*["']([^"']+)["']/);
1076
+ if (bindingMatch) {
1077
+ if (!result.r2_buckets) result.r2_buckets = [];
1078
+ result.r2_buckets.push({ binding: bindingMatch[1] });
1079
+ }
1080
+ }
1081
+ const queueMatches = content.matchAll(/\[\[queues\.producers\]\]\s*\n([^[]*)/g);
1082
+ for (const match of queueMatches) {
1083
+ const bindingMatch = match[1].match(/binding\s*=\s*["']([^"']+)["']/);
1084
+ if (bindingMatch) {
1085
+ if (!result.queues) result.queues = {};
1086
+ if (!result.queues.producers) result.queues.producers = [];
1087
+ result.queues.producers.push({ binding: bindingMatch[1] });
1088
+ }
1089
+ }
1090
+ const doMatches = content.matchAll(/\[\[durable_objects\.bindings\]\]\s*\n([^[]*)/g);
1091
+ for (const match of doMatches) {
1092
+ const nameMatch = match[1].match(/name\s*=\s*["']([^"']+)["']/);
1093
+ if (nameMatch) {
1094
+ if (!result.durable_objects) result.durable_objects = {};
1095
+ if (!result.durable_objects.bindings) result.durable_objects.bindings = [];
1096
+ result.durable_objects.bindings.push({ name: nameMatch[1] });
1097
+ }
1098
+ }
1099
+ const serviceMatches = content.matchAll(/\[\[services\]\]\s*\n([^[]*)/g);
1100
+ for (const match of serviceMatches) {
1101
+ const bindingMatch = match[1].match(/binding\s*=\s*["']([^"']+)["']/);
1102
+ if (bindingMatch) {
1103
+ if (!result.services) result.services = [];
1104
+ result.services.push({ binding: bindingMatch[1] });
1105
+ }
1106
+ }
1107
+ const varsMatch = content.match(/\[vars\]\s*\n([^[]*)/s);
1108
+ if (varsMatch) {
1109
+ const varsSection = varsMatch[1];
1110
+ const varMatches = varsSection.matchAll(/(\w+)\s*=\s*["']([^"']*)["']/g);
1111
+ result.vars = {};
1112
+ for (const match of varMatches) {
1113
+ result.vars[match[1]] = match[2];
1114
+ }
1115
+ }
1116
+ const aiMatch = content.match(/\[ai\]\s*\n([^[]*)/s);
1117
+ if (aiMatch) {
1118
+ const bindingMatch = aiMatch[1].match(/binding\s*=\s*["']([^"']+)["']/);
1119
+ if (bindingMatch) {
1120
+ result.ai = { binding: bindingMatch[1] };
1121
+ }
1122
+ }
1123
+ const vectorizeMatches = content.matchAll(/\[\[vectorize\]\]\s*\n([^[]*)/g);
1124
+ for (const match of vectorizeMatches) {
1125
+ const bindingMatch = match[1].match(/binding\s*=\s*["']([^"']+)["']/);
1126
+ if (bindingMatch) {
1127
+ if (!result.vectorize) result.vectorize = [];
1128
+ result.vectorize.push({ binding: bindingMatch[1] });
1129
+ }
1130
+ }
1131
+ const hyperdriveMatches = content.matchAll(/\[\[hyperdrive\]\]\s*\n([^[]*)/g);
1132
+ for (const match of hyperdriveMatches) {
1133
+ const bindingMatch = match[1].match(/binding\s*=\s*["']([^"']+)["']/);
1134
+ if (bindingMatch) {
1135
+ if (!result.hyperdrive) result.hyperdrive = [];
1136
+ result.hyperdrive.push({ binding: bindingMatch[1] });
1137
+ }
1138
+ }
1139
+ return result;
1140
+ }
1141
+ function extractBindings(config) {
1142
+ const bindings = [];
1143
+ if (config.d1_databases) {
1144
+ for (const db of config.d1_databases) {
1145
+ bindings.push({ type: "d1", name: db.binding });
1146
+ }
1147
+ }
1148
+ if (config.kv_namespaces) {
1149
+ for (const kv of config.kv_namespaces) {
1150
+ bindings.push({ type: "kv", name: kv.binding });
1151
+ }
1152
+ }
1153
+ if (config.r2_buckets) {
1154
+ for (const r2 of config.r2_buckets) {
1155
+ bindings.push({ type: "r2", name: r2.binding });
1156
+ }
1157
+ }
1158
+ if (config.queues?.producers) {
1159
+ for (const queue of config.queues.producers) {
1160
+ bindings.push({ type: "queue", name: queue.binding });
1161
+ }
1162
+ }
1163
+ if (config.durable_objects?.bindings) {
1164
+ for (const doBinding of config.durable_objects.bindings) {
1165
+ bindings.push({ type: "do", name: doBinding.name });
1166
+ }
1167
+ }
1168
+ if (config.services) {
1169
+ for (const service of config.services) {
1170
+ bindings.push({ type: "service", name: service.binding });
1171
+ }
1172
+ }
1173
+ if (config.vars) {
1174
+ for (const name of Object.keys(config.vars)) {
1175
+ bindings.push({ type: "secret", name });
1176
+ }
1177
+ }
1178
+ if (config.ai) {
1179
+ bindings.push({ type: "ai", name: config.ai.binding });
1180
+ }
1181
+ if (config.vectorize) {
1182
+ for (const vec of config.vectorize) {
1183
+ bindings.push({ type: "vectorize", name: vec.binding });
1184
+ }
1185
+ }
1186
+ if (config.hyperdrive) {
1187
+ for (const hd of config.hyperdrive) {
1188
+ bindings.push({ type: "hyperdrive", name: hd.binding });
1189
+ }
1190
+ }
1191
+ return bindings;
1192
+ }
1193
+ function getSectionName(type) {
1194
+ switch (type) {
1195
+ case "d1":
1196
+ return "D1 Databases";
1197
+ case "kv":
1198
+ return "KV Namespaces";
1199
+ case "r2":
1200
+ return "R2 Buckets";
1201
+ case "queue":
1202
+ return "Queues";
1203
+ case "do":
1204
+ return "Durable Objects";
1205
+ case "service":
1206
+ return "Services";
1207
+ case "secret":
1208
+ return "Environment Variables";
1209
+ case "ai":
1210
+ return "AI";
1211
+ case "vectorize":
1212
+ return "Vectorize Indexes";
1213
+ case "hyperdrive":
1214
+ return "Hyperdrive";
1215
+ default:
1216
+ return "Other";
1217
+ }
1218
+ }
1219
+ function groupBindingsByType(bindings) {
1220
+ const grouped = /* @__PURE__ */ new Map();
1221
+ for (const binding of bindings) {
1222
+ const existing = grouped.get(binding.type) || [];
1223
+ existing.push(binding);
1224
+ grouped.set(binding.type, existing);
1225
+ }
1226
+ return grouped;
1227
+ }
1228
+ function generateBindingsDts(bindings) {
1229
+ const lines = [];
1230
+ lines.push("// Auto-generated by cloudwerk - DO NOT EDIT");
1231
+ lines.push(`// Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
1232
+ lines.push("//");
1233
+ lines.push("// This file provides type information for @cloudwerk/core/bindings");
1234
+ lines.push("");
1235
+ lines.push("declare module '@cloudwerk/core/bindings' {");
1236
+ const bindingsByType = groupBindingsByType(bindings);
1237
+ const typeOrder = [
1238
+ "d1",
1239
+ "kv",
1240
+ "r2",
1241
+ "queue",
1242
+ "do",
1243
+ "service",
1244
+ "ai",
1245
+ "vectorize",
1246
+ "hyperdrive",
1247
+ "secret"
1248
+ ];
1249
+ let firstSection = true;
1250
+ for (const type of typeOrder) {
1251
+ const typeBindings = bindingsByType.get(type);
1252
+ if (!typeBindings || typeBindings.length === 0) continue;
1253
+ const tsType = TYPE_MAPPINGS[type];
1254
+ const sectionName = getSectionName(type);
1255
+ if (!firstSection) {
1256
+ lines.push("");
1257
+ }
1258
+ lines.push(` // ${sectionName}`);
1259
+ firstSection = false;
1260
+ for (const binding of typeBindings) {
1261
+ lines.push(` export const ${binding.name}: ${tsType}`);
1262
+ }
1263
+ }
1264
+ lines.push("");
1265
+ lines.push(" // Bindings proxy object (for dynamic access)");
1266
+ lines.push(" export const bindings: Record<string, unknown>");
1267
+ lines.push("");
1268
+ lines.push(" // Helper functions");
1269
+ lines.push(" export function getBinding<T = unknown>(name: string): T");
1270
+ lines.push(" export function hasBinding(name: string): boolean");
1271
+ lines.push(" export function getBindingNames(): string[]");
1272
+ lines.push("}");
1273
+ lines.push("");
1274
+ return lines.join("\n");
1275
+ }
1276
+ function generateContextDts(bindings) {
1277
+ const lines = [];
1278
+ lines.push("// Auto-generated by cloudwerk - DO NOT EDIT");
1279
+ lines.push(`// Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
1280
+ lines.push("//");
1281
+ lines.push("// This file provides type information for @cloudwerk/core/context");
1282
+ lines.push("");
1283
+ lines.push("interface CloudwerkEnv {");
1284
+ const bindingsByType = groupBindingsByType(bindings);
1285
+ const typeOrder = [
1286
+ "d1",
1287
+ "kv",
1288
+ "r2",
1289
+ "queue",
1290
+ "do",
1291
+ "service",
1292
+ "ai",
1293
+ "vectorize",
1294
+ "hyperdrive",
1295
+ "secret"
1296
+ ];
1297
+ let firstSection = true;
1298
+ for (const type of typeOrder) {
1299
+ const typeBindings = bindingsByType.get(type);
1300
+ if (!typeBindings || typeBindings.length === 0) continue;
1301
+ const tsType = TYPE_MAPPINGS[type];
1302
+ const sectionName = getSectionName(type);
1303
+ if (!firstSection) {
1304
+ lines.push("");
1305
+ }
1306
+ lines.push(` // ${sectionName}`);
1307
+ firstSection = false;
1308
+ for (const binding of typeBindings) {
1309
+ lines.push(` ${binding.name}: ${tsType}`);
1310
+ }
1311
+ }
1312
+ lines.push("}");
1313
+ lines.push("");
1314
+ lines.push("declare module '@cloudwerk/core/context' {");
1315
+ lines.push(" export const params: Record<string, string>");
1316
+ lines.push(" export const request: Request");
1317
+ lines.push(" export const env: CloudwerkEnv");
1318
+ lines.push(" export const executionCtx: {");
1319
+ lines.push(" waitUntil(promise: Promise<unknown>): void");
1320
+ lines.push(" passThroughOnException(): void");
1321
+ lines.push(" }");
1322
+ lines.push(" export function getRequestId(): string");
1323
+ lines.push(" export function get<T>(key: string): T | undefined");
1324
+ lines.push(" export function set<T>(key: string, value: T): void");
1325
+ lines.push("}");
1326
+ lines.push("");
1327
+ return lines.join("\n");
1328
+ }
1329
+ function findWranglerTomlPath(root) {
1330
+ const tomlPath = path2.join(root, "wrangler.toml");
1331
+ if (fs.existsSync(tomlPath)) {
1332
+ return tomlPath;
1333
+ }
1334
+ const jsonPath = path2.join(root, "wrangler.json");
1335
+ if (fs.existsSync(jsonPath)) {
1336
+ return jsonPath;
1337
+ }
1338
+ return null;
1339
+ }
1340
+ function regenerateCloudwerkTypes(root) {
1341
+ const wranglerPath = findWranglerTomlPath(root);
1342
+ if (!wranglerPath) {
1343
+ return null;
1344
+ }
1345
+ const content = fs.readFileSync(wranglerPath, "utf-8");
1346
+ let config;
1347
+ if (wranglerPath.endsWith(".json")) {
1348
+ config = JSON.parse(content);
1349
+ } else {
1350
+ config = parseSimpleToml(content);
1351
+ }
1352
+ const bindings = extractBindings(config);
1353
+ if (bindings.length === 0) {
1354
+ return null;
1355
+ }
1356
+ const typesDir = path2.join(root, ".cloudwerk", "types");
1357
+ fs.mkdirSync(typesDir, { recursive: true });
1358
+ const bindingsPath = path2.join(typesDir, "bindings.d.ts");
1359
+ const bindingsContent = generateBindingsDts(bindings);
1360
+ fs.writeFileSync(bindingsPath, bindingsContent, "utf-8");
1361
+ const contextPath = path2.join(typesDir, "context.d.ts");
1362
+ const contextContent = generateContextDts(bindings);
1363
+ fs.writeFileSync(contextPath, contextContent, "utf-8");
1364
+ return {
1365
+ bindingCount: bindings.length,
1366
+ files: {
1367
+ bindings: bindingsPath,
1368
+ context: contextPath
1369
+ }
1370
+ };
1371
+ }
1372
+
1040
1373
  // src/plugin.ts
1041
1374
  async function scanClientComponents(root, state) {
1042
- const appDir = path2.resolve(root, state.options.appDir);
1375
+ const appDir = path3.resolve(root, state.options.appDir);
1043
1376
  try {
1044
- await fs.promises.access(appDir);
1377
+ await fs2.promises.access(appDir);
1045
1378
  } catch {
1046
1379
  return;
1047
1380
  }
1048
1381
  async function scanDir(dir) {
1049
- const entries = await fs.promises.readdir(dir, { withFileTypes: true });
1382
+ const entries = await fs2.promises.readdir(dir, { withFileTypes: true });
1050
1383
  await Promise.all(
1051
1384
  entries.map(async (entry) => {
1052
- const fullPath = path2.join(dir, entry.name);
1385
+ const fullPath = path3.join(dir, entry.name);
1053
1386
  if (entry.isDirectory()) {
1054
1387
  if (entry.name !== "node_modules" && !entry.name.startsWith(".")) {
1055
1388
  await scanDir(fullPath);
1056
1389
  }
1057
1390
  } else if (entry.isFile() && (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts"))) {
1058
- const content = await fs.promises.readFile(fullPath, "utf-8");
1391
+ const content = await fs2.promises.readFile(fullPath, "utf-8");
1059
1392
  if (hasUseClientDirective(content)) {
1060
1393
  const componentId = generateComponentId(fullPath, root);
1061
1394
  const bundlePath = `${state.options.hydrationEndpoint}/${componentId}.js`;
@@ -1106,9 +1439,9 @@ function cloudwerkPlugin(options = {}) {
1106
1439
  }
1107
1440
  function isRouteFile(filePath) {
1108
1441
  if (!state) return false;
1109
- const appDir = path2.resolve(state.options.root, state.options.appDir);
1442
+ const appDir = path3.resolve(state.options.root, state.options.appDir);
1110
1443
  if (!filePath.startsWith(appDir)) return false;
1111
- const basename2 = path2.basename(filePath);
1444
+ const basename2 = path3.basename(filePath);
1112
1445
  const nameWithoutExt = basename2.replace(/\.(ts|tsx|js|jsx)$/, "");
1113
1446
  return ROUTE_FILE_NAMES.includes(nameWithoutExt);
1114
1447
  }
@@ -1148,16 +1481,16 @@ function cloudwerkPlugin(options = {}) {
1148
1481
  */
1149
1482
  async configResolved(config) {
1150
1483
  const root = config.root;
1151
- let detectedServerEntry = options.serverEntry ? path2.resolve(root, options.serverEntry) : null;
1484
+ let detectedServerEntry = options.serverEntry ? path3.resolve(root, options.serverEntry) : null;
1152
1485
  if (!detectedServerEntry) {
1153
1486
  const conventionalPaths = [
1154
- path2.resolve(root, "app/server.ts"),
1155
- path2.resolve(root, "app/server.tsx"),
1156
- path2.resolve(root, "src/server.ts"),
1157
- path2.resolve(root, "src/server.tsx")
1487
+ path3.resolve(root, "app/server.ts"),
1488
+ path3.resolve(root, "app/server.tsx"),
1489
+ path3.resolve(root, "src/server.ts"),
1490
+ path3.resolve(root, "src/server.tsx")
1158
1491
  ];
1159
1492
  for (const p of conventionalPaths) {
1160
- if (fs.existsSync(p)) {
1493
+ if (fs2.existsSync(p)) {
1161
1494
  detectedServerEntry = p;
1162
1495
  if (options.verbose) {
1163
1496
  console.log(`[cloudwerk] Detected custom server entry: ${p}`);
@@ -1215,11 +1548,13 @@ function cloudwerkPlugin(options = {}) {
1215
1548
  configureServer(devServer) {
1216
1549
  server = devServer;
1217
1550
  if (!state) return;
1218
- const appDir = path2.resolve(state.options.root, state.options.appDir);
1551
+ const appDir = path3.resolve(state.options.root, state.options.appDir);
1552
+ const root = state.options.root;
1553
+ const verbose = state.options.verbose;
1219
1554
  devServer.watcher.on("add", async (filePath) => {
1220
1555
  if (isRouteFile(filePath)) {
1221
1556
  if (state?.options.verbose) {
1222
- console.log(`[cloudwerk] Route added: ${path2.relative(appDir, filePath)}`);
1557
+ console.log(`[cloudwerk] Route added: ${path3.relative(appDir, filePath)}`);
1223
1558
  }
1224
1559
  await buildManifest(state.options.root);
1225
1560
  invalidateVirtualModules();
@@ -1228,7 +1563,7 @@ function cloudwerkPlugin(options = {}) {
1228
1563
  devServer.watcher.on("unlink", async (filePath) => {
1229
1564
  if (isRouteFile(filePath)) {
1230
1565
  if (state?.options.verbose) {
1231
- console.log(`[cloudwerk] Route removed: ${path2.relative(appDir, filePath)}`);
1566
+ console.log(`[cloudwerk] Route removed: ${path3.relative(appDir, filePath)}`);
1232
1567
  }
1233
1568
  await buildManifest(state.options.root);
1234
1569
  invalidateVirtualModules();
@@ -1237,12 +1572,26 @@ function cloudwerkPlugin(options = {}) {
1237
1572
  devServer.watcher.on("change", async (filePath) => {
1238
1573
  if (isRouteFile(filePath)) {
1239
1574
  if (state?.options.verbose) {
1240
- console.log(`[cloudwerk] Route changed: ${path2.relative(appDir, filePath)}`);
1575
+ console.log(`[cloudwerk] Route changed: ${path3.relative(appDir, filePath)}`);
1241
1576
  }
1242
1577
  await buildManifest(state.options.root);
1243
1578
  invalidateVirtualModules();
1244
1579
  }
1580
+ const wranglerPath2 = findWranglerTomlPath(root);
1581
+ if (wranglerPath2 && filePath === wranglerPath2) {
1582
+ if (verbose) {
1583
+ console.log(`[cloudwerk] wrangler.toml changed, regenerating types...`);
1584
+ }
1585
+ const result = regenerateCloudwerkTypes(root);
1586
+ if (result && verbose) {
1587
+ console.log(`[cloudwerk] Regenerated .cloudwerk/types/ with ${result.bindingCount} binding(s)`);
1588
+ }
1589
+ }
1245
1590
  });
1591
+ const wranglerPath = findWranglerTomlPath(root);
1592
+ if (wranglerPath) {
1593
+ devServer.watcher.add(wranglerPath);
1594
+ }
1246
1595
  },
1247
1596
  /**
1248
1597
  * Resolve virtual module IDs.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudwerk/vite-plugin",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Vite plugin for Cloudwerk file-based routing with virtual entry generation",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,8 +19,8 @@
19
19
  ],
20
20
  "dependencies": {
21
21
  "@swc/core": "^1.3.100",
22
- "@cloudwerk/core": "^0.11.0",
23
- "@cloudwerk/ui": "^0.11.0"
22
+ "@cloudwerk/core": "^0.12.0",
23
+ "@cloudwerk/ui": "^0.12.0"
24
24
  },
25
25
  "peerDependencies": {
26
26
  "vite": "^5.0.0 || ^6.0.0",