devflare 1.0.0-next.6 → 1.0.0-next.8

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.
@@ -10,7 +10,8 @@ import {
10
10
  findDurableObjectClasses
11
11
  } from "./index-gz1gndna.js";
12
12
  import {
13
- loadConfig
13
+ loadConfig,
14
+ resolveConfigPath
14
15
  } from "./index-hcex3rgh.js";
15
16
  import {
16
17
  __require
@@ -21,7 +22,7 @@ import { createConsola } from "consola";
21
22
  import { resolve as resolve3 } from "pathe";
22
23
 
23
24
  // src/dev-server/server.ts
24
- import { dirname as dirname2, resolve as resolve2 } from "pathe";
25
+ import { dirname as dirname2, relative as relative2, resolve as resolve2 } from "pathe";
25
26
 
26
27
  // src/bundler/do-bundler.ts
27
28
  import { resolve, dirname, relative } from "pathe";
@@ -1092,6 +1093,40 @@ async function checkRemoteBindingRequirements(config) {
1092
1093
  };
1093
1094
  }
1094
1095
 
1096
+ // src/dev-server/miniflare-log.ts
1097
+ var ANSI_ESCAPE_REGEX = /\u001B\[[0-9;]*m/g;
1098
+ var COMPATIBILITY_DATE_FALLBACK_REGEX = /^The latest compatibility date supported by the installed Cloudflare Workers Runtime is "([^"]+)", but you've requested "([^"]+)"\. Falling back to "([^"]+)"\.\.\.$/;
1099
+ function normalizeMiniflareMessage(message) {
1100
+ return message.replace(ANSI_ESCAPE_REGEX, "").replace(/\s+/g, " ").trim();
1101
+ }
1102
+ function formatCompatibilityDateFallbackNotice(message) {
1103
+ const normalizedMessage = normalizeMiniflareMessage(message);
1104
+ const match = COMPATIBILITY_DATE_FALLBACK_REGEX.exec(normalizedMessage);
1105
+ if (!match) {
1106
+ return null;
1107
+ }
1108
+ const [, _supportedDate, requestedDate, fallbackDate] = match;
1109
+ return `Using latest supported Cloudflare Workers Runtime compatibility date ${fallbackDate} (requested ${requestedDate})`;
1110
+ }
1111
+ function createCompatibilityAwareMiniflareLog(BaseLog, level, logger) {
1112
+ const log = new BaseLog(level);
1113
+ const originalWarn = log.warn.bind(log);
1114
+ const originalInfo = log.info.bind(log);
1115
+ log.warn = (message) => {
1116
+ const notice = formatCompatibilityDateFallbackNotice(message);
1117
+ if (!notice) {
1118
+ originalWarn(message);
1119
+ return;
1120
+ }
1121
+ if (logger) {
1122
+ logger.info(notice);
1123
+ return;
1124
+ }
1125
+ originalInfo(notice);
1126
+ };
1127
+ return log;
1128
+ }
1129
+
1095
1130
  // src/dev-server/server.ts
