devflare 1.0.0-next.6 → 1.0.0-next.7

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";
@@ -1099,17 +1100,35 @@ var DEFAULT_FETCH_ENTRY_FILES = [
1099
1100
  "src/fetch.mts",
1100
1101
  "src/fetch.mjs"
1101
1102
  ];
1103
+ var DEFAULT_QUEUE_ENTRY_FILES = [
1104
+ "src/queue.ts",
1105
+ "src/queue.js",
1106
+ "src/queue.mts",
1107
+ "src/queue.mjs"
1108
+ ];
1109
+ var DEFAULT_SCHEDULED_ENTRY_FILES = [
1110
+ "src/scheduled.ts",
1111
+ "src/scheduled.js",
1112
+ "src/scheduled.mts",
1113
+ "src/scheduled.mjs"
1114
+ ];
1115
+ var DEFAULT_EMAIL_ENTRY_FILES = [
1116
+ "src/email.ts",
1117
+ "src/email.js",
1118
+ "src/email.mts",
1119
+ "src/email.mjs"
1120
+ ];
1102
1121
  var INTERNAL_APP_SERVICE_BINDING = "__DEVFLARE_APP";
1103
- async function resolveMainWorkerScriptPath(cwd, config) {
1104
- if (config.files?.fetch === false) {
1122
+ async function resolveWorkerHandlerPath(cwd, configuredPath, defaultEntries) {
1123
+ if (configuredPath === false) {
1105
1124
  return null;
1106
1125
  }
1107
1126
  const fs = await import("node:fs/promises");
1108
1127
  const candidates = new Set;
1109
- if (typeof config.files?.fetch === "string" && config.files.fetch) {
1110
- candidates.add(config.files.fetch);
1128
+ if (typeof configuredPath === "string" && configuredPath) {
1129
+ candidates.add(configuredPath);
1111
1130
  }
1112
- for (const defaultEntry of DEFAULT_FETCH_ENTRY_FILES) {
1131
+ for (const defaultEntry of defaultEntries) {
1113
1132
  candidates.add(defaultEntry);
1114
1133
  }
1115
1134
  for (const candidate of candidates) {
@@ -1123,7 +1142,214 @@ async function resolveMainWorkerScriptPath(cwd, config) {
1123
1142
  }
1124
1143
  return null;
1125
1144
  }
1126
- function collectWorkerWatchRoots(cwd, config, mainWorkerScriptPath) {
1145
+ async function resolveMainWorkerSurfacePaths(cwd, config) {
1146
+ return {
1147
+ fetch: await resolveWorkerHandlerPath(cwd, config.files?.fetch, DEFAULT_FETCH_ENTRY_FILES),
1148
+ queue: await resolveWorkerHandlerPath(cwd, config.files?.queue, DEFAULT_QUEUE_ENTRY_FILES),
1149
+ scheduled: await resolveWorkerHandlerPath(cwd, config.files?.scheduled, DEFAULT_SCHEDULED_ENTRY_FILES),
1150
+ email: await resolveWorkerHandlerPath(cwd, config.files?.email, DEFAULT_EMAIL_ENTRY_FILES)
1151
+ };
1152
+ }
1153
+ function getFirstWorkerSurfacePath(surfacePaths) {
1154
+ return surfacePaths.fetch ?? surfacePaths.queue ?? surfacePaths.scheduled ?? surfacePaths.email;
1155
+ }
1156
+ function hasWorkerSurfacePaths(surfacePaths) {
1157
+ return Object.values(surfacePaths).some((surfacePath) => typeof surfacePath === "string" && surfacePath.length > 0);
1158
+ }
1159
+ function toImportSpecifier(fromFilePath, toFilePath) {
1160
+ const specifier = relative2(dirname2(fromFilePath), toFilePath).replace(/\\/g, "/");
1161
+ return specifier.startsWith(".") ? specifier : `./${specifier}`;
1162
+ }
1163
+ function getMainWorkerEntryScript(surfaceImportPaths) {
1164
+ const importLines = [`import { runWithContext } from 'devflare/runtime'`];
1165
+ const moduleFallbackLines = [];
1166
+ const registerSurfaceModule = (identifier, importPath) => {
1167
+ if (importPath) {
1168
+ importLines.push(`import * as ${identifier} from '${importPath}'`);
1169
+ return;
1170
+ }
1171
+ moduleFallbackLines.push(`const ${identifier} = {}`);
1172
+ };
1173
+ registerSurfaceModule("__devflareFetchModule", surfaceImportPaths.fetch);
1174
+ registerSurfaceModule("__devflareQueueModule", surfaceImportPaths.queue);
1175
+ registerSurfaceModule("__devflareScheduledModule", surfaceImportPaths.scheduled);
1176
+ registerSurfaceModule("__devflareEmailModule", surfaceImportPaths.email);
1177
+ return `
1178
+ ${importLines.join(`
1179
+ `)}
1180
+ ${moduleFallbackLines.join(`
1181
+ `)}
1182
+
1183
+ const __devflareResolveHandler = (module, namedExport) => {
1184
+ const defaultExport = module.default
1185
+
1186
+ if (typeof defaultExport === 'function') {
1187
+ return defaultExport
1188
+ }
1189
+
1190
+ if (defaultExport && typeof defaultExport[namedExport] === 'function') {
1191
+ return defaultExport[namedExport].bind(defaultExport)
1192
+ }
1193
+
1194
+ if (typeof module[namedExport] === 'function') {
1195
+ return module[namedExport]
1196
+ }
1197
+
1198
+ return null
1199
+ }
1200
+
1201
+ const __devflareFetchHandler = __devflareResolveHandler(__devflareFetchModule, 'fetch')
1202
+ const __devflareQueueHandler = __devflareResolveHandler(__devflareQueueModule, 'queue')
1203
+ const __devflareScheduledHandler = __devflareResolveHandler(__devflareScheduledModule, 'scheduled')
1204
+ const __devflareEmailHandler = __devflareResolveHandler(__devflareEmailModule, 'email')
1205
+
1206
+ function __devflareCreateEmailHeaders(rawBody) {
1207
+ const headers = new Headers()
1208
+ const lines = rawBody.split(/\\r?\\n/)
1209
+
1210
+ for (const line of lines) {
1211
+ if (line.trim() === '') {
1212
+ break
1213
+ }
1214
+
1215
+ const colonIndex = line.indexOf(':')
1216
+ if (colonIndex <= 0) {
1217
+ continue
1218
+ }
1219
+
1220
+ headers.append(line.slice(0, colonIndex).trim(), line.slice(colonIndex + 1).trim())
1221
+ }
1222
+
1223
+ return headers
1224
+ }
1225
+
1226
+ function __devflareCreateEmailRawStream(rawBody) {
1227
+ return new ReadableStream({
1228
+ start(controller) {
1229
+ controller.enqueue(new TextEncoder().encode(rawBody))
1230
+ controller.close()
1231
+ }
1232
+ })
1233
+ }
1234
+
1235
+ async function __devflareHandleInternalEmail(request, env, ctx) {
1236
+ if (!__devflareEmailHandler) {
1237
+ return new Response('Email handler not configured', { status: 501 })
1238
+ }
1239
+
1240
+ const from = request.headers.get('x-devflare-email-from') || 'unknown@example.com'
1241
+ const to = request.headers.get('x-devflare-email-to') || 'worker@example.com'
1242
+ const rawBody = await request.text()
1243
+ const emailMessage = {
1244
+ from,
1245
+ to,
1246
+ headers: __devflareCreateEmailHeaders(rawBody),
1247
+ raw: __devflareCreateEmailRawStream(rawBody),
1248
+ rawSize: rawBody.length,
1249
+ setReject(reason) {
1250
+ console.warn('[Devflare email rejected]', reason)
1251
+ },
1252
+ async forward(rcptTo) {
1253
+ console.log('[Devflare email forwarded]', rcptTo)
1254
+ return Promise.resolve()
1255
+ },
1256
+ async reply(message) {
1257
+ console.log('[Devflare email reply sent]', message?.from)
1258
+ return Promise.resolve()
1259
+ }
1260
+ }
1261
+
1262
+ await runWithContext(
1263
+ env,
1264
+ ctx,
1265
+ null,
1266
+ () => __devflareEmailHandler(emailMessage, env, ctx),
1267
+ 'email'
1268
+ )
1269
+
1270
+ return new Response(JSON.stringify({ ok: true, from, to }), {
1271
+ headers: { 'Content-Type': 'application/json' }
1272
+ })
1273
+ }
1274
+
1275
+ export default {
1276
+ async fetch(request, env, ctx) {
1277
+ const url = new URL(request.url)
1278
+
1279
+ if (
1280
+ request.headers.get('x-devflare-event') === 'email' &&
1281
+ url.pathname === '/_devflare/internal/email'
1282
+ ) {
1283
+ return __devflareHandleInternalEmail(request, env, ctx)
1284
+ }
1285
+
1286
+ if (!__devflareFetchHandler) {
1287
+ return new Response('Fetch handler not configured', { status: 404 })
1288
+ }
1289
+
1290
+ return runWithContext(
1291
+ env,
1292
+ ctx,
1293
+ request,
1294
+ () => __devflareFetchHandler(request, env, ctx),
1295
+ 'fetch'
1296
+ )
1297
+ },
1298
+ ...(__devflareQueueHandler
1299
+ ? {
1300
+ async queue(batch, env, ctx) {
1301
+ return runWithContext(
1302
+ env,
1303
+ ctx,
1304
+ null,
1305
+ () => __devflareQueueHandler(batch, env, ctx),
1306
+ 'queue'
1307
+ )
1308
+ }
1309
+ }
1310
+ : {}),
1311
+ ...(__devflareScheduledHandler
1312
+ ? {
1313
+ async scheduled(controller, env, ctx) {
1314
+ return runWithContext(
1315
+ env,
1316
+ ctx,
1317
+ null,
1318
+ () => __devflareScheduledHandler(controller, env, ctx),
1319
+ 'scheduled'
1320
+ )
1321
+ }
1322
+ }
1323
+ : {}),
1324
+ ...(__devflareEmailHandler
1325
+ ? {
1326
+ async email(message, env, ctx) {
1327
+ return runWithContext(
1328
+ env,
1329
+ ctx,
1330
+ null,
1331
+ () => __devflareEmailHandler(message, env, ctx),
1332
+ 'email'
1333
+ )
1334
+ }
1335
+ }
1336
+ : {})
1337
+ }
1338
+ `;
1339
+ }
1340
+ function addWorkerWatchRoots(roots, cwd, configuredPath, defaultEntries) {
1341
+ if (configuredPath === false) {
1342
+ return;
1343
+ }
1344
+ if (typeof configuredPath === "string" && configuredPath) {
1345
+ roots.add(dirname2(resolve2(cwd, configuredPath)));
1346
+ return;
1347
+ }
1348
+ for (const defaultEntry of defaultEntries) {
1349
+ roots.add(dirname2(resolve2(cwd, defaultEntry)));
1350
+ }
1351
+ }
1352
+ function collectWorkerWatchRoots(cwd, config, mainWorkerSurfacePaths) {
1127
1353
  const roots = new Set;
1128
1354
  const addFileParent = (filePath) => {
1129
1355
  if (typeof filePath !== "string" || !filePath) {
@@ -1131,13 +1357,15 @@ function collectWorkerWatchRoots(cwd, config, mainWorkerScriptPath) {
1131
1357
  }
1132
1358
  roots.add(dirname2(resolve2(cwd, filePath)));
1133
1359
  };
1134
- if (mainWorkerScriptPath) {
1135
- roots.add(dirname2(mainWorkerScriptPath));
1360
+ for (const surfacePath of Object.values(mainWorkerSurfacePaths)) {
1361
+ if (surfacePath) {
1362
+ roots.add(dirname2(surfacePath));
1363
+ }
1136
1364
  }
1137
- addFileParent(config.files?.fetch);
1138
- addFileParent(config.files?.queue);
1139
- addFileParent(config.files?.scheduled);
1140
- addFileParent(config.files?.email);
1365
+ addWorkerWatchRoots(roots, cwd, config.files?.fetch, DEFAULT_FETCH_ENTRY_FILES);
1366
+ addWorkerWatchRoots(roots, cwd, config.files?.queue, DEFAULT_QUEUE_ENTRY_FILES);
1367
+ addWorkerWatchRoots(roots, cwd, config.files?.scheduled, DEFAULT_SCHEDULED_ENTRY_FILES);
1368
+ addWorkerWatchRoots(roots, cwd, config.files?.email, DEFAULT_EMAIL_ENTRY_FILES);
1141
1369
  addFileParent(config.files?.transport);
1142
1370
  if (config.files?.routes && typeof config.files.routes === "object") {
1143
1371
  roots.add(resolve2(cwd, config.files.routes.dir));
@@ -1263,69 +1491,27 @@ async function handleEmailIncoming(request, env, ctx, url) {
1263
1491
  const rawBody = await request.text()
1264
1492
 
1265
1493
  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()
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
+ }
1317
1512
  }
1318
1513
  }
1319
1514
 
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
1515
  return new Response(JSON.stringify({ ok: true, from, to }), {
1330
1516
  headers: { 'Content-Type': 'application/json' }
1331
1517
  })
@@ -1737,16 +1923,24 @@ function createDevServer(options) {
1737
1923
  let miniflare = null;
1738
1924
  let doBundler = null;
1739
1925
  let workerSourceWatcher = null;
1926
+ let workerWatchTargets = [];
1740
1927
  let viteProcess = null;
1741
1928
  let config = null;
1742
1929
  let browserShim = null;
1743
1930
  let browserShimPort = 8788;
1931
+ let mainWorkerSurfacePaths = {
1932
+ fetch: null,
1933
+ queue: null,
1934
+ scheduled: null,
1935
+ email: null
1936
+ };
1937
+ let resolvedWorkerConfigPath = null;
1744
1938
  let mainWorkerScriptPath = null;
1745
1939
  let bundledMainWorkerScriptPath = null;
1746
1940
  let currentDoResult = null;
1747
1941
  let reloadChain = Promise.resolve();
1748
1942
  async function bundleMainWorker() {
1749
- if (!mainWorkerScriptPath) {
1943
+ if (!hasWorkerSurfacePaths(mainWorkerSurfacePaths)) {
1750
1944
  bundledMainWorkerScriptPath = null;
1751
1945
  return;
1752
1946
  }
@@ -1755,17 +1949,27 @@ function createDevServer(options) {
1755
1949
  }
1756
1950
  const fs = await import("node:fs/promises");
1757
1951
  const outDir = resolve2(cwd, ".devflare", "worker-bundles");
1952
+ const entryDir = resolve2(cwd, ".devflare", "worker-entrypoints");
1758
1953
  await fs.rm(outDir, { recursive: true, force: true });
1759
1954
  await fs.mkdir(outDir, { recursive: true });
1955
+ await fs.mkdir(entryDir, { recursive: true });
1956
+ const entryPath = resolve2(entryDir, "fetch-entry.ts");
1957
+ const surfaceImportPaths = {
1958
+ fetch: mainWorkerSurfacePaths.fetch ? toImportSpecifier(entryPath, mainWorkerSurfacePaths.fetch) : null,
1959
+ queue: mainWorkerSurfacePaths.queue ? toImportSpecifier(entryPath, mainWorkerSurfacePaths.queue) : null,
1960
+ scheduled: mainWorkerSurfacePaths.scheduled ? toImportSpecifier(entryPath, mainWorkerSurfacePaths.scheduled) : null,
1961
+ email: mainWorkerSurfacePaths.email ? toImportSpecifier(entryPath, mainWorkerSurfacePaths.email) : null
1962
+ };
1963
+ await fs.writeFile(entryPath, getMainWorkerEntryScript(surfaceImportPaths));
1760
1964
  const result = await Bun.build({
1761
- entrypoints: [mainWorkerScriptPath],
1965
+ entrypoints: [entryPath],
1762
1966
  outdir: outDir,
1763
1967
  target: "browser",
1764
1968
  conditions: ["browser"],
1765
1969
  format: "esm",
1766
1970
  minify: false,
1767
1971
  splitting: false,
1768
- external: ["cloudflare:workers", "cloudflare:*"]
1972
+ external: ["cloudflare:workers", "cloudflare:*", "node:*"]
1769
1973
  });
1770
1974
  if (!result.success || result.outputs.length === 0) {
1771
1975
  const logs = result.logs.map((log) => ("message" in log) ? log.message : String(log)).join(`
@@ -1783,7 +1987,7 @@ ${logs}`);
1783
1987
  const bindings = loadedConfig.bindings ?? {};
1784
1988
  const persistPath = resolve2(cwd, ".devflare/data");
1785
1989
  const appWorkerName = loadedConfig.name;
1786
- const shouldRunMainWorker = !enableVite && !!mainWorkerScriptPath;
1990
+ const shouldRunMainWorker = !enableVite && hasWorkerSurfacePaths(mainWorkerSurfacePaths);
1787
1991
  const queueProducers = (() => {
1788
1992
  if (!bindings.queues?.producers) {
1789
1993
  return;
@@ -1794,6 +1998,23 @@ ${logs}`);
1794
1998
  }
1795
1999
  return producers;
1796
2000
  })();
2001
+ const queueConsumers = (() => {
2002
+ if (!bindings.queues?.consumers || bindings.queues.consumers.length === 0) {
2003
+ return;
2004
+ }
2005
+ const consumers = {};
2006
+ for (const consumer of bindings.queues.consumers) {
2007
+ consumers[consumer.queue] = {
2008
+ ...consumer.maxBatchSize !== undefined && { maxBatchSize: consumer.maxBatchSize },
2009
+ ...consumer.maxBatchTimeout !== undefined && { maxBatchTimeout: consumer.maxBatchTimeout },
2010
+ ...consumer.maxRetries !== undefined && { maxRetries: consumer.maxRetries },
2011
+ ...consumer.deadLetterQueue && { deadLetterQueue: consumer.deadLetterQueue },
2012
+ ...consumer.maxConcurrency !== undefined && { maxConcurrency: consumer.maxConcurrency },
2013
+ ...consumer.retryDelay !== undefined && { retryDelay: consumer.retryDelay }
2014
+ };
2015
+ }
2016
+ return consumers;
2017
+ })();
1797
2018
  const sharedOptions = {
1798
2019
  port: miniflarePort,
1799
2020
  host: "127.0.0.1",
@@ -1829,7 +2050,9 @@ ${logs}`);
1829
2050
  ...bindings.r2 && { r2Buckets: bindings.r2 },
1830
2051
  ...bindings.d1 && { d1Databases: bindings.d1 },
1831
2052
  ...loadedConfig.vars && Object.keys(loadedConfig.vars).length > 0 && { bindings: loadedConfig.vars },
1832
- ...queueProducers && { queueProducers }
2053
+ ...queueProducers && { queueProducers },
2054
+ ...options2.queueConsumers && { queueConsumers: options2.queueConsumers },
2055
+ ...options2.triggers && { triggers: options2.triggers }
1833
2056
  };
1834
2057
  if (options2.scriptPath) {
1835
2058
  workerConfig.scriptPath = options2.scriptPath;
@@ -1879,7 +2102,9 @@ ${logs}`);
1879
2102
  const mainWorkerConfig = createWorkerConfig({
1880
2103
  name: appWorkerName,
1881
2104
  scriptPath: bundledMainWorkerScriptPath ?? mainWorkerScriptPath,
1882
- serviceBindings: mainWorkerServiceBindings
2105
+ serviceBindings: mainWorkerServiceBindings,
2106
+ queueConsumers,
2107
+ triggers: loadedConfig.triggers?.crons?.length ? { crons: loadedConfig.triggers.crons } : undefined
1883
2108
  });
1884
2109
  workers.push(mainWorkerConfig);
1885
2110
  }
@@ -1994,12 +2219,68 @@ ${logs}`);
1994
2219
  reloadChain = queuedReload.catch(() => {});
1995
2220
  await queuedReload;
1996
2221
  }
2222
+ async function resolveWorkerConfigWatchPath() {
2223
+ if (configPath) {
2224
+ const explicitPath = resolve2(cwd, configPath);
2225
+ const fs = await import("node:fs/promises");
2226
+ try {
2227
+ await fs.access(explicitPath);
2228
+ return explicitPath;
2229
+ } catch {}
2230
+ }
2231
+ return await resolveConfigPath(cwd) ?? null;
2232
+ }
2233
+ async function refreshWorkerOnlySurfaceState() {
2234
+ if (!config) {
2235
+ return;
2236
+ }
2237
+ mainWorkerSurfacePaths = await resolveMainWorkerSurfacePaths(cwd, config);
2238
+ mainWorkerScriptPath = getFirstWorkerSurfacePath(mainWorkerSurfacePaths);
2239
+ if (hasWorkerSurfacePaths(mainWorkerSurfacePaths)) {
2240
+ await bundleMainWorker();
2241
+ } else {
2242
+ bundledMainWorkerScriptPath = null;
2243
+ }
2244
+ await syncWorkerWatchTargets();
2245
+ }
2246
+ function getWorkerWatchTargets() {
2247
+ if (enableVite || !config) {
2248
+ return [];
2249
+ }
2250
+ const targets = collectWorkerWatchRoots(cwd, config, mainWorkerSurfacePaths);
2251
+ if (resolvedWorkerConfigPath) {
2252
+ targets.push(resolvedWorkerConfigPath);
2253
+ }
2254
+ return [...new Set(targets)];
2255
+ }
2256
+ async function syncWorkerWatchTargets() {
2257
+ if (!workerSourceWatcher) {
2258
+ return;
2259
+ }
2260
+ const nextWatchTargets = getWorkerWatchTargets();
2261
+ const nextWatchTargetSet = new Set(nextWatchTargets);
2262
+ const targetsToRemove = workerWatchTargets.filter((target) => !nextWatchTargetSet.has(target));
2263
+ const targetsToAdd = nextWatchTargets.filter((target) => !workerWatchTargets.includes(target));
2264
+ if (targetsToRemove.length > 0) {
2265
+ await workerSourceWatcher.unwatch(targetsToRemove);
2266
+ }
2267
+ if (targetsToAdd.length > 0) {
2268
+ workerSourceWatcher.add(targetsToAdd);
2269
+ }
2270
+ workerWatchTargets = nextWatchTargets;
2271
+ }
2272
+ async function reloadWorkerOnlyConfig() {
2273
+ config = await loadConfig({ cwd, configFile: configPath });
2274
+ resolvedWorkerConfigPath = await resolveWorkerConfigWatchPath();
2275
+ await refreshWorkerOnlySurfaceState();
2276
+ await reloadMiniflare(currentDoResult);
2277
+ }
1997
2278
  async function startWorkerSourceWatcher() {
1998
- if (enableVite || !config || !mainWorkerScriptPath) {
2279
+ if (enableVite || !config) {
1999
2280
  return;
2000
2281
  }
2001
- const watchRoots = collectWorkerWatchRoots(cwd, config, mainWorkerScriptPath);
2002
- if (watchRoots.length === 0) {
2282
+ const watchTargets = getWorkerWatchTargets();
2283
+ if (watchTargets.length === 0) {
2003
2284
  return;
2004
2285
  }
2005
2286
  const chokidar = await import("chokidar");
@@ -2020,8 +2301,14 @@ ${logs}`);
2020
2301
  }
2021
2302
  reloadInProgress = true;
2022
2303
  try {
2304
+ const normalizedConfigPath = resolvedWorkerConfigPath ? normalizePath(resolvedWorkerConfigPath) : null;
2305
+ if (normalizedConfigPath && normalizePath(filePath) === normalizedConfigPath) {
2306
+ logger?.info(`Devflare config changed: ${filePath}`);
2307
+ await reloadWorkerOnlyConfig();
2308
+ return;
2309
+ }
2023
2310
  logger?.info(`Worker source changed: ${filePath}`);
2024
- await bundleMainWorker();
2311
+ await refreshWorkerOnlySurfaceState();
2025
2312
  await reloadMiniflare(currentDoResult);
2026
2313
  } catch (error) {
2027
2314
  logger?.error("Worker source reload failed:", error);
@@ -2042,7 +2329,8 @@ ${logs}`);
2042
2329
  triggerReload(filePath);
2043
2330
  }, 150);
2044
2331
  };
2045
- workerSourceWatcher = chokidar.watch(watchRoots, {
2332
+ workerWatchTargets = watchTargets;
2333
+ workerSourceWatcher = chokidar.watch(watchTargets, {
2046
2334
  ignoreInitial: true,
2047
2335
  usePolling: isWindows,
2048
2336
  interval: isWindows ? 300 : undefined,
@@ -2062,7 +2350,7 @@ ${logs}`);
2062
2350
  workerSourceWatcher.on("add", onFileEvent);
2063
2351
  workerSourceWatcher.on("unlink", onFileEvent);
2064
2352
  workerSourceWatcher.on("ready", () => {
2065
- logger?.info(`Worker source watcher ready (${watchRoots.length} root(s))`);
2353
+ logger?.info(`Worker source watcher ready (${watchTargets.length} target(s))`);
2066
2354
  });
2067
2355
  workerSourceWatcher.on("error", (error) => {
2068
2356
  logger?.error("Worker source watcher error:", error);
@@ -2152,13 +2440,14 @@ ${logs}`);
2152
2440
  async function start() {
2153
2441
  logger?.info("Starting unified dev server...");
2154
2442
  config = await loadConfig({ cwd, configFile: configPath });
2443
+ resolvedWorkerConfigPath = await resolveWorkerConfigWatchPath();
2155
2444
  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();
2445
+ await refreshWorkerOnlySurfaceState();
2446
+ if (!enableVite && hasWorkerSurfacePaths(mainWorkerSurfacePaths)) {
2447
+ const detectedWorkerHandlers = Object.entries(mainWorkerSurfacePaths).filter(([, surfacePath]) => !!surfacePath).map(([surfaceName, surfacePath]) => `${surfaceName}=${surfacePath}`).join(", ");
2448
+ logger?.info(`Worker handlers detected: ${detectedWorkerHandlers}`);
2160
2449
  } else if (!enableVite) {
2161
- logger?.warn("No local fetch worker entry was found for worker-only mode");
2450
+ logger?.warn("No local worker handler entry was found for worker-only mode");
2162
2451
  }
2163
2452
  const remoteCheck = await checkRemoteBindingRequirements(config);
2164
2453
  if (remoteCheck.hasRemoteBindings) {
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dev-server/server.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAC9C,OAAO,KAAK,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,WAAW,CAAA;AAc3D,MAAM,WAAW,gBAAgB;IAChC,6BAA6B;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iDAAiD;IACjD,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,2BAA2B;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,sBAAsB;IACtB,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,6BAA6B;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,OAAO,CAAA;CACf;AAED,MAAM,WAAW,SAAS;IACzB,2BAA2B;IAC3B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,0BAA0B;IAC1B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACrB,yCAAyC;IACzC,YAAY,IAAI,aAAa,GAAG,IAAI,CAAA;CACpC;AAmqBD,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAmuBpE"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/dev-server/server.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAC9C,OAAO,KAAK,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,WAAW,CAAA;AAc3D,MAAM,WAAW,gBAAgB;IAChC,6BAA6B;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iDAAiD;IACjD,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,2BAA2B;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,sBAAsB;IACtB,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,6BAA6B;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,OAAO,CAAA;CACf;AAED,MAAM,WAAW,SAAS;IACzB,2BAA2B;IAC3B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,0BAA0B;IAC1B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACrB,yCAAyC;IACzC,YAAY,IAAI,aAAa,GAAG,IAAI,CAAA;CACpC;AAy3BD,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAi3BpE"}