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.
Files changed (123) hide show
  1. package/LLM.md +775 -637
  2. package/R2.md +200 -0
  3. package/README.md +285 -514
  4. package/bin/devflare.js +8 -8
  5. package/dist/{account-rvrj687w.js → account-8psavtg6.js} +27 -4
  6. package/dist/bridge/miniflare.d.ts +6 -0
  7. package/dist/bridge/miniflare.d.ts.map +1 -1
  8. package/dist/bridge/proxy.d.ts +5 -6
  9. package/dist/bridge/proxy.d.ts.map +1 -1
  10. package/dist/bridge/server.d.ts.map +1 -1
  11. package/dist/browser.d.ts +50 -0
  12. package/dist/browser.d.ts.map +1 -0
  13. package/dist/{build-mnf6v8gd.js → build-k36xrzvy.js} +26 -7
  14. package/dist/bundler/do-bundler.d.ts +7 -0
  15. package/dist/bundler/do-bundler.d.ts.map +1 -1
  16. package/dist/cli/commands/account.d.ts.map +1 -1
  17. package/dist/cli/commands/build.d.ts.map +1 -1
  18. package/dist/cli/commands/deploy.d.ts.map +1 -1
  19. package/dist/cli/commands/dev.d.ts.map +1 -1
  20. package/dist/cli/commands/doctor.d.ts.map +1 -1
  21. package/dist/cli/commands/init.d.ts.map +1 -1
  22. package/dist/cli/commands/types.d.ts.map +1 -1
  23. package/dist/cli/config-path.d.ts +5 -0
  24. package/dist/cli/config-path.d.ts.map +1 -0
  25. package/dist/cli/index.d.ts.map +1 -1
  26. package/dist/cli/package-metadata.d.ts +16 -0
  27. package/dist/cli/package-metadata.d.ts.map +1 -0
  28. package/dist/config/compiler.d.ts +7 -0
  29. package/dist/config/compiler.d.ts.map +1 -1
  30. package/dist/config/index.d.ts +1 -1
  31. package/dist/config/index.d.ts.map +1 -1
  32. package/dist/config/schema.d.ts +2575 -1221
  33. package/dist/config/schema.d.ts.map +1 -1
  34. package/dist/{deploy-nhceck39.js → deploy-dbvfq8vq.js} +33 -15
  35. package/dist/{dev-qnxet3j9.js → dev-rk8p6pse.js} +900 -234
  36. package/dist/dev-server/miniflare-log.d.ts +12 -0
  37. package/dist/dev-server/miniflare-log.d.ts.map +1 -0
  38. package/dist/dev-server/runtime-stdio.d.ts +8 -0
  39. package/dist/dev-server/runtime-stdio.d.ts.map +1 -0
  40. package/dist/dev-server/server.d.ts +2 -0
  41. package/dist/dev-server/server.d.ts.map +1 -1
  42. package/dist/dev-server/vite-utils.d.ts +37 -0
  43. package/dist/dev-server/vite-utils.d.ts.map +1 -0
  44. package/dist/{doctor-e8fy6fj5.js → doctor-06y8nxd4.js} +73 -50
  45. package/dist/{durable-object-t4kbb0yt.js → durable-object-yt8v1dyn.js} +1 -1
  46. package/dist/index-05fyzwne.js +195 -0
  47. package/dist/index-1p814k7s.js +227 -0
  48. package/dist/{index-hcex3rgh.js → index-1phx14av.js} +84 -7
  49. package/dist/{index-tk6ej9dj.js → index-2q3pmzrx.js} +12 -16
  50. package/dist/{index-pf5s73n9.js → index-59df49vn.js} +11 -281
  51. package/dist/index-5yxg30va.js +304 -0
  52. package/dist/index-62b3gt2g.js +12 -0
  53. package/dist/index-6h8xbs75.js +44 -0
  54. package/dist/{index-67qcae0f.js → index-6v3wjg1r.js} +16 -1
  55. package/dist/index-8gtqgb3q.js +529 -0
  56. package/dist/{index-gz1gndna.js → index-9wt9x09k.js} +42 -62
  57. package/dist/index-fef08w43.js +231 -0
  58. package/dist/{index-ep3445yc.js → index-jht2j546.js} +393 -170
  59. package/dist/index-k7r18na8.js +0 -0
  60. package/dist/{index-m2q41jwa.js → index-n932ytmq.js} +9 -1
  61. package/dist/index-pwgyy2q9.js +39 -0
  62. package/dist/{index-07q6yxyc.js → index-v8vvsn9x.js} +1 -0
  63. package/dist/index-vky23txa.js +70 -0
  64. package/dist/index-vs49yxn4.js +322 -0
  65. package/dist/{index-z14anrqp.js → index-wfbfz02q.js} +14 -15
  66. package/dist/index-ws68xvq2.js +311 -0
  67. package/dist/index-y1d8za14.js +196 -0
  68. package/dist/{init-f9mgmew3.js → init-na2atvz2.js} +42 -55
  69. package/dist/router/types.d.ts +24 -0
  70. package/dist/router/types.d.ts.map +1 -0
  71. package/dist/runtime/context.d.ts +249 -8
  72. package/dist/runtime/context.d.ts.map +1 -1
  73. package/dist/runtime/exports.d.ts +50 -55
  74. package/dist/runtime/exports.d.ts.map +1 -1
  75. package/dist/runtime/index.d.ts +8 -1
  76. package/dist/runtime/index.d.ts.map +1 -1
  77. package/dist/runtime/middleware.d.ts +77 -60
  78. package/dist/runtime/middleware.d.ts.map +1 -1
  79. package/dist/runtime/router.d.ts +7 -0
  80. package/dist/runtime/router.d.ts.map +1 -0
  81. package/dist/runtime/validation.d.ts +1 -1
  82. package/dist/runtime/validation.d.ts.map +1 -1
  83. package/dist/src/browser.js +150 -0
  84. package/dist/src/cli/index.js +10 -0
  85. package/dist/{cloudflare → src/cloudflare}/index.js +3 -3
  86. package/dist/{decorators → src/decorators}/index.js +2 -2
  87. package/dist/src/index.js +132 -0
  88. package/dist/src/runtime/index.js +111 -0
  89. package/dist/{sveltekit → src/sveltekit}/index.js +14 -6
  90. package/dist/{test → src/test}/index.js +22 -13
  91. package/dist/{vite → src/vite}/index.js +128 -59
  92. package/dist/sveltekit/platform.d.ts.map +1 -1
  93. package/dist/test/bridge-context.d.ts +5 -2
  94. package/dist/test/bridge-context.d.ts.map +1 -1
  95. package/dist/test/cf.d.ts +25 -11
  96. package/dist/test/cf.d.ts.map +1 -1
  97. package/dist/test/email.d.ts +16 -7
  98. package/dist/test/email.d.ts.map +1 -1
  99. package/dist/test/queue.d.ts.map +1 -1
  100. package/dist/test/resolve-service-bindings.d.ts.map +1 -1
  101. package/dist/test/scheduled.d.ts.map +1 -1
  102. package/dist/test/simple-context.d.ts +1 -1
  103. package/dist/test/simple-context.d.ts.map +1 -1
  104. package/dist/test/tail.d.ts +2 -1
  105. package/dist/test/tail.d.ts.map +1 -1
  106. package/dist/test/worker.d.ts +6 -0
  107. package/dist/test/worker.d.ts.map +1 -1
  108. package/dist/transform/durable-object.d.ts.map +1 -1
  109. package/dist/transform/worker-entrypoint.d.ts.map +1 -1
  110. package/dist/{types-5nyrz1sz.js → types-x9q7t491.js} +30 -16
  111. package/dist/utils/entrypoint-discovery.d.ts +6 -3
  112. package/dist/utils/entrypoint-discovery.d.ts.map +1 -1
  113. package/dist/utils/send-email.d.ts +15 -0
  114. package/dist/utils/send-email.d.ts.map +1 -0
  115. package/dist/vite/plugin.d.ts.map +1 -1
  116. package/dist/worker-entry/composed-worker.d.ts +13 -0
  117. package/dist/worker-entry/composed-worker.d.ts.map +1 -0
  118. package/dist/worker-entry/routes.d.ts +22 -0
  119. package/dist/worker-entry/routes.d.ts.map +1 -0
  120. package/dist/{worker-entrypoint-m9th0rg0.js → worker-entrypoint-c259fmfs.js} +1 -1
  121. package/package.json +21 -19
  122. package/dist/index.js +0 -298
  123. 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