1096
1131
  var DEFAULT_FETCH_ENTRY_FILES = [
1097
1132
  "src/fetch.ts",
@@ -1099,17 +1134,38 @@ var DEFAULT_FETCH_ENTRY_FILES = [
1099
1134
  "src/fetch.mts",
1100
1135
  "src/fetch.mjs"
1101
1136
  ];
1137
+ var DEFAULT_QUEUE_ENTRY_FILES = [
1138
+ "src/queue.ts",
1139
+ "src/queue.js",
1140
+ "src/queue.mts",
1141
+ "src/queue.mjs"
1142
+ ];
1143
+ var DEFAULT_SCHEDULED_ENTRY_FILES = [
1144
+ "src/scheduled.ts",
1145
+ "src/scheduled.js",
1146
+ "src/scheduled.mts",
1147
+ "src/scheduled.mjs"
1148
+ ];
1149
+ var DEFAULT_EMAIL_ENTRY_FILES = [
1150
+ "src/email.ts",
1151
+ "src/email.js",
1152
+ "src/email.mts",
1153
+ "src/email.mjs"
1154
+ ];
1102
1155
  var INTERNAL_APP_SERVICE_BINDING = "__DEVFLARE_APP";
1103
- async function resolveMainWorkerScriptPath(cwd, config) {
1104
- if (config.files?.fetch === false) {
1156
+ function formatErrorMessage(error) {
1157
+ return error instanceof Error ? error.message : String(error);
1158
+ }
1159
+ async function resolveWorkerHandlerPath(cwd, configuredPath, defaultEntries) {
1160
+ if (configuredPath === false) {
1105
1161
  return null;
1106
1162
  }
1107
1163
  const fs = await import("node:fs/promises");
1108
1164
  const candidates = new Set;
1109
- if (typeof config.files?.fetch === "string" && config.files.fetch) {
1110
- candidates.add(config.files.fetch);
1165
+ if (typeof configuredPath === "string" && configuredPath) {
1166
+ candidates.add(configuredPath);
1111
1167
  }
1112
- for (const defaultEntry of DEFAULT_FETCH_ENTRY_FILES) {
1168
+ for (const defaultEntry of defaultEntries) {
1113
1169
  candidates.add(defaultEntry);
1114
1170
  }
1115
1171
  for (const candidate of candidates) {
@@ -1123,7 +1179,214 @@ async function resolveMainWorkerScriptPath(cwd, config) {
1123
1179
  }
1124
1180
  return null;
1125
1181
  }
1126
- function collectWorkerWatchRoots(cwd, config, mainWorkerScriptPath) {
1182
+ async function resolveMainWorkerSurfacePaths(cwd, config) {
1183
+ return {
1184
+ fetch: await resolveWorkerHandlerPath(cwd, config.files?.fetch, DEFAULT_FETCH_ENTRY_FILES),
1185
+ queue: await resolveWorkerHandlerPath(cwd, config.files?.queue, DEFAULT_QUEUE_ENTRY_FILES),
1186
+ scheduled: await resolveWorkerHandlerPath(cwd, config.files?.scheduled, DEFAULT_SCHEDULED_ENTRY_FILES),
1187
+ email: await resolveWorkerHandlerPath(cwd, config.files?.email, DEFAULT_EMAIL_ENTRY_FILES)
1188
+ };
1189
+ }
1190
+ function getFirstWorkerSurfacePath(surfacePaths) {
1191
+ return surfacePaths.fetch ?? surfacePaths.queue ?? surfacePaths.scheduled ?? surfacePaths.email;
1192
+ }
1193
+ function hasWorkerSurfacePaths(surfacePaths) {
1194
+ return Object.values(surfacePaths).some((surfacePath) => typeof surfacePath === "string" && surfacePath.length > 0);
1195
+ }
1196
+ function toImportSpecifier(fromFilePath, toFilePath) {
1197
+ const specifier = relative2(dirname2(fromFilePath), toFilePath).replace(/\\/g, "/");
1198
+ return specifier.startsWith(".") ? specifier : `./${specifier}`;
1199
+ }
1200
+ function getMainWorkerEntryScript(surfaceImportPaths) {
1201
+ const importLines = [`import { runWithContext } from 'devflare/runtime'`];
1202
+ const moduleFallbackLines = [];
1203
+ const registerSurfaceModule = (identifier, importPath) => {
1204
+ if (importPath) {
1205
+ importLines.push(`import * as ${identifier} from '${importPath}'`);
1206
+ return;
1207
+ }
1208
+ moduleFallbackLines.push(`const ${identifier} = {}`);
1209
+ };
1210
+ registerSurfaceModule("__devflareFetchModule", surfaceImportPaths.fetch);
1211
+ registerSurfaceModule("__devflareQueueModule", surfaceImportPaths.queue);
1212
+ registerSurfaceModule("__devflareScheduledModule", surfaceImportPaths.scheduled);
1213
+ registerSurfaceModule("__devflareEmailModule", surfaceImportPaths.email);
1214
+ return `
1215
+ ${importLines.join(`
1216
+ `)}
1217
+ ${moduleFallbackLines.join(`
1218
+ `)}
1219
+
1220
+ const __devflareResolveHandler = (module, namedExport) => {
1221
+ const defaultExport = module.default
1222
+
1223
+ if (typeof defaultExport === 'function') {
1224
+ return defaultExport
1225
+ }
1226
+
1227
+ if (defaultExport && typeof defaultExport[namedExport] === 'function') {
1228
+ return defaultExport[namedExport].bind(defaultExport)
1229
+ }
1230
+
1231
+ if (typeof module[namedExport] === 'function') {
1232
+ return module[namedExport]
1233
+ }
1234
+
1235
+ return null
1236
+ }
1237
+
1238
+ const __devflareFetchHandler = __devflareResolveHandler(__devflareFetchModule, 'fetch')
1239
+ const __devflareQueueHandler = __devflareResolveHandler(__devflareQueueModule, 'queue')
1240
+ const __devflareScheduledHandler = __devflareResolveHandler(__devflareScheduledModule, 'scheduled')
1241
+ const __devflareEmailHandler = __devflareResolveHandler(__devflareEmailModule, 'email')
1242
+
1243
+ function __devflareCreateEmailHeaders(rawBody) {
1244
+ const headers = new Headers()
1245
+ const lines = rawBody.split(/\\r?\\n/)
1246
+
1247
+ for (const line of lines) {
1248
+ if (line.trim() === '') {
1249
+ break
1250
+ }
1251
+
1252
+ const colonIndex = line.indexOf(':')
1253
+ if (colonIndex <= 0) {
1254
+ continue
1255
+ }
1256
+
1257
+ headers.append(line.slice(0, colonIndex).trim(), line.slice(colonIndex + 1).trim())
1258
+ }
1259
+
1260
+ return headers
1261
+ }
1262
+
1263
+ function __devflareCreateEmailRawStream(rawBody) {
1264
+ return new ReadableStream({
1265
+ start(controller) {
1266
+ controller.enqueue(new TextEncoder().encode(rawBody))
1267
+ controller.close()
1268
+ }
1269
+ })
1270
+ }
1271
+
1272
+ async function __devflareHandleInternalEmail(request, env, ctx) {
1273
+ if (!__devflareEmailHandler) {
1274
+ return new Response('Email handler not configured', { status: 501 })
1275
+ }
1276
+
1277
+ const from = request.headers.get('x-devflare-email-from') || 'unknown@example.com'
1278
+ const to = request.headers.get('x-devflare-email-to') || 'worker@example.com'
1279
+ const rawBody = await request.text()
1280
+ const emailMessage = {
1281
+ from,
1282
+ to,
1283
+ headers: __devflareCreateEmailHeaders(rawBody),
1284
+ raw: __devflareCreateEmailRawStream(rawBody),
1285
+ rawSize: rawBody.length,
1286
+ setReject(reason) {
1287
+ console.warn('[Devflare email rejected]', reason)
1288
+ },
1289
+ async forward(rcptTo) {
1290
+ console.log('[Devflare email forwarded]', rcptTo)
1291
+ return Promise.resolve()
1292
+ },
1293
+ async reply(message) {
1294
+ console.log('[Devflare email reply sent]', message?.from)
1295
+ return Promise.resolve()
1296
+ }
1297
+ }
1298
+
1299
+ await runWithContext(
1300
+ env,
1301
+ ctx,
1302
+ null,
1303
+ () => __devflareEmailHandler(emailMessage, env, ctx),
1304
+ 'email'
1305
+ )
1306
+
1307
+ return new Response(JSON.stringify({ ok: true, from, to }), {
1308
+ headers: { 'Content-Type': 'application/json' }
1309
+ })
1310
+ }
1311
+
1312
+ export default {
1313
+ async fetch(request, env, ctx) {
1314
+ const url = new URL(request.url)
1315
+
1316
+ if (
1317
+ request.headers.get('x-devflare-event') === 'email' &&
1318
+ url.pathname === '/_devflare/internal/email'
1319
+ ) {
1320
+ return __devflareHandleInternalEmail(request, env, ctx)
1321
+ }
1322
+
1323
+ if (!__devflareFetchHandler) {
1324
+ return new Response('Fetch handler not configured', { status: 404 })
1325
+ }
1326
+
1327
+ return runWithContext(
1328
+ env,
1329
+ ctx,
1330
+ request,
1331
+ () => __devflareFetchHandler(request, env, ctx),
1332
+ 'fetch'
1333
+ )
1334
+ },
1335
+ ...(__devflareQueueHandler
1336
+ ? {
1337
+ async queue(batch, env, ctx) {
1338
+ return runWithContext(
1339
+ env,
1340
+ ctx,
1341
+ null,
1342
+ () => __devflareQueueHandler(batch, env, ctx),
1343
+ 'queue'
1344
+ )
1345
+ }
1346
+ }
1347
+ : {}),
1348
+ ...(__devflareScheduledHandler
1349
+ ? {
1350
+ async scheduled(controller, env, ctx) {
1351
+ return runWithContext(
1352
+ env,
1353
+ ctx,
1354
+ null,
1355
+ () => __devflareScheduledHandler(controller, env, ctx),
1356
+ 'scheduled'
1357
+ )
1358
+ }
1359
+ }
1360
+ : {}),
1361
+ ...(__devflareEmailHandler
1362
+ ? {
1363
+ async email(message, env, ctx) {
1364
+ return runWithContext(
1365
+ env,
1366
+ ctx,
1367
+ null,
1368
+ () => __devflareEmailHandler(message, env, ctx),
1369
+ 'email'
1370
+ )
1371
+ }
1372
+ }
1373
+ : {})
1374
+ }
1375
+ `;
1376
+ }
1377
+ function addWorkerWatchRoots(roots, cwd, configuredPath, defaultEntries) {
1378
+ if (configuredPath === false) {
1379
+ return;
1380
+ }
1381
+ if (typeof configuredPath === "string" && configuredPath) {
1382
+ roots.add(dirname2(resolve2(cwd, configuredPath)));
1383
+ return;
1384
+ }
1385
+ for (const defaultEntry of defaultEntries) {
1386
+ roots.add(dirname2(resolve2(cwd, defaultEntry)));
1387
+ }
1388
+ }
1389
+ function collectWorkerWatchRoots(cwd, config, mainWorkerSurfacePaths) {
1127
1390
  const roots = new Set;
1128
1391
  const addFileParent = (filePath) => {
1129
1392
  if (typeof filePath !== "string" || !filePath) {
@@ -1131,13 +1394,15 @@ function collectWorkerWatchRoots(cwd, config, mainWorkerScriptPath) {
1131
1394
  }
1132
1395
  roots.add(dirname2(resolve2(cwd, filePath)));
1133
1396
  };
1134
- if (mainWorkerScriptPath) {
1135
- roots.add(dirname2(mainWorkerScriptPath));
1397
+ for (const surfacePath of Object.values(mainWorkerSurfacePaths)) {
1398
+ if (surfacePath) {
1399
+ roots.add(dirname2(surfacePath));
1400
+ }
1136
1401
  }
1137
- addFileParent(config.files?.fetch);
1138
- addFileParent(config.files?.queue);
1139
- addFileParent(config.files?.scheduled);
1140
- addFileParent(config.files?.email);
1402
+ addWorkerWatchRoots(roots, cwd, config.files?.fetch, DEFAULT_FETCH_ENTRY_FILES);
1403
+ addWorkerWatchRoots(roots, cwd, config.files?.queue, DEFAULT_QUEUE_ENTRY_FILES);
1404
+ addWorkerWatchRoots(roots, cwd, config.files?.scheduled, DEFAULT_SCHEDULED_ENTRY_FILES);
1405
+ addWorkerWatchRoots(roots, cwd, config.files?.email, DEFAULT_EMAIL_ENTRY_FILES);
1141
1406
  addFileParent(config.files?.transport);
1142
1407
  if (config.files?.routes && typeof config.files.routes === "object") {
1143
1408
  roots.add(resolve2(cwd, config.files.routes.dir));
@@ -1263,69 +1528,27 @@ async function handleEmailIncoming(request, env, ctx, url) {
1263
1528
  const rawBody = await request.text()
1264
1529
 
1265
1530
  log('Email incoming:', { from, to, bodyLength: rawBody.length })
1266
-
1267
- // Parse headers from raw email for the Headers object
1268
- const headerLines = []
1269
- const lines = rawBody.split(/\\r?\\n/)
1270
- let bodyStart = 0
1271
- for (let i = 0; i < lines.length; i++) {
1272
- if (lines[i].trim() === '') {
1273
- bodyStart = i + 1
1274
- break
1275
- }
1276
- headerLines.push(lines[i])
1277
- }
1278
-
1279
- const headers = new Headers()
1280
- for (const line of headerLines) {
1281
- const colonIdx = line.indexOf(':')
1282
- if (colonIdx > 0) {
1283
- const key = line.slice(0, colonIdx).trim()
1284
- const value = line.slice(colonIdx + 1).trim()
1285
- headers.append(key, value)
1286
- }
1287
- }
1288
-
1289
- // Create ReadableStream from raw email
1290
- const rawStream = new ReadableStream({
1291
- start(controller) {
1292
- controller.enqueue(new TextEncoder().encode(rawBody))
1293
- controller.close()
1294
- }
1295
- })
1296
-
1297
- // Create ForwardableEmailMessage-like object
1298
- const emailMessage = {
1299
- from,
1300
- to,
1301
- headers,
1302
- raw: rawStream,
1303
- rawSize: rawBody.length,
1304
-
1305
- setReject(reason) {
1306
- log('Email rejected:', reason)
1307
- },
1308
-
1309
- async forward(rcptTo, extraHeaders) {
1310
- log('Email forwarded to:', rcptTo)
1311
- return Promise.resolve()
1312
- },
1313
-
1314
- async reply(message) {
1315
- log('Email reply sent to:', message.from)
1316
- return Promise.resolve()
1531
+
1532
+ if (APP_SERVICE_BINDING) {
1533
+ const appWorker = env[APP_SERVICE_BINDING]
1534
+ if (appWorker && typeof appWorker.fetch === 'function') {
1535
+ const response = await appWorker.fetch(new Request('http://devflare.internal/_devflare/internal/email', {
1536
+ method: 'POST',
1537
+ headers: {
1538
+ 'x-devflare-event': 'email',
1539
+ 'x-devflare-email-from': from,
1540
+ 'x-devflare-email-to': to,
1541
+ 'content-type': request.headers.get('content-type') || 'text/plain'
1542
+ },
1543
+ body: rawBody
1544
+ }))
1545
+
1546
+ if (!response.ok) {
1547
+ return response
1548
+ }
1317
1549
  }
1318
1550
  }
1319
1551
 
1320
- // Look for email handler in the worker module
1321
- // For now, we call via a special RPC method that DO workers can implement
1322
- // The email binding should be configured in the worker
1323
-
1324
- // Check if there's an EMAIL_HANDLER binding (special DO for email handling)
1325
- if (env.__emailHandler && typeof env.__emailHandler.email === 'function') {
1326
- await env.__emailHandler.email(emailMessage, env, ctx)
1327
- }
1328
-
1329
1552
  return new Response(JSON.stringify({ ok: true, from, to }), {
1330
1553
  headers: { 'Content-Type': 'application/json' }
1331
1554
  })
@@ -1737,16 +1960,24 @@ function createDevServer(options) {
1737
1960
  let miniflare = null;
1738
1961
  let doBundler = null;
1739
1962
  let workerSourceWatcher = null;
1963
+ let workerWatchTargets = [];
1740
1964
  let viteProcess = null;
1741
1965
  let config = null;
1742
1966
  let browserShim = null;
1743
1967
  let browserShimPort = 8788;
1968
+ let mainWorkerSurfacePaths = {
1969
+ fetch: null,
1970
+ queue: null,
1971
+ scheduled: null,
1972
+ email: null
1973
+ };
1974
+ let resolvedWorkerConfigPath = null;
1744
1975
  let mainWorkerScriptPath = null;
1745
1976
  let bundledMainWorkerScriptPath = null;
1746
1977
  let currentDoResult = null;
1747
1978
  let reloadChain = Promise.resolve();
1748
1979
  async function bundleMainWorker() {
1749
- if (!mainWorkerScriptPath) {
1980
+ if (!hasWorkerSurfacePaths(mainWorkerSurfacePaths)) {
1750
1981
  bundledMainWorkerScriptPath = null;
1751
1982
  return;
1752
1983
  }
@@ -1755,17 +1986,27 @@ function createDevServer(options) {
1755
1986
  }
1756
1987
  const fs = await import("node:fs/promises");
1757
1988
  const outDir = resolve2(cwd, ".devflare", "worker-bundles");
1989
+ const entryDir = resolve2(cwd, ".devflare", "worker-entrypoints");
1758
1990
  await fs.rm(outDir, { recursive: true, force: true });
1759
1991
  await fs.mkdir(outDir, { recursive: true });
1992
+ await fs.mkdir(entryDir, { recursive: true });
1993
+ const entryPath = resolve2(entryDir, "fetch-entry.ts");
1994
+ const surfaceImportPaths = {
1995
+ fetch: mainWorkerSurfacePaths.fetch ? toImportSpecifier(entryPath, mainWorkerSurfacePaths.fetch) : null,
1996
+ queue: mainWorkerSurfacePaths.queue ? toImportSpecifier(entryPath, mainWorkerSurfacePaths.queue) : null,
1997
+ scheduled: mainWorkerSurfacePaths.scheduled ? toImportSpecifier(entryPath, mainWorkerSurfacePaths.scheduled) : null,
1998
+ email: mainWorkerSurfacePaths.email ? toImportSpecifier(entryPath, mainWorkerSurfacePaths.email) : null
1999
+ };
2000
+ await fs.writeFile(entryPath, getMainWorkerEntryScript(surfaceImportPaths));
1760
2001
  const result = await Bun.build({
1761
- entrypoints: [mainWorkerScriptPath],
2002
+ entrypoints: [entryPath],
1762
2003
  outdir: outDir,
1763
2004
  target: "browser",
1764
2005
  conditions: ["browser"],
1765
2006
  format: "esm",
1766
2007
  minify: false,
1767
2008
  splitting: false,
1768
- external: ["cloudflare:workers", "cloudflare:*"]
2009
+ external: ["cloudflare:workers", "cloudflare:*", "node:*"]
1769
2010
  });
1770
2011
  if (!result.success || result.outputs.length === 0) {
1771
2012
  const logs = result.logs.map((log) => ("message" in log) ? log.message : String(log)).join(`
@@ -1783,7 +2024,7 @@ ${logs}`);
1783
2024
  const bindings = loadedConfig.bindings ?? {};
1784
2025
  const persistPath = resolve2(cwd, ".devflare/data");
1785
2026
  const appWorkerName = loadedConfig.name;
1786
- const shouldRunMainWorker = !enableVite && !!mainWorkerScriptPath;
2027
+ const shouldRunMainWorker = !enableVite && hasWorkerSurfacePaths(mainWorkerSurfacePaths);
1787
2028
  const queueProducers = (() => {
1788
2029
  if (!bindings.queues?.producers) {
1789
2030
  return;
@@ -1794,6 +2035,23 @@ ${logs}`);
1794
2035
  }
1795
2036
  return producers;
1796
2037
  })();
2038
+ const queueConsumers = (() => {
2039
+ if (!bindings.queues?.consumers || bindings.queues.consumers.length === 0) {
2040
+ return;
2041
+ }
2042
+ const consumers = {};
2043
+ for (const consumer of bindings.queues.consumers) {
2044
+ consumers[consumer.queue] = {
2045
+ ...consumer.maxBatchSize !== undefined && { maxBatchSize: consumer.maxBatchSize },
2046
+ ...consumer.maxBatchTimeout !== undefined && { maxBatchTimeout: consumer.maxBatchTimeout },
2047
+ ...consumer.maxRetries !== undefined && { maxRetries: consumer.maxRetries },
2048
+ ...consumer.deadLetterQueue && { deadLetterQueue: consumer.deadLetterQueue },
2049
+ ...consumer.maxConcurrency !== undefined && { maxConcurrency: consumer.maxConcurrency },
2050
+ ...consumer.retryDelay !== undefined && { retryDelay: consumer.retryDelay }
2051
+ };
2052
+ }
2053
+ return consumers;
2054
+ })();
1797
2055
  const sharedOptions = {
1798
2056
  port: miniflarePort,
1799
2057
  host: "127.0.0.1",
@@ -1829,7 +2087,9 @@ ${logs}`);
1829
2087
  ...bindings.r2 && { r2Buckets: bindings.r2 },
1830
2088
  ...bindings.d1 && { d1Databases: bindings.d1 },
1831
2089
  ...loadedConfig.vars && Object.keys(loadedConfig.vars).length > 0 && { bindings: loadedConfig.vars },
1832
- ...queueProducers && { queueProducers }
2090
+ ...queueProducers && { queueProducers },
2091
+ ...options2.queueConsumers && { queueConsumers: options2.queueConsumers },
2092
+ ...options2.triggers && { triggers: options2.triggers }
1833
2093
  };
1834
2094
  if (options2.scriptPath) {
1835
2095
  workerConfig.scriptPath = options2.scriptPath;
@@ -1879,7 +2139,9 @@ ${logs}`);
1879
2139
  const mainWorkerConfig = createWorkerConfig({
1880
2140
  name: appWorkerName,
1881
2141
  scriptPath: bundledMainWorkerScriptPath ?? mainWorkerScriptPath,
1882
- serviceBindings: mainWorkerServiceBindings
2142
+ serviceBindings: mainWorkerServiceBindings,
2143
+ queueConsumers,
2144
+ triggers: loadedConfig.triggers?.crons?.length ? { crons: loadedConfig.triggers.crons } : undefined
1883
2145
  });
1884
2146
  workers.push(mainWorkerConfig);
1885
2147
  }
@@ -1935,48 +2197,53 @@ ${logs}`);
1935
2197
  async function startMiniflare(doResult) {
1936
2198
  const { Miniflare, Log, LogLevel } = await import("miniflare");
1937
2199
  const mfConfig = buildMiniflareConfig(doResult);
1938
- mfConfig.log = new Log(LogLevel.DEBUG);
1939
- logger?.info("=== MINIFLARE CONFIG DEBUG ===");
1940
- logger?.info("Full config:", JSON.stringify(mfConfig, (key, value) => {
1941
- if (key === "script" && typeof value === "string" && value.length > 200) {
1942
- return value.substring(0, 200) + "...[truncated]";
1943
- }
1944
- return value;
1945
- }, 2));
1946
- if (mfConfig.workers) {
1947
- logger?.info("Workers order:");
1948
- for (const w of mfConfig.workers) {
1949
- logger?.info(` → ${w.name}:`);
1950
- logger?.info(` script: ${w.script ? "inline" : w.scriptPath}`);
1951
- logger?.info(` browserRendering: ${JSON.stringify(w.browserRendering)}`);
1952
- logger?.info(` durableObjects: ${JSON.stringify(w.durableObjects)}`);
2200
+ mfConfig.log = createCompatibilityAwareMiniflareLog(Log, LogLevel.DEBUG, logger);
2201
+ const shouldLogMiniflareDiagnostics = verbose || debug;
2202
+ if (shouldLogMiniflareDiagnostics) {
2203
+ logger?.info("=== MINIFLARE CONFIG DEBUG ===");
2204
+ logger?.info("Full config:", JSON.stringify(mfConfig, (key, value) => {
2205
+ if (key === "script" && typeof value === "string" && value.length > 200) {
2206
+ return value.substring(0, 200) + "...[truncated]";
2207
+ }
2208
+ return value;
2209
+ }, 2));
2210
+ if (mfConfig.workers) {
2211
+ logger?.info("Workers order:");
2212
+ for (const w of mfConfig.workers) {
2213
+ logger?.info(` ${w.name}:`);
2214
+ logger?.info(` script: ${w.script ? "inline" : w.scriptPath}`);
2215
+ logger?.info(` browserRendering: ${JSON.stringify(w.browserRendering)}`);
2216
+ logger?.info(` durableObjects: ${JSON.stringify(w.durableObjects)}`);
2217
+ }
1953
2218
  }
1954
2219
  }
1955
2220
  miniflare = new Miniflare(mfConfig);
1956
2221
  await miniflare.ready;
1957
2222
  logger?.success(`Miniflare ready on http://localhost:${miniflarePort}`);
1958
- try {
1959
- const gatewayBindings = await miniflare.getBindings("gateway");
1960
- logger?.info("Gateway worker bindings:", Object.keys(gatewayBindings));
1961
- if (mfConfig.workers) {
1962
- for (const w of mfConfig.workers) {
1963
- if (w.name !== "gateway") {
1964
- try {
1965
- const doBindings = await miniflare.getBindings(w.name);
1966
- logger?.info(`${w.name} worker bindings:`, Object.keys(doBindings));
1967
- if ("BROWSER" in doBindings) {
1968
- logger?.success(`${w.name} has BROWSER binding!`);
1969
- } else {
1970
- logger?.warn(`${w.name} is MISSING BROWSER binding`);
2223
+ if (shouldLogMiniflareDiagnostics) {
2224
+ try {
2225
+ const gatewayBindings = await miniflare.getBindings("gateway");
2226
+ logger?.info("Gateway worker bindings:", Object.keys(gatewayBindings));
2227
+ if (mfConfig.workers) {
2228
+ for (const w of mfConfig.workers) {
2229
+ if (w.name !== "gateway") {
2230
+ try {
2231
+ const doBindings = await miniflare.getBindings(w.name);
2232
+ logger?.info(`${w.name} worker bindings:`, Object.keys(doBindings));
2233
+ if ("BROWSER" in doBindings) {
2234
+ logger?.success(`${w.name} has BROWSER binding!`);
2235
+ } else {
2236
+ logger?.warn(`${w.name} is MISSING BROWSER binding`);
2237
+ }
2238
+ } catch (error) {
2239
+ logger?.debug(`Skipping binding diagnostics for ${w.name}: ${formatErrorMessage(error)}`);
1971
2240
  }
1972
- } catch (e) {
1973
- logger?.warn(`Could not get bindings for ${w.name}:`, e);
1974
2241
  }
1975
2242
  }
1976
2243
  }
2244
+ } catch (error) {
2245
+ logger?.debug(`Skipping Miniflare binding diagnostics: ${formatErrorMessage(error)}`);
1977
2246
  }
1978
- } catch (e) {
1979
- logger?.warn("Error getting bindings:", e);
1980
2247
  }
1981
2248
  }
1982
2249
  async function reloadMiniflare(doResult) {
@@ -1986,7 +2253,7 @@ ${logs}`);
1986
2253
  return;
1987
2254
  const { Log, LogLevel } = await import("miniflare");
1988
2255
  const mfConfig = buildMiniflareConfig(currentDoResult);
1989
- mfConfig.log = new Log(LogLevel.DEBUG);
2256
+ mfConfig.log = createCompatibilityAwareMiniflareLog(Log, LogLevel.DEBUG, logger);
1990
2257
  logger?.info("Reloading Miniflare...");
1991
2258
  await miniflare.setOptions(mfConfig);
1992
2259
  logger?.success("Miniflare reloaded");
@@ -1994,12 +2261,68 @@ ${logs}`);
1994
2261
  reloadChain = queuedReload.catch(() => {});
1995
2262
  await queuedReload;
1996
2263
  }
2264
+ async function resolveWorkerConfigWatchPath() {
2265
+ if (configPath) {
2266
+ const explicitPath = resolve2(cwd, configPath);
2267
+ const fs = await import("node:fs/promises");
2268
+ try {
2269
+ await fs.access(explicitPath);
2270
+ return explicitPath;
2271
+ } catch {}
2272
+ }
2273
+ return await resolveConfigPath(cwd) ?? null;
2274
+ }
2275
+ async function refreshWorkerOnlySurfaceState() {
2276
+ if (!config) {
2277
+ return;
2278
+ }
2279
+ mainWorkerSurfacePaths = await resolveMainWorkerSurfacePaths(cwd, config);
2280
+ mainWorkerScriptPath = getFirstWorkerSurfacePath(mainWorkerSurfacePaths);
2281
+ if (hasWorkerSurfacePaths(mainWorkerSurfacePaths)) {
2282
+ await bundleMainWorker();
2283
+ } else {
2284
+ bundledMainWorkerScriptPath = null;
2285
+ }
2286
+ await syncWorkerWatchTargets();
2287
+ }
2288
+ function getWorkerWatchTargets() {
2289
+ if (enableVite || !config) {
2290
+ return [];
2291
+ }
2292
+ const targets = collectWorkerWatchRoots(cwd, config, mainWorkerSurfacePaths);
2293
+ if (resolvedWorkerConfigPath) {
2294
+ targets.push(resolvedWorkerConfigPath);
2295
+ }
2296
+ return [...new Set(targets)];
2297
+ }
2298
+ async function syncWorkerWatchTargets() {
2299
+ if (!workerSourceWatcher) {
2300
+ return;
2301
+ }
2302
+ const nextWatchTargets = getWorkerWatchTargets();
2303
+ const nextWatchTargetSet = new Set(nextWatchTargets);
2304
+ const targetsToRemove = workerWatchTargets.filter((target) => !nextWatchTargetSet.has(target));
2305
+ const targetsToAdd = nextWatchTargets.filter((target) => !workerWatchTargets.includes(target));
2306
+ if (targetsToRemove.length > 0) {
2307
+ await workerSourceWatcher.unwatch(targetsToRemove);
2308
+ }
2309
+ if (targetsToAdd.length > 0) {
2310
+ workerSourceWatcher.add(targetsToAdd);
2311
+ }
2312
+ workerWatchTargets = nextWatchTargets;
2313
+ }
2314
+ async function reloadWorkerOnlyConfig() {
2315
+ config = await loadConfig({ cwd, configFile: configPath });
2316
+ resolvedWorkerConfigPath = await resolveWorkerConfigWatchPath();
2317
+ await refreshWorkerOnlySurfaceState();
2318
+ await reloadMiniflare(currentDoResult);
2319
+ }
1997
2320
  async function startWorkerSourceWatcher() {
1998
- if (enableVite || !config || !mainWorkerScriptPath) {
2321
+ if (enableVite || !config) {
1999
2322
  return;
2000
2323
  }
2001
- const watchRoots = collectWorkerWatchRoots(cwd, config, mainWorkerScriptPath);
2002
- if (watchRoots.length === 0) {
2324
+ const watchTargets = getWorkerWatchTargets();
2325
+ if (watchTargets.length === 0) {
2003
2326
  return;
2004
2327
  }
2005
2328
  const chokidar = await import("chokidar");
@@ -2020,8 +2343,14 @@ ${logs}`);
2020
2343
  }
2021
2344
  reloadInProgress = true;
2022
2345
  try {
2346
+ const normalizedConfigPath = resolvedWorkerConfigPath ? normalizePath(resolvedWorkerConfigPath) : null;
2347
+ if (normalizedConfigPath && normalizePath(filePath) === normalizedConfigPath) {
2348
+ logger?.info(`Devflare config changed: ${filePath}`);
2349
+ await reloadWorkerOnlyConfig();
2350
+ return;
2351
+ }
2023
2352
  logger?.info(`Worker source changed: ${filePath}`);
2024
- await bundleMainWorker();
2353
+ await refreshWorkerOnlySurfaceState();
2025
2354
  await reloadMiniflare(currentDoResult);
2026
2355
  } catch (error) {
2027
2356
  logger?.error("Worker source reload failed:", error);
@@ -2042,7 +2371,8 @@ ${logs}`);
2042
2371
  triggerReload(filePath);
2043
2372
  }, 150);
2044
2373
  };
2045
- workerSourceWatcher = chokidar.watch(watchRoots, {
2374
+ workerWatchTargets = watchTargets;
2375
+ workerSourceWatcher = chokidar.watch(watchTargets, {
2046
2376
  ignoreInitial: true,
2047
2377
  usePolling: isWindows,
2048
2378
  interval: isWindows ? 300 : undefined,
@@ -2061,12 +2391,22 @@ ${logs}`);
2061
2391
  workerSourceWatcher.on("change", onFileEvent);
2062
2392
  workerSourceWatcher.on("add", onFileEvent);
2063
2393
  workerSourceWatcher.on("unlink", onFileEvent);
2064
- workerSourceWatcher.on("ready", () => {
2065
- logger?.info(`Worker source watcher ready (${watchRoots.length} root(s))`);
2066
- });
2067
2394
  workerSourceWatcher.on("error", (error) => {
2068
2395
  logger?.error("Worker source watcher error:", error);
2069
2396
  });
2397
+ await new Promise((resolvePromise, rejectPromise) => {
2398
+ const handleReady = () => {
2399
+ workerSourceWatcher?.off("error", handleInitialError);
2400
+ logger?.info(`Worker source watcher ready (${watchTargets.length} target(s))`);
2401
+ resolvePromise();
2402
+ };
2403
+ const handleInitialError = (error) => {
2404
+ workerSourceWatcher?.off("ready", handleReady);
2405
+ rejectPromise(error instanceof Error ? error : new Error(String(error)));
2406
+ };
2407
+ workerSourceWatcher?.once("ready", handleReady);
2408
+ workerSourceWatcher?.once("error", handleInitialError);
2409
+ });
2070
2410
  }
2071
2411
  async function runD1Migrations() {
2072
2412
  if (!miniflare || !config?.bindings?.d1)
@@ -2152,13 +2492,14 @@ ${logs}`);
2152
2492
  async function start() {
2153
2493
  logger?.info("Starting unified dev server...");
2154
2494
  config = await loadConfig({ cwd, configFile: configPath });
2495
+ resolvedWorkerConfigPath = await resolveWorkerConfigWatchPath();
2155
2496
  logger?.debug("Loaded config:", config.name);
2156
- mainWorkerScriptPath = await resolveMainWorkerScriptPath(cwd, config);
2157
- if (!enableVite && mainWorkerScriptPath) {
2158
- logger?.info(`Worker entry detected: ${mainWorkerScriptPath}`);
2159
- await bundleMainWorker();
2497
+ await refreshWorkerOnlySurfaceState();
2498
+ if (!enableVite && hasWorkerSurfacePaths(mainWorkerSurfacePaths)) {
2499
+ const detectedWorkerHandlers = Object.entries(mainWorkerSurfacePaths).filter(([, surfacePath]) => !!surfacePath).map(([surfaceName, surfacePath]) => `${surfaceName}=${surfacePath}`).join(", ");
2500
+ logger?.info(`Worker handlers detected: ${detectedWorkerHandlers}`);
2160
2501
  } else if (!enableVite) {
2161
- logger?.warn("No local fetch worker entry was found for worker-only mode");
2502
+ logger?.warn("No local worker handler entry was found for worker-only mode");
2162
2503
  }
2163
2504
  const remoteCheck = await checkRemoteBindingRequirements(config);
2164
2505
  if (remoteCheck.hasRemoteBindings) {