@cloudwerk/vite-plugin 0.4.1 → 0.6.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.
- package/dist/index.d.ts +11 -2
- package/dist/index.js +635 -22
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
|
-
import { CloudwerkConfig, RouteManifest, ScanResult } from '@cloudwerk/core/build';
|
|
2
|
+
import { CloudwerkConfig, RouteManifest, ScanResult, QueueManifest, ServiceManifest } from '@cloudwerk/core/build';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @cloudwerk/vite-plugin - Types
|
|
@@ -150,6 +150,15 @@ declare function cloudwerkPlugin(options?: CloudwerkVitePluginOptions): Plugin;
|
|
|
150
150
|
* a Hono app with all routes registered from the file-based routing manifest.
|
|
151
151
|
*/
|
|
152
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Options for generating server entry.
|
|
155
|
+
*/
|
|
156
|
+
interface GenerateServerEntryOptions {
|
|
157
|
+
/** Queue manifest if queues are configured */
|
|
158
|
+
queueManifest?: QueueManifest | null;
|
|
159
|
+
/** Service manifest if services are configured */
|
|
160
|
+
serviceManifest?: ServiceManifest | null;
|
|
161
|
+
}
|
|
153
162
|
/**
|
|
154
163
|
* Generate the server entry module code.
|
|
155
164
|
*
|
|
@@ -165,7 +174,7 @@ declare function cloudwerkPlugin(options?: CloudwerkVitePluginOptions): Plugin;
|
|
|
165
174
|
* @param options - Resolved plugin options
|
|
166
175
|
* @returns Generated TypeScript/JavaScript code
|
|
167
176
|
*/
|
|
168
|
-
declare function generateServerEntry(manifest: RouteManifest, scanResult: ScanResult, options: ResolvedCloudwerkOptions): string;
|
|
177
|
+
declare function generateServerEntry(manifest: RouteManifest, scanResult: ScanResult, options: ResolvedCloudwerkOptions, entryOptions?: GenerateServerEntryOptions): string;
|
|
169
178
|
|
|
170
179
|
/**
|
|
171
180
|
* Client Entry Virtual Module Generator
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/plugin.ts
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
2
|
+
import * as path3 from "path";
|
|
3
|
+
import * as fs2 from "fs";
|
|
4
4
|
import {
|
|
5
5
|
scanRoutes,
|
|
6
6
|
buildRouteManifest,
|
|
@@ -12,7 +12,14 @@ import {
|
|
|
12
12
|
resolveRoutesPath,
|
|
13
13
|
hasUseClientDirective,
|
|
14
14
|
generateComponentId,
|
|
15
|
-
ROUTE_FILE_NAMES
|
|
15
|
+
ROUTE_FILE_NAMES,
|
|
16
|
+
scanQueues,
|
|
17
|
+
buildQueueManifest,
|
|
18
|
+
QUEUES_DIR,
|
|
19
|
+
scanServices,
|
|
20
|
+
buildServiceManifest,
|
|
21
|
+
SERVICES_DIR,
|
|
22
|
+
SERVICE_FILE_NAME
|
|
16
23
|
} from "@cloudwerk/core/build";
|
|
17
24
|
|
|
18
25
|
// src/types.ts
|
|
@@ -29,7 +36,9 @@ var RESOLVED_VIRTUAL_IDS = {
|
|
|
29
36
|
|
|
30
37
|
// src/virtual-modules/server-entry.ts
|
|
31
38
|
import * as path from "path";
|
|
32
|
-
function generateServerEntry(manifest, scanResult, options) {
|
|
39
|
+
function generateServerEntry(manifest, scanResult, options, entryOptions) {
|
|
40
|
+
const queueManifest = entryOptions?.queueManifest;
|
|
41
|
+
const serviceManifest = entryOptions?.serviceManifest;
|
|
33
42
|
const imports = [];
|
|
34
43
|
const pageRegistrations = [];
|
|
35
44
|
const routeRegistrations = [];
|
|
@@ -532,8 +541,137 @@ app.onError(async (err, c) => {
|
|
|
532
541
|
// ============================================================================
|
|
533
542
|
|
|
534
543
|
export default app
|
|
544
|
+
${generateQueueExports(queueManifest)}
|
|
545
|
+
${generateServiceRegistration(serviceManifest)}
|
|
535
546
|
`;
|
|
536
547
|
}
|
|
548
|
+
function generateQueueExports(queueManifest) {
|
|
549
|
+
if (!queueManifest || queueManifest.queues.length === 0) {
|
|
550
|
+
return "";
|
|
551
|
+
}
|
|
552
|
+
const lines = [];
|
|
553
|
+
const imports = [];
|
|
554
|
+
const queueHandlers = [];
|
|
555
|
+
lines.push("");
|
|
556
|
+
lines.push("// ============================================================================");
|
|
557
|
+
lines.push("// Queue Consumer Handlers");
|
|
558
|
+
lines.push("// ============================================================================");
|
|
559
|
+
lines.push("");
|
|
560
|
+
for (let i = 0; i < queueManifest.queues.length; i++) {
|
|
561
|
+
const queue = queueManifest.queues[i];
|
|
562
|
+
const varName = `queueDef_${i}`;
|
|
563
|
+
imports.push(`import ${varName} from '${queue.absolutePath}'`);
|
|
564
|
+
queueHandlers.push(`
|
|
565
|
+
/**
|
|
566
|
+
* Queue consumer handler for '${queue.name}'
|
|
567
|
+
*/
|
|
568
|
+
async function handle_${queue.name}_queue(batch, env, ctx) {
|
|
569
|
+
const definition = ${varName}
|
|
570
|
+
|
|
571
|
+
// Create message wrappers
|
|
572
|
+
const messages = batch.messages.map((msg) => ({
|
|
573
|
+
id: msg.id,
|
|
574
|
+
body: msg.body,
|
|
575
|
+
timestamp: new Date(msg.timestamp),
|
|
576
|
+
attempts: msg.attempts,
|
|
577
|
+
ack: () => msg.ack(),
|
|
578
|
+
retry: (options) => msg.retry(options),
|
|
579
|
+
deadLetter: (reason) => {
|
|
580
|
+
// Mark for DLQ if configured
|
|
581
|
+
if (definition.config?.deadLetterQueue) {
|
|
582
|
+
msg.retry({ delaySeconds: 0 })
|
|
583
|
+
}
|
|
584
|
+
},
|
|
585
|
+
}))
|
|
586
|
+
|
|
587
|
+
// Validate messages if schema is defined
|
|
588
|
+
if (definition.schema) {
|
|
589
|
+
for (const message of messages) {
|
|
590
|
+
const result = definition.schema.safeParse(message.body)
|
|
591
|
+
if (!result.success) {
|
|
592
|
+
console.error('Queue message validation failed:', result.error)
|
|
593
|
+
message.retry({ delaySeconds: 60 })
|
|
594
|
+
return
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
try {
|
|
600
|
+
// Use batch processor if available
|
|
601
|
+
if (definition.processBatch) {
|
|
602
|
+
await definition.processBatch(messages)
|
|
603
|
+
} else if (definition.process) {
|
|
604
|
+
// Process messages individually
|
|
605
|
+
for (const message of messages) {
|
|
606
|
+
try {
|
|
607
|
+
await definition.process(message)
|
|
608
|
+
} catch (error) {
|
|
609
|
+
if (definition.onError) {
|
|
610
|
+
await definition.onError(error, message)
|
|
611
|
+
} else {
|
|
612
|
+
throw error
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
} catch (error) {
|
|
618
|
+
console.error('Queue processing error:', error)
|
|
619
|
+
// Retry all messages
|
|
620
|
+
batch.retryAll()
|
|
621
|
+
}
|
|
622
|
+
}`);
|
|
623
|
+
}
|
|
624
|
+
lines.push(imports.join("\n"));
|
|
625
|
+
lines.push(queueHandlers.join("\n"));
|
|
626
|
+
lines.push("");
|
|
627
|
+
lines.push("/**");
|
|
628
|
+
lines.push(" * Main queue handler that routes to specific queue handlers.");
|
|
629
|
+
lines.push(" * Export this as the `queue` handler in your worker.");
|
|
630
|
+
lines.push(" */");
|
|
631
|
+
lines.push("export async function queue(batch, env, ctx) {");
|
|
632
|
+
lines.push(" const queueName = batch.queue");
|
|
633
|
+
lines.push("");
|
|
634
|
+
for (const queue of queueManifest.queues) {
|
|
635
|
+
lines.push(` if (queueName === '${queue.queueName}') {`);
|
|
636
|
+
lines.push(` return handle_${queue.name}_queue(batch, env, ctx)`);
|
|
637
|
+
lines.push(" }");
|
|
638
|
+
lines.push("");
|
|
639
|
+
}
|
|
640
|
+
lines.push(" console.warn(\\`Unknown queue: \\${queueName}\\`)");
|
|
641
|
+
lines.push("}");
|
|
642
|
+
return lines.join("\n");
|
|
643
|
+
}
|
|
644
|
+
function generateServiceRegistration(serviceManifest) {
|
|
645
|
+
if (!serviceManifest || serviceManifest.services.length === 0) {
|
|
646
|
+
return "";
|
|
647
|
+
}
|
|
648
|
+
const lines = [];
|
|
649
|
+
const imports = [];
|
|
650
|
+
const registrations = [];
|
|
651
|
+
lines.push("");
|
|
652
|
+
lines.push("// ============================================================================");
|
|
653
|
+
lines.push("// Service Registration");
|
|
654
|
+
lines.push("// ============================================================================");
|
|
655
|
+
lines.push("");
|
|
656
|
+
imports.push("import { registerLocalService } from '@cloudwerk/core/bindings'");
|
|
657
|
+
for (let i = 0; i < serviceManifest.services.length; i++) {
|
|
658
|
+
const service = serviceManifest.services[i];
|
|
659
|
+
const varName = `serviceDef_${i}`;
|
|
660
|
+
imports.push(`import ${varName} from '${service.absolutePath}'`);
|
|
661
|
+
if (service.mode === "local") {
|
|
662
|
+
registrations.push(`registerLocalService('${service.name}', ${varName})`);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
lines.push(imports.join("\n"));
|
|
666
|
+
lines.push("");
|
|
667
|
+
if (registrations.length > 0) {
|
|
668
|
+
lines.push("// Register local services");
|
|
669
|
+
for (const reg of registrations) {
|
|
670
|
+
lines.push(reg);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return lines.join("\n");
|
|
674
|
+
}
|
|
537
675
|
|
|
538
676
|
// src/virtual-modules/client-entry.ts
|
|
539
677
|
function generateClientEntry(clientComponents, options) {
|
|
@@ -1037,25 +1175,358 @@ export default __WrappedComponent
|
|
|
1037
1175
|
}
|
|
1038
1176
|
}
|
|
1039
1177
|
|
|
1178
|
+
// src/wrangler-watcher.ts
|
|
1179
|
+
import * as fs from "fs";
|
|
1180
|
+
import * as path2 from "path";
|
|
1181
|
+
var TYPE_MAPPINGS = {
|
|
1182
|
+
d1: "D1Database",
|
|
1183
|
+
kv: "KVNamespace",
|
|
1184
|
+
r2: "R2Bucket",
|
|
1185
|
+
queue: "Queue",
|
|
1186
|
+
do: "DurableObjectNamespace",
|
|
1187
|
+
service: "Fetcher",
|
|
1188
|
+
secret: "string",
|
|
1189
|
+
ai: "Ai",
|
|
1190
|
+
vectorize: "VectorizeIndex",
|
|
1191
|
+
hyperdrive: "Hyperdrive"
|
|
1192
|
+
};
|
|
1193
|
+
function parseSimpleToml(content) {
|
|
1194
|
+
const result = {};
|
|
1195
|
+
const d1Matches = content.matchAll(/\[\[d1_databases\]\]\s*\n([^[]*)/g);
|
|
1196
|
+
for (const match of d1Matches) {
|
|
1197
|
+
const bindingMatch = match[1].match(/binding\s*=\s*["']([^"']+)["']/);
|
|
1198
|
+
if (bindingMatch) {
|
|
1199
|
+
if (!result.d1_databases) result.d1_databases = [];
|
|
1200
|
+
result.d1_databases.push({ binding: bindingMatch[1] });
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
const kvMatches = content.matchAll(/\[\[kv_namespaces\]\]\s*\n([^[]*)/g);
|
|
1204
|
+
for (const match of kvMatches) {
|
|
1205
|
+
const bindingMatch = match[1].match(/binding\s*=\s*["']([^"']+)["']/);
|
|
1206
|
+
if (bindingMatch) {
|
|
1207
|
+
if (!result.kv_namespaces) result.kv_namespaces = [];
|
|
1208
|
+
result.kv_namespaces.push({ binding: bindingMatch[1] });
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
const r2Matches = content.matchAll(/\[\[r2_buckets\]\]\s*\n([^[]*)/g);
|
|
1212
|
+
for (const match of r2Matches) {
|
|
1213
|
+
const bindingMatch = match[1].match(/binding\s*=\s*["']([^"']+)["']/);
|
|
1214
|
+
if (bindingMatch) {
|
|
1215
|
+
if (!result.r2_buckets) result.r2_buckets = [];
|
|
1216
|
+
result.r2_buckets.push({ binding: bindingMatch[1] });
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
const queueMatches = content.matchAll(/\[\[queues\.producers\]\]\s*\n([^[]*)/g);
|
|
1220
|
+
for (const match of queueMatches) {
|
|
1221
|
+
const bindingMatch = match[1].match(/binding\s*=\s*["']([^"']+)["']/);
|
|
1222
|
+
if (bindingMatch) {
|
|
1223
|
+
if (!result.queues) result.queues = {};
|
|
1224
|
+
if (!result.queues.producers) result.queues.producers = [];
|
|
1225
|
+
result.queues.producers.push({ binding: bindingMatch[1] });
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
const doMatches = content.matchAll(/\[\[durable_objects\.bindings\]\]\s*\n([^[]*)/g);
|
|
1229
|
+
for (const match of doMatches) {
|
|
1230
|
+
const nameMatch = match[1].match(/name\s*=\s*["']([^"']+)["']/);
|
|
1231
|
+
if (nameMatch) {
|
|
1232
|
+
if (!result.durable_objects) result.durable_objects = {};
|
|
1233
|
+
if (!result.durable_objects.bindings) result.durable_objects.bindings = [];
|
|
1234
|
+
result.durable_objects.bindings.push({ name: nameMatch[1] });
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
const serviceMatches = content.matchAll(/\[\[services\]\]\s*\n([^[]*)/g);
|
|
1238
|
+
for (const match of serviceMatches) {
|
|
1239
|
+
const bindingMatch = match[1].match(/binding\s*=\s*["']([^"']+)["']/);
|
|
1240
|
+
if (bindingMatch) {
|
|
1241
|
+
if (!result.services) result.services = [];
|
|
1242
|
+
result.services.push({ binding: bindingMatch[1] });
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
const varsMatch = content.match(/\[vars\]\s*\n([^[]*)/s);
|
|
1246
|
+
if (varsMatch) {
|
|
1247
|
+
const varsSection = varsMatch[1];
|
|
1248
|
+
const varMatches = varsSection.matchAll(/(\w+)\s*=\s*["']([^"']*)["']/g);
|
|
1249
|
+
result.vars = {};
|
|
1250
|
+
for (const match of varMatches) {
|
|
1251
|
+
result.vars[match[1]] = match[2];
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
const aiMatch = content.match(/\[ai\]\s*\n([^[]*)/s);
|
|
1255
|
+
if (aiMatch) {
|
|
1256
|
+
const bindingMatch = aiMatch[1].match(/binding\s*=\s*["']([^"']+)["']/);
|
|
1257
|
+
if (bindingMatch) {
|
|
1258
|
+
result.ai = { binding: bindingMatch[1] };
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
const vectorizeMatches = content.matchAll(/\[\[vectorize\]\]\s*\n([^[]*)/g);
|
|
1262
|
+
for (const match of vectorizeMatches) {
|
|
1263
|
+
const bindingMatch = match[1].match(/binding\s*=\s*["']([^"']+)["']/);
|
|
1264
|
+
if (bindingMatch) {
|
|
1265
|
+
if (!result.vectorize) result.vectorize = [];
|
|
1266
|
+
result.vectorize.push({ binding: bindingMatch[1] });
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
const hyperdriveMatches = content.matchAll(/\[\[hyperdrive\]\]\s*\n([^[]*)/g);
|
|
1270
|
+
for (const match of hyperdriveMatches) {
|
|
1271
|
+
const bindingMatch = match[1].match(/binding\s*=\s*["']([^"']+)["']/);
|
|
1272
|
+
if (bindingMatch) {
|
|
1273
|
+
if (!result.hyperdrive) result.hyperdrive = [];
|
|
1274
|
+
result.hyperdrive.push({ binding: bindingMatch[1] });
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
return result;
|
|
1278
|
+
}
|
|
1279
|
+
function extractBindings(config) {
|
|
1280
|
+
const bindings = [];
|
|
1281
|
+
if (config.d1_databases) {
|
|
1282
|
+
for (const db of config.d1_databases) {
|
|
1283
|
+
bindings.push({ type: "d1", name: db.binding });
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
if (config.kv_namespaces) {
|
|
1287
|
+
for (const kv of config.kv_namespaces) {
|
|
1288
|
+
bindings.push({ type: "kv", name: kv.binding });
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
if (config.r2_buckets) {
|
|
1292
|
+
for (const r2 of config.r2_buckets) {
|
|
1293
|
+
bindings.push({ type: "r2", name: r2.binding });
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
if (config.queues?.producers) {
|
|
1297
|
+
for (const queue of config.queues.producers) {
|
|
1298
|
+
bindings.push({ type: "queue", name: queue.binding });
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
if (config.durable_objects?.bindings) {
|
|
1302
|
+
for (const doBinding of config.durable_objects.bindings) {
|
|
1303
|
+
bindings.push({ type: "do", name: doBinding.name });
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
if (config.services) {
|
|
1307
|
+
for (const service of config.services) {
|
|
1308
|
+
bindings.push({ type: "service", name: service.binding });
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
if (config.vars) {
|
|
1312
|
+
for (const name of Object.keys(config.vars)) {
|
|
1313
|
+
bindings.push({ type: "secret", name });
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
if (config.ai) {
|
|
1317
|
+
bindings.push({ type: "ai", name: config.ai.binding });
|
|
1318
|
+
}
|
|
1319
|
+
if (config.vectorize) {
|
|
1320
|
+
for (const vec of config.vectorize) {
|
|
1321
|
+
bindings.push({ type: "vectorize", name: vec.binding });
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
if (config.hyperdrive) {
|
|
1325
|
+
for (const hd of config.hyperdrive) {
|
|
1326
|
+
bindings.push({ type: "hyperdrive", name: hd.binding });
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
return bindings;
|
|
1330
|
+
}
|
|
1331
|
+
function getSectionName(type) {
|
|
1332
|
+
switch (type) {
|
|
1333
|
+
case "d1":
|
|
1334
|
+
return "D1 Databases";
|
|
1335
|
+
case "kv":
|
|
1336
|
+
return "KV Namespaces";
|
|
1337
|
+
case "r2":
|
|
1338
|
+
return "R2 Buckets";
|
|
1339
|
+
case "queue":
|
|
1340
|
+
return "Queues";
|
|
1341
|
+
case "do":
|
|
1342
|
+
return "Durable Objects";
|
|
1343
|
+
case "service":
|
|
1344
|
+
return "Services";
|
|
1345
|
+
case "secret":
|
|
1346
|
+
return "Environment Variables";
|
|
1347
|
+
case "ai":
|
|
1348
|
+
return "AI";
|
|
1349
|
+
case "vectorize":
|
|
1350
|
+
return "Vectorize Indexes";
|
|
1351
|
+
case "hyperdrive":
|
|
1352
|
+
return "Hyperdrive";
|
|
1353
|
+
default:
|
|
1354
|
+
return "Other";
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
function groupBindingsByType(bindings) {
|
|
1358
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1359
|
+
for (const binding of bindings) {
|
|
1360
|
+
const existing = grouped.get(binding.type) || [];
|
|
1361
|
+
existing.push(binding);
|
|
1362
|
+
grouped.set(binding.type, existing);
|
|
1363
|
+
}
|
|
1364
|
+
return grouped;
|
|
1365
|
+
}
|
|
1366
|
+
function generateBindingsDts(bindings) {
|
|
1367
|
+
const lines = [];
|
|
1368
|
+
lines.push("// Auto-generated by cloudwerk - DO NOT EDIT");
|
|
1369
|
+
lines.push(`// Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
1370
|
+
lines.push("//");
|
|
1371
|
+
lines.push("// This file provides type information for @cloudwerk/core/bindings");
|
|
1372
|
+
lines.push("");
|
|
1373
|
+
lines.push("declare module '@cloudwerk/core/bindings' {");
|
|
1374
|
+
const bindingsByType = groupBindingsByType(bindings);
|
|
1375
|
+
const typeOrder = [
|
|
1376
|
+
"d1",
|
|
1377
|
+
"kv",
|
|
1378
|
+
"r2",
|
|
1379
|
+
"queue",
|
|
1380
|
+
"do",
|
|
1381
|
+
"service",
|
|
1382
|
+
"ai",
|
|
1383
|
+
"vectorize",
|
|
1384
|
+
"hyperdrive",
|
|
1385
|
+
"secret"
|
|
1386
|
+
];
|
|
1387
|
+
let firstSection = true;
|
|
1388
|
+
for (const type of typeOrder) {
|
|
1389
|
+
const typeBindings = bindingsByType.get(type);
|
|
1390
|
+
if (!typeBindings || typeBindings.length === 0) continue;
|
|
1391
|
+
const tsType = TYPE_MAPPINGS[type];
|
|
1392
|
+
const sectionName = getSectionName(type);
|
|
1393
|
+
if (!firstSection) {
|
|
1394
|
+
lines.push("");
|
|
1395
|
+
}
|
|
1396
|
+
lines.push(` // ${sectionName}`);
|
|
1397
|
+
firstSection = false;
|
|
1398
|
+
for (const binding of typeBindings) {
|
|
1399
|
+
lines.push(` export const ${binding.name}: ${tsType}`);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
lines.push("");
|
|
1403
|
+
lines.push(" // Bindings proxy object (for dynamic access)");
|
|
1404
|
+
lines.push(" export const bindings: Record<string, unknown>");
|
|
1405
|
+
lines.push("");
|
|
1406
|
+
lines.push(" // Helper functions");
|
|
1407
|
+
lines.push(" export function getBinding<T = unknown>(name: string): T");
|
|
1408
|
+
lines.push(" export function hasBinding(name: string): boolean");
|
|
1409
|
+
lines.push(" export function getBindingNames(): string[]");
|
|
1410
|
+
lines.push("}");
|
|
1411
|
+
lines.push("");
|
|
1412
|
+
return lines.join("\n");
|
|
1413
|
+
}
|
|
1414
|
+
function generateContextDts(bindings) {
|
|
1415
|
+
const lines = [];
|
|
1416
|
+
lines.push("// Auto-generated by cloudwerk - DO NOT EDIT");
|
|
1417
|
+
lines.push(`// Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
1418
|
+
lines.push("//");
|
|
1419
|
+
lines.push("// This file provides type information for @cloudwerk/core/context");
|
|
1420
|
+
lines.push("");
|
|
1421
|
+
lines.push("interface CloudwerkEnv {");
|
|
1422
|
+
const bindingsByType = groupBindingsByType(bindings);
|
|
1423
|
+
const typeOrder = [
|
|
1424
|
+
"d1",
|
|
1425
|
+
"kv",
|
|
1426
|
+
"r2",
|
|
1427
|
+
"queue",
|
|
1428
|
+
"do",
|
|
1429
|
+
"service",
|
|
1430
|
+
"ai",
|
|
1431
|
+
"vectorize",
|
|
1432
|
+
"hyperdrive",
|
|
1433
|
+
"secret"
|
|
1434
|
+
];
|
|
1435
|
+
let firstSection = true;
|
|
1436
|
+
for (const type of typeOrder) {
|
|
1437
|
+
const typeBindings = bindingsByType.get(type);
|
|
1438
|
+
if (!typeBindings || typeBindings.length === 0) continue;
|
|
1439
|
+
const tsType = TYPE_MAPPINGS[type];
|
|
1440
|
+
const sectionName = getSectionName(type);
|
|
1441
|
+
if (!firstSection) {
|
|
1442
|
+
lines.push("");
|
|
1443
|
+
}
|
|
1444
|
+
lines.push(` // ${sectionName}`);
|
|
1445
|
+
firstSection = false;
|
|
1446
|
+
for (const binding of typeBindings) {
|
|
1447
|
+
lines.push(` ${binding.name}: ${tsType}`);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
lines.push("}");
|
|
1451
|
+
lines.push("");
|
|
1452
|
+
lines.push("declare module '@cloudwerk/core/context' {");
|
|
1453
|
+
lines.push(" export const params: Record<string, string>");
|
|
1454
|
+
lines.push(" export const request: Request");
|
|
1455
|
+
lines.push(" export const env: CloudwerkEnv");
|
|
1456
|
+
lines.push(" export const executionCtx: {");
|
|
1457
|
+
lines.push(" waitUntil(promise: Promise<unknown>): void");
|
|
1458
|
+
lines.push(" passThroughOnException(): void");
|
|
1459
|
+
lines.push(" }");
|
|
1460
|
+
lines.push(" export function getRequestId(): string");
|
|
1461
|
+
lines.push(" export function get<T>(key: string): T | undefined");
|
|
1462
|
+
lines.push(" export function set<T>(key: string, value: T): void");
|
|
1463
|
+
lines.push("}");
|
|
1464
|
+
lines.push("");
|
|
1465
|
+
return lines.join("\n");
|
|
1466
|
+
}
|
|
1467
|
+
function findWranglerTomlPath(root) {
|
|
1468
|
+
const tomlPath = path2.join(root, "wrangler.toml");
|
|
1469
|
+
if (fs.existsSync(tomlPath)) {
|
|
1470
|
+
return tomlPath;
|
|
1471
|
+
}
|
|
1472
|
+
const jsonPath = path2.join(root, "wrangler.json");
|
|
1473
|
+
if (fs.existsSync(jsonPath)) {
|
|
1474
|
+
return jsonPath;
|
|
1475
|
+
}
|
|
1476
|
+
return null;
|
|
1477
|
+
}
|
|
1478
|
+
function regenerateCloudwerkTypes(root) {
|
|
1479
|
+
const wranglerPath = findWranglerTomlPath(root);
|
|
1480
|
+
if (!wranglerPath) {
|
|
1481
|
+
return null;
|
|
1482
|
+
}
|
|
1483
|
+
const content = fs.readFileSync(wranglerPath, "utf-8");
|
|
1484
|
+
let config;
|
|
1485
|
+
if (wranglerPath.endsWith(".json")) {
|
|
1486
|
+
config = JSON.parse(content);
|
|
1487
|
+
} else {
|
|
1488
|
+
config = parseSimpleToml(content);
|
|
1489
|
+
}
|
|
1490
|
+
const bindings = extractBindings(config);
|
|
1491
|
+
if (bindings.length === 0) {
|
|
1492
|
+
return null;
|
|
1493
|
+
}
|
|
1494
|
+
const typesDir = path2.join(root, ".cloudwerk", "types");
|
|
1495
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
1496
|
+
const bindingsPath = path2.join(typesDir, "bindings.d.ts");
|
|
1497
|
+
const bindingsContent = generateBindingsDts(bindings);
|
|
1498
|
+
fs.writeFileSync(bindingsPath, bindingsContent, "utf-8");
|
|
1499
|
+
const contextPath = path2.join(typesDir, "context.d.ts");
|
|
1500
|
+
const contextContent = generateContextDts(bindings);
|
|
1501
|
+
fs.writeFileSync(contextPath, contextContent, "utf-8");
|
|
1502
|
+
return {
|
|
1503
|
+
bindingCount: bindings.length,
|
|
1504
|
+
files: {
|
|
1505
|
+
bindings: bindingsPath,
|
|
1506
|
+
context: contextPath
|
|
1507
|
+
}
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1040
1511
|
// src/plugin.ts
|
|
1041
1512
|
async function scanClientComponents(root, state) {
|
|
1042
|
-
const appDir =
|
|
1513
|
+
const appDir = path3.resolve(root, state.options.appDir);
|
|
1043
1514
|
try {
|
|
1044
|
-
await
|
|
1515
|
+
await fs2.promises.access(appDir);
|
|
1045
1516
|
} catch {
|
|
1046
1517
|
return;
|
|
1047
1518
|
}
|
|
1048
1519
|
async function scanDir(dir) {
|
|
1049
|
-
const entries = await
|
|
1520
|
+
const entries = await fs2.promises.readdir(dir, { withFileTypes: true });
|
|
1050
1521
|
await Promise.all(
|
|
1051
1522
|
entries.map(async (entry) => {
|
|
1052
|
-
const fullPath =
|
|
1523
|
+
const fullPath = path3.join(dir, entry.name);
|
|
1053
1524
|
if (entry.isDirectory()) {
|
|
1054
1525
|
if (entry.name !== "node_modules" && !entry.name.startsWith(".")) {
|
|
1055
1526
|
await scanDir(fullPath);
|
|
1056
1527
|
}
|
|
1057
1528
|
} else if (entry.isFile() && (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts"))) {
|
|
1058
|
-
const content = await
|
|
1529
|
+
const content = await fs2.promises.readFile(fullPath, "utf-8");
|
|
1059
1530
|
if (hasUseClientDirective(content)) {
|
|
1060
1531
|
const componentId = generateComponentId(fullPath, root);
|
|
1061
1532
|
const bundlePath = `${state.options.hydrationEndpoint}/${componentId}.js`;
|
|
@@ -1104,14 +1575,82 @@ function cloudwerkPlugin(options = {}) {
|
|
|
1104
1575
|
console.log(`[cloudwerk] Found ${state.manifest.routes.length} routes`);
|
|
1105
1576
|
}
|
|
1106
1577
|
}
|
|
1578
|
+
async function buildQueueManifestIfExists(root) {
|
|
1579
|
+
if (!state) {
|
|
1580
|
+
throw new Error("Plugin state not initialized");
|
|
1581
|
+
}
|
|
1582
|
+
const queuesPath = path3.resolve(root, state.options.appDir, QUEUES_DIR);
|
|
1583
|
+
try {
|
|
1584
|
+
await fs2.promises.access(queuesPath);
|
|
1585
|
+
} catch {
|
|
1586
|
+
state.queueScanResult = null;
|
|
1587
|
+
state.queueManifest = null;
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
state.queueScanResult = await scanQueues(
|
|
1591
|
+
path3.resolve(root, state.options.appDir),
|
|
1592
|
+
{ extensions: state.options.config.extensions }
|
|
1593
|
+
);
|
|
1594
|
+
state.queueManifest = buildQueueManifest(
|
|
1595
|
+
state.queueScanResult,
|
|
1596
|
+
root,
|
|
1597
|
+
{ appName: "cloudwerk" }
|
|
1598
|
+
);
|
|
1599
|
+
if (state.options.verbose && state.queueManifest.queues.length > 0) {
|
|
1600
|
+
console.log(`[cloudwerk] Found ${state.queueManifest.queues.length} queue(s)`);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
async function buildServiceManifestIfExists(root) {
|
|
1604
|
+
if (!state) {
|
|
1605
|
+
throw new Error("Plugin state not initialized");
|
|
1606
|
+
}
|
|
1607
|
+
const servicesPath = path3.resolve(root, state.options.appDir, SERVICES_DIR);
|
|
1608
|
+
try {
|
|
1609
|
+
await fs2.promises.access(servicesPath);
|
|
1610
|
+
} catch {
|
|
1611
|
+
state.serviceScanResult = null;
|
|
1612
|
+
state.serviceManifest = null;
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
state.serviceScanResult = await scanServices(
|
|
1616
|
+
path3.resolve(root, state.options.appDir),
|
|
1617
|
+
{ extensions: state.options.config.extensions }
|
|
1618
|
+
);
|
|
1619
|
+
state.serviceManifest = buildServiceManifest(
|
|
1620
|
+
state.serviceScanResult,
|
|
1621
|
+
root
|
|
1622
|
+
);
|
|
1623
|
+
if (state.options.verbose && state.serviceManifest.services.length > 0) {
|
|
1624
|
+
console.log(`[cloudwerk] Found ${state.serviceManifest.services.length} service(s)`);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1107
1627
|
function isRouteFile(filePath) {
|
|
1108
1628
|
if (!state) return false;
|
|
1109
|
-
const appDir =
|
|
1629
|
+
const appDir = path3.resolve(state.options.root, state.options.appDir);
|
|
1110
1630
|
if (!filePath.startsWith(appDir)) return false;
|
|
1111
|
-
const basename2 =
|
|
1631
|
+
const basename2 = path3.basename(filePath);
|
|
1112
1632
|
const nameWithoutExt = basename2.replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
1113
1633
|
return ROUTE_FILE_NAMES.includes(nameWithoutExt);
|
|
1114
1634
|
}
|
|
1635
|
+
function isQueueFile(filePath) {
|
|
1636
|
+
if (!state) return false;
|
|
1637
|
+
const queuesDir = path3.resolve(state.options.root, state.options.appDir, QUEUES_DIR);
|
|
1638
|
+
if (!filePath.startsWith(queuesDir)) return false;
|
|
1639
|
+
const relativePath = path3.relative(queuesDir, filePath);
|
|
1640
|
+
if (relativePath.includes(path3.sep)) return false;
|
|
1641
|
+
const ext = path3.extname(filePath);
|
|
1642
|
+
return state.options.config.extensions.includes(ext);
|
|
1643
|
+
}
|
|
1644
|
+
function isServiceFile(filePath) {
|
|
1645
|
+
if (!state) return false;
|
|
1646
|
+
const servicesDir = path3.resolve(state.options.root, state.options.appDir, SERVICES_DIR);
|
|
1647
|
+
if (!filePath.startsWith(servicesDir)) return false;
|
|
1648
|
+
const basename2 = path3.basename(filePath);
|
|
1649
|
+
const ext = path3.extname(filePath);
|
|
1650
|
+
const nameWithoutExt = basename2.replace(ext, "");
|
|
1651
|
+
if (nameWithoutExt !== SERVICE_FILE_NAME) return false;
|
|
1652
|
+
return state.options.config.extensions.includes(ext);
|
|
1653
|
+
}
|
|
1115
1654
|
function invalidateVirtualModules() {
|
|
1116
1655
|
if (!server) return;
|
|
1117
1656
|
const idsToInvalidate = [
|
|
@@ -1148,16 +1687,16 @@ function cloudwerkPlugin(options = {}) {
|
|
|
1148
1687
|
*/
|
|
1149
1688
|
async configResolved(config) {
|
|
1150
1689
|
const root = config.root;
|
|
1151
|
-
let detectedServerEntry = options.serverEntry ?
|
|
1690
|
+
let detectedServerEntry = options.serverEntry ? path3.resolve(root, options.serverEntry) : null;
|
|
1152
1691
|
if (!detectedServerEntry) {
|
|
1153
1692
|
const conventionalPaths = [
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1693
|
+
path3.resolve(root, "app/server.ts"),
|
|
1694
|
+
path3.resolve(root, "app/server.tsx"),
|
|
1695
|
+
path3.resolve(root, "src/server.ts"),
|
|
1696
|
+
path3.resolve(root, "src/server.tsx")
|
|
1158
1697
|
];
|
|
1159
1698
|
for (const p of conventionalPaths) {
|
|
1160
|
-
if (
|
|
1699
|
+
if (fs2.existsSync(p)) {
|
|
1161
1700
|
detectedServerEntry = p;
|
|
1162
1701
|
if (options.verbose) {
|
|
1163
1702
|
console.log(`[cloudwerk] Detected custom server entry: ${p}`);
|
|
@@ -1202,11 +1741,17 @@ function cloudwerkPlugin(options = {}) {
|
|
|
1202
1741
|
errors: [],
|
|
1203
1742
|
notFound: []
|
|
1204
1743
|
},
|
|
1744
|
+
queueManifest: null,
|
|
1745
|
+
queueScanResult: null,
|
|
1746
|
+
serviceManifest: null,
|
|
1747
|
+
serviceScanResult: null,
|
|
1205
1748
|
clientComponents: /* @__PURE__ */ new Map(),
|
|
1206
1749
|
serverEntryCache: null,
|
|
1207
1750
|
clientEntryCache: null
|
|
1208
1751
|
};
|
|
1209
1752
|
await buildManifest(root);
|
|
1753
|
+
await buildQueueManifestIfExists(root);
|
|
1754
|
+
await buildServiceManifestIfExists(root);
|
|
1210
1755
|
await scanClientComponents(root, state);
|
|
1211
1756
|
},
|
|
1212
1757
|
/**
|
|
@@ -1215,34 +1760,98 @@ function cloudwerkPlugin(options = {}) {
|
|
|
1215
1760
|
configureServer(devServer) {
|
|
1216
1761
|
server = devServer;
|
|
1217
1762
|
if (!state) return;
|
|
1218
|
-
const appDir =
|
|
1763
|
+
const appDir = path3.resolve(state.options.root, state.options.appDir);
|
|
1764
|
+
const root = state.options.root;
|
|
1765
|
+
const verbose = state.options.verbose;
|
|
1219
1766
|
devServer.watcher.on("add", async (filePath) => {
|
|
1220
1767
|
if (isRouteFile(filePath)) {
|
|
1221
1768
|
if (state?.options.verbose) {
|
|
1222
|
-
console.log(`[cloudwerk] Route added: ${
|
|
1769
|
+
console.log(`[cloudwerk] Route added: ${path3.relative(appDir, filePath)}`);
|
|
1223
1770
|
}
|
|
1224
1771
|
await buildManifest(state.options.root);
|
|
1225
1772
|
invalidateVirtualModules();
|
|
1226
1773
|
}
|
|
1774
|
+
if (isQueueFile(filePath)) {
|
|
1775
|
+
const queuesDir = path3.resolve(root, state.options.appDir, QUEUES_DIR);
|
|
1776
|
+
if (state?.options.verbose) {
|
|
1777
|
+
console.log(`[cloudwerk] Queue added: ${path3.relative(queuesDir, filePath)}`);
|
|
1778
|
+
}
|
|
1779
|
+
await buildQueueManifestIfExists(state.options.root);
|
|
1780
|
+
invalidateVirtualModules();
|
|
1781
|
+
}
|
|
1782
|
+
if (isServiceFile(filePath)) {
|
|
1783
|
+
const servicesDir = path3.resolve(root, state.options.appDir, SERVICES_DIR);
|
|
1784
|
+
if (state?.options.verbose) {
|
|
1785
|
+
console.log(`[cloudwerk] Service added: ${path3.relative(servicesDir, filePath)}`);
|
|
1786
|
+
}
|
|
1787
|
+
await buildServiceManifestIfExists(state.options.root);
|
|
1788
|
+
invalidateVirtualModules();
|
|
1789
|
+
}
|
|
1227
1790
|
});
|
|
1228
1791
|
devServer.watcher.on("unlink", async (filePath) => {
|
|
1229
1792
|
if (isRouteFile(filePath)) {
|
|
1230
1793
|
if (state?.options.verbose) {
|
|
1231
|
-
console.log(`[cloudwerk] Route removed: ${
|
|
1794
|
+
console.log(`[cloudwerk] Route removed: ${path3.relative(appDir, filePath)}`);
|
|
1232
1795
|
}
|
|
1233
1796
|
await buildManifest(state.options.root);
|
|
1234
1797
|
invalidateVirtualModules();
|
|
1235
1798
|
}
|
|
1799
|
+
if (isQueueFile(filePath)) {
|
|
1800
|
+
const queuesDir = path3.resolve(root, state.options.appDir, QUEUES_DIR);
|
|
1801
|
+
if (state?.options.verbose) {
|
|
1802
|
+
console.log(`[cloudwerk] Queue removed: ${path3.relative(queuesDir, filePath)}`);
|
|
1803
|
+
}
|
|
1804
|
+
await buildQueueManifestIfExists(state.options.root);
|
|
1805
|
+
invalidateVirtualModules();
|
|
1806
|
+
}
|
|
1807
|
+
if (isServiceFile(filePath)) {
|
|
1808
|
+
const servicesDir = path3.resolve(root, state.options.appDir, SERVICES_DIR);
|
|
1809
|
+
if (state?.options.verbose) {
|
|
1810
|
+
console.log(`[cloudwerk] Service removed: ${path3.relative(servicesDir, filePath)}`);
|
|
1811
|
+
}
|
|
1812
|
+
await buildServiceManifestIfExists(state.options.root);
|
|
1813
|
+
invalidateVirtualModules();
|
|
1814
|
+
}
|
|
1236
1815
|
});
|
|
1237
1816
|
devServer.watcher.on("change", async (filePath) => {
|
|
1238
1817
|
if (isRouteFile(filePath)) {
|
|
1239
1818
|
if (state?.options.verbose) {
|
|
1240
|
-
console.log(`[cloudwerk] Route changed: ${
|
|
1819
|
+
console.log(`[cloudwerk] Route changed: ${path3.relative(appDir, filePath)}`);
|
|
1241
1820
|
}
|
|
1242
1821
|
await buildManifest(state.options.root);
|
|
1243
1822
|
invalidateVirtualModules();
|
|
1244
1823
|
}
|
|
1824
|
+
if (isQueueFile(filePath)) {
|
|
1825
|
+
const queuesDir = path3.resolve(root, state.options.appDir, QUEUES_DIR);
|
|
1826
|
+
if (state?.options.verbose) {
|
|
1827
|
+
console.log(`[cloudwerk] Queue changed: ${path3.relative(queuesDir, filePath)}`);
|
|
1828
|
+
}
|
|
1829
|
+
await buildQueueManifestIfExists(state.options.root);
|
|
1830
|
+
invalidateVirtualModules();
|
|
1831
|
+
}
|
|
1832
|
+
if (isServiceFile(filePath)) {
|
|
1833
|
+
const servicesDir = path3.resolve(root, state.options.appDir, SERVICES_DIR);
|
|
1834
|
+
if (state?.options.verbose) {
|
|
1835
|
+
console.log(`[cloudwerk] Service changed: ${path3.relative(servicesDir, filePath)}`);
|
|
1836
|
+
}
|
|
1837
|
+
await buildServiceManifestIfExists(state.options.root);
|
|
1838
|
+
invalidateVirtualModules();
|
|
1839
|
+
}
|
|
1840
|
+
const wranglerPath2 = findWranglerTomlPath(root);
|
|
1841
|
+
if (wranglerPath2 && filePath === wranglerPath2) {
|
|
1842
|
+
if (verbose) {
|
|
1843
|
+
console.log(`[cloudwerk] wrangler.toml changed, regenerating types...`);
|
|
1844
|
+
}
|
|
1845
|
+
const result = regenerateCloudwerkTypes(root);
|
|
1846
|
+
if (result && verbose) {
|
|
1847
|
+
console.log(`[cloudwerk] Regenerated .cloudwerk/types/ with ${result.bindingCount} binding(s)`);
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1245
1850
|
});
|
|
1851
|
+
const wranglerPath = findWranglerTomlPath(root);
|
|
1852
|
+
if (wranglerPath) {
|
|
1853
|
+
devServer.watcher.add(wranglerPath);
|
|
1854
|
+
}
|
|
1246
1855
|
},
|
|
1247
1856
|
/**
|
|
1248
1857
|
* Resolve virtual module IDs.
|
|
@@ -1272,7 +1881,11 @@ function cloudwerkPlugin(options = {}) {
|
|
|
1272
1881
|
state.serverEntryCache = generateServerEntry(
|
|
1273
1882
|
state.manifest,
|
|
1274
1883
|
state.scanResult,
|
|
1275
|
-
state.options
|
|
1884
|
+
state.options,
|
|
1885
|
+
{
|
|
1886
|
+
queueManifest: state.queueManifest,
|
|
1887
|
+
serviceManifest: state.serviceManifest
|
|
1888
|
+
}
|
|
1276
1889
|
);
|
|
1277
1890
|
}
|
|
1278
1891
|
return state.serverEntryCache;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudwerk/vite-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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.
|
|
23
|
-
"@cloudwerk/ui": "^0.
|
|
22
|
+
"@cloudwerk/core": "^0.13.0",
|
|
23
|
+
"@cloudwerk/ui": "^0.13.0"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"vite": "^5.0.0 || ^6.0.0",
|