devflare 1.0.0-next.1 → 1.0.0-next.11
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 +1424 -610
- package/R2.md +200 -0
- package/README.md +302 -505
- 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-ezksv2dd.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 +2594 -1234
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/{deploy-nhceck39.js → deploy-jdpy21t6.js} +33 -15
- package/dist/{dev-qnxet3j9.js → dev-9mq7zhww.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-z4ffybce.js} +73 -50
- package/dist/{durable-object-t4kbb0yt.js → durable-object-yt8v1dyn.js} +1 -1
- package/dist/index-1p814k7s.js +227 -0
- package/dist/{index-tk6ej9dj.js → index-2q3pmzrx.js} +12 -16
- package/dist/{index-67qcae0f.js → index-51s1hkw4.js} +16 -1
- package/dist/{index-ep3445yc.js → index-53xcakh8.js} +414 -171
- 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-8gtqgb3q.js +529 -0
- package/dist/{index-gz1gndna.js → index-9wt9x09k.js} +42 -62
- package/dist/index-dr6sbp8d.js +39 -0
- package/dist/index-fef08w43.js +231 -0
- package/dist/index-k7r18na8.js +0 -0
- package/dist/{index-m2q41jwa.js → index-n932ytmq.js} +9 -1
- package/dist/{index-07q6yxyc.js → index-v8vvsn9x.js} +1 -0
- package/dist/index-vky23txa.js +70 -0
- package/dist/{index-z14anrqp.js → index-wfbfz02q.js} +14 -15
- package/dist/index-ws68xvq2.js +311 -0
- package/dist/{index-hcex3rgh.js → index-wyf3s77s.js} +85 -8
- package/dist/index-xqfbd9fx.js +195 -0
- package/dist/index-xxwbb2nt.js +322 -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-nq5acrwh.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-xxwbb2nt.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-wyf3s77s.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
|
}
|
|
@@ -1113,6 +1208,12 @@ var globalEnvProxy = null;
|
|
|
1113
1208
|
var globalTransportDecode = null;
|
|
1114
1209
|
var globalRemoteBindings = null;
|
|
1115
1210
|
var globalMiniflareBindings = null;
|
|
1211
|
+
var DEFAULT_TRANSPORT_ENTRY_FILES = [
|
|
1212
|
+
"src/transport.ts",
|
|
1213
|
+
"src/transport.js",
|
|
1214
|
+
"src/transport.mts",
|
|
1215
|
+
"src/transport.mjs"
|
|
1216
|
+
];
|
|
1116
1217
|
function getCallerDirectory() {
|
|
1117
1218
|
const bun = getBunRuntime2();
|
|
1118
1219
|
if (bun?.main) {
|
|
@@ -1134,15 +1235,12 @@ function getCallerDirectory() {
|
|
|
1134
1235
|
}
|
|
1135
1236
|
return process.cwd();
|
|
1136
1237
|
}
|
|
1137
|
-
function findNearestConfig(startDir) {
|
|
1138
|
-
const configNames = ["devflare.config.ts", "devflare.config.js"];
|
|
1238
|
+
async function findNearestConfig(startDir) {
|
|
1139
1239
|
let currentDir = startDir;
|
|
1140
1240
|
while (true) {
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
return configPath;
|
|
1145
|
-
}
|
|
1241
|
+
const configPath = await resolveConfigPath(currentDir);
|
|
1242
|
+
if (configPath) {
|
|
1243
|
+
return configPath;
|
|
1146
1244
|
}
|
|
1147
1245
|
const parentDir = dirname2(currentDir);
|
|
1148
1246
|
if (parentDir === currentDir) {
|
|
@@ -1151,22 +1249,37 @@ function findNearestConfig(startDir) {
|
|
|
1151
1249
|
currentDir = parentDir;
|
|
1152
1250
|
}
|
|
1153
1251
|
}
|
|
1252
|
+
function resolveTransportFile(configDir6, configuredPath) {
|
|
1253
|
+
if (typeof configuredPath === "string") {
|
|
1254
|
+
return configuredPath;
|
|
1255
|
+
}
|
|
1256
|
+
if (configuredPath === null) {
|
|
1257
|
+
return null;
|
|
1258
|
+
}
|
|
1259
|
+
for (const defaultEntry of DEFAULT_TRANSPORT_ENTRY_FILES) {
|
|
1260
|
+
if (existsSync2(join7(configDir6, defaultEntry))) {
|
|
1261
|
+
return defaultEntry;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
return null;
|
|
1265
|
+
}
|
|
1154
1266
|
async function createTestContext(configPath) {
|
|
1155
1267
|
const callerDir = getCallerDirectory();
|
|
1156
1268
|
let absolutePath;
|
|
1157
1269
|
if (configPath) {
|
|
1158
1270
|
absolutePath = resolve2(callerDir, configPath);
|
|
1159
1271
|
} else {
|
|
1160
|
-
const found = findNearestConfig(callerDir);
|
|
1272
|
+
const found = await findNearestConfig(callerDir);
|
|
1161
1273
|
if (!found) {
|
|
1162
|
-
throw new Error(`Could not find devflare
|
|
1274
|
+
throw new Error(`Could not find a devflare config file. Searched upward from: ${callerDir}
|
|
1275
|
+
` + `Expected one of: devflare.config.ts, devflare.config.mts, devflare.config.js, devflare.config.mjs
|
|
1163
1276
|
` + `Either create a config file or provide an explicit path: createTestContext('./path/to/config.ts')`);
|
|
1164
1277
|
}
|
|
1165
1278
|
absolutePath = found;
|
|
1166
1279
|
}
|
|
1167
|
-
const
|
|
1280
|
+
const configDir6 = dirname2(absolutePath);
|
|
1168
1281
|
const config = await loadConfig({
|
|
1169
|
-
cwd:
|
|
1282
|
+
cwd: configDir6,
|
|
1170
1283
|
configFile: absolutePath.split(/[/\\]/).pop()
|
|
1171
1284
|
});
|
|
1172
1285
|
globalRemoteBindings = {};
|
|
@@ -1186,6 +1299,11 @@ async function createTestContext(configPath) {
|
|
|
1186
1299
|
globalRemoteBindings[key] = value;
|
|
1187
1300
|
}
|
|
1188
1301
|
}
|
|
1302
|
+
if (config.bindings?.sendEmail) {
|
|
1303
|
+
for (const [name, binding] of Object.entries(config.bindings.sendEmail)) {
|
|
1304
|
+
globalRemoteBindings[name] = createLocalSendEmailBinding(binding);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1189
1307
|
const hints = {};
|
|
1190
1308
|
if (config.bindings?.kv) {
|
|
1191
1309
|
for (const name of Object.keys(config.bindings.kv))
|
|
@@ -1207,18 +1325,23 @@ async function createTestContext(configPath) {
|
|
|
1207
1325
|
for (const name of Object.keys(config.bindings.services))
|
|
1208
1326
|
hints[name] = "service";
|
|
1209
1327
|
}
|
|
1328
|
+
if (config.bindings?.sendEmail) {
|
|
1329
|
+
for (const name of Object.keys(config.bindings.sendEmail))
|
|
1330
|
+
hints[name] = "sendEmail";
|
|
1331
|
+
}
|
|
1210
1332
|
const needsMultiWorkerForServices = hasServiceBindings(config);
|
|
1211
1333
|
const needsMultiWorkerForDOs = hasCrossWorkerDOs(config);
|
|
1212
1334
|
const needsMultiWorker = needsMultiWorkerForServices || needsMultiWorkerForDOs;
|
|
1213
1335
|
let serviceBindingResolution = null;
|
|
1214
1336
|
let doBindingResolution = null;
|
|
1215
1337
|
if (needsMultiWorkerForServices) {
|
|
1216
|
-
serviceBindingResolution = await resolveServiceBindings(config,
|
|
1338
|
+
serviceBindingResolution = await resolveServiceBindings(config, configDir6);
|
|
1217
1339
|
}
|
|
1218
1340
|
if (needsMultiWorkerForDOs) {
|
|
1219
|
-
doBindingResolution = await resolveDOBindings(config,
|
|
1341
|
+
doBindingResolution = await resolveDOBindings(config, configDir6);
|
|
1220
1342
|
}
|
|
1221
1343
|
const randomPort = 1e4 + Math.floor(Math.random() * 50000);
|
|
1344
|
+
const localWorkerBindings = config.vars ?? {};
|
|
1222
1345
|
const mfConfig = {
|
|
1223
1346
|
modules: true,
|
|
1224
1347
|
port: randomPort
|
|
@@ -1236,9 +1359,28 @@ async function createTestContext(configPath) {
|
|
|
1236
1359
|
}
|
|
1237
1360
|
mfConfig.queueProducers = queueProducers;
|
|
1238
1361
|
}
|
|
1239
|
-
|
|
1362
|
+
if (Object.keys(localWorkerBindings).length > 0) {
|
|
1363
|
+
mfConfig.bindings = localWorkerBindings;
|
|
1364
|
+
}
|
|
1365
|
+
if (config.bindings?.sendEmail) {
|
|
1366
|
+
mfConfig.email = {
|
|
1367
|
+
send_email: Object.entries(config.bindings.sendEmail).map(([name, binding]) => ({
|
|
1368
|
+
name,
|
|
1369
|
+
...binding.destinationAddress && {
|
|
1370
|
+
destination_address: binding.destinationAddress
|
|
1371
|
+
},
|
|
1372
|
+
...binding.allowedDestinationAddresses && {
|
|
1373
|
+
allowed_destination_addresses: binding.allowedDestinationAddresses
|
|
1374
|
+
},
|
|
1375
|
+
...binding.allowedSenderAddresses && {
|
|
1376
|
+
allowed_sender_addresses: binding.allowedSenderAddresses
|
|
1377
|
+
}
|
|
1378
|
+
}))
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
const transportFile = resolveTransportFile(configDir6, config.files?.transport);
|
|
1240
1382
|
if (transportFile) {
|
|
1241
|
-
const transportPath =
|
|
1383
|
+
const transportPath = join7(configDir6, transportFile);
|
|
1242
1384
|
const transportModule = await import(transportPath);
|
|
1243
1385
|
if (!transportModule.transport) {
|
|
1244
1386
|
console.warn(`[devflare] Warning: Transport file "${transportFile}" does not export a named "transport" object.
|
|
@@ -1260,7 +1402,7 @@ Transport encoding/decoding will be disabled.`);
|
|
|
1260
1402
|
const doPattern = typeof doPatternConfig === "string" ? doPatternConfig : DEFAULT_DO_PATTERN;
|
|
1261
1403
|
if (doPatternConfig !== false) {
|
|
1262
1404
|
const fs = await import("fs/promises");
|
|
1263
|
-
const doFiles = await findFiles(doPattern, { cwd:
|
|
1405
|
+
const doFiles = await findFiles(doPattern, { cwd: configDir6 });
|
|
1264
1406
|
for (const filePath of doFiles) {
|
|
1265
1407
|
try {
|
|
1266
1408
|
const code = await fs.readFile(filePath, "utf-8");
|
|
@@ -1278,7 +1420,7 @@ Transport encoding/decoding will be disabled.`);
|
|
|
1278
1420
|
}
|
|
1279
1421
|
let scriptPath;
|
|
1280
1422
|
if (doInfo.scriptName) {
|
|
1281
|
-
scriptPath =
|
|
1423
|
+
scriptPath = join7(configDir6, "src", doInfo.scriptName);
|
|
1282
1424
|
} else {
|
|
1283
1425
|
const discoveredPath = classToFilePath.get(doInfo.className);
|
|
1284
1426
|
if (!discoveredPath) {
|
|
@@ -1295,7 +1437,7 @@ Either:
|
|
|
1295
1437
|
const virtualImports = [];
|
|
1296
1438
|
const virtualExports = [];
|
|
1297
1439
|
if (transportFile) {
|
|
1298
|
-
const transportPath =
|
|
1440
|
+
const transportPath = join7(configDir6, transportFile);
|
|
1299
1441
|
virtualImports.push(`import { transport } from '${transportPath.replace(/\\/g, "/")}'`);
|
|
1300
1442
|
virtualExports.push("export { transport }");
|
|
1301
1443
|
}
|
|
@@ -1307,7 +1449,7 @@ Either:
|
|
|
1307
1449
|
if (virtualImports.length > 0) {
|
|
1308
1450
|
const virtualEntry = [...virtualImports, "", ...virtualExports].join(`
|
|
1309
1451
|
`);
|
|
1310
|
-
const virtualPath =
|
|
1452
|
+
const virtualPath = join7(configDir6, ".devflare", "__test_entry.ts");
|
|
1311
1453
|
const { writeFileSync, mkdirSync } = await import("fs");
|
|
1312
1454
|
mkdirSync(dirname2(virtualPath), { recursive: true });
|
|
1313
1455
|
writeFileSync(virtualPath, virtualEntry);
|
|
@@ -1348,6 +1490,7 @@ Either:
|
|
|
1348
1490
|
...mfConfig.kvNamespaces && { kvNamespaces: mfConfig.kvNamespaces },
|
|
1349
1491
|
...mfConfig.r2Buckets && { r2Buckets: mfConfig.r2Buckets },
|
|
1350
1492
|
...mfConfig.d1Databases && { d1Databases: mfConfig.d1Databases },
|
|
1493
|
+
...mfConfig.email && { email: mfConfig.email },
|
|
1351
1494
|
...Object.keys(primaryDurableObjects).length > 0 && { durableObjects: primaryDurableObjects },
|
|
1352
1495
|
...serviceBindingResolution?.primaryServiceBindings && { serviceBindings: serviceBindingResolution.primaryServiceBindings }
|
|
1353
1496
|
};
|
|
@@ -1381,7 +1524,7 @@ Either:
|
|
|
1381
1524
|
const { Miniflare } = await import("miniflare");
|
|
1382
1525
|
globalMiniflare = new Miniflare(mfConfig);
|
|
1383
1526
|
await globalMiniflare.ready;
|
|
1384
|
-
globalMiniflareBindings = await globalMiniflare.getBindings();
|
|
1527
|
+
globalMiniflareBindings = wrapEnvSendEmailBindings(await globalMiniflare.getBindings());
|
|
1385
1528
|
const disposeContext = async () => {
|
|
1386
1529
|
if (globalClient) {
|
|
1387
1530
|
await globalClient.disconnect();
|
|
@@ -1403,26 +1546,42 @@ Either:
|
|
|
1403
1546
|
__clearTestContext();
|
|
1404
1547
|
};
|
|
1405
1548
|
const getTestEnv = () => {
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1549
|
+
return new Proxy({}, {
|
|
1550
|
+
get(_, prop) {
|
|
1551
|
+
if (globalRemoteBindings && prop in globalRemoteBindings) {
|
|
1552
|
+
return globalRemoteBindings[prop];
|
|
1553
|
+
}
|
|
1554
|
+
if (hints[prop] === "sendEmail" && globalEnvProxy && prop in globalEnvProxy) {
|
|
1555
|
+
return globalEnvProxy[prop];
|
|
1556
|
+
}
|
|
1557
|
+
if (globalMiniflareBindings && prop in globalMiniflareBindings) {
|
|
1558
|
+
return globalMiniflareBindings[prop];
|
|
1559
|
+
}
|
|
1560
|
+
if (globalEnvProxy && prop in globalEnvProxy) {
|
|
1561
|
+
return globalEnvProxy[prop];
|
|
1562
|
+
}
|
|
1563
|
+
return;
|
|
1564
|
+
},
|
|
1565
|
+
has(_, prop) {
|
|
1566
|
+
return Boolean(globalRemoteBindings && prop in globalRemoteBindings || globalMiniflareBindings && prop in globalMiniflareBindings || globalEnvProxy && prop in globalEnvProxy);
|
|
1567
|
+
}
|
|
1568
|
+
});
|
|
1413
1569
|
};
|
|
1414
1570
|
const queuePath = config.files?.queue;
|
|
1415
1571
|
const scheduledPath = config.files?.scheduled;
|
|
1416
1572
|
const fetchPath = config.files?.fetch;
|
|
1573
|
+
const emailPath = config.files?.email;
|
|
1417
1574
|
const DEFAULT_FETCH_PATH = "src/fetch.ts";
|
|
1418
1575
|
const DEFAULT_QUEUE_PATH = "src/queue.ts";
|
|
1419
1576
|
const DEFAULT_SCHEDULED_PATH = "src/scheduled.ts";
|
|
1577
|
+
const DEFAULT_EMAIL_PATH = "src/email.ts";
|
|
1578
|
+
const DEFAULT_TAIL_PATH = "src/tail.ts";
|
|
1420
1579
|
const resolvePath = async (configValue, defaultPath) => {
|
|
1421
1580
|
if (typeof configValue === "string")
|
|
1422
1581
|
return configValue;
|
|
1423
1582
|
if (configValue === false)
|
|
1424
1583
|
return null;
|
|
1425
|
-
const defaultAbsolute =
|
|
1584
|
+
const defaultAbsolute = join7(configDir6, defaultPath);
|
|
1426
1585
|
try {
|
|
1427
1586
|
const fs = await import("fs/promises");
|
|
1428
1587
|
await fs.access(defaultAbsolute);
|
|
@@ -1431,32 +1590,45 @@ Either:
|
|
|
1431
1590
|
return null;
|
|
1432
1591
|
}
|
|
1433
1592
|
};
|
|
1434
|
-
const [resolvedFetchPath, resolvedQueuePath, resolvedScheduledPath] = await Promise.all([
|
|
1593
|
+
const [resolvedFetchPath, resolvedQueuePath, resolvedScheduledPath, resolvedEmailPath, resolvedTailPath, resolvedRoutes] = await Promise.all([
|
|
1435
1594
|
resolvePath(fetchPath, DEFAULT_FETCH_PATH),
|
|
1436
1595
|
resolvePath(queuePath, DEFAULT_QUEUE_PATH),
|
|
1437
|
-
resolvePath(scheduledPath, DEFAULT_SCHEDULED_PATH)
|
|
1596
|
+
resolvePath(scheduledPath, DEFAULT_SCHEDULED_PATH),
|
|
1597
|
+
resolvePath(emailPath, DEFAULT_EMAIL_PATH),
|
|
1598
|
+
resolvePath(undefined, DEFAULT_TAIL_PATH),
|
|
1599
|
+
discoverRoutes(configDir6, config)
|
|
1438
1600
|
]);
|
|
1439
1601
|
configureQueue({
|
|
1440
1602
|
handlerPath: resolvedQueuePath,
|
|
1441
|
-
configDir:
|
|
1603
|
+
configDir: configDir6,
|
|
1442
1604
|
getEnv: getTestEnv
|
|
1443
1605
|
});
|
|
1444
1606
|
configureScheduled({
|
|
1445
1607
|
handlerPath: resolvedScheduledPath,
|
|
1446
|
-
configDir:
|
|
1608
|
+
configDir: configDir6,
|
|
1447
1609
|
getEnv: getTestEnv
|
|
1448
1610
|
});
|
|
1449
1611
|
configureWorker({
|
|
1450
1612
|
handlerPath: resolvedFetchPath,
|
|
1451
|
-
|
|
1613
|
+
routes: resolvedRoutes?.routes.map((route) => ({
|
|
1614
|
+
filePath: route.filePath,
|
|
1615
|
+
routePath: route.routePath,
|
|
1616
|
+
segments: route.segments
|
|
1617
|
+
})) ?? [],
|
|
1618
|
+
configDir: configDir6,
|
|
1452
1619
|
getEnv: getTestEnv
|
|
1453
1620
|
});
|
|
1454
1621
|
configureTail({
|
|
1455
|
-
handlerPath:
|
|
1456
|
-
configDir:
|
|
1622
|
+
handlerPath: resolvedTailPath,
|
|
1623
|
+
configDir: configDir6,
|
|
1624
|
+
getEnv: getTestEnv
|
|
1625
|
+
});
|
|
1626
|
+
configureEmail({
|
|
1627
|
+
port: randomPort,
|
|
1628
|
+
handlerPath: resolvedEmailPath,
|
|
1629
|
+
configDir: configDir6,
|
|
1457
1630
|
getEnv: getTestEnv
|
|
1458
1631
|
});
|
|
1459
|
-
configureEmail({ port: randomPort });
|
|
1460
1632
|
if (hasMultiWorkerServices || hasMultiWorkerDOs) {
|
|
1461
1633
|
setBindingHints(hints);
|
|
1462
1634
|
const envAccessor2 = new Proxy({}, {
|
|
@@ -1490,6 +1662,9 @@ Either:
|
|
|
1490
1662
|
if (globalRemoteBindings && prop in globalRemoteBindings) {
|
|
1491
1663
|
return globalRemoteBindings[prop];
|
|
1492
1664
|
}
|
|
1665
|
+
if (hints[prop] === "sendEmail" && globalEnvProxy && prop in globalEnvProxy) {
|
|
1666
|
+
return globalEnvProxy[prop];
|
|
1667
|
+
}
|
|
1493
1668
|
if (globalMiniflareBindings && prop in globalMiniflareBindings) {
|
|
1494
1669
|
return globalMiniflareBindings[prop];
|
|
1495
1670
|
}
|
|
@@ -1588,6 +1763,7 @@ async function executeRpc(env, method, params) {
|
|
|
1588
1763
|
const [bindingName, ...rest] = method.split('.')
|
|
1589
1764
|
const op = rest.join('.')
|
|
1590
1765
|
const binding = env[bindingName]
|
|
1766
|
+
const RAW_EMAIL = 'EmailMessage::raw'
|
|
1591
1767
|
if (!binding) throw new Error('Binding not found: ' + bindingName)
|
|
1592
1768
|
|
|
1593
1769
|
// KV operations
|
|
@@ -1619,6 +1795,11 @@ async function executeRpc(env, method, params) {
|
|
|
1619
1795
|
if (op === 'prepare.first') return binding.prepare(params[0]).bind(...(params[1] || [])).first(params[2])
|
|
1620
1796
|
if (op === 'prepare.raw') return binding.prepare(params[0]).bind(...(params[1] || [])).raw({ columnNames: params[2] })
|
|
1621
1797
|
|
|
1798
|
+
// Send email operations
|
|
1799
|
+
if (op === 'email.send') {
|
|
1800
|
+
return binding.send(__normalizeEmailMessage(params[0]))
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1622
1803
|
// DO operations
|
|
1623
1804
|
if (op === 'idFromName') {
|
|
1624
1805
|
return { __type: 'DOId', hex: binding.idFromName(params[0]).toString() }
|
|
@@ -1637,6 +1818,63 @@ async function executeRpc(env, method, params) {
|
|
|
1637
1818
|
|
|
1638
1819
|
throw new Error('Unknown operation: ' + method)
|
|
1639
1820
|
}
|
|
1821
|
+
|
|
1822
|
+
function __createEmailMessageRaw(raw) {
|
|
1823
|
+
if (typeof raw === 'string' || raw instanceof ReadableStream) {
|
|
1824
|
+
return raw
|
|
1825
|
+
}
|
|
1826
|
+
if (raw instanceof Uint8Array || raw instanceof ArrayBuffer) {
|
|
1827
|
+
return new Response(raw).body
|
|
1828
|
+
}
|
|
1829
|
+
throw new Error('Unsupported EmailMessage raw payload')
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
function __buildRawEmail(message) {
|
|
1833
|
+
const lines = []
|
|
1834
|
+
const messageId = '<' + Date.now() + '-' + Math.random().toString(36).slice(2) + '@devflare.dev>'
|
|
1835
|
+
|
|
1836
|
+
lines.push('From: ' + message.from)
|
|
1837
|
+
lines.push('To: ' + (Array.isArray(message.to) ? message.to.join(', ') : message.to))
|
|
1838
|
+
lines.push('Date: ' + new Date().toUTCString())
|
|
1839
|
+
lines.push('Message-ID: ' + messageId)
|
|
1840
|
+
|
|
1841
|
+
if (message.subject) lines.push('Subject: ' + message.subject)
|
|
1842
|
+
if (message.replyTo) lines.push('Reply-To: ' + String(message.replyTo))
|
|
1843
|
+
if (message.cc) lines.push('Cc: ' + (Array.isArray(message.cc) ? message.cc.join(', ') : message.cc))
|
|
1844
|
+
if (message.bcc) lines.push('Bcc: ' + (Array.isArray(message.bcc) ? message.bcc.join(', ') : message.bcc))
|
|
1845
|
+
|
|
1846
|
+
for (const [key, value] of Object.entries(message.headers || {})) {
|
|
1847
|
+
lines.push(key + ': ' + value)
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
lines.push('MIME-Version: 1.0')
|
|
1851
|
+
lines.push('Content-Type: ' + (message.html ? 'text/html' : 'text/plain') + '; charset=UTF-8')
|
|
1852
|
+
lines.push('')
|
|
1853
|
+
lines.push(String(message.html ?? message.text ?? '').replace(/\\r?\\n/g, '\\r\\n'))
|
|
1854
|
+
|
|
1855
|
+
return lines.join('\\r\\n')
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
function __normalizeEmailMessage(message) {
|
|
1859
|
+
if (!message || typeof message !== 'object' || !('from' in message) || !('to' in message)) {
|
|
1860
|
+
return message
|
|
1861
|
+
}
|
|
1862
|
+
if ('EmailMessage::raw' in message) {
|
|
1863
|
+
return message
|
|
1864
|
+
}
|
|
1865
|
+
if ('raw' in message && message.raw !== undefined) {
|
|
1866
|
+
return {
|
|
1867
|
+
from: message.from,
|
|
1868
|
+
to: message.to,
|
|
1869
|
+
[RAW_EMAIL]: __createEmailMessageRaw(message.raw)
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
return {
|
|
1873
|
+
from: message.from,
|
|
1874
|
+
to: message.to,
|
|
1875
|
+
[RAW_EMAIL]: __createEmailMessageRaw(__buildRawEmail(message))
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1640
1878
|
`;
|
|
1641
1879
|
}
|
|
1642
1880
|
// src/test/cf.ts
|
|
@@ -2146,7 +2384,7 @@ async function createBridgeTestContext(options = {}) {
|
|
|
2146
2384
|
verbose: options.verbose ?? false
|
|
2147
2385
|
});
|
|
2148
2386
|
}
|
|
2149
|
-
const bindings = await miniflare.getBindings();
|
|
2387
|
+
const bindings = wrapEnvSendEmailBindings(await miniflare.getBindings());
|
|
2150
2388
|
if (config?.bindings) {
|
|
2151
2389
|
const hints = {};
|
|
2152
2390
|
if (config.bindings.kv) {
|
|
@@ -2176,6 +2414,11 @@ async function createBridgeTestContext(options = {}) {
|
|
|
2176
2414
|
}
|
|
2177
2415
|
if (config.bindings.ai)
|
|
2178
2416
|
hints[config.bindings.ai.binding] = "ai";
|
|
2417
|
+
if (config.bindings.sendEmail) {
|
|
2418
|
+
Object.keys(config.bindings.sendEmail).forEach((name) => {
|
|
2419
|
+
hints[name] = "sendEmail";
|
|
2420
|
+
});
|
|
2421
|
+
}
|
|
2179
2422
|
setBindingHints(hints);
|
|
2180
2423
|
}
|
|
2181
2424
|
const ctx = {
|
|
@@ -2222,4 +2465,4 @@ var testEnv = new Proxy({}, {
|
|
|
2222
2465
|
function isKVNamespace(binding) {
|
|
2223
2466
|
return typeof binding === "object" && binding !== null && "get" in binding && "put" in binding && "delete" in binding && "list" in binding;
|
|
2224
2467
|
}
|
|
2225
|
-
export {
|
|
2468
|
+
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 };
|