- findDurableObjectClasses
6
- } from "./index-gz1gndna.js";
21
+ clearLocalSendEmailBindings,
22
+ setLocalSendEmailBindings
23
+ } from "./index-fef08w43.js";
7
24
  import {
8
- loadConfig
9
- } from "./index-hcex3rgh.js";
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
- async function bundleDOFile(sourcePath, className, outDir, cwd) {
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 cleanedCode = stripDecoratorSyntax(sourceCode);
64
- const entryCode = `${cleanedCode}
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(outDir, `_temp_${className}.ts`);
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
- await bundle.write({
133
- file: outFile,
134
- format: "esm",
135
- sourcemap: false,
136
- inlineDynamicImports: true
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
- function getGatewayScript(wsRoutes = [], debug = false) {
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
- // Parse headers from raw email for the Headers object
1203
- const headerLines = []
1204
- const lines = rawBody.split(/\\r?\\n/)
1205
- let bodyStart = 0
1206
- for (let i = 0; i < lines.length; i++) {
1207
- if (lines[i].trim() === '') {
1208
- bodyStart = i + 1
1209
- break
1210
- }
1211
- headerLines.push(lines[i])
1212
- }
1213
-
1214
- const headers = new Headers()
1215
- for (const line of headerLines) {
1216
- const colonIdx = line.indexOf(':')
1217
- if (colonIdx > 0) {
1218
- const key = line.slice(0, colonIdx).trim()
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
- log('Email send:', { from: params[0]?.from, to: params[0]?.to })
1511
- // In local dev, we just log the email - Miniflare handles writing to file
1762
+ const message = params[0]
1763
+ log('Email send:', { from: message?.from, to: message?.to })
1512
1764
  if (binding && typeof binding.send === 'function') {
1513
- return binding.send(params[0])
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 bindings = config.bindings ?? {};
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 gatewayWorker = {
1691
- name: "gateway",
1692
- modules: true,
1693
- script: getGatewayScript(config.wsRoutes, debug),
1694
- compatibilityDate: config.compatibilityDate,
1695
- compatibilityFlags: config.compatibilityFlags ?? [],
1696
- routes: ["*"],
1697
- kvNamespaces: bindings.kv ? bindings.kv : undefined,
1698
- r2Buckets: bindings.r2 ? bindings.r2 : undefined,
1699
- d1Databases: bindings.d1 ? bindings.d1 : undefined,
1700
- bindings: config.vars
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
- if (!doResult || doResult.bundles.size === 0) {
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
- for (const [bindingName, bundlePath] of doResult.bundles) {
1714
- const className = doResult.classes.get(bindingName);
1715
- if (!className)
1716
- continue;
1717
- const workerName = `do-${bindingName.toLowerCase()}`;
1718
- const baseFlags = config.compatibilityFlags ?? [];
1719
- const compatFlags = baseFlags.includes("nodejs_compat") ? baseFlags : [...baseFlags, "nodejs_compat"];
1720
- const workerConfig = {
1721
- name: workerName,
1722
- modules: true,
1723
- modulesRoot: cwd,
1724
- modulesRules: [
1725
- { type: "CommonJS", include: ["**/*.js", "**/*.cjs"] },
1726
- { type: "ESModule", include: ["**/*.mjs"] }
1727
- ],
1728
- scriptPath: bundlePath,
1729
- compatibilityDate: config.compatibilityDate,
1730
- compatibilityFlags: compatFlags,
1731
- durableObjects: {
1732
- [bindingName]: className
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
- if (browserBindingName) {
1736
- workerConfig.serviceBindings = {
1737
- ...workerConfig.serviceBindings,
1738
- [browserBindingName]: browserWorkerName
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 (browserBindingName) {
1750
- const browserWorker = {
2163
+ if (needsBrowserWorker) {
2164
+ const browserWorker = createWorkerConfig({
1751
2165
  name: browserWorkerName,
1752
- modules: true,
1753
- script: getBrowserBindingScript(browserShimUrl, debug),
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
- gatewayWorker.durableObjects = durableObjects;
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 = new Log(LogLevel.DEBUG);
1770
- logger?.info("=== MINIFLARE CONFIG DEBUG ===");
1771
- logger?.info("Full config:", JSON.stringify(mfConfig, (key, value) => {
1772
- if (key === "script" && typeof value === "string" && value.length > 200) {
1773
- return value.substring(0, 200) + "...[truncated]";
1774
- }
1775
- return value;
1776
- }, 2));
1777
- if (mfConfig.workers) {
1778
- logger?.info("Workers order:");
1779
- for (const w of mfConfig.workers) {
1780
- logger?.info(` → ${w.name}:`);
1781
- logger?.info(` script: ${w.script ? "inline" : w.scriptPath}`);
1782
- logger?.info(` browserRendering: ${JSON.stringify(w.browserRendering)}`);
1783
- logger?.info(` durableObjects: ${JSON.stringify(w.durableObjects)}`);
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
- try {
1790
- const gatewayBindings = await miniflare.getBindings("gateway");
1791
- logger?.info("Gateway worker bindings:", Object.keys(gatewayBindings));
1792
- if (mfConfig.workers) {
1793
- for (const w of mfConfig.workers) {
1794
- if (w.name !== "gateway") {
1795
- try {
1796
- const doBindings = await miniflare.getBindings(w.name);
1797
- logger?.info(`${w.name} worker bindings:`, Object.keys(doBindings));
1798
- if ("BROWSER" in doBindings) {
1799
- logger?.success(`${w.name} has BROWSER binding!`);
1800
- } else {
1801
- logger?.warn(`${w.name} is MISSING BROWSER binding`);
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
- if (!miniflare)
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
- const { Log, LogLevel } = await import("miniflare");
1817
- const mfConfig = buildMiniflareConfig(doResult);
1818
- mfConfig.log = new Log(LogLevel.DEBUG);
1819
- logger?.info("Reloading Miniflare with updated DOs...");
1820
- await miniflare.setOptions(mfConfig);
1821
- logger?.success("Miniflare reloaded");
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
- logger?.success(`Vite dev server started on http://localhost:${vitePort}`);
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 startVite();
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.kill("SIGTERM");
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 = logTempEnabled ? ".log" : `.log-{datetime}`;
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
- logger.info("\uD83D\uDE80 Devflare Unified Dev Server");
2056
- logger.info(" ├─ Vite: Full HMR for frontend");
2057
- logger.info(" ├─ Miniflare: All Cloudflare bindings");
2058
- logger.info(" ├─ Rolldown: Fast DO bundling with watch");
2059
- logger.info(" └─ Bridge: WebSocket RPC connection");
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
- const cleanup = async () => {
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
- await devServer.stop();
2075
- logWriter?.close();
2076
- process.exit(0);
2720
+ try {
2721
+ await devServer.stop();
2722
+ } finally {
2723
+ logWriter?.close();
2724
+ process.exit(exitCode);
2725
+ }
2077
2726
  };
2078
- process.on("SIGINT", cleanup);
2079
- process.on("SIGTERM", cleanup);
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
  }