devflare 1.0.0-next.1 → 1.0.0-next.10
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/LLM.md +775 -637
- package/R2.md +200 -0
- package/README.md +285 -514
- package/bin/devflare.js +8 -8
- package/dist/{account-rvrj687w.js → account-8psavtg6.js} +27 -4
- package/dist/bridge/miniflare.d.ts +6 -0
- package/dist/bridge/miniflare.d.ts.map +1 -1
- package/dist/bridge/proxy.d.ts +5 -6
- package/dist/bridge/proxy.d.ts.map +1 -1
- package/dist/bridge/server.d.ts.map +1 -1
- package/dist/browser.d.ts +50 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/{build-mnf6v8gd.js → build-k36xrzvy.js} +26 -7
- package/dist/bundler/do-bundler.d.ts +7 -0
- package/dist/bundler/do-bundler.d.ts.map +1 -1
- package/dist/cli/commands/account.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/deploy.d.ts.map +1 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/types.d.ts.map +1 -1
- package/dist/cli/config-path.d.ts +5 -0
- package/dist/cli/config-path.d.ts.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/package-metadata.d.ts +16 -0
- package/dist/cli/package-metadata.d.ts.map +1 -0
- package/dist/config/compiler.d.ts +7 -0
- package/dist/config/compiler.d.ts.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/schema.d.ts +2575 -1221
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/{deploy-nhceck39.js → deploy-dbvfq8vq.js} +33 -15
- package/dist/{dev-qnxet3j9.js → dev-rk8p6pse.js} +900 -234
- package/dist/dev-server/miniflare-log.d.ts +12 -0
- package/dist/dev-server/miniflare-log.d.ts.map +1 -0
- package/dist/dev-server/runtime-stdio.d.ts +8 -0
- package/dist/dev-server/runtime-stdio.d.ts.map +1 -0
- package/dist/dev-server/server.d.ts +2 -0
- package/dist/dev-server/server.d.ts.map +1 -1
- package/dist/dev-server/vite-utils.d.ts +37 -0
- package/dist/dev-server/vite-utils.d.ts.map +1 -0
- package/dist/{doctor-e8fy6fj5.js → doctor-06y8nxd4.js} +73 -50
- package/dist/{durable-object-t4kbb0yt.js → durable-object-yt8v1dyn.js} +1 -1
- package/dist/index-05fyzwne.js +195 -0
- package/dist/index-1p814k7s.js +227 -0
- package/dist/{index-hcex3rgh.js → index-1phx14av.js} +84 -7
- package/dist/{index-tk6ej9dj.js → index-2q3pmzrx.js} +12 -16
- package/dist/{index-pf5s73n9.js → index-59df49vn.js} +11 -281
- package/dist/index-5yxg30va.js +304 -0
- package/dist/index-62b3gt2g.js +12 -0
- package/dist/index-6h8xbs75.js +44 -0
- package/dist/{index-67qcae0f.js → index-6v3wjg1r.js} +16 -1
- package/dist/index-8gtqgb3q.js +529 -0
- package/dist/{index-gz1gndna.js → index-9wt9x09k.js} +42 -62
- package/dist/index-fef08w43.js +231 -0
- package/dist/{index-ep3445yc.js → index-jht2j546.js} +393 -170
- package/dist/index-k7r18na8.js +0 -0
- package/dist/{index-m2q41jwa.js → index-n932ytmq.js} +9 -1
- package/dist/index-pwgyy2q9.js +39 -0
- package/dist/{index-07q6yxyc.js → index-v8vvsn9x.js} +1 -0
- package/dist/index-vky23txa.js +70 -0
- package/dist/index-vs49yxn4.js +322 -0
- package/dist/{index-z14anrqp.js → index-wfbfz02q.js} +14 -15
- package/dist/index-ws68xvq2.js +311 -0
- package/dist/index-y1d8za14.js +196 -0
- package/dist/{init-f9mgmew3.js → init-na2atvz2.js} +42 -55
- package/dist/router/types.d.ts +24 -0
- package/dist/router/types.d.ts.map +1 -0
- package/dist/runtime/context.d.ts +249 -8
- package/dist/runtime/context.d.ts.map +1 -1
- package/dist/runtime/exports.d.ts +50 -55
- package/dist/runtime/exports.d.ts.map +1 -1
- package/dist/runtime/index.d.ts +8 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/middleware.d.ts +77 -60
- package/dist/runtime/middleware.d.ts.map +1 -1
- package/dist/runtime/router.d.ts +7 -0
- package/dist/runtime/router.d.ts.map +1 -0
- package/dist/runtime/validation.d.ts +1 -1
- package/dist/runtime/validation.d.ts.map +1 -1
- package/dist/src/browser.js +150 -0
- package/dist/src/cli/index.js +10 -0
- package/dist/{cloudflare → src/cloudflare}/index.js +3 -3
- package/dist/{decorators → src/decorators}/index.js +2 -2
- package/dist/src/index.js +132 -0
- package/dist/src/runtime/index.js +111 -0
- package/dist/{sveltekit → src/sveltekit}/index.js +14 -6
- package/dist/{test → src/test}/index.js +22 -13
- package/dist/{vite → src/vite}/index.js +128 -59
- package/dist/sveltekit/platform.d.ts.map +1 -1
- package/dist/test/bridge-context.d.ts +5 -2
- package/dist/test/bridge-context.d.ts.map +1 -1
- package/dist/test/cf.d.ts +25 -11
- package/dist/test/cf.d.ts.map +1 -1
- package/dist/test/email.d.ts +16 -7
- package/dist/test/email.d.ts.map +1 -1
- package/dist/test/queue.d.ts.map +1 -1
- package/dist/test/resolve-service-bindings.d.ts.map +1 -1
- package/dist/test/scheduled.d.ts.map +1 -1
- package/dist/test/simple-context.d.ts +1 -1
- package/dist/test/simple-context.d.ts.map +1 -1
- package/dist/test/tail.d.ts +2 -1
- package/dist/test/tail.d.ts.map +1 -1
- package/dist/test/worker.d.ts +6 -0
- package/dist/test/worker.d.ts.map +1 -1
- package/dist/transform/durable-object.d.ts.map +1 -1
- package/dist/transform/worker-entrypoint.d.ts.map +1 -1
- package/dist/{types-5nyrz1sz.js → types-x9q7t491.js} +30 -16
- package/dist/utils/entrypoint-discovery.d.ts +6 -3
- package/dist/utils/entrypoint-discovery.d.ts.map +1 -1
- package/dist/utils/send-email.d.ts +15 -0
- package/dist/utils/send-email.d.ts.map +1 -0
- package/dist/vite/plugin.d.ts.map +1 -1
- package/dist/worker-entry/composed-worker.d.ts +13 -0
- package/dist/worker-entry/composed-worker.d.ts.map +1 -0
- package/dist/worker-entry/routes.d.ts +22 -0
- package/dist/worker-entry/routes.d.ts.map +1 -0
- package/dist/{worker-entrypoint-m9th0rg0.js → worker-entrypoint-c259fmfs.js} +1 -1
- package/package.json +21 -19
- package/dist/index.js +0 -298
- package/dist/runtime/index.js +0 -111
|
@@ -1,34 +1,62 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getRemoteModeStatus,
|
|
3
|
+
isRemoteModeActive
|
|
4
|
+
} from "./index-d8bdkx2h.js";
|
|
1
5
|
import {
|
|
2
6
|
transformWorkerEntrypoint
|
|
3
|
-
} from "./index-
|
|
7
|
+
} from "./index-wfbfz02q.js";
|
|
4
8
|
import {
|
|
5
9
|
discoverEntrypointsSync,
|
|
6
10
|
resolvePackageSpecifier
|
|
7
|
-
} from "./index-
|
|
11
|
+
} from "./index-2q3pmzrx.js";
|
|
8
12
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from "./index-
|
|
13
|
+
__clearTestContext,
|
|
14
|
+
__setTestContext
|
|
15
|
+
} from "./index-vky23txa.js";
|
|
16
|
+
import {
|
|
17
|
+
createRouteResolve,
|
|
18
|
+
invokeFetchModule,
|
|
19
|
+
matchFetchRoute,
|
|
20
|
+
resolveFetchHandler
|
|
21
|
+
} from "./index-8gtqgb3q.js";
|
|
22
|
+
import {
|
|
23
|
+
createEmailEvent,
|
|
24
|
+
createFetchEvent,
|
|
25
|
+
createQueueEvent,
|
|
26
|
+
createScheduledEvent,
|
|
27
|
+
createTailEvent,
|
|
28
|
+
runWithContext,
|
|
29
|
+
runWithEventContext
|
|
30
|
+
} from "./index-5yxg30va.js";
|
|
31
|
+
import {
|
|
32
|
+
discoverRoutes
|
|
33
|
+
} from "./index-1p814k7s.js";
|
|
34
|
+
import {
|
|
35
|
+
findDurableObjectClasses
|
|
36
|
+
} from "./index-9wt9x09k.js";
|
|
12
37
|
import {
|
|
13
38
|
DEFAULT_DO_PATTERN,
|
|
14
39
|
findFiles,
|
|
15
40
|
findFilesSync
|
|
16
41
|
} from "./index-rbht7m9r.js";
|
|
17
42
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
43
|
+
startMiniflare,
|
|
44
|
+
startMiniflareFromConfig
|
|
45
|
+
} from "./index-vs49yxn4.js";
|
|
20
46
|
import {
|
|
21
47
|
BridgeClient,
|
|
22
|
-
bridgeEnv,
|
|
23
48
|
createEnvProxy,
|
|
24
|
-
setBindingHints
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
49
|
+
setBindingHints
|
|
50
|
+
} from "./index-59df49vn.js";
|
|
51
|
+
import {
|
|
52
|
+
createLocalSendEmailBinding,
|
|
53
|
+
wrapEnvSendEmailBindings
|
|
54
|
+
} from "./index-fef08w43.js";
|
|
28
55
|
import {
|
|
29
56
|
loadConfig,
|
|
30
|
-
normalizeDOBinding
|
|
31
|
-
|
|
57
|
+
normalizeDOBinding,
|
|
58
|
+
resolveConfigPath
|
|
59
|
+
} from "./index-1phx14av.js";
|
|
32
60
|
import {
|
|
33
61
|
canProceedWithTest,
|
|
34
62
|
getApiToken,
|
|
@@ -41,7 +69,7 @@ import {
|
|
|
41
69
|
} from "./index-37x76zdn.js";
|
|
42
70
|
|
|
43
71
|
// src/test/simple-context.ts
|
|
44
|
-
import { resolve as resolve2, dirname as dirname2, join as
|
|
72
|
+
import { resolve as resolve2, dirname as dirname2, join as join7 } from "path";
|
|
45
73
|
import { existsSync as existsSync2 } from "fs";
|
|
46
74
|
|
|
47
75
|
// src/test/remote-ai.ts
|
|
@@ -242,6 +270,15 @@ var bundleCache = new Map;
|
|
|
242
270
|
function clearBundleCache() {
|
|
243
271
|
bundleCache.clear();
|
|
244
272
|
}
|
|
273
|
+
function findDefaultServiceWorkerEntrypoint(refConfigDir) {
|
|
274
|
+
for (const candidate of ["src/worker.ts", "src/worker.js"]) {
|
|
275
|
+
const absolutePath = resolve(refConfigDir, candidate);
|
|
276
|
+
if (existsSync(absolutePath)) {
|
|
277
|
+
return absolutePath;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
245
282
|
function hasServiceBindings(config) {
|
|
246
283
|
const services = config.bindings?.services;
|
|
247
284
|
if (!services)
|
|
@@ -297,24 +334,23 @@ async function resolveRefWorker(ref, _entrypoint, parentConfigDir) {
|
|
|
297
334
|
}
|
|
298
335
|
const refConfigDir = resolve(parentConfigDir, dirname(configPath));
|
|
299
336
|
const entrypoints = [];
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
const workerTsPath = resolve(refConfigDir, mainPath);
|
|
303
|
-
if (existsSync(workerTsPath)) {
|
|
304
|
-
const isWorkerTs = mainPath.endsWith("worker.ts") || mainPath.endsWith("worker.js");
|
|
337
|
+
const workerEntrypointPath = findDefaultServiceWorkerEntrypoint(refConfigDir);
|
|
338
|
+
if (workerEntrypointPath) {
|
|
305
339
|
entrypoints.push({
|
|
306
|
-
path:
|
|
340
|
+
path: workerEntrypointPath,
|
|
307
341
|
className: "Worker",
|
|
308
|
-
isWorkerTs
|
|
342
|
+
isWorkerTs: true
|
|
309
343
|
});
|
|
310
344
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
345
|
+
if (config.files?.entrypoints !== false) {
|
|
346
|
+
const discoveredEntrypoints = discoverEntrypointsSync(refConfigDir, typeof config.files?.entrypoints === "string" ? config.files.entrypoints : undefined);
|
|
347
|
+
for (const ep of discoveredEntrypoints) {
|
|
348
|
+
entrypoints.push({
|
|
349
|
+
path: ep.filePath,
|
|
350
|
+
className: ep.className,
|
|
351
|
+
isWorkerTs: false
|
|
352
|
+
});
|
|
353
|
+
}
|
|
318
354
|
}
|
|
319
355
|
if (entrypoints.length === 0) {
|
|
320
356
|
console.warn(`[devflare] Worker "${ref.name}" has no entry points`);
|
|
@@ -574,86 +610,6 @@ export default {
|
|
|
574
610
|
}
|
|
575
611
|
}
|
|
576
612
|
|
|
577
|
-
// src/runtime/context.ts
|
|
578
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
579
|
-
var storage = new AsyncLocalStorage;
|
|
580
|
-
function runWithContext(env, ctx, request, fn, type = "fetch") {
|
|
581
|
-
const context = {
|
|
582
|
-
env,
|
|
583
|
-
ctx,
|
|
584
|
-
request,
|
|
585
|
-
locals: {},
|
|
586
|
-
type
|
|
587
|
-
};
|
|
588
|
-
return storage.run(context, fn);
|
|
589
|
-
}
|
|
590
|
-
function getContextOrNull() {
|
|
591
|
-
const context = storage.getStore();
|
|
592
|
-
return context ?? null;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
// src/env.ts
|
|
596
|
-
var testContextEnv = null;
|
|
597
|
-
var testContextDispose = null;
|
|
598
|
-
function __setTestContext(envBindings, dispose) {
|
|
599
|
-
testContextEnv = envBindings;
|
|
600
|
-
testContextDispose = dispose;
|
|
601
|
-
}
|
|
602
|
-
function __clearTestContext() {
|
|
603
|
-
testContextEnv = null;
|
|
604
|
-
testContextDispose = null;
|
|
605
|
-
}
|
|
606
|
-
var env = new Proxy({}, {
|
|
607
|
-
get(_target, prop) {
|
|
608
|
-
if (prop === "dispose") {
|
|
609
|
-
return async () => {
|
|
610
|
-
if (testContextDispose) {
|
|
611
|
-
await testContextDispose();
|
|
612
|
-
__clearTestContext();
|
|
613
|
-
}
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
const ctx = getContextOrNull();
|
|
617
|
-
if (ctx?.env) {
|
|
618
|
-
return ctx.env[prop];
|
|
619
|
-
}
|
|
620
|
-
if (testContextEnv) {
|
|
621
|
-
return testContextEnv[prop];
|
|
622
|
-
}
|
|
623
|
-
return bridgeEnv[prop];
|
|
624
|
-
},
|
|
625
|
-
has(_target, prop) {
|
|
626
|
-
if (prop === "dispose")
|
|
627
|
-
return true;
|
|
628
|
-
const ctx = getContextOrNull();
|
|
629
|
-
if (ctx?.env) {
|
|
630
|
-
return prop in ctx.env;
|
|
631
|
-
}
|
|
632
|
-
if (testContextEnv) {
|
|
633
|
-
return prop in testContextEnv;
|
|
634
|
-
}
|
|
635
|
-
return prop in bridgeEnv;
|
|
636
|
-
},
|
|
637
|
-
ownKeys(_target) {
|
|
638
|
-
const ctx = getContextOrNull();
|
|
639
|
-
if (ctx?.env) {
|
|
640
|
-
return Reflect.ownKeys(ctx.env);
|
|
641
|
-
}
|
|
642
|
-
if (testContextEnv) {
|
|
643
|
-
return Reflect.ownKeys(testContextEnv);
|
|
644
|
-
}
|
|
645
|
-
return Reflect.ownKeys(bridgeEnv);
|
|
646
|
-
},
|
|
647
|
-
getOwnPropertyDescriptor(_target, prop) {
|
|
648
|
-
if (prop === "dispose") {
|
|
649
|
-
return { configurable: true, enumerable: false, writable: false };
|
|
650
|
-
}
|
|
651
|
-
const ctx = getContextOrNull();
|
|
652
|
-
const source = ctx?.env ?? testContextEnv ?? bridgeEnv;
|
|
653
|
-
return Reflect.getOwnPropertyDescriptor(source, prop);
|
|
654
|
-
}
|
|
655
|
-
});
|
|
656
|
-
|
|
657
613
|
// src/test/queue.ts
|
|
658
614
|
import { join as join2 } from "path";
|
|
659
615
|
var queueHandlerPath = null;
|
|
@@ -721,8 +677,8 @@ async function trigger(messages) {
|
|
|
721
677
|
const queueHandler = handlerModule.default ?? handlerModule.queue;
|
|
722
678
|
if (typeof queueHandler !== "function") {
|
|
723
679
|
throw new Error(`Queue handler at "${queueHandlerPath}" must export a default function or named "queue" export.
|
|
724
|
-
Expected: export
|
|
725
|
-
|
|
680
|
+
Expected: export async function queue(event) { ... }
|
|
681
|
+
Legacy compatibility is still supported for queue(batch, env, ctx).`);
|
|
726
682
|
}
|
|
727
683
|
const normalizedMessages = messages.map((msg) => {
|
|
728
684
|
if (typeof msg === "object" && msg !== null && "body" in msg) {
|
|
@@ -740,8 +696,9 @@ Or: export async function queue(batch, env, ctx) { ... }`);
|
|
|
740
696
|
passThroughOnException() {},
|
|
741
697
|
props: {}
|
|
742
698
|
};
|
|
743
|
-
const
|
|
744
|
-
|
|
699
|
+
const env = testEnvGetter();
|
|
700
|
+
const queueEvent = createQueueEvent(batch, env, ctx);
|
|
701
|
+
await runWithEventContext(queueEvent, () => queueHandler(queueEvent, env, ctx));
|
|
745
702
|
await Promise.all(waitUntilPromises);
|
|
746
703
|
const acked = [];
|
|
747
704
|
const retried = [];
|
|
@@ -804,8 +761,8 @@ async function trigger2(cronOrOptions) {
|
|
|
804
761
|
const scheduledHandler = handlerModule.default ?? handlerModule.scheduled;
|
|
805
762
|
if (typeof scheduledHandler !== "function") {
|
|
806
763
|
throw new Error(`Scheduled handler at "${scheduledHandlerPath}" must export a default function or named "scheduled" export.
|
|
807
|
-
Expected: export
|
|
808
|
-
|
|
764
|
+
Expected: export async function scheduled(event) { ... }
|
|
765
|
+
Legacy compatibility is still supported for scheduled(controller, env, ctx).`);
|
|
809
766
|
}
|
|
810
767
|
const controller = {
|
|
811
768
|
scheduledTime,
|
|
@@ -820,9 +777,10 @@ Or: export async function scheduled(controller, env, ctx) { ... }`);
|
|
|
820
777
|
passThroughOnException() {},
|
|
821
778
|
props: {}
|
|
822
779
|
};
|
|
823
|
-
const
|
|
780
|
+
const env = testEnvGetter2();
|
|
781
|
+
const scheduledEvent = createScheduledEvent(controller, env, ctx);
|
|
824
782
|
try {
|
|
825
|
-
await scheduledHandler(
|
|
783
|
+
await runWithEventContext(scheduledEvent, () => scheduledHandler(scheduledEvent, env, ctx));
|
|
826
784
|
await Promise.all(waitUntilPromises);
|
|
827
785
|
return {
|
|
828
786
|
success: true,
|
|
@@ -847,23 +805,28 @@ import { join as join4 } from "path";
|
|
|
847
805
|
var fetchHandlerPath = null;
|
|
848
806
|
var configDir3 = null;
|
|
849
807
|
var testEnvGetter3 = null;
|
|
808
|
+
var fileRoutes = [];
|
|
850
809
|
function configureWorker(options) {
|
|
851
810
|
fetchHandlerPath = options.handlerPath;
|
|
811
|
+
fileRoutes = options.routes ?? [];
|
|
852
812
|
configDir3 = options.configDir;
|
|
853
813
|
testEnvGetter3 = options.getEnv;
|
|
854
814
|
}
|
|
855
815
|
function resetWorkerState() {
|
|
856
816
|
fetchHandlerPath = null;
|
|
817
|
+
fileRoutes = [];
|
|
857
818
|
configDir3 = null;
|
|
858
819
|
testEnvGetter3 = null;
|
|
859
820
|
}
|
|
860
821
|
async function fetch2(request, options) {
|
|
861
|
-
if (!fetchHandlerPath) {
|
|
862
|
-
throw new Error("Fetch handler not configured. Make sure your devflare.config.ts has files.fetch set, " + "and the
|
|
822
|
+
if (!fetchHandlerPath && fileRoutes.length === 0) {
|
|
823
|
+
throw new Error("Fetch handler not configured. Make sure your devflare.config.ts has files.fetch set or a routes directory is available, " + "and that the corresponding files exist (defaults: src/fetch.ts and src/routes/**).");
|
|
863
824
|
}
|
|
864
825
|
if (!configDir3 || !testEnvGetter3) {
|
|
865
826
|
throw new Error("Worker helper not initialized. Call createTestContext() before using cf.worker.fetch()");
|
|
866
827
|
}
|
|
828
|
+
const workerConfigDir = configDir3;
|
|
829
|
+
const getEnv = testEnvGetter3;
|
|
867
830
|
let req;
|
|
868
831
|
if (typeof request === "string") {
|
|
869
832
|
const url = request.startsWith("http") ? request : `http://localhost${request.startsWith("/") ? "" : "/"}${request}`;
|
|
@@ -887,13 +850,24 @@ async function fetch2(request, options) {
|
|
|
887
850
|
} else {
|
|
888
851
|
req = request;
|
|
889
852
|
}
|
|
890
|
-
const
|
|
891
|
-
const
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
853
|
+
const handlerModule = fetchHandlerPath ? await import(join4(workerConfigDir, fetchHandlerPath)) : {};
|
|
854
|
+
const routeModules = await Promise.all(fileRoutes.map(async (route) => {
|
|
855
|
+
return {
|
|
856
|
+
...route,
|
|
857
|
+
module: await import(join4(workerConfigDir, route.filePath))
|
|
858
|
+
};
|
|
859
|
+
}));
|
|
860
|
+
const methodExports = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "ALL"];
|
|
861
|
+
const hasMethodHandler = methodExports.some((method) => {
|
|
862
|
+
return typeof handlerModule[method] === "function" || typeof handlerModule.default?.[method] === "function";
|
|
863
|
+
});
|
|
864
|
+
if (!resolveFetchHandler(handlerModule) && !hasMethodHandler && routeModules.length === 0) {
|
|
865
|
+
throw new Error(`Fetch handler at "${fetchHandlerPath}" must export one of:
|
|
866
|
+
- request-wide "handle" middleware
|
|
867
|
+
- named "fetch"
|
|
868
|
+
- default fetch handler
|
|
869
|
+
- HTTP method exports such as "GET" or "POST"
|
|
870
|
+
Legacy compatibility is still supported for fetch(request, env, ctx).`);
|
|
897
871
|
}
|
|
898
872
|
const waitUntilPromises = [];
|
|
899
873
|
const ctx = {
|
|
@@ -903,8 +877,12 @@ Or: export async function fetch(request, env, ctx) { ... }`);
|
|
|
903
877
|
passThroughOnException() {},
|
|
904
878
|
props: {}
|
|
905
879
|
};
|
|
906
|
-
const
|
|
907
|
-
const
|
|
880
|
+
const env = getEnv();
|
|
881
|
+
const initialRouteMatch = routeModules.length > 0 ? matchFetchRoute(routeModules, req) : null;
|
|
882
|
+
const fetchEvent = createFetchEvent(req, env, ctx, {
|
|
883
|
+
params: initialRouteMatch?.params ?? {}
|
|
884
|
+
});
|
|
885
|
+
const response = await runWithEventContext(fetchEvent, () => invokeFetchModule(handlerModule, fetchEvent, routeModules.length > 0 ? createRouteResolve(routeModules, fetchEvent) : undefined));
|
|
908
886
|
return response;
|
|
909
887
|
}
|
|
910
888
|
async function get(path, headers) {
|
|
@@ -967,7 +945,7 @@ function createTraceItem(options) {
|
|
|
967
945
|
}
|
|
968
946
|
async function trigger3(items) {
|
|
969
947
|
if (!tailHandlerPath) {
|
|
970
|
-
throw new Error("Tail handler not configured.
|
|
948
|
+
throw new Error("Tail handler not configured. Add a src/tail.ts file exporting tail(), " + "or configure a tail handler before calling cf.tail.trigger().");
|
|
971
949
|
}
|
|
972
950
|
if (!configDir4 || !testEnvGetter4) {
|
|
973
951
|
throw new Error("Tail helper not initialized. Call createTestContext() before using cf.tail.trigger()");
|
|
@@ -983,8 +961,8 @@ async function trigger3(items) {
|
|
|
983
961
|
const tailHandler = handlerModule.default ?? handlerModule.tail;
|
|
984
962
|
if (typeof tailHandler !== "function") {
|
|
985
963
|
throw new Error(`Tail handler at "${tailHandlerPath}" must export a default function or named "tail" export.
|
|
986
|
-
Expected: export
|
|
987
|
-
|
|
964
|
+
Expected: export async function tail(event) { ... }
|
|
965
|
+
Legacy compatibility is still supported for tail(events, env, ctx).`);
|
|
988
966
|
}
|
|
989
967
|
const waitUntilPromises = [];
|
|
990
968
|
const ctx = {
|
|
@@ -994,9 +972,10 @@ Or: export async function tail(events, env, ctx) { ... }`);
|
|
|
994
972
|
passThroughOnException() {},
|
|
995
973
|
props: {}
|
|
996
974
|
};
|
|
997
|
-
const
|
|
975
|
+
const env = testEnvGetter4();
|
|
976
|
+
const tailEvent = createTailEvent(traceItems, env, ctx);
|
|
998
977
|
try {
|
|
999
|
-
await tailHandler(
|
|
978
|
+
await runWithEventContext(tailEvent, () => tailHandler(tailEvent, env, ctx));
|
|
1000
979
|
await Promise.all(waitUntilPromises);
|
|
1001
980
|
return {
|
|
1002
981
|
success: true,
|
|
@@ -1019,13 +998,20 @@ var tail = {
|
|
|
1019
998
|
};
|
|
1020
999
|
|
|
1021
1000
|
// src/test/email.ts
|
|
1001
|
+
import { join as join6 } from "path";
|
|
1022
1002
|
var miniflarePort = 8787;
|
|
1023
1003
|
var emailListeners = [];
|
|
1024
1004
|
var sentEmails = [];
|
|
1005
|
+
var emailHandlerPath = null;
|
|
1006
|
+
var configDir5 = null;
|
|
1007
|
+
var testEnvGetter5 = null;
|
|
1025
1008
|
function configureEmail(options = {}) {
|
|
1026
1009
|
if (options.port) {
|
|
1027
1010
|
miniflarePort = options.port;
|
|
1028
1011
|
}
|
|
1012
|
+
emailHandlerPath = options.handlerPath ?? emailHandlerPath;
|
|
1013
|
+
configDir5 = options.configDir ?? configDir5;
|
|
1014
|
+
testEnvGetter5 = options.getEnv ?? testEnvGetter5;
|
|
1029
1015
|
}
|
|
1030
1016
|
function buildRawEmail(options) {
|
|
1031
1017
|
if (options.raw) {
|
|
@@ -1053,8 +1039,103 @@ function buildRawEmail(options) {
|
|
|
1053
1039
|
return lines.join(`\r
|
|
1054
1040
|
`);
|
|
1055
1041
|
}
|
|
1042
|
+
function createEmailHeaders(rawEmail) {
|
|
1043
|
+
const headers = new Headers;
|
|
1044
|
+
const lines = rawEmail.split(/\r?\n/);
|
|
1045
|
+
for (const line of lines) {
|
|
1046
|
+
if (!line.trim()) {
|
|
1047
|
+
break;
|
|
1048
|
+
}
|
|
1049
|
+
const colonIndex = line.indexOf(":");
|
|
1050
|
+
if (colonIndex <= 0) {
|
|
1051
|
+
continue;
|
|
1052
|
+
}
|
|
1053
|
+
headers.append(line.slice(0, colonIndex).trim(), line.slice(colonIndex + 1).trim());
|
|
1054
|
+
}
|
|
1055
|
+
return headers;
|
|
1056
|
+
}
|
|
1057
|
+
function createRawEmailStream(rawEmail) {
|
|
1058
|
+
return new ReadableStream({
|
|
1059
|
+
start(controller) {
|
|
1060
|
+
controller.enqueue(new TextEncoder().encode(rawEmail));
|
|
1061
|
+
controller.close();
|
|
1062
|
+
}
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
function resolveEmailHandler(module) {
|
|
1066
|
+
if (typeof module.default === "function") {
|
|
1067
|
+
return module.default;
|
|
1068
|
+
}
|
|
1069
|
+
if (module.default && typeof module.default.email === "function") {
|
|
1070
|
+
return module.default.email.bind(module.default);
|
|
1071
|
+
}
|
|
1072
|
+
if (typeof module.email === "function") {
|
|
1073
|
+
return module.email;
|
|
1074
|
+
}
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
function getRecordedRawContent(raw) {
|
|
1078
|
+
if (typeof raw === "string") {
|
|
1079
|
+
return raw;
|
|
1080
|
+
}
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1056
1083
|
async function send2(options) {
|
|
1057
1084
|
const raw = buildRawEmail(options);
|
|
1085
|
+
if (emailHandlerPath && configDir5 && testEnvGetter5) {
|
|
1086
|
+
const absolutePath = join6(configDir5, emailHandlerPath);
|
|
1087
|
+
const handlerModule = await import(absolutePath);
|
|
1088
|
+
const emailHandler = resolveEmailHandler(handlerModule);
|
|
1089
|
+
if (!emailHandler) {
|
|
1090
|
+
throw new Error(`Email handler at "${emailHandlerPath}" must export a default function or named "email" export.
|
|
1091
|
+
Expected: export async function email(message) { ... }
|
|
1092
|
+
Legacy compatibility is still supported for email(message, env, ctx).`);
|
|
1093
|
+
}
|
|
1094
|
+
const waitUntilPromises = [];
|
|
1095
|
+
const ctx = {
|
|
1096
|
+
waitUntil(promise) {
|
|
1097
|
+
waitUntilPromises.push(promise);
|
|
1098
|
+
},
|
|
1099
|
+
passThroughOnException() {},
|
|
1100
|
+
props: {}
|
|
1101
|
+
};
|
|
1102
|
+
const runtimeEnv = testEnvGetter5();
|
|
1103
|
+
const timestamp = new Date;
|
|
1104
|
+
const message = {
|
|
1105
|
+
from: options.from,
|
|
1106
|
+
to: options.to,
|
|
1107
|
+
headers: createEmailHeaders(raw),
|
|
1108
|
+
raw: createRawEmailStream(raw),
|
|
1109
|
+
rawSize: raw.length,
|
|
1110
|
+
setReject(reason) {
|
|
1111
|
+
throw new Error(`Email rejected: ${reason}`);
|
|
1112
|
+
},
|
|
1113
|
+
async forward(rcptTo) {
|
|
1114
|
+
recordSentEmail({
|
|
1115
|
+
type: "forward",
|
|
1116
|
+
from: options.from,
|
|
1117
|
+
to: rcptTo,
|
|
1118
|
+
raw,
|
|
1119
|
+
timestamp
|
|
1120
|
+
});
|
|
1121
|
+
},
|
|
1122
|
+
async reply(message2) {
|
|
1123
|
+
recordSentEmail({
|
|
1124
|
+
type: "reply",
|
|
1125
|
+
from: message2.from ?? options.to,
|
|
1126
|
+
to: message2.to ?? options.from,
|
|
1127
|
+
raw: getRecordedRawContent(message2.raw),
|
|
1128
|
+
timestamp
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
const emailEvent = createEmailEvent(message, runtimeEnv, ctx);
|
|
1133
|
+
await runWithEventContext(emailEvent, () => emailHandler(emailEvent, runtimeEnv, ctx));
|
|
1134
|
+
await Promise.all(waitUntilPromises);
|
|
1135
|
+
return new Response(JSON.stringify({ ok: true, from: options.from, to: options.to }), {
|
|
1136
|
+
headers: { "Content-Type": "application/json" }
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1058
1139
|
const url = new URL(`http://localhost:${miniflarePort}/cdn-cgi/handler/email`);
|
|
1059
1140
|
url.searchParams.set("from", options.from);
|
|
1060
1141
|
url.searchParams.set("to", options.to);
|
|
@@ -1079,7 +1160,21 @@ function getSentEmails() {
|
|
|
1079
1160
|
function clearSentEmails() {
|
|
1080
1161
|
sentEmails = [];
|
|
1081
1162
|
}
|
|
1163
|
+
function recordSentEmail(email) {
|
|
1164
|
+
sentEmails.push(email);
|
|
1165
|
+
for (const listener of emailListeners) {
|
|
1166
|
+
try {
|
|
1167
|
+
listener(email);
|
|
1168
|
+
} catch (error) {
|
|
1169
|
+
console.error("[devflare/test] Email listener error:", error);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1082
1173
|
function resetEmailState() {
|
|
1174
|
+
miniflarePort = 8787;
|
|
1175
|
+
emailHandlerPath = null;
|
|
1176
|
+
configDir5 = null;
|
|
1177
|
+
testEnvGetter5 = null;
|
|
1083
1178
|
emailListeners = [];
|
|
1084
1179
|
sentEmails = [];
|
|
1085
1180
|
}
|
|
@@ -1134,15 +1229,12 @@ function getCallerDirectory() {
|
|
|
1134
1229
|
}
|
|
1135
1230
|
return process.cwd();
|
|
1136
1231
|
}
|
|
1137
|
-
function findNearestConfig(startDir) {
|
|
1138
|
-
const configNames = ["devflare.config.ts", "devflare.config.js"];
|
|
1232
|
+
async function findNearestConfig(startDir) {
|
|
1139
1233
|
let currentDir = startDir;
|
|
1140
1234
|
while (true) {
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
return configPath;
|
|
1145
|
-
}
|
|
1235
|
+
const configPath = await resolveConfigPath(currentDir);
|
|
1236
|
+
if (configPath) {
|
|
1237
|
+
return configPath;
|
|
1146
1238
|
}
|
|
1147
1239
|
const parentDir = dirname2(currentDir);
|
|
1148
1240
|
if (parentDir === currentDir) {
|
|
@@ -1157,16 +1249,17 @@ async function createTestContext(configPath) {
|
|
|
1157
1249
|
if (configPath) {
|
|
1158
1250
|
absolutePath = resolve2(callerDir, configPath);
|
|
1159
1251
|
} else {
|
|
1160
|
-
const found = findNearestConfig(callerDir);
|
|
1252
|
+
const found = await findNearestConfig(callerDir);
|
|
1161
1253
|
if (!found) {
|
|
1162
|
-
throw new Error(`Could not find devflare
|
|
1254
|
+
throw new Error(`Could not find a devflare config file. Searched upward from: ${callerDir}
|
|
1255
|
+
` + `Expected one of: devflare.config.ts, devflare.config.mts, devflare.config.js, devflare.config.mjs
|
|
1163
1256
|
` + `Either create a config file or provide an explicit path: createTestContext('./path/to/config.ts')`);
|
|
1164
1257
|
}
|
|
1165
1258
|
absolutePath = found;
|
|
1166
1259
|
}
|
|
1167
|
-
const
|
|
1260
|
+
const configDir6 = dirname2(absolutePath);
|
|
1168
1261
|
const config = await loadConfig({
|
|
1169
|
-
cwd:
|
|
1262
|
+
cwd: configDir6,
|
|
1170
1263
|
configFile: absolutePath.split(/[/\\]/).pop()
|
|
1171
1264
|
});
|
|
1172
1265
|
globalRemoteBindings = {};
|
|
@@ -1186,6 +1279,11 @@ async function createTestContext(configPath) {
|
|
|
1186
1279
|
globalRemoteBindings[key] = value;
|
|
1187
1280
|
}
|
|
1188
1281
|
}
|
|
1282
|
+
if (config.bindings?.sendEmail) {
|
|
1283
|
+
for (const [name, binding] of Object.entries(config.bindings.sendEmail)) {
|
|
1284
|
+
globalRemoteBindings[name] = createLocalSendEmailBinding(binding);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1189
1287
|
const hints = {};
|
|
1190
1288
|
if (config.bindings?.kv) {
|
|
1191
1289
|
for (const name of Object.keys(config.bindings.kv))
|
|
@@ -1207,18 +1305,23 @@ async function createTestContext(configPath) {
|
|
|
1207
1305
|
for (const name of Object.keys(config.bindings.services))
|
|
1208
1306
|
hints[name] = "service";
|
|
1209
1307
|
}
|
|
1308
|
+
if (config.bindings?.sendEmail) {
|
|
1309
|
+
for (const name of Object.keys(config.bindings.sendEmail))
|
|
1310
|
+
hints[name] = "sendEmail";
|
|
1311
|
+
}
|
|
1210
1312
|
const needsMultiWorkerForServices = hasServiceBindings(config);
|
|
1211
1313
|
const needsMultiWorkerForDOs = hasCrossWorkerDOs(config);
|
|
1212
1314
|
const needsMultiWorker = needsMultiWorkerForServices || needsMultiWorkerForDOs;
|
|
1213
1315
|
let serviceBindingResolution = null;
|
|
1214
1316
|
let doBindingResolution = null;
|
|
1215
1317
|
if (needsMultiWorkerForServices) {
|
|
1216
|
-
serviceBindingResolution = await resolveServiceBindings(config,
|
|
1318
|
+
serviceBindingResolution = await resolveServiceBindings(config, configDir6);
|
|
1217
1319
|
}
|
|
1218
1320
|
if (needsMultiWorkerForDOs) {
|
|
1219
|
-
doBindingResolution = await resolveDOBindings(config,
|
|
1321
|
+
doBindingResolution = await resolveDOBindings(config, configDir6);
|
|
1220
1322
|
}
|
|
1221
1323
|
const randomPort = 1e4 + Math.floor(Math.random() * 50000);
|
|
1324
|
+
const localWorkerBindings = config.vars ?? {};
|
|
1222
1325
|
const mfConfig = {
|
|
1223
1326
|
modules: true,
|
|
1224
1327
|
port: randomPort
|
|
@@ -1236,9 +1339,28 @@ async function createTestContext(configPath) {
|
|
|
1236
1339
|
}
|
|
1237
1340
|
mfConfig.queueProducers = queueProducers;
|
|
1238
1341
|
}
|
|
1342
|
+
if (Object.keys(localWorkerBindings).length > 0) {
|
|
1343
|
+
mfConfig.bindings = localWorkerBindings;
|
|
1344
|
+
}
|
|
1345
|
+
if (config.bindings?.sendEmail) {
|
|
1346
|
+
mfConfig.email = {
|
|
1347
|
+
send_email: Object.entries(config.bindings.sendEmail).map(([name, binding]) => ({
|
|
1348
|
+
name,
|
|
1349
|
+
...binding.destinationAddress && {
|
|
1350
|
+
destination_address: binding.destinationAddress
|
|
1351
|
+
},
|
|
1352
|
+
...binding.allowedDestinationAddresses && {
|
|
1353
|
+
allowed_destination_addresses: binding.allowedDestinationAddresses
|
|
1354
|
+
},
|
|
1355
|
+
...binding.allowedSenderAddresses && {
|
|
1356
|
+
allowed_sender_addresses: binding.allowedSenderAddresses
|
|
1357
|
+
}
|
|
1358
|
+
}))
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1239
1361
|
const transportFile = config.files?.transport;
|
|
1240
1362
|
if (transportFile) {
|
|
1241
|
-
const transportPath =
|
|
1363
|
+
const transportPath = join7(configDir6, transportFile);
|
|
1242
1364
|
const transportModule = await import(transportPath);
|
|
1243
1365
|
if (!transportModule.transport) {
|
|
1244
1366
|
console.warn(`[devflare] Warning: Transport file "${transportFile}" does not export a named "transport" object.
|
|
@@ -1260,7 +1382,7 @@ Transport encoding/decoding will be disabled.`);
|
|
|
1260
1382
|
const doPattern = typeof doPatternConfig === "string" ? doPatternConfig : DEFAULT_DO_PATTERN;
|
|
1261
1383
|
if (doPatternConfig !== false) {
|
|
1262
1384
|
const fs = await import("fs/promises");
|
|
1263
|
-
const doFiles = await findFiles(doPattern, { cwd:
|
|
1385
|
+
const doFiles = await findFiles(doPattern, { cwd: configDir6 });
|
|
1264
1386
|
for (const filePath of doFiles) {
|
|
1265
1387
|
try {
|
|
1266
1388
|
const code = await fs.readFile(filePath, "utf-8");
|
|
@@ -1278,7 +1400,7 @@ Transport encoding/decoding will be disabled.`);
|
|
|
1278
1400
|
}
|
|
1279
1401
|
let scriptPath;
|
|
1280
1402
|
if (doInfo.scriptName) {
|
|
1281
|
-
scriptPath =
|
|
1403
|
+
scriptPath = join7(configDir6, "src", doInfo.scriptName);
|
|
1282
1404
|
} else {
|
|
1283
1405
|
const discoveredPath = classToFilePath.get(doInfo.className);
|
|
1284
1406
|
if (!discoveredPath) {
|
|
@@ -1295,7 +1417,7 @@ Either:
|
|
|
1295
1417
|
const virtualImports = [];
|
|
1296
1418
|
const virtualExports = [];
|
|
1297
1419
|
if (transportFile) {
|
|
1298
|
-
const transportPath =
|
|
1420
|
+
const transportPath = join7(configDir6, transportFile);
|
|
1299
1421
|
virtualImports.push(`import { transport } from '${transportPath.replace(/\\/g, "/")}'`);
|
|
1300
1422
|
virtualExports.push("export { transport }");
|
|
1301
1423
|
}
|
|
@@ -1307,7 +1429,7 @@ Either:
|
|
|
1307
1429
|
if (virtualImports.length > 0) {
|
|
1308
1430
|
const virtualEntry = [...virtualImports, "", ...virtualExports].join(`
|
|
1309
1431
|
`);
|
|
1310
|
-
const virtualPath =
|
|
1432
|
+
const virtualPath = join7(configDir6, ".devflare", "__test_entry.ts");
|
|
1311
1433
|
const { writeFileSync, mkdirSync } = await import("fs");
|
|
1312
1434
|
mkdirSync(dirname2(virtualPath), { recursive: true });
|
|
1313
1435
|
writeFileSync(virtualPath, virtualEntry);
|
|
@@ -1348,6 +1470,7 @@ Either:
|
|
|
1348
1470
|
...mfConfig.kvNamespaces && { kvNamespaces: mfConfig.kvNamespaces },
|
|
1349
1471
|
...mfConfig.r2Buckets && { r2Buckets: mfConfig.r2Buckets },
|
|
1350
1472
|
...mfConfig.d1Databases && { d1Databases: mfConfig.d1Databases },
|
|
1473
|
+
...mfConfig.email && { email: mfConfig.email },
|
|
1351
1474
|
...Object.keys(primaryDurableObjects).length > 0 && { durableObjects: primaryDurableObjects },
|
|
1352
1475
|
...serviceBindingResolution?.primaryServiceBindings && { serviceBindings: serviceBindingResolution.primaryServiceBindings }
|
|
1353
1476
|
};
|
|
@@ -1381,7 +1504,7 @@ Either:
|
|
|
1381
1504
|
const { Miniflare } = await import("miniflare");
|
|
1382
1505
|
globalMiniflare = new Miniflare(mfConfig);
|
|
1383
1506
|
await globalMiniflare.ready;
|
|
1384
|
-
globalMiniflareBindings = await globalMiniflare.getBindings();
|
|
1507
|
+
globalMiniflareBindings = wrapEnvSendEmailBindings(await globalMiniflare.getBindings());
|
|
1385
1508
|
const disposeContext = async () => {
|
|
1386
1509
|
if (globalClient) {
|
|
1387
1510
|
await globalClient.disconnect();
|
|
@@ -1403,26 +1526,42 @@ Either:
|
|
|
1403
1526
|
__clearTestContext();
|
|
1404
1527
|
};
|
|
1405
1528
|
const getTestEnv = () => {
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1529
|
+
return new Proxy({}, {
|
|
1530
|
+
get(_, prop) {
|
|
1531
|
+
if (globalRemoteBindings && prop in globalRemoteBindings) {
|
|
1532
|
+
return globalRemoteBindings[prop];
|
|
1533
|
+
}
|
|
1534
|
+
if (hints[prop] === "sendEmail" && globalEnvProxy && prop in globalEnvProxy) {
|
|
1535
|
+
return globalEnvProxy[prop];
|
|
1536
|
+
}
|
|
1537
|
+
if (globalMiniflareBindings && prop in globalMiniflareBindings) {
|
|
1538
|
+
return globalMiniflareBindings[prop];
|
|
1539
|
+
}
|
|
1540
|
+
if (globalEnvProxy && prop in globalEnvProxy) {
|
|
1541
|
+
return globalEnvProxy[prop];
|
|
1542
|
+
}
|
|
1543
|
+
return;
|
|
1544
|
+
},
|
|
1545
|
+
has(_, prop) {
|
|
1546
|
+
return Boolean(globalRemoteBindings && prop in globalRemoteBindings || globalMiniflareBindings && prop in globalMiniflareBindings || globalEnvProxy && prop in globalEnvProxy);
|
|
1547
|
+
}
|
|
1548
|
+
});
|
|
1413
1549
|
};
|
|
1414
1550
|
const queuePath = config.files?.queue;
|
|
1415
1551
|
const scheduledPath = config.files?.scheduled;
|
|
1416
1552
|
const fetchPath = config.files?.fetch;
|
|
1553
|
+
const emailPath = config.files?.email;
|
|
1417
1554
|
const DEFAULT_FETCH_PATH = "src/fetch.ts";
|
|
1418
1555
|
const DEFAULT_QUEUE_PATH = "src/queue.ts";
|
|
1419
1556
|
const DEFAULT_SCHEDULED_PATH = "src/scheduled.ts";
|
|
1557
|
+
const DEFAULT_EMAIL_PATH = "src/email.ts";
|
|
1558
|
+
const DEFAULT_TAIL_PATH = "src/tail.ts";
|
|
1420
1559
|
const resolvePath = async (configValue, defaultPath) => {
|
|
1421
1560
|
if (typeof configValue === "string")
|
|
1422
1561
|
return configValue;
|
|
1423
1562
|
if (configValue === false)
|
|
1424
1563
|
return null;
|
|
1425
|
-
const defaultAbsolute =
|
|
1564
|
+
const defaultAbsolute = join7(configDir6, defaultPath);
|
|
1426
1565
|
try {
|
|
1427
1566
|
const fs = await import("fs/promises");
|
|
1428
1567
|
await fs.access(defaultAbsolute);
|
|
@@ -1431,32 +1570,45 @@ Either:
|
|
|
1431
1570
|
return null;
|
|
1432
1571
|
}
|
|
1433
1572
|
};
|
|
1434
|
-
const [resolvedFetchPath, resolvedQueuePath, resolvedScheduledPath] = await Promise.all([
|
|
1573
|
+
const [resolvedFetchPath, resolvedQueuePath, resolvedScheduledPath, resolvedEmailPath, resolvedTailPath, resolvedRoutes] = await Promise.all([
|
|
1435
1574
|
resolvePath(fetchPath, DEFAULT_FETCH_PATH),
|
|
1436
1575
|
resolvePath(queuePath, DEFAULT_QUEUE_PATH),
|
|
1437
|
-
resolvePath(scheduledPath, DEFAULT_SCHEDULED_PATH)
|
|
1576
|
+
resolvePath(scheduledPath, DEFAULT_SCHEDULED_PATH),
|
|
1577
|
+
resolvePath(emailPath, DEFAULT_EMAIL_PATH),
|
|
1578
|
+
resolvePath(undefined, DEFAULT_TAIL_PATH),
|
|
1579
|
+
discoverRoutes(configDir6, config)
|
|
1438
1580
|
]);
|
|
1439
1581
|
configureQueue({
|
|
1440
1582
|
handlerPath: resolvedQueuePath,
|
|
1441
|
-
configDir:
|
|
1583
|
+
configDir: configDir6,
|
|
1442
1584
|
getEnv: getTestEnv
|
|
1443
1585
|
});
|
|
1444
1586
|
configureScheduled({
|
|
1445
1587
|
handlerPath: resolvedScheduledPath,
|
|
1446
|
-
configDir:
|
|
1588
|
+
configDir: configDir6,
|
|
1447
1589
|
getEnv: getTestEnv
|
|
1448
1590
|
});
|
|
1449
1591
|
configureWorker({
|
|
1450
1592
|
handlerPath: resolvedFetchPath,
|
|
1451
|
-
|
|
1593
|
+
routes: resolvedRoutes?.routes.map((route) => ({
|
|
1594
|
+
filePath: route.filePath,
|
|
1595
|
+
routePath: route.routePath,
|
|
1596
|
+
segments: route.segments
|
|
1597
|
+
})) ?? [],
|
|
1598
|
+
configDir: configDir6,
|
|
1452
1599
|
getEnv: getTestEnv
|
|
1453
1600
|
});
|
|
1454
1601
|
configureTail({
|
|
1455
|
-
handlerPath:
|
|
1456
|
-
configDir:
|
|
1602
|
+
handlerPath: resolvedTailPath,
|
|
1603
|
+
configDir: configDir6,
|
|
1604
|
+
getEnv: getTestEnv
|
|
1605
|
+
});
|
|
1606
|
+
configureEmail({
|
|
1607
|
+
port: randomPort,
|
|
1608
|
+
handlerPath: resolvedEmailPath,
|
|
1609
|
+
configDir: configDir6,
|
|
1457
1610
|
getEnv: getTestEnv
|
|
1458
1611
|
});
|
|
1459
|
-
configureEmail({ port: randomPort });
|
|
1460
1612
|
if (hasMultiWorkerServices || hasMultiWorkerDOs) {
|
|
1461
1613
|
setBindingHints(hints);
|
|
1462
1614
|
const envAccessor2 = new Proxy({}, {
|
|
@@ -1490,6 +1642,9 @@ Either:
|
|
|
1490
1642
|
if (globalRemoteBindings && prop in globalRemoteBindings) {
|
|
1491
1643
|
return globalRemoteBindings[prop];
|
|
1492
1644
|
}
|
|
1645
|
+
if (hints[prop] === "sendEmail" && globalEnvProxy && prop in globalEnvProxy) {
|
|
1646
|
+
return globalEnvProxy[prop];
|
|
1647
|
+
}
|
|
1493
1648
|
if (globalMiniflareBindings && prop in globalMiniflareBindings) {
|
|
1494
1649
|
return globalMiniflareBindings[prop];
|
|
1495
1650
|
}
|
|
@@ -1588,6 +1743,7 @@ async function executeRpc(env, method, params) {
|
|
|
1588
1743
|
const [bindingName, ...rest] = method.split('.')
|
|
1589
1744
|
const op = rest.join('.')
|
|
1590
1745
|
const binding = env[bindingName]
|
|
1746
|
+
const RAW_EMAIL = 'EmailMessage::raw'
|
|
1591
1747
|
if (!binding) throw new Error('Binding not found: ' + bindingName)
|
|
1592
1748
|
|
|
1593
1749
|
// KV operations
|
|
@@ -1619,6 +1775,11 @@ async function executeRpc(env, method, params) {
|
|
|
1619
1775
|
if (op === 'prepare.first') return binding.prepare(params[0]).bind(...(params[1] || [])).first(params[2])
|
|
1620
1776
|
if (op === 'prepare.raw') return binding.prepare(params[0]).bind(...(params[1] || [])).raw({ columnNames: params[2] })
|
|
1621
1777
|
|
|
1778
|
+
// Send email operations
|
|
1779
|
+
if (op === 'email.send') {
|
|
1780
|
+
return binding.send(__normalizeEmailMessage(params[0]))
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1622
1783
|
// DO operations
|
|
1623
1784
|
if (op === 'idFromName') {
|
|
1624
1785
|
return { __type: 'DOId', hex: binding.idFromName(params[0]).toString() }
|
|
@@ -1637,6 +1798,63 @@ async function executeRpc(env, method, params) {
|
|
|
1637
1798
|
|
|
1638
1799
|
throw new Error('Unknown operation: ' + method)
|
|
1639
1800
|
}
|
|
1801
|
+
|
|
1802
|
+
function __createEmailMessageRaw(raw) {
|
|
1803
|
+
if (typeof raw === 'string' || raw instanceof ReadableStream) {
|
|
1804
|
+
return raw
|
|
1805
|
+
}
|
|
1806
|
+
if (raw instanceof Uint8Array || raw instanceof ArrayBuffer) {
|
|
1807
|
+
return new Response(raw).body
|
|
1808
|
+
}
|
|
1809
|
+
throw new Error('Unsupported EmailMessage raw payload')
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
function __buildRawEmail(message) {
|
|
1813
|
+
const lines = []
|
|
1814
|
+
const messageId = '<' + Date.now() + '-' + Math.random().toString(36).slice(2) + '@devflare.dev>'
|
|
1815
|
+
|
|
1816
|
+
lines.push('From: ' + message.from)
|
|
1817
|
+
lines.push('To: ' + (Array.isArray(message.to) ? message.to.join(', ') : message.to))
|
|
1818
|
+
lines.push('Date: ' + new Date().toUTCString())
|
|
1819
|
+
lines.push('Message-ID: ' + messageId)
|
|
1820
|
+
|
|
1821
|
+
if (message.subject) lines.push('Subject: ' + message.subject)
|
|
1822
|
+
if (message.replyTo) lines.push('Reply-To: ' + String(message.replyTo))
|
|
1823
|
+
if (message.cc) lines.push('Cc: ' + (Array.isArray(message.cc) ? message.cc.join(', ') : message.cc))
|
|
1824
|
+
if (message.bcc) lines.push('Bcc: ' + (Array.isArray(message.bcc) ? message.bcc.join(', ') : message.bcc))
|
|
1825
|
+
|
|
1826
|
+
for (const [key, value] of Object.entries(message.headers || {})) {
|
|
1827
|
+
lines.push(key + ': ' + value)
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
lines.push('MIME-Version: 1.0')
|
|
1831
|
+
lines.push('Content-Type: ' + (message.html ? 'text/html' : 'text/plain') + '; charset=UTF-8')
|
|
1832
|
+
lines.push('')
|
|
1833
|
+
lines.push(String(message.html ?? message.text ?? '').replace(/\\r?\\n/g, '\\r\\n'))
|
|
1834
|
+
|
|
1835
|
+
return lines.join('\\r\\n')
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
function __normalizeEmailMessage(message) {
|
|
1839
|
+
if (!message || typeof message !== 'object' || !('from' in message) || !('to' in message)) {
|
|
1840
|
+
return message
|
|
1841
|
+
}
|
|
1842
|
+
if ('EmailMessage::raw' in message) {
|
|
1843
|
+
return message
|
|
1844
|
+
}
|
|
1845
|
+
if ('raw' in message && message.raw !== undefined) {
|
|
1846
|
+
return {
|
|
1847
|
+
from: message.from,
|
|
1848
|
+
to: message.to,
|
|
1849
|
+
[RAW_EMAIL]: __createEmailMessageRaw(message.raw)
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
return {
|
|
1853
|
+
from: message.from,
|
|
1854
|
+
to: message.to,
|
|
1855
|
+
[RAW_EMAIL]: __createEmailMessageRaw(__buildRawEmail(message))
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1640
1858
|
`;
|
|
1641
1859
|
}
|
|
1642
1860
|
// src/test/cf.ts
|
|
@@ -2146,7 +2364,7 @@ async function createBridgeTestContext(options = {}) {
|
|
|
2146
2364
|
verbose: options.verbose ?? false
|
|
2147
2365
|
});
|
|
2148
2366
|
}
|
|
2149
|
-
const bindings = await miniflare.getBindings();
|
|
2367
|
+
const bindings = wrapEnvSendEmailBindings(await miniflare.getBindings());
|
|
2150
2368
|
if (config?.bindings) {
|
|
2151
2369
|
const hints = {};
|
|
2152
2370
|
if (config.bindings.kv) {
|
|
@@ -2176,6 +2394,11 @@ async function createBridgeTestContext(options = {}) {
|
|
|
2176
2394
|
}
|
|
2177
2395
|
if (config.bindings.ai)
|
|
2178
2396
|
hints[config.bindings.ai.binding] = "ai";
|
|
2397
|
+
if (config.bindings.sendEmail) {
|
|
2398
|
+
Object.keys(config.bindings.sendEmail).forEach((name) => {
|
|
2399
|
+
hints[name] = "sendEmail";
|
|
2400
|
+
});
|
|
2401
|
+
}
|
|
2179
2402
|
setBindingHints(hints);
|
|
2180
2403
|
}
|
|
2181
2404
|
const ctx = {
|
|
@@ -2222,4 +2445,4 @@ var testEnv = new Proxy({}, {
|
|
|
2222
2445
|
function isKVNamespace(binding) {
|
|
2223
2446
|
return typeof binding === "object" && binding !== null && "get" in binding && "put" in binding && "delete" in binding && "list" in binding;
|
|
2224
2447
|
}
|
|
2225
|
-
export {
|
|
2448
|
+
export { clearBundleCache, hasServiceBindings, resolveServiceBindings, hasCrossWorkerDOs, resolveDOBindings, queue, scheduled, worker, tail, email, createTestContext, cf, createMultiWorkerContext, createEntrypointScript, isRemoteModeEnabled, shouldSkip, createMockTestContext, withTestContext, createMockKV, createMockD1, createMockR2, createMockQueue, createMockEnv, createBridgeTestContext, stopBridgeTestContext, getBridgeTestContext, testEnv };
|