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,22 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
detectViteProject,
|
|
3
|
+
stopSpawnedProcessTree,
|
|
4
|
+
waitForViteReady
|
|
5
|
+
} from "./index-y1d8za14.js";
|
|
6
|
+
import {
|
|
7
|
+
prepareComposedWorkerEntrypoint
|
|
8
|
+
} from "./index-ws68xvq2.js";
|
|
9
|
+
import {
|
|
10
|
+
discoverRoutes,
|
|
11
|
+
getRouteDirectoryCandidate
|
|
12
|
+
} from "./index-1p814k7s.js";
|
|
13
|
+
import {
|
|
14
|
+
findDurableObjectClasses,
|
|
15
|
+
transformDurableObject
|
|
16
|
+
} from "./index-9wt9x09k.js";
|
|
1
17
|
import {
|
|
2
18
|
findFiles
|
|
3
19
|
} from "./index-rbht7m9r.js";
|
|
4
20
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
21
|
+
clearLocalSendEmailBindings,
|
|
22
|
+
setLocalSendEmailBindings
|
|
23
|
+
} from "./index-fef08w43.js";
|
|
7
24
|
import {
|
|
8
|
-
loadConfig
|
|
9
|
-
|
|
25
|
+
loadConfig,
|
|
26
|
+
resolveConfigPath
|
|
27
|
+
} from "./index-1phx14av.js";
|
|
10
28
|
import {
|
|
11
29
|
__require
|
|
12
30
|
} from "./index-37x76zdn.js";
|
|
13
31
|
|
|
14
32
|
// src/cli/commands/dev.ts
|
|
15
33
|
import { createConsola } from "consola";
|
|
16
|
-
import { resolve as resolve3 } from "pathe";
|
|
34
|
+
import { relative as relative2, resolve as resolve3 } from "pathe";
|
|
17
35
|
|
|
18
36
|
// src/dev-server/server.ts
|
|
19
|
-
import { resolve as resolve2 } from "pathe";
|
|
37
|
+
import { dirname as dirname2, resolve as resolve2 } from "pathe";
|
|
20
38
|
|
|
21
39
|
// src/bundler/do-bundler.ts
|
|
22
40
|
import { resolve, dirname, relative } from "pathe";
|
|
@@ -55,13 +73,141 @@ function stripDecoratorSyntax(code) {
|
|
|
55
73
|
});
|
|
56
74
|
return result;
|
|
57
75
|
}
|
|
58
|
-
|
|
76
|
+
function toArray(value) {
|
|
77
|
+
return Array.isArray(value) ? value : [value];
|
|
78
|
+
}
|
|
79
|
+
function matchesExternalPattern(pattern, id) {
|
|
80
|
+
if (pattern instanceof RegExp) {
|
|
81
|
+
pattern.lastIndex = 0;
|
|
82
|
+
return pattern.test(id);
|
|
83
|
+
}
|
|
84
|
+
return pattern === id;
|
|
85
|
+
}
|
|
86
|
+
function matchesExternalOption(option, id, parentId, isResolved) {
|
|
87
|
+
if (!option) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
if (typeof option === "function") {
|
|
91
|
+
return option(id, parentId, isResolved) ?? false;
|
|
92
|
+
}
|
|
93
|
+
return toArray(option).some((pattern) => matchesExternalPattern(pattern, id));
|
|
94
|
+
}
|
|
95
|
+
function mergeExternalOptions(base, user) {
|
|
96
|
+
if (!base) {
|
|
97
|
+
return user;
|
|
98
|
+
}
|
|
99
|
+
if (!user) {
|
|
100
|
+
return base;
|
|
101
|
+
}
|
|
102
|
+
if (typeof base !== "function" && typeof user !== "function") {
|
|
103
|
+
return [...toArray(base), ...toArray(user)];
|
|
104
|
+
}
|
|
105
|
+
return (id, parentId, isResolved) => {
|
|
106
|
+
return matchesExternalOption(base, id, parentId, isResolved) || matchesExternalOption(user, id, parentId, isResolved) || false;
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function mergePluginOptions(base, user) {
|
|
110
|
+
if (!base) {
|
|
111
|
+
return user;
|
|
112
|
+
}
|
|
113
|
+
if (!user) {
|
|
114
|
+
return base;
|
|
115
|
+
}
|
|
116
|
+
return [base, user];
|
|
117
|
+
}
|
|
118
|
+
function mergeResolveOptions(base, user) {
|
|
119
|
+
if (!base) {
|
|
120
|
+
return user;
|
|
121
|
+
}
|
|
122
|
+
if (!user) {
|
|
123
|
+
return base;
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
...user,
|
|
127
|
+
...base,
|
|
128
|
+
alias: {
|
|
129
|
+
...user.alias ?? {},
|
|
130
|
+
...base.alias ?? {}
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function resolveDOBundleRolldownConfig(options) {
|
|
135
|
+
const {
|
|
136
|
+
output: userOutputOptions,
|
|
137
|
+
input: _ignoredInput,
|
|
138
|
+
cwd: _ignoredCwd,
|
|
139
|
+
platform: _ignoredPlatform,
|
|
140
|
+
watch: _ignoredWatch,
|
|
141
|
+
external: userExternal,
|
|
142
|
+
plugins: userPlugins,
|
|
143
|
+
resolve: userResolve,
|
|
144
|
+
tsconfig: userTsconfig,
|
|
145
|
+
...userInputOptions
|
|
146
|
+
} = options.rolldownOptions ?? {};
|
|
147
|
+
const {
|
|
148
|
+
codeSplitting: _ignoredCodeSplitting,
|
|
149
|
+
dir: _ignoredDir,
|
|
150
|
+
file: _ignoredFile,
|
|
151
|
+
format: _ignoredFormat,
|
|
152
|
+
inlineDynamicImports: _ignoredInlineDynamicImports,
|
|
153
|
+
...safeUserOutputOptions
|
|
154
|
+
} = userOutputOptions ?? {};
|
|
155
|
+
const defaultExternalModules = [
|
|
156
|
+
/^cloudflare:/,
|
|
157
|
+
/^node:/,
|
|
158
|
+
"buffer",
|
|
159
|
+
"crypto",
|
|
160
|
+
"events",
|
|
161
|
+
"http",
|
|
162
|
+
"https",
|
|
163
|
+
"net",
|
|
164
|
+
"os",
|
|
165
|
+
"path",
|
|
166
|
+
"stream",
|
|
167
|
+
"tls",
|
|
168
|
+
"url",
|
|
169
|
+
"util",
|
|
170
|
+
"zlib",
|
|
171
|
+
"fs",
|
|
172
|
+
"child_process",
|
|
173
|
+
"async_hooks",
|
|
174
|
+
"querystring",
|
|
175
|
+
"string_decoder",
|
|
176
|
+
"assert",
|
|
177
|
+
"dns"
|
|
178
|
+
];
|
|
179
|
+
return {
|
|
180
|
+
inputOptions: {
|
|
181
|
+
...userInputOptions,
|
|
182
|
+
input: options.inputFile,
|
|
183
|
+
cwd: options.cwd,
|
|
184
|
+
platform: "neutral",
|
|
185
|
+
tsconfig: userTsconfig ?? resolve(options.cwd, "tsconfig.json"),
|
|
186
|
+
external: mergeExternalOptions(defaultExternalModules, userExternal),
|
|
187
|
+
plugins: mergePluginOptions(undefined, userPlugins),
|
|
188
|
+
resolve: mergeResolveOptions({
|
|
189
|
+
alias: {
|
|
190
|
+
debug: options.debugShimPath
|
|
191
|
+
}
|
|
192
|
+
}, userResolve)
|
|
193
|
+
},
|
|
194
|
+
outputOptions: {
|
|
195
|
+
...safeUserOutputOptions,
|
|
196
|
+
file: options.outFile,
|
|
197
|
+
format: "esm",
|
|
198
|
+
sourcemap: safeUserOutputOptions.sourcemap ?? options.sourcemap ?? false,
|
|
199
|
+
minify: safeUserOutputOptions.minify ?? options.minify,
|
|
200
|
+
codeSplitting: false
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
async function bundleDOFile(sourcePath, className, outDir, cwd, bundleOptions) {
|
|
59
205
|
const { rolldown } = await import("rolldown");
|
|
60
206
|
const fs = await import("node:fs/promises");
|
|
61
207
|
await fs.mkdir(outDir, { recursive: true });
|
|
62
208
|
const sourceCode = await fs.readFile(sourcePath, "utf-8");
|
|
63
|
-
const
|
|
64
|
-
const entryCode = `${
|
|
209
|
+
const transformedCode = (await transformDurableObject(sourceCode, sourcePath))?.code ?? stripDecoratorSyntax(sourceCode);
|
|
210
|
+
const entryCode = `${transformedCode}
|
|
65
211
|
|
|
66
212
|
// Default export for worker (required by Miniflare)
|
|
67
213
|
export default {
|
|
@@ -70,7 +216,7 @@ export default {
|
|
|
70
216
|
}
|
|
71
217
|
};
|
|
72
218
|
`;
|
|
73
|
-
const tempFilePath = resolve(
|
|
219
|
+
const tempFilePath = resolve(dirname(sourcePath), `.devflare-temp-${className}.ts`);
|
|
74
220
|
await fs.writeFile(tempFilePath, entryCode, "utf-8");
|
|
75
221
|
const classOutDir = resolve(outDir, className);
|
|
76
222
|
try {
|
|
@@ -94,54 +240,25 @@ export default createDebug
|
|
|
94
240
|
`;
|
|
95
241
|
const debugShimPath = resolve(outDir, "_debug_shim.js");
|
|
96
242
|
await fs.writeFile(debugShimPath, debugShimCode, "utf-8");
|
|
97
|
-
const bundle = await rolldown({
|
|
98
|
-
input: tempFilePath,
|
|
99
|
-
platform: "neutral",
|
|
100
|
-
tsconfig: resolve(cwd, "tsconfig.json"),
|
|
101
|
-
external: [
|
|
102
|
-
/^cloudflare:/,
|
|
103
|
-
/^node:/,
|
|
104
|
-
"buffer",
|
|
105
|
-
"crypto",
|
|
106
|
-
"events",
|
|
107
|
-
"http",
|
|
108
|
-
"https",
|
|
109
|
-
"net",
|
|
110
|
-
"os",
|
|
111
|
-
"path",
|
|
112
|
-
"stream",
|
|
113
|
-
"tls",
|
|
114
|
-
"url",
|
|
115
|
-
"util",
|
|
116
|
-
"zlib",
|
|
117
|
-
"fs",
|
|
118
|
-
"child_process",
|
|
119
|
-
"async_hooks",
|
|
120
|
-
"querystring",
|
|
121
|
-
"string_decoder",
|
|
122
|
-
"assert",
|
|
123
|
-
"dns"
|
|
124
|
-
],
|
|
125
|
-
resolve: {
|
|
126
|
-
alias: {
|
|
127
|
-
debug: debugShimPath
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
243
|
const outFile = resolve(classOutDir, "index.js");
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
244
|
+
const { inputOptions, outputOptions } = resolveDOBundleRolldownConfig({
|
|
245
|
+
cwd,
|
|
246
|
+
inputFile: tempFilePath,
|
|
247
|
+
outFile,
|
|
248
|
+
debugShimPath,
|
|
249
|
+
rolldownOptions: bundleOptions?.rolldownOptions,
|
|
250
|
+
sourcemap: bundleOptions?.sourcemap,
|
|
251
|
+
minify: bundleOptions?.minify
|
|
137
252
|
});
|
|
253
|
+
const bundle = await rolldown(inputOptions);
|
|
254
|
+
await bundle.write(outputOptions);
|
|
138
255
|
await bundle.close();
|
|
139
256
|
try {
|
|
140
257
|
await fs.unlink(tempFilePath);
|
|
141
258
|
} catch {}
|
|
142
259
|
return resolve(classOutDir, "index.js");
|
|
143
260
|
}
|
|
144
|
-
async function bundleAllDOs(discovered, outDir, cwd, logger) {
|
|
261
|
+
async function bundleAllDOs(discovered, outDir, cwd, logger, bundleOptions) {
|
|
145
262
|
const fs = await import("node:fs/promises");
|
|
146
263
|
const bundles = new Map;
|
|
147
264
|
const classes = new Map;
|
|
@@ -155,7 +272,7 @@ async function bundleAllDOs(discovered, outDir, cwd, logger) {
|
|
|
155
272
|
for (const do_ of discovered) {
|
|
156
273
|
try {
|
|
157
274
|
logger?.debug(`Bundling ${do_.className} from ${do_.filePath}`);
|
|
158
|
-
const outFile = await bundleDOFile(do_.filePath, do_.className, outDir, cwd);
|
|
275
|
+
const outFile = await bundleDOFile(do_.filePath, do_.className, outDir, cwd, bundleOptions);
|
|
159
276
|
bundles.set(do_.bindingName, outFile);
|
|
160
277
|
classes.set(do_.bindingName, do_.className);
|
|
161
278
|
logger?.debug(` → ${outFile}`);
|
|
@@ -168,7 +285,7 @@ async function bundleAllDOs(discovered, outDir, cwd, logger) {
|
|
|
168
285
|
return { bundles, classes, sourceFiles, errors };
|
|
169
286
|
}
|
|
170
287
|
function createDOBundler(options) {
|
|
171
|
-
const { cwd, pattern, outDir, logger, onRebuild } = options;
|
|
288
|
+
const { cwd, pattern, outDir, logger, onRebuild, rolldownOptions, sourcemap, minify } = options;
|
|
172
289
|
let result = {
|
|
173
290
|
bundles: new Map,
|
|
174
291
|
classes: new Map,
|
|
@@ -187,7 +304,11 @@ function createDOBundler(options) {
|
|
|
187
304
|
for (const do_ of discovered) {
|
|
188
305
|
logger?.info(` • ${do_.className} → ${do_.bindingName}`);
|
|
189
306
|
}
|
|
190
|
-
result = await bundleAllDOs(discovered, outDir, cwd, logger
|
|
307
|
+
result = await bundleAllDOs(discovered, outDir, cwd, logger, {
|
|
308
|
+
rolldownOptions,
|
|
309
|
+
sourcemap,
|
|
310
|
+
minify
|
|
311
|
+
});
|
|
191
312
|
if (result.errors.length === 0) {
|
|
192
313
|
logger?.success(`Bundled ${result.bundles.size} DO(s) to ${outDir}`);
|
|
193
314
|
}
|
|
@@ -1087,9 +1208,173 @@ async function checkRemoteBindingRequirements(config) {
|
|
|
1087
1208
|
};
|
|
1088
1209
|
}
|
|
1089
1210
|
|
|
1211
|
+
// src/dev-server/miniflare-log.ts
|
|
1212
|
+
var ANSI_ESCAPE_REGEX = /\u001B\[[0-9;]*m/g;
|
|
1213
|
+
var COMPATIBILITY_DATE_FALLBACK_REGEX = /^The latest compatibility date supported by the installed Cloudflare Workers Runtime is "([^"]+)", but you've requested "([^"]+)"\. Falling back to "([^"]+)"\.\.\.$/;
|
|
1214
|
+
function normalizeMiniflareMessage(message) {
|
|
1215
|
+
return message.replace(ANSI_ESCAPE_REGEX, "").replace(/\s+/g, " ").trim();
|
|
1216
|
+
}
|
|
1217
|
+
function formatCompatibilityDateFallbackNotice(message) {
|
|
1218
|
+
const normalizedMessage = normalizeMiniflareMessage(message);
|
|
1219
|
+
const match = COMPATIBILITY_DATE_FALLBACK_REGEX.exec(normalizedMessage);
|
|
1220
|
+
if (!match) {
|
|
1221
|
+
return null;
|
|
1222
|
+
}
|
|
1223
|
+
const [, _supportedDate, requestedDate, fallbackDate] = match;
|
|
1224
|
+
return `Using latest supported Cloudflare Workers Runtime compatibility date ${fallbackDate} (requested ${requestedDate})`;
|
|
1225
|
+
}
|
|
1226
|
+
function createCompatibilityAwareMiniflareLog(BaseLog, level, logger) {
|
|
1227
|
+
const log = new BaseLog(level);
|
|
1228
|
+
const originalWarn = log.warn.bind(log);
|
|
1229
|
+
const originalInfo = log.info.bind(log);
|
|
1230
|
+
log.warn = (message) => {
|
|
1231
|
+
const notice = formatCompatibilityDateFallbackNotice(message);
|
|
1232
|
+
if (!notice) {
|
|
1233
|
+
originalWarn(message);
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
if (logger) {
|
|
1237
|
+
logger.info(notice);
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
originalInfo(notice);
|
|
1241
|
+
};
|
|
1242
|
+
return log;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// src/dev-server/runtime-stdio.ts
|
|
1246
|
+
import { createInterface } from "node:readline";
|
|
1247
|
+
function writeStdout(logger, message) {
|
|
1248
|
+
if (typeof logger?.log === "function") {
|
|
1249
|
+
logger.log(message);
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
if (typeof logger?.info === "function") {
|
|
1253
|
+
logger.info(message);
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
console.log(message);
|
|
1257
|
+
}
|
|
1258
|
+
function writeStderr(logger, message) {
|
|
1259
|
+
if (typeof logger?.error === "function") {
|
|
1260
|
+
logger.error(message);
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
console.error(message);
|
|
1264
|
+
}
|
|
1265
|
+
function createRuntimeStdioForwarder(logger) {
|
|
1266
|
+
return (stdout, stderr) => {
|
|
1267
|
+
createInterface({ input: stdout }).on("line", (data) => {
|
|
1268
|
+
writeStdout(logger, data);
|
|
1269
|
+
});
|
|
1270
|
+
createInterface({ input: stderr }).on("line", (data) => {
|
|
1271
|
+
writeStderr(logger, data);
|
|
1272
|
+
});
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1090
1276
|
// src/dev-server/server.ts
|
|
1091
|
-
|
|
1277
|
+
var DEFAULT_FETCH_ENTRY_FILES = [
|
|
1278
|
+
"src/fetch.ts",
|
|
1279
|
+
"src/fetch.js",
|
|
1280
|
+
"src/fetch.mts",
|
|
1281
|
+
"src/fetch.mjs"
|
|
1282
|
+
];
|
|
1283
|
+
var DEFAULT_QUEUE_ENTRY_FILES = [
|
|
1284
|
+
"src/queue.ts",
|
|
1285
|
+
"src/queue.js",
|
|
1286
|
+
"src/queue.mts",
|
|
1287
|
+
"src/queue.mjs"
|
|
1288
|
+
];
|
|
1289
|
+
var DEFAULT_SCHEDULED_ENTRY_FILES = [
|
|
1290
|
+
"src/scheduled.ts",
|
|
1291
|
+
"src/scheduled.js",
|
|
1292
|
+
"src/scheduled.mts",
|
|
1293
|
+
"src/scheduled.mjs"
|
|
1294
|
+
];
|
|
1295
|
+
var DEFAULT_EMAIL_ENTRY_FILES = [
|
|
1296
|
+
"src/email.ts",
|
|
1297
|
+
"src/email.js",
|
|
1298
|
+
"src/email.mts",
|
|
1299
|
+
"src/email.mjs"
|
|
1300
|
+
];
|
|
1301
|
+
var INTERNAL_APP_SERVICE_BINDING = "__DEVFLARE_APP";
|
|
1302
|
+
function formatErrorMessage(error) {
|
|
1303
|
+
return error instanceof Error ? error.message : String(error);
|
|
1304
|
+
}
|
|
1305
|
+
async function resolveWorkerHandlerPath(cwd, configuredPath, defaultEntries) {
|
|
1306
|
+
if (configuredPath === false) {
|
|
1307
|
+
return null;
|
|
1308
|
+
}
|
|
1309
|
+
const fs = await import("node:fs/promises");
|
|
1310
|
+
const candidates = new Set;
|
|
1311
|
+
if (typeof configuredPath === "string" && configuredPath) {
|
|
1312
|
+
candidates.add(configuredPath);
|
|
1313
|
+
}
|
|
1314
|
+
for (const defaultEntry of defaultEntries) {
|
|
1315
|
+
candidates.add(defaultEntry);
|
|
1316
|
+
}
|
|
1317
|
+
for (const candidate of candidates) {
|
|
1318
|
+
const absolutePath = resolve2(cwd, candidate);
|
|
1319
|
+
try {
|
|
1320
|
+
await fs.access(absolutePath);
|
|
1321
|
+
return absolutePath;
|
|
1322
|
+
} catch {
|
|
1323
|
+
continue;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
return null;
|
|
1327
|
+
}
|
|
1328
|
+
async function resolveMainWorkerSurfacePaths(cwd, config) {
|
|
1329
|
+
return {
|
|
1330
|
+
fetch: await resolveWorkerHandlerPath(cwd, config.files?.fetch, DEFAULT_FETCH_ENTRY_FILES),
|
|
1331
|
+
queue: await resolveWorkerHandlerPath(cwd, config.files?.queue, DEFAULT_QUEUE_ENTRY_FILES),
|
|
1332
|
+
scheduled: await resolveWorkerHandlerPath(cwd, config.files?.scheduled, DEFAULT_SCHEDULED_ENTRY_FILES),
|
|
1333
|
+
email: await resolveWorkerHandlerPath(cwd, config.files?.email, DEFAULT_EMAIL_ENTRY_FILES)
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
function hasWorkerSurfacePaths(surfacePaths) {
|
|
1337
|
+
return Object.values(surfacePaths).some((surfacePath) => typeof surfacePath === "string" && surfacePath.length > 0);
|
|
1338
|
+
}
|
|
1339
|
+
function addWorkerWatchRoots(roots, cwd, configuredPath, defaultEntries) {
|
|
1340
|
+
if (configuredPath === false) {
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
if (typeof configuredPath === "string" && configuredPath) {
|
|
1344
|
+
roots.add(dirname2(resolve2(cwd, configuredPath)));
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
for (const defaultEntry of defaultEntries) {
|
|
1348
|
+
roots.add(dirname2(resolve2(cwd, defaultEntry)));
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
function collectWorkerWatchRoots(cwd, config, mainWorkerSurfacePaths) {
|
|
1352
|
+
const roots = new Set;
|
|
1353
|
+
const addFileParent = (filePath) => {
|
|
1354
|
+
if (typeof filePath !== "string" || !filePath) {
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
roots.add(dirname2(resolve2(cwd, filePath)));
|
|
1358
|
+
};
|
|
1359
|
+
for (const surfacePath of Object.values(mainWorkerSurfacePaths)) {
|
|
1360
|
+
if (surfacePath) {
|
|
1361
|
+
roots.add(dirname2(surfacePath));
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
addWorkerWatchRoots(roots, cwd, config.files?.fetch, DEFAULT_FETCH_ENTRY_FILES);
|
|
1365
|
+
addWorkerWatchRoots(roots, cwd, config.files?.queue, DEFAULT_QUEUE_ENTRY_FILES);
|
|
1366
|
+
addWorkerWatchRoots(roots, cwd, config.files?.scheduled, DEFAULT_SCHEDULED_ENTRY_FILES);
|
|
1367
|
+
addWorkerWatchRoots(roots, cwd, config.files?.email, DEFAULT_EMAIL_ENTRY_FILES);
|
|
1368
|
+
addFileParent(config.files?.transport);
|
|
1369
|
+
const routeDirectory = getRouteDirectoryCandidate(cwd, config);
|
|
1370
|
+
if (routeDirectory) {
|
|
1371
|
+
roots.add(routeDirectory.absoluteDir);
|
|
1372
|
+
}
|
|
1373
|
+
return [...roots];
|
|
1374
|
+
}
|
|
1375
|
+
function getGatewayScript(wsRoutes = [], debug = false, appServiceBindingName = null) {
|
|
1092
1376
|
const wsRoutesJson = JSON.stringify(wsRoutes);
|
|
1377
|
+
const appServiceBindingJson = JSON.stringify(appServiceBindingName);
|
|
1093
1378
|
return `
|
|
1094
1379
|
// Bridge Gateway Worker — RPC Handler
|
|
1095
1380
|
// Handles all binding operations via WebSocket RPC
|
|
@@ -1104,6 +1389,7 @@ const incomingStreams = new Map()
|
|
|
1104
1389
|
|
|
1105
1390
|
// WebSocket routes configuration (injected at build time)
|
|
1106
1391
|
const WS_ROUTES = ${wsRoutesJson}
|
|
1392
|
+
const APP_SERVICE_BINDING = ${appServiceBindingJson}
|
|
1107
1393
|
|
|
1108
1394
|
export default {
|
|
1109
1395
|
async fetch(request, env, ctx) {
|
|
@@ -1144,6 +1430,13 @@ export default {
|
|
|
1144
1430
|
}), { headers: { 'Content-Type': 'application/json' } })
|
|
1145
1431
|
}
|
|
1146
1432
|
|
|
1433
|
+
if (APP_SERVICE_BINDING) {
|
|
1434
|
+
const appWorker = env[APP_SERVICE_BINDING]
|
|
1435
|
+
if (appWorker && typeof appWorker.fetch === 'function') {
|
|
1436
|
+
return appWorker.fetch(request)
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1147
1440
|
return new Response('Devflare Bridge Gateway', { status: 200 })
|
|
1148
1441
|
}
|
|
1149
1442
|
}
|
|
@@ -1198,69 +1491,27 @@ async function handleEmailIncoming(request, env, ctx, url) {
|
|
|
1198
1491
|
const rawBody = await request.text()
|
|
1199
1492
|
|
|
1200
1493
|
log('Email incoming:', { from, to, bodyLength: rawBody.length })
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
const value = line.slice(colonIdx + 1).trim()
|
|
1220
|
-
headers.append(key, value)
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
// Create ReadableStream from raw email
|
|
1225
|
-
const rawStream = new ReadableStream({
|
|
1226
|
-
start(controller) {
|
|
1227
|
-
controller.enqueue(new TextEncoder().encode(rawBody))
|
|
1228
|
-
controller.close()
|
|
1229
|
-
}
|
|
1230
|
-
})
|
|
1231
|
-
|
|
1232
|
-
// Create ForwardableEmailMessage-like object
|
|
1233
|
-
const emailMessage = {
|
|
1234
|
-
from,
|
|
1235
|
-
to,
|
|
1236
|
-
headers,
|
|
1237
|
-
raw: rawStream,
|
|
1238
|
-
rawSize: rawBody.length,
|
|
1239
|
-
|
|
1240
|
-
setReject(reason) {
|
|
1241
|
-
log('Email rejected:', reason)
|
|
1242
|
-
},
|
|
1243
|
-
|
|
1244
|
-
async forward(rcptTo, extraHeaders) {
|
|
1245
|
-
log('Email forwarded to:', rcptTo)
|
|
1246
|
-
return Promise.resolve()
|
|
1247
|
-
},
|
|
1248
|
-
|
|
1249
|
-
async reply(message) {
|
|
1250
|
-
log('Email reply sent to:', message.from)
|
|
1251
|
-
return Promise.resolve()
|
|
1494
|
+
|
|
1495
|
+
if (APP_SERVICE_BINDING) {
|
|
1496
|
+
const appWorker = env[APP_SERVICE_BINDING]
|
|
1497
|
+
if (appWorker && typeof appWorker.fetch === 'function') {
|
|
1498
|
+
const response = await appWorker.fetch(new Request('http://devflare.internal/_devflare/internal/email', {
|
|
1499
|
+
method: 'POST',
|
|
1500
|
+
headers: {
|
|
1501
|
+
'x-devflare-event': 'email',
|
|
1502
|
+
'x-devflare-email-from': from,
|
|
1503
|
+
'x-devflare-email-to': to,
|
|
1504
|
+
'content-type': request.headers.get('content-type') || 'text/plain'
|
|
1505
|
+
},
|
|
1506
|
+
body: rawBody
|
|
1507
|
+
}))
|
|
1508
|
+
|
|
1509
|
+
if (!response.ok) {
|
|
1510
|
+
return response
|
|
1511
|
+
}
|
|
1252
1512
|
}
|
|
1253
1513
|
}
|
|
1254
1514
|
|
|
1255
|
-
// Look for email handler in the worker module
|
|
1256
|
-
// For now, we call via a special RPC method that DO workers can implement
|
|
1257
|
-
// The email binding should be configured in the worker
|
|
1258
|
-
|
|
1259
|
-
// Check if there's an EMAIL_HANDLER binding (special DO for email handling)
|
|
1260
|
-
if (env.__emailHandler && typeof env.__emailHandler.email === 'function') {
|
|
1261
|
-
await env.__emailHandler.email(emailMessage, env, ctx)
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
1515
|
return new Response(JSON.stringify({ ok: true, from, to }), {
|
|
1265
1516
|
headers: { 'Content-Type': 'application/json' }
|
|
1266
1517
|
})
|
|
@@ -1373,6 +1624,7 @@ async function executeRpcMethod(method, params, env, ctx) {
|
|
|
1373
1624
|
const bindingName = parts[0]
|
|
1374
1625
|
const operation = parts.slice(1).join('.')
|
|
1375
1626
|
const binding = env[bindingName]
|
|
1627
|
+
const RAW_EMAIL = 'EmailMessage::raw'
|
|
1376
1628
|
|
|
1377
1629
|
if (!binding) throw new Error('Binding not found: ' + bindingName)
|
|
1378
1630
|
|
|
@@ -1507,10 +1759,17 @@ async function executeRpcMethod(method, params, env, ctx) {
|
|
|
1507
1759
|
|
|
1508
1760
|
// Email send operations (send_email binding)
|
|
1509
1761
|
if (operation === 'email.send') {
|
|
1510
|
-
|
|
1511
|
-
|
|
1762
|
+
const message = params[0]
|
|
1763
|
+
log('Email send:', { from: message?.from, to: message?.to })
|
|
1512
1764
|
if (binding && typeof binding.send === 'function') {
|
|
1513
|
-
|
|
1765
|
+
if (message && typeof message === 'object' && 'from' in message && 'to' in message && 'raw' in message) {
|
|
1766
|
+
return binding.send({
|
|
1767
|
+
from: message.from,
|
|
1768
|
+
to: message.to,
|
|
1769
|
+
[RAW_EMAIL]: createEmailMessageRaw(message.raw)
|
|
1770
|
+
})
|
|
1771
|
+
}
|
|
1772
|
+
return binding.send(message)
|
|
1514
1773
|
}
|
|
1515
1774
|
// Return success even if no real binding (simulated)
|
|
1516
1775
|
return { ok: true, simulated: true }
|
|
@@ -1519,6 +1778,16 @@ async function executeRpcMethod(method, params, env, ctx) {
|
|
|
1519
1778
|
throw new Error('Unknown operation: ' + method)
|
|
1520
1779
|
}
|
|
1521
1780
|
|
|
1781
|
+
function createEmailMessageRaw(raw) {
|
|
1782
|
+
if (typeof raw === 'string' || raw instanceof ReadableStream) {
|
|
1783
|
+
return raw
|
|
1784
|
+
}
|
|
1785
|
+
if (raw instanceof ArrayBuffer || raw instanceof Uint8Array) {
|
|
1786
|
+
return new Response(raw).body
|
|
1787
|
+
}
|
|
1788
|
+
throw new Error('Unsupported EmailMessage raw payload')
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1522
1791
|
async function handleWsOpen(msg, ws, env) {
|
|
1523
1792
|
try {
|
|
1524
1793
|
const binding = env[msg.target.binding]
|
|
@@ -1663,6 +1932,7 @@ function createDevServer(options) {
|
|
|
1663
1932
|
configPath,
|
|
1664
1933
|
vitePort = 5173,
|
|
1665
1934
|
miniflarePort = 8787,
|
|
1935
|
+
enableVite = true,
|
|
1666
1936
|
persist = true,
|
|
1667
1937
|
logger,
|
|
1668
1938
|
verbose = false,
|
|
@@ -1670,15 +1940,90 @@ function createDevServer(options) {
|
|
|
1670
1940
|
} = options;
|
|
1671
1941
|
let miniflare = null;
|
|
1672
1942
|
let doBundler = null;
|
|
1943
|
+
let workerSourceWatcher = null;
|
|
1944
|
+
let workerWatchTargets = [];
|
|
1673
1945
|
let viteProcess = null;
|
|
1674
1946
|
let config = null;
|
|
1675
1947
|
let browserShim = null;
|
|
1676
1948
|
let browserShimPort = 8788;
|
|
1949
|
+
let mainWorkerSurfacePaths = {
|
|
1950
|
+
fetch: null,
|
|
1951
|
+
queue: null,
|
|
1952
|
+
scheduled: null,
|
|
1953
|
+
email: null
|
|
1954
|
+
};
|
|
1955
|
+
let resolvedWorkerConfigPath = null;
|
|
1956
|
+
let mainWorkerScriptPath = null;
|
|
1957
|
+
let bundledMainWorkerScriptPath = null;
|
|
1958
|
+
let currentDoResult = null;
|
|
1959
|
+
let mainWorkerRoutes = null;
|
|
1960
|
+
let reloadChain = Promise.resolve();
|
|
1961
|
+
async function bundleMainWorker() {
|
|
1962
|
+
if (!mainWorkerScriptPath) {
|
|
1963
|
+
bundledMainWorkerScriptPath = null;
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1966
|
+
if (typeof Bun === "undefined" || typeof Bun.build !== "function") {
|
|
1967
|
+
throw new Error("Worker-only dev mode requires the Bun runtime for main worker bundling");
|
|
1968
|
+
}
|
|
1969
|
+
const fs = await import("node:fs/promises");
|
|
1970
|
+
const outDir = resolve2(cwd, ".devflare", "worker-bundles");
|
|
1971
|
+
await fs.rm(outDir, { recursive: true, force: true });
|
|
1972
|
+
await fs.mkdir(outDir, { recursive: true });
|
|
1973
|
+
const result = await Bun.build({
|
|
1974
|
+
entrypoints: [mainWorkerScriptPath],
|
|
1975
|
+
outdir: outDir,
|
|
1976
|
+
target: "browser",
|
|
1977
|
+
conditions: ["browser"],
|
|
1978
|
+
format: "esm",
|
|
1979
|
+
minify: false,
|
|
1980
|
+
splitting: false,
|
|
1981
|
+
external: ["cloudflare:workers", "cloudflare:*", "node:*"]
|
|
1982
|
+
});
|
|
1983
|
+
if (!result.success || result.outputs.length === 0) {
|
|
1984
|
+
const logs = result.logs.map((log) => ("message" in log) ? log.message : String(log)).join(`
|
|
1985
|
+
`);
|
|
1986
|
+
throw new Error(`Failed to bundle main worker:
|
|
1987
|
+
${logs}`);
|
|
1988
|
+
}
|
|
1989
|
+
bundledMainWorkerScriptPath = result.outputs[0].path;
|
|
1990
|
+
logger?.debug(`Bundled main worker → ${bundledMainWorkerScriptPath}`);
|
|
1991
|
+
}
|
|
1677
1992
|
function buildMiniflareConfig(doResult) {
|
|
1678
1993
|
if (!config)
|
|
1679
1994
|
throw new Error("Config not loaded");
|
|
1680
|
-
const
|
|
1995
|
+
const loadedConfig = config;
|
|
1996
|
+
const bindings = loadedConfig.bindings ?? {};
|
|
1681
1997
|
const persistPath = resolve2(cwd, ".devflare/data");
|
|
1998
|
+
const appWorkerName = loadedConfig.name;
|
|
1999
|
+
const shouldRunMainWorker = !enableVite && (hasWorkerSurfacePaths(mainWorkerSurfacePaths) || Boolean(mainWorkerRoutes?.routes.length));
|
|
2000
|
+
const queueProducers = (() => {
|
|
2001
|
+
if (!bindings.queues?.producers) {
|
|
2002
|
+
return;
|
|
2003
|
+
}
|
|
2004
|
+
const producers = {};
|
|
2005
|
+
for (const [bindingName, queueName] of Object.entries(bindings.queues.producers)) {
|
|
2006
|
+
producers[bindingName] = { queueName };
|
|
2007
|
+
}
|
|
2008
|
+
return producers;
|
|
2009
|
+
})();
|
|
2010
|
+
const queueConsumers = (() => {
|
|
2011
|
+
if (!bindings.queues?.consumers || bindings.queues.consumers.length === 0) {
|
|
2012
|
+
return;
|
|
2013
|
+
}
|
|
2014
|
+
const consumers = {};
|
|
2015
|
+
for (const consumer of bindings.queues.consumers) {
|
|
2016
|
+
consumers[consumer.queue] = {
|
|
2017
|
+
...consumer.maxBatchSize !== undefined && { maxBatchSize: consumer.maxBatchSize },
|
|
2018
|
+
...consumer.maxBatchTimeout !== undefined && { maxBatchTimeout: consumer.maxBatchTimeout },
|
|
2019
|
+
...consumer.maxRetries !== undefined && { maxRetries: consumer.maxRetries },
|
|
2020
|
+
...consumer.deadLetterQueue && { deadLetterQueue: consumer.deadLetterQueue },
|
|
2021
|
+
...consumer.maxConcurrency !== undefined && { maxConcurrency: consumer.maxConcurrency },
|
|
2022
|
+
...consumer.retryDelay !== undefined && { retryDelay: consumer.retryDelay }
|
|
2023
|
+
};
|
|
2024
|
+
}
|
|
2025
|
+
return consumers;
|
|
2026
|
+
})();
|
|
1682
2027
|
const sharedOptions = {
|
|
1683
2028
|
port: miniflarePort,
|
|
1684
2029
|
host: "127.0.0.1",
|
|
@@ -1687,19 +2032,85 @@ function createDevServer(options) {
|
|
|
1687
2032
|
d1Persist: persist ? `${persistPath}/d1` : undefined,
|
|
1688
2033
|
durableObjectsPersist: persist ? `${persistPath}/do` : undefined
|
|
1689
2034
|
};
|
|
1690
|
-
const
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
2035
|
+
const createServiceBindings = (extraBindings = {}) => {
|
|
2036
|
+
const serviceBindings = {};
|
|
2037
|
+
if (bindings.services) {
|
|
2038
|
+
for (const [bindingName, serviceConfig] of Object.entries(bindings.services)) {
|
|
2039
|
+
serviceBindings[bindingName] = {
|
|
2040
|
+
name: serviceConfig.service,
|
|
2041
|
+
...serviceConfig.entrypoint && { entrypoint: serviceConfig.entrypoint }
|
|
2042
|
+
};
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
for (const [bindingName, target] of Object.entries(extraBindings)) {
|
|
2046
|
+
serviceBindings[bindingName] = target;
|
|
2047
|
+
}
|
|
2048
|
+
return Object.keys(serviceBindings).length > 0 ? serviceBindings : undefined;
|
|
1701
2049
|
};
|
|
1702
|
-
|
|
2050
|
+
const sendEmailConfig = bindings.sendEmail ? {
|
|
2051
|
+
send_email: Object.entries(bindings.sendEmail).map(([name, binding]) => ({
|
|
2052
|
+
name,
|
|
2053
|
+
...binding.destinationAddress && {
|
|
2054
|
+
destination_address: binding.destinationAddress
|
|
2055
|
+
},
|
|
2056
|
+
...binding.allowedDestinationAddresses && {
|
|
2057
|
+
allowed_destination_addresses: binding.allowedDestinationAddresses
|
|
2058
|
+
},
|
|
2059
|
+
...binding.allowedSenderAddresses && {
|
|
2060
|
+
allowed_sender_addresses: binding.allowedSenderAddresses
|
|
2061
|
+
}
|
|
2062
|
+
}))
|
|
2063
|
+
} : undefined;
|
|
2064
|
+
const createWorkerConfig = (options2) => {
|
|
2065
|
+
const baseFlags = loadedConfig.compatibilityFlags ?? [];
|
|
2066
|
+
const compatFlags = baseFlags.includes("nodejs_compat") ? baseFlags : [...baseFlags, "nodejs_compat"];
|
|
2067
|
+
const workerBindings = loadedConfig.vars ?? {};
|
|
2068
|
+
const workerConfig = {
|
|
2069
|
+
name: options2.name,
|
|
2070
|
+
modules: true,
|
|
2071
|
+
compatibilityDate: loadedConfig.compatibilityDate,
|
|
2072
|
+
compatibilityFlags: compatFlags,
|
|
2073
|
+
...bindings.kv && { kvNamespaces: bindings.kv },
|
|
2074
|
+
...bindings.r2 && { r2Buckets: bindings.r2 },
|
|
2075
|
+
...bindings.d1 && { d1Databases: bindings.d1 },
|
|
2076
|
+
...Object.keys(workerBindings).length > 0 && { bindings: workerBindings },
|
|
2077
|
+
...sendEmailConfig && { email: sendEmailConfig },
|
|
2078
|
+
...queueProducers && { queueProducers },
|
|
2079
|
+
...options2.queueConsumers && { queueConsumers: options2.queueConsumers },
|
|
2080
|
+
...options2.triggers && { triggers: options2.triggers }
|
|
2081
|
+
};
|
|
2082
|
+
if (options2.scriptPath) {
|
|
2083
|
+
workerConfig.scriptPath = options2.scriptPath;
|
|
2084
|
+
workerConfig.modulesRoot = cwd;
|
|
2085
|
+
workerConfig.modulesRules = [
|
|
2086
|
+
{ type: "ESModule", include: ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.mjs"] },
|
|
2087
|
+
{ type: "CommonJS", include: ["**/*.js", "**/*.cjs"] },
|
|
2088
|
+
{ type: "ESModule", include: ["**/*.jsx"] }
|
|
2089
|
+
];
|
|
2090
|
+
}
|
|
2091
|
+
if (options2.script) {
|
|
2092
|
+
workerConfig.script = options2.script;
|
|
2093
|
+
}
|
|
2094
|
+
if (options2.durableObjects && Object.keys(options2.durableObjects).length > 0) {
|
|
2095
|
+
workerConfig.durableObjects = options2.durableObjects;
|
|
2096
|
+
}
|
|
2097
|
+
if (options2.serviceBindings && Object.keys(options2.serviceBindings).length > 0) {
|
|
2098
|
+
workerConfig.serviceBindings = options2.serviceBindings;
|
|
2099
|
+
}
|
|
2100
|
+
return workerConfig;
|
|
2101
|
+
};
|
|
2102
|
+
const gatewayWorker = createWorkerConfig({
|
|
2103
|
+
name: "gateway",
|
|
2104
|
+
script: getGatewayScript(loadedConfig.wsRoutes, debug, shouldRunMainWorker ? INTERNAL_APP_SERVICE_BINDING : null),
|
|
2105
|
+
serviceBindings: shouldRunMainWorker ? createServiceBindings({
|
|
2106
|
+
[INTERNAL_APP_SERVICE_BINDING]: { name: appWorkerName }
|
|
2107
|
+
}) : createServiceBindings()
|
|
2108
|
+
});
|
|
2109
|
+
gatewayWorker.routes = ["*"];
|
|
2110
|
+
const hasDurableObjectBundles = !!doResult && doResult.bundles.size > 0;
|
|
2111
|
+
const browserBindingName = bindings.browser?.binding;
|
|
2112
|
+
const needsBrowserWorker = Boolean(browserBindingName && (hasDurableObjectBundles || shouldRunMainWorker));
|
|
2113
|
+
if (!shouldRunMainWorker && !hasDurableObjectBundles && !needsBrowserWorker) {
|
|
1703
2114
|
return {
|
|
1704
2115
|
...sharedOptions,
|
|
1705
2116
|
...gatewayWorker
|
|
@@ -1708,56 +2119,64 @@ function createDevServer(options) {
|
|
|
1708
2119
|
const workers = [];
|
|
1709
2120
|
const durableObjects = {};
|
|
1710
2121
|
const browserShimUrl = `http://127.0.0.1:${browserShimPort}`;
|
|
1711
|
-
const browserBindingName = bindings.browser?.binding;
|
|
1712
2122
|
const browserWorkerName = "browser-binding";
|
|
1713
|
-
|
|
1714
|
-
const
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
const
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
2123
|
+
if (shouldRunMainWorker && mainWorkerScriptPath) {
|
|
2124
|
+
const mainWorkerServiceBindings = createServiceBindings(browserBindingName ? {
|
|
2125
|
+
[browserBindingName]: { name: browserWorkerName }
|
|
2126
|
+
} : {});
|
|
2127
|
+
const mainWorkerConfig = createWorkerConfig({
|
|
2128
|
+
name: appWorkerName,
|
|
2129
|
+
scriptPath: bundledMainWorkerScriptPath ?? mainWorkerScriptPath,
|
|
2130
|
+
serviceBindings: mainWorkerServiceBindings,
|
|
2131
|
+
queueConsumers,
|
|
2132
|
+
triggers: loadedConfig.triggers?.crons?.length ? { crons: loadedConfig.triggers.crons } : undefined
|
|
2133
|
+
});
|
|
2134
|
+
workers.push(mainWorkerConfig);
|
|
2135
|
+
}
|
|
2136
|
+
if (doResult) {
|
|
2137
|
+
for (const [bindingName, bundlePath] of doResult.bundles) {
|
|
2138
|
+
const className = doResult.classes.get(bindingName);
|
|
2139
|
+
if (!className)
|
|
2140
|
+
continue;
|
|
2141
|
+
const workerName = `do-${bindingName.toLowerCase()}`;
|
|
2142
|
+
const workerConfig = createWorkerConfig({
|
|
2143
|
+
name: workerName,
|
|
2144
|
+
scriptPath: bundlePath,
|
|
2145
|
+
durableObjects: {
|
|
2146
|
+
[bindingName]: className
|
|
2147
|
+
},
|
|
2148
|
+
serviceBindings: createServiceBindings(browserBindingName ? {
|
|
2149
|
+
[browserBindingName]: { name: browserWorkerName }
|
|
2150
|
+
} : {})
|
|
2151
|
+
});
|
|
2152
|
+
if (browserBindingName) {
|
|
2153
|
+
logger?.debug(`DO ${workerName} has browser service binding: ${browserBindingName} → ${browserWorkerName}`);
|
|
1733
2154
|
}
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
2155
|
+
logger?.debug(`DO ${workerName} config:`, JSON.stringify(workerConfig, null, 2));
|
|
2156
|
+
workers.push(workerConfig);
|
|
2157
|
+
durableObjects[bindingName] = {
|
|
2158
|
+
className,
|
|
2159
|
+
scriptName: workerName
|
|
1739
2160
|
};
|
|
1740
|
-
logger?.debug(`DO ${workerName} has browser service binding: ${browserBindingName} → ${browserWorkerName}`);
|
|
1741
2161
|
}
|
|
1742
|
-
logger?.debug(`DO ${workerName} config:`, JSON.stringify(workerConfig, null, 2));
|
|
1743
|
-
workers.push(workerConfig);
|
|
1744
|
-
durableObjects[bindingName] = {
|
|
1745
|
-
className,
|
|
1746
|
-
scriptName: workerName
|
|
1747
|
-
};
|
|
1748
2162
|
}
|
|
1749
|
-
if (
|
|
1750
|
-
const browserWorker = {
|
|
2163
|
+
if (needsBrowserWorker) {
|
|
2164
|
+
const browserWorker = createWorkerConfig({
|
|
1751
2165
|
name: browserWorkerName,
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
compatibilityDate: config.compatibilityDate,
|
|
1755
|
-
compatibilityFlags: config.compatibilityFlags ?? []
|
|
1756
|
-
};
|
|
2166
|
+
script: getBrowserBindingScript(browserShimUrl, debug)
|
|
2167
|
+
});
|
|
1757
2168
|
workers.push(browserWorker);
|
|
1758
2169
|
logger?.info(`Browser binding worker configured: ${browserBindingName} → ${browserShimUrl}`);
|
|
1759
2170
|
}
|
|
1760
|
-
|
|
2171
|
+
if (Object.keys(durableObjects).length > 0) {
|
|
2172
|
+
gatewayWorker.durableObjects = durableObjects;
|
|
2173
|
+
if (shouldRunMainWorker) {
|
|
2174
|
+
const mainWorker = workers.find((worker) => worker.name === appWorkerName);
|
|
2175
|
+
if (mainWorker) {
|
|
2176
|
+
mainWorker.durableObjects = durableObjects;
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
1761
2180
|
return {
|
|
1762
2181
|
...sharedOptions,
|
|
1763
2182
|
workers: [gatewayWorker, ...workers]
|
|
@@ -1766,59 +2185,223 @@ function createDevServer(options) {
|
|
|
1766
2185
|
async function startMiniflare(doResult) {
|
|
1767
2186
|
const { Miniflare, Log, LogLevel } = await import("miniflare");
|
|
1768
2187
|
const mfConfig = buildMiniflareConfig(doResult);
|
|
1769
|
-
mfConfig.log =
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
logger?.info(
|
|
1782
|
-
|
|
1783
|
-
|
|
2188
|
+
mfConfig.log = createCompatibilityAwareMiniflareLog(Log, LogLevel.DEBUG, logger);
|
|
2189
|
+
mfConfig.handleRuntimeStdio = createRuntimeStdioForwarder(logger);
|
|
2190
|
+
const shouldLogMiniflareDiagnostics = verbose || debug;
|
|
2191
|
+
if (shouldLogMiniflareDiagnostics) {
|
|
2192
|
+
logger?.info("=== MINIFLARE CONFIG DEBUG ===");
|
|
2193
|
+
logger?.info("Full config:", JSON.stringify(mfConfig, (key, value) => {
|
|
2194
|
+
if (key === "script" && typeof value === "string" && value.length > 200) {
|
|
2195
|
+
return value.substring(0, 200) + "...[truncated]";
|
|
2196
|
+
}
|
|
2197
|
+
return value;
|
|
2198
|
+
}, 2));
|
|
2199
|
+
if (mfConfig.workers) {
|
|
2200
|
+
logger?.info("Workers order:");
|
|
2201
|
+
for (const w of mfConfig.workers) {
|
|
2202
|
+
logger?.info(` → ${w.name}:`);
|
|
2203
|
+
logger?.info(` script: ${w.script ? "inline" : w.scriptPath}`);
|
|
2204
|
+
logger?.info(` browserRendering: ${JSON.stringify(w.browserRendering)}`);
|
|
2205
|
+
logger?.info(` durableObjects: ${JSON.stringify(w.durableObjects)}`);
|
|
2206
|
+
}
|
|
1784
2207
|
}
|
|
1785
2208
|
}
|
|
1786
2209
|
miniflare = new Miniflare(mfConfig);
|
|
1787
2210
|
await miniflare.ready;
|
|
1788
2211
|
logger?.success(`Miniflare ready on http://localhost:${miniflarePort}`);
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
2212
|
+
if (shouldLogMiniflareDiagnostics) {
|
|
2213
|
+
try {
|
|
2214
|
+
const gatewayBindings = await miniflare.getBindings("gateway");
|
|
2215
|
+
logger?.info("Gateway worker bindings:", Object.keys(gatewayBindings));
|
|
2216
|
+
if (mfConfig.workers) {
|
|
2217
|
+
for (const w of mfConfig.workers) {
|
|
2218
|
+
if (w.name !== "gateway") {
|
|
2219
|
+
try {
|
|
2220
|
+
const doBindings = await miniflare.getBindings(w.name);
|
|
2221
|
+
logger?.info(`${w.name} worker bindings:`, Object.keys(doBindings));
|
|
2222
|
+
if ("BROWSER" in doBindings) {
|
|
2223
|
+
logger?.success(`${w.name} has BROWSER binding!`);
|
|
2224
|
+
} else {
|
|
2225
|
+
logger?.warn(`${w.name} is MISSING BROWSER binding`);
|
|
2226
|
+
}
|
|
2227
|
+
} catch (error) {
|
|
2228
|
+
logger?.debug(`Skipping binding diagnostics for ${w.name}: ${formatErrorMessage(error)}`);
|
|
1802
2229
|
}
|
|
1803
|
-
} catch (e) {
|
|
1804
|
-
logger?.warn(`Could not get bindings for ${w.name}:`, e);
|
|
1805
2230
|
}
|
|
1806
2231
|
}
|
|
1807
2232
|
}
|
|
2233
|
+
} catch (error) {
|
|
2234
|
+
logger?.debug(`Skipping Miniflare binding diagnostics: ${formatErrorMessage(error)}`);
|
|
1808
2235
|
}
|
|
1809
|
-
} catch (e) {
|
|
1810
|
-
logger?.warn("Error getting bindings:", e);
|
|
1811
2236
|
}
|
|
1812
2237
|
}
|
|
1813
2238
|
async function reloadMiniflare(doResult) {
|
|
1814
|
-
|
|
2239
|
+
currentDoResult = doResult;
|
|
2240
|
+
const queuedReload = reloadChain.then(async () => {
|
|
2241
|
+
if (!miniflare)
|
|
2242
|
+
return;
|
|
2243
|
+
const { Log, LogLevel } = await import("miniflare");
|
|
2244
|
+
const mfConfig = buildMiniflareConfig(currentDoResult);
|
|
2245
|
+
mfConfig.log = createCompatibilityAwareMiniflareLog(Log, LogLevel.DEBUG, logger);
|
|
2246
|
+
mfConfig.handleRuntimeStdio = createRuntimeStdioForwarder(logger);
|
|
2247
|
+
logger?.info("Reloading Miniflare...");
|
|
2248
|
+
await miniflare.setOptions(mfConfig);
|
|
2249
|
+
logger?.success("Miniflare reloaded");
|
|
2250
|
+
});
|
|
2251
|
+
reloadChain = queuedReload.catch(() => {});
|
|
2252
|
+
await queuedReload;
|
|
2253
|
+
}
|
|
2254
|
+
async function resolveWorkerConfigWatchPath() {
|
|
2255
|
+
if (configPath) {
|
|
2256
|
+
const explicitPath = resolve2(cwd, configPath);
|
|
2257
|
+
const fs = await import("node:fs/promises");
|
|
2258
|
+
try {
|
|
2259
|
+
await fs.access(explicitPath);
|
|
2260
|
+
return explicitPath;
|
|
2261
|
+
} catch {}
|
|
2262
|
+
}
|
|
2263
|
+
return await resolveConfigPath(cwd) ?? null;
|
|
2264
|
+
}
|
|
2265
|
+
async function refreshWorkerOnlySurfaceState() {
|
|
2266
|
+
if (!config) {
|
|
1815
2267
|
return;
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
2268
|
+
}
|
|
2269
|
+
mainWorkerSurfacePaths = await resolveMainWorkerSurfacePaths(cwd, config);
|
|
2270
|
+
mainWorkerRoutes = await discoverRoutes(cwd, config);
|
|
2271
|
+
const composedMainEntry = await prepareComposedWorkerEntrypoint(cwd, config, undefined, {
|
|
2272
|
+
devInternalEmail: true
|
|
2273
|
+
});
|
|
2274
|
+
mainWorkerScriptPath = composedMainEntry ? resolve2(cwd, composedMainEntry) : null;
|
|
2275
|
+
if (mainWorkerScriptPath) {
|
|
2276
|
+
await bundleMainWorker();
|
|
2277
|
+
} else {
|
|
2278
|
+
bundledMainWorkerScriptPath = null;
|
|
2279
|
+
}
|
|
2280
|
+
await syncWorkerWatchTargets();
|
|
2281
|
+
}
|
|
2282
|
+
function getWorkerWatchTargets() {
|
|
2283
|
+
if (enableVite || !config) {
|
|
2284
|
+
return [];
|
|
2285
|
+
}
|
|
2286
|
+
const targets = collectWorkerWatchRoots(cwd, config, mainWorkerSurfacePaths);
|
|
2287
|
+
if (resolvedWorkerConfigPath) {
|
|
2288
|
+
targets.push(resolvedWorkerConfigPath);
|
|
2289
|
+
}
|
|
2290
|
+
return [...new Set(targets)];
|
|
2291
|
+
}
|
|
2292
|
+
async function syncWorkerWatchTargets() {
|
|
2293
|
+
if (!workerSourceWatcher) {
|
|
2294
|
+
return;
|
|
2295
|
+
}
|
|
2296
|
+
const nextWatchTargets = getWorkerWatchTargets();
|
|
2297
|
+
const nextWatchTargetSet = new Set(nextWatchTargets);
|
|
2298
|
+
const targetsToRemove = workerWatchTargets.filter((target) => !nextWatchTargetSet.has(target));
|
|
2299
|
+
const targetsToAdd = nextWatchTargets.filter((target) => !workerWatchTargets.includes(target));
|
|
2300
|
+
if (targetsToRemove.length > 0) {
|
|
2301
|
+
await workerSourceWatcher.unwatch(targetsToRemove);
|
|
2302
|
+
}
|
|
2303
|
+
if (targetsToAdd.length > 0) {
|
|
2304
|
+
workerSourceWatcher.add(targetsToAdd);
|
|
2305
|
+
}
|
|
2306
|
+
workerWatchTargets = nextWatchTargets;
|
|
2307
|
+
}
|
|
2308
|
+
async function reloadWorkerOnlyConfig() {
|
|
2309
|
+
config = await loadConfig({ cwd, configFile: configPath });
|
|
2310
|
+
setLocalSendEmailBindings(config.bindings?.sendEmail ?? {});
|
|
2311
|
+
resolvedWorkerConfigPath = await resolveWorkerConfigWatchPath();
|
|
2312
|
+
await refreshWorkerOnlySurfaceState();
|
|
2313
|
+
await reloadMiniflare(currentDoResult);
|
|
2314
|
+
}
|
|
2315
|
+
async function startWorkerSourceWatcher() {
|
|
2316
|
+
if (enableVite || !config) {
|
|
2317
|
+
return;
|
|
2318
|
+
}
|
|
2319
|
+
const watchTargets = getWorkerWatchTargets();
|
|
2320
|
+
if (watchTargets.length === 0) {
|
|
2321
|
+
return;
|
|
2322
|
+
}
|
|
2323
|
+
const chokidar = await import("chokidar");
|
|
2324
|
+
const isWindows = process.platform === "win32";
|
|
2325
|
+
const ignoredSegments = ["/node_modules/", "/.git/", "/.devflare/", "/dist/"];
|
|
2326
|
+
const normalizePath = (filePath) => filePath.replace(/\\/g, "/");
|
|
2327
|
+
const isIgnoredPath = (filePath) => {
|
|
2328
|
+
const normalizedPath = normalizePath(filePath);
|
|
2329
|
+
return ignoredSegments.some((segment) => normalizedPath.includes(segment));
|
|
2330
|
+
};
|
|
2331
|
+
let reloadTimeout = null;
|
|
2332
|
+
let reloadInProgress = false;
|
|
2333
|
+
let pendingReloadPath = null;
|
|
2334
|
+
const triggerReload = async (filePath) => {
|
|
2335
|
+
if (reloadInProgress) {
|
|
2336
|
+
pendingReloadPath = filePath;
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2339
|
+
reloadInProgress = true;
|
|
2340
|
+
try {
|
|
2341
|
+
const normalizedConfigPath = resolvedWorkerConfigPath ? normalizePath(resolvedWorkerConfigPath) : null;
|
|
2342
|
+
if (normalizedConfigPath && normalizePath(filePath) === normalizedConfigPath) {
|
|
2343
|
+
logger?.info(`Devflare config changed: ${filePath}`);
|
|
2344
|
+
await reloadWorkerOnlyConfig();
|
|
2345
|
+
return;
|
|
2346
|
+
}
|
|
2347
|
+
logger?.info(`Worker source changed: ${filePath}`);
|
|
2348
|
+
await refreshWorkerOnlySurfaceState();
|
|
2349
|
+
await reloadMiniflare(currentDoResult);
|
|
2350
|
+
} catch (error) {
|
|
2351
|
+
logger?.error("Worker source reload failed:", error);
|
|
2352
|
+
} finally {
|
|
2353
|
+
reloadInProgress = false;
|
|
2354
|
+
if (pendingReloadPath) {
|
|
2355
|
+
const nextPath = pendingReloadPath;
|
|
2356
|
+
pendingReloadPath = null;
|
|
2357
|
+
await triggerReload(nextPath);
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
};
|
|
2361
|
+
const scheduleReload = (filePath) => {
|
|
2362
|
+
if (reloadTimeout) {
|
|
2363
|
+
clearTimeout(reloadTimeout);
|
|
2364
|
+
}
|
|
2365
|
+
reloadTimeout = setTimeout(() => {
|
|
2366
|
+
triggerReload(filePath);
|
|
2367
|
+
}, 150);
|
|
2368
|
+
};
|
|
2369
|
+
workerWatchTargets = watchTargets;
|
|
2370
|
+
workerSourceWatcher = chokidar.watch(watchTargets, {
|
|
2371
|
+
ignoreInitial: true,
|
|
2372
|
+
usePolling: isWindows,
|
|
2373
|
+
interval: isWindows ? 300 : undefined,
|
|
2374
|
+
awaitWriteFinish: {
|
|
2375
|
+
stabilityThreshold: 100,
|
|
2376
|
+
pollInterval: 50
|
|
2377
|
+
},
|
|
2378
|
+
ignored: (filePath) => isIgnoredPath(filePath)
|
|
2379
|
+
});
|
|
2380
|
+
const onFileEvent = (filePath) => {
|
|
2381
|
+
if (isIgnoredPath(filePath)) {
|
|
2382
|
+
return;
|
|
2383
|
+
}
|
|
2384
|
+
scheduleReload(filePath);
|
|
2385
|
+
};
|
|
2386
|
+
workerSourceWatcher.on("change", onFileEvent);
|
|
2387
|
+
workerSourceWatcher.on("add", onFileEvent);
|
|
2388
|
+
workerSourceWatcher.on("unlink", onFileEvent);
|
|
2389
|
+
workerSourceWatcher.on("error", (error) => {
|
|
2390
|
+
logger?.error("Worker source watcher error:", error);
|
|
2391
|
+
});
|
|
2392
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
2393
|
+
const handleReady = () => {
|
|
2394
|
+
workerSourceWatcher?.off("error", handleInitialError);
|
|
2395
|
+
logger?.info(`Worker source watcher ready (${watchTargets.length} target(s))`);
|
|
2396
|
+
resolvePromise();
|
|
2397
|
+
};
|
|
2398
|
+
const handleInitialError = (error) => {
|
|
2399
|
+
workerSourceWatcher?.off("ready", handleReady);
|
|
2400
|
+
rejectPromise(error instanceof Error ? error : new Error(String(error)));
|
|
2401
|
+
};
|
|
2402
|
+
workerSourceWatcher?.once("ready", handleReady);
|
|
2403
|
+
workerSourceWatcher?.once("error", handleInitialError);
|
|
2404
|
+
});
|
|
1822
2405
|
}
|
|
1823
2406
|
async function runD1Migrations() {
|
|
1824
2407
|
if (!miniflare || !config?.bindings?.d1)
|
|
@@ -1878,7 +2461,8 @@ function createDevServer(options) {
|
|
|
1878
2461
|
const args = ["vite", "dev", "--port", String(vitePort)];
|
|
1879
2462
|
viteProcess = spawn("bunx", args, {
|
|
1880
2463
|
cwd,
|
|
1881
|
-
stdio: "inherit",
|
|
2464
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
2465
|
+
windowsHide: true,
|
|
1882
2466
|
env: {
|
|
1883
2467
|
...process.env,
|
|
1884
2468
|
DEVFLARE_DEV: "true",
|
|
@@ -1886,12 +2470,34 @@ function createDevServer(options) {
|
|
|
1886
2470
|
FORCE_COLOR: "1"
|
|
1887
2471
|
}
|
|
1888
2472
|
});
|
|
1889
|
-
|
|
2473
|
+
const readyUrl = await waitForViteReady(viteProcess, {
|
|
2474
|
+
onStdout(chunk) {
|
|
2475
|
+
process.stdout.write(chunk);
|
|
2476
|
+
},
|
|
2477
|
+
onStderr(chunk) {
|
|
2478
|
+
process.stderr.write(chunk);
|
|
2479
|
+
}
|
|
2480
|
+
});
|
|
2481
|
+
if (readyUrl) {
|
|
2482
|
+
logger?.success(`Vite dev server started on ${readyUrl}`);
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
2485
|
+
logger?.warn("Vite process started, but the final local URL could not be confirmed yet");
|
|
1890
2486
|
}
|
|
1891
2487
|
async function start() {
|
|
1892
2488
|
logger?.info("Starting unified dev server...");
|
|
1893
2489
|
config = await loadConfig({ cwd, configFile: configPath });
|
|
2490
|
+
setLocalSendEmailBindings(config.bindings?.sendEmail ?? {});
|
|
2491
|
+
resolvedWorkerConfigPath = await resolveWorkerConfigWatchPath();
|
|
1894
2492
|
logger?.debug("Loaded config:", config.name);
|
|
2493
|
+
await refreshWorkerOnlySurfaceState();
|
|
2494
|
+
if (!enableVite && (hasWorkerSurfacePaths(mainWorkerSurfacePaths) || Boolean(mainWorkerRoutes?.routes.length))) {
|
|
2495
|
+
const detectedWorkerHandlers = Object.entries(mainWorkerSurfacePaths).filter(([, surfacePath]) => !!surfacePath).map(([surfaceName, surfacePath]) => `${surfaceName}=${surfacePath}`);
|
|
2496
|
+
const detectedRouteHandlers = mainWorkerRoutes?.routes.map((route) => `route=${route.filePath}`) ?? [];
|
|
2497
|
+
logger?.info(`Worker handlers detected: ${[...detectedWorkerHandlers, ...detectedRouteHandlers].join(", ")}`);
|
|
2498
|
+
} else if (!enableVite) {
|
|
2499
|
+
logger?.warn("No local worker handler entry was found for worker-only mode");
|
|
2500
|
+
}
|
|
1895
2501
|
const remoteCheck = await checkRemoteBindingRequirements(config);
|
|
1896
2502
|
if (remoteCheck.hasRemoteBindings) {
|
|
1897
2503
|
logger?.info("");
|
|
@@ -1936,43 +2542,51 @@ function createDevServer(options) {
|
|
|
1936
2542
|
cwd,
|
|
1937
2543
|
pattern: doPattern,
|
|
1938
2544
|
outDir,
|
|
2545
|
+
rolldownOptions: config.rolldown?.options,
|
|
2546
|
+
sourcemap: config.rolldown?.sourcemap,
|
|
2547
|
+
minify: config.rolldown?.minify,
|
|
1939
2548
|
logger,
|
|
1940
2549
|
onRebuild: async (result) => {
|
|
1941
2550
|
await reloadMiniflare(result);
|
|
1942
2551
|
}
|
|
1943
2552
|
});
|
|
1944
2553
|
doResult = await doBundler.build();
|
|
2554
|
+
currentDoResult = doResult;
|
|
1945
2555
|
await doBundler.watch();
|
|
1946
2556
|
}
|
|
2557
|
+
currentDoResult = doResult;
|
|
1947
2558
|
await startMiniflare(doResult);
|
|
1948
|
-
await
|
|
2559
|
+
await startWorkerSourceWatcher();
|
|
2560
|
+
if (enableVite) {
|
|
2561
|
+
await startVite();
|
|
2562
|
+
} else {
|
|
2563
|
+
logger?.info("Vite startup skipped (no local vite.config.* found for this package)");
|
|
2564
|
+
}
|
|
1949
2565
|
await new Promise((r) => setTimeout(r, 1000));
|
|
1950
2566
|
await runD1Migrations();
|
|
1951
|
-
const cleanup = async () => {
|
|
1952
|
-
logger?.info("Shutting down...");
|
|
1953
|
-
await stop();
|
|
1954
|
-
process.exit(0);
|
|
1955
|
-
};
|
|
1956
|
-
process.on("SIGINT", cleanup);
|
|
1957
|
-
process.on("SIGTERM", cleanup);
|
|
1958
2567
|
}
|
|
1959
2568
|
async function stop() {
|
|
1960
2569
|
if (doBundler) {
|
|
1961
2570
|
await doBundler.close();
|
|
1962
2571
|
doBundler = null;
|
|
1963
2572
|
}
|
|
2573
|
+
if (workerSourceWatcher) {
|
|
2574
|
+
await workerSourceWatcher.close();
|
|
2575
|
+
workerSourceWatcher = null;
|
|
2576
|
+
}
|
|
1964
2577
|
if (miniflare) {
|
|
1965
2578
|
await miniflare.dispose();
|
|
1966
2579
|
miniflare = null;
|
|
1967
2580
|
}
|
|
1968
2581
|
if (viteProcess) {
|
|
1969
|
-
viteProcess
|
|
2582
|
+
await stopSpawnedProcessTree(viteProcess);
|
|
1970
2583
|
viteProcess = null;
|
|
1971
2584
|
}
|
|
1972
2585
|
if (browserShim) {
|
|
1973
2586
|
await browserShim.stop();
|
|
1974
2587
|
browserShim = null;
|
|
1975
2588
|
}
|
|
2589
|
+
clearLocalSendEmailBindings();
|
|
1976
2590
|
}
|
|
1977
2591
|
function getMiniflare() {
|
|
1978
2592
|
return miniflare;
|
|
@@ -2000,6 +2614,7 @@ async function createLogWriter(cwd, options) {
|
|
|
2000
2614
|
const fileStream = fs.createWriteStream(logPath, { flags: "w" });
|
|
2001
2615
|
const ansiRegex = /\x1b\[[0-9;]*m/g;
|
|
2002
2616
|
return {
|
|
2617
|
+
path: logPath,
|
|
2003
2618
|
write(data, source) {
|
|
2004
2619
|
const str = typeof data === "string" ? data : data.toString();
|
|
2005
2620
|
if (!str.trim())
|
|
@@ -2025,12 +2640,13 @@ async function runDevCommand(parsed, logger, options) {
|
|
|
2025
2640
|
const persistEnabled = parsed.options.persist === true;
|
|
2026
2641
|
const debugEnabled = parsed.options.debug === true || process.env.DEVFLARE_DEBUG === "true";
|
|
2027
2642
|
const verbose = parsed.options.verbose === true || debugEnabled;
|
|
2643
|
+
const viteProject = await detectViteProject(cwd);
|
|
2028
2644
|
const logWriter = await createLogWriter(cwd, {
|
|
2029
2645
|
log: logEnabled,
|
|
2030
2646
|
logTemp: logTempEnabled
|
|
2031
2647
|
});
|
|
2032
2648
|
if (logWriter) {
|
|
2033
|
-
const logFile =
|
|
2649
|
+
const logFile = relative2(cwd, logWriter.path) || ".log";
|
|
2034
2650
|
logger.info(`\uD83D\uDCDD Logging enabled → ${logFile}`);
|
|
2035
2651
|
}
|
|
2036
2652
|
const devLogger = createConsola({
|
|
@@ -2044,6 +2660,7 @@ async function runDevCommand(parsed, logger, options) {
|
|
|
2044
2660
|
logWriter.write(formatted);
|
|
2045
2661
|
};
|
|
2046
2662
|
};
|
|
2663
|
+
Object.assign(devLogger.log, wrapLog(devLogger.log.bind(devLogger)));
|
|
2047
2664
|
Object.assign(devLogger.info, wrapLog(devLogger.info.bind(devLogger)));
|
|
2048
2665
|
Object.assign(devLogger.error, wrapLog(devLogger.error.bind(devLogger), "[ERROR]"));
|
|
2049
2666
|
Object.assign(devLogger.warn, wrapLog(devLogger.warn.bind(devLogger), "[WARN]"));
|
|
@@ -2052,42 +2669,91 @@ async function runDevCommand(parsed, logger, options) {
|
|
|
2052
2669
|
}
|
|
2053
2670
|
try {
|
|
2054
2671
|
logger.info("");
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2672
|
+
if (viteProject.shouldStartVite) {
|
|
2673
|
+
logger.info("\uD83D\uDE80 Devflare Unified Dev Server");
|
|
2674
|
+
logger.info(" ├─ Vite: Full HMR for frontend");
|
|
2675
|
+
logger.info(" ├─ Miniflare: All Cloudflare bindings");
|
|
2676
|
+
logger.info(" ├─ Rolldown: Fast DO bundling with watch");
|
|
2677
|
+
logger.info(" └─ Bridge: WebSocket RPC connection");
|
|
2678
|
+
} else {
|
|
2679
|
+
logger.info("\uD83D\uDE80 Devflare Worker Dev Server");
|
|
2680
|
+
logger.info(" ├─ Miniflare: All Cloudflare bindings");
|
|
2681
|
+
logger.info(" ├─ Rolldown: Fast DO bundling with watch");
|
|
2682
|
+
logger.info(" └─ Vite: Disabled (no local vite.config.* found)");
|
|
2683
|
+
if (viteProject.wantsViteIntegration) {
|
|
2684
|
+
logger.warn("Vite-related dependencies were found, but no local vite.config.* was detected");
|
|
2685
|
+
logger.warn("Skipping Vite startup and running in worker-only mode");
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2060
2688
|
logger.info("");
|
|
2061
2689
|
const devServer = createDevServer({
|
|
2062
2690
|
cwd,
|
|
2063
2691
|
configPath,
|
|
2064
2692
|
vitePort: port ? parseInt(port, 10) : 5173,
|
|
2065
2693
|
miniflarePort: 8787,
|
|
2694
|
+
enableVite: viteProject.shouldStartVite,
|
|
2066
2695
|
persist: persistEnabled,
|
|
2067
2696
|
logger: devLogger,
|
|
2068
2697
|
verbose,
|
|
2069
2698
|
debug: debugEnabled
|
|
2070
2699
|
});
|
|
2071
|
-
|
|
2700
|
+
let isCleaningUp = false;
|
|
2701
|
+
const cleanupHandlers = new Map;
|
|
2702
|
+
const removeCleanupHandlers = () => {
|
|
2703
|
+
for (const [event, handler] of cleanupHandlers) {
|
|
2704
|
+
process.off(event, handler);
|
|
2705
|
+
}
|
|
2706
|
+
cleanupHandlers.clear();
|
|
2707
|
+
};
|
|
2708
|
+
const cleanup = async (exitCode, reason) => {
|
|
2709
|
+
if (isCleaningUp) {
|
|
2710
|
+
return;
|
|
2711
|
+
}
|
|
2712
|
+
isCleaningUp = true;
|
|
2713
|
+
removeCleanupHandlers();
|
|
2714
|
+
if (reason) {
|
|
2715
|
+
const message = reason instanceof Error ? reason.stack ?? reason.message : String(reason);
|
|
2716
|
+
logger.error(message);
|
|
2717
|
+
}
|
|
2072
2718
|
logger.info("");
|
|
2073
2719
|
logger.info("Shutting down...");
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2720
|
+
try {
|
|
2721
|
+
await devServer.stop();
|
|
2722
|
+
} finally {
|
|
2723
|
+
logWriter?.close();
|
|
2724
|
+
process.exit(exitCode);
|
|
2725
|
+
}
|
|
2077
2726
|
};
|
|
2078
|
-
|
|
2079
|
-
|
|
2727
|
+
const registerCleanupHandler = (event, handler) => {
|
|
2728
|
+
cleanupHandlers.set(event, handler);
|
|
2729
|
+
process.on(event, handler);
|
|
2730
|
+
};
|
|
2731
|
+
registerCleanupHandler("SIGINT", () => {
|
|
2732
|
+
cleanup(0);
|
|
2733
|
+
});
|
|
2734
|
+
registerCleanupHandler("SIGTERM", () => {
|
|
2735
|
+
cleanup(0);
|
|
2736
|
+
});
|
|
2737
|
+
registerCleanupHandler("SIGHUP", () => {
|
|
2738
|
+
cleanup(0);
|
|
2739
|
+
});
|
|
2740
|
+
registerCleanupHandler("uncaughtException", (error) => {
|
|
2741
|
+
cleanup(1, error);
|
|
2742
|
+
});
|
|
2743
|
+
registerCleanupHandler("unhandledRejection", (reason) => {
|
|
2744
|
+
cleanup(1, reason);
|
|
2745
|
+
});
|
|
2080
2746
|
await devServer.start();
|
|
2081
2747
|
await new Promise(() => {});
|
|
2082
2748
|
return { exitCode: 0 };
|
|
2083
2749
|
} catch (error) {
|
|
2750
|
+
logWriter?.close();
|
|
2084
2751
|
if (error instanceof Error) {
|
|
2085
2752
|
logger.error("Dev server failed:", error.message);
|
|
2086
2753
|
if (verbose) {
|
|
2087
2754
|
logger.error(error.stack);
|
|
2088
2755
|
}
|
|
2089
2756
|
}
|
|
2090
|
-
logWriter?.close();
|
|
2091
2757
|
return { exitCode: 1 };
|
|
2092
2758
|
}
|
|
2093
2759
|
}
|