devflare 1.0.0-next.65 → 1.0.0-next.67

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 (94) hide show
  1. package/README.md +2 -1
  2. package/dist/_chunks/{account-Cflj6rw3.js → account-BHY3fvrA.js} +6 -6
  3. package/dist/_chunks/{account-resources-CsmVowJp.js → account-resources-BdKj8q1h.js} +3 -3
  4. package/dist/_chunks/{ai-CBEuLdoP.js → ai-CN_SREim.js} +1 -1
  5. package/dist/_chunks/{build-BZ6lIG7n.js → build-DCgW1i8g.js} +2 -2
  6. package/dist/_chunks/{build-artifacts-CXLdD1PE.js → build-artifacts-Btqs5gPy.js} +12 -12
  7. package/dist/_chunks/{cli-z3FJ-9cO.js → cli-DXnofibT.js} +63 -20
  8. package/dist/_chunks/{cloudflare-BaloqI3H.js → cloudflare-Ic5ubQza.js} +6 -6
  9. package/dist/_chunks/{command-utils-CSnoFdxM.js → command-utils-DjaaMQX7.js} +2 -2
  10. package/dist/_chunks/{compiler-CgIFPfRA.js → compiler-UxXQgEPq.js} +3 -3
  11. package/dist/_chunks/{config-path-C3ac6r66.js → config-path-CYGlwKDs.js} +2 -2
  12. package/dist/_chunks/{config-BwRb2_vD.js → config-zQd4jmXU.js} +3 -3
  13. package/dist/_chunks/{deploy-CujD7j-R.js → deploy-DqYme109.js} +13 -13
  14. package/dist/_chunks/dev-DOoDj7SJ.js +757 -0
  15. package/dist/_chunks/{doctor-n_eqvv52.js → doctor-CAsJ-rSZ.js} +7 -7
  16. package/dist/_chunks/{env-_QKteEYJ.js → env-DyL6JOXI.js} +1 -1
  17. package/dist/_chunks/{family-D3SV6I0N.js → family-BTTpTwUk.js} +1 -1
  18. package/dist/_chunks/{init-DnkJOmpW.js → init-DJ5y3mu5.js} +3 -3
  19. package/dist/_chunks/{loader-E_UGyTrJ.js → loader-BC0skbnH.js} +5 -5
  20. package/dist/_chunks/{local-secrets-6sMstHXw.js → local-secrets-C0n5oRgI.js} +1 -1
  21. package/dist/_chunks/{login-D9-EwpSA.js → login-CgNcbpEq.js} +4 -4
  22. package/dist/_chunks/{preferences-BKp_7XJx.js → preferences-CbYERA_m.js} +1 -1
  23. package/dist/_chunks/{preview-registry-ClAbLTHH.js → preview-registry-CTXU4mBc.js} +2 -2
  24. package/dist/_chunks/{preview-resources-D_aFwtZR.js → preview-resources-CQIu2-NY.js} +5 -5
  25. package/dist/_chunks/{previews-D-GPVCeq.js → previews-BIaw6NVC.js} +10 -10
  26. package/dist/_chunks/{productions-me2YvmWK.js → productions-CF8oQcX7.js} +7 -7
  27. package/dist/_chunks/{r2-presign-runtime-zTOLS0Ea.js → r2-presign-runtime-C75FkLMd.js} +4 -4
  28. package/dist/_chunks/{remote-CZnP-HLJ.js → remote-8jEHLVGg.js} +2 -2
  29. package/dist/_chunks/{resolve-package-qizBfCIm.js → resolve-package-DeJUGxoE.js} +1 -1
  30. package/dist/_chunks/{routes-BMU7ga_l.js → routes-CRXB3-iw.js} +6 -6
  31. package/dist/_chunks/{runtime-Ckf-KKc7.js → runtime-CuMe2D6n.js} +1 -1
  32. package/dist/_chunks/{secrets-CxVCXmdh.js → secrets-BJuEa9Jz.js} +1 -1
  33. package/dist/_chunks/{tail-aMFzd-2K.js → tail-DjGRt8gw.js} +4 -4
  34. package/dist/_chunks/{token-Dmi0KXyX.js → token-wmeF8-IQ.js} +4 -4
  35. package/dist/_chunks/{tokens-iVcgNXi-.js → tokens-KaEr4B8e.js} +1 -1
  36. package/dist/_chunks/{types-ByjwxoVA.js → types-BLGfxIq-.js} +9 -9
  37. package/dist/_chunks/{ui-BUoZApvE.js → ui-BhCdDC2N.js} +1 -1
  38. package/dist/_chunks/{usage-BBTGzjon.js → usage-rRusKejc.js} +2 -2
  39. package/dist/_chunks/{vite-BBiqD3k7.js → vite-Dxuhm1BP.js} +11 -11
  40. package/dist/_chunks/{dev-C7Z23WNH.js → vite-process-BxZ8pb7i.js} +24 -765
  41. package/dist/_chunks/{worker-CtTVZeTH.js → worker-DgczaXVW.js} +6 -6
  42. package/dist/_chunks/workspace-DE60No0K.js +517 -0
  43. package/dist/_chunks/workspace-Ds3xMjOE.js +175 -0
  44. package/dist/browser.js +4 -4
  45. package/dist/cli/commands/workspace.d.ts +8 -0
  46. package/dist/cli/commands/workspace.d.ts.map +1 -0
  47. package/dist/cli/help-pages/pages/core.d.ts +1 -1
  48. package/dist/cli/help-pages/pages/core.d.ts.map +1 -1
  49. package/dist/cli/help-pages/shared.d.ts +1 -1
  50. package/dist/cli/help-pages/shared.d.ts.map +1 -1
  51. package/dist/cli/index.d.ts.map +1 -1
  52. package/dist/cli/index.js +1 -1
  53. package/dist/cloudflare/index.js +3 -3
  54. package/dist/config/index.d.ts +1 -0
  55. package/dist/config/index.d.ts.map +1 -1
  56. package/dist/config/workspace.d.ts +282 -0
  57. package/dist/config/workspace.d.ts.map +1 -0
  58. package/dist/config-entry.js +1 -1
  59. package/dist/decorators/index.js +1 -1
  60. package/dist/dev-server/miniflare-dev-config.d.ts +8 -0
  61. package/dist/dev-server/miniflare-dev-config.d.ts.map +1 -1
  62. package/dist/dev-server/workspace/merge-config.d.ts +81 -0
  63. package/dist/dev-server/workspace/merge-config.d.ts.map +1 -0
  64. package/dist/dev-server/workspace/prepare-app.d.ts +84 -0
  65. package/dist/dev-server/workspace/prepare-app.d.ts.map +1 -0
  66. package/dist/dev-server/workspace/server.d.ts +47 -0
  67. package/dist/dev-server/workspace/server.d.ts.map +1 -0
  68. package/dist/index.d.ts +1 -1
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +9 -8
  71. package/dist/runtime/index.js +3 -3
  72. package/dist/sveltekit/index.js +6 -6
  73. package/dist/test/index.js +17 -17
  74. package/dist/vite/index.js +1 -1
  75. package/package.json +1 -1
  76. package/dist/_chunks/{api-TzdliH-6.js → api-BKClUlUf.js} +0 -0
  77. package/dist/_chunks/{colors-DiK6wPJ5.js → colors-D0NVzAFN.js} +0 -0
  78. package/dist/_chunks/{context-CQkLyah9.js → context-ZQydom21.js} +0 -0
  79. package/dist/_chunks/{decorators-QmV57ixr.js → decorators-eXNZ1S0N.js} +0 -0
  80. package/dist/_chunks/{dependencies-C2oxFjU_.js → dependencies-wKqrxKV_.js} +0 -0
  81. package/dist/_chunks/{durable-object-DMtH0XYX.js → durable-object-DuMPM8re.js} +0 -0
  82. package/dist/_chunks/{generated-artifacts-dMJY4KAt.js → generated-artifacts-Bb13pGUG.js} +0 -0
  83. package/dist/_chunks/{glob-CmQOvunB.js → glob-DRw9sbfF.js} +0 -0
  84. package/dist/_chunks/{local-dev-vars-CTSa-wvF.js → local-dev-vars-BFLS-HoI.js} +1 -1
  85. package/dist/_chunks/{local-hyperdrive-CJ90j46O.js → local-hyperdrive-CcORzo76.js} +0 -0
  86. package/dist/_chunks/{local-media-bindings-DsAW4AMF.js → local-media-bindings-ZQe-epuw.js} +0 -0
  87. package/dist/_chunks/{package-metadata-DjHBLB-z.js → package-metadata-CLI488gB.js} +1 -1
  88. package/dist/_chunks/{preview-C3Cdr8an.js → preview-j9Kbx4-G.js} +1 -1
  89. /package/dist/_chunks/{remote-config-D39BY9ji.js → remote-config-Bw6qIVM4.js} +0 -0
  90. /package/dist/_chunks/{schema-normalization-Zgm4W6O6.js → schema-normalization-CTdiY17P.js} +0 -0
  91. /package/dist/_chunks/{vite-utils-OVLMV605.js → vite-utils-D8kPPxUy.js} +0 -0
  92. /package/dist/_chunks/{worker-bundler-dTTnYU3n.js → worker-bundler-Isj5jiW6.js} +0 -0
  93. /package/dist/_chunks/{worker-entrypoint-CQW77lG8.js → worker-entrypoint-C8j23t6d.js} +0 -0
  94. /package/dist/_chunks/{workerName-CFJsLZA-.js → workerName-kEXwmV2m.js} +0 -0
@@ -0,0 +1,757 @@
1
+ import { c as getDevflareDotenvPaths, d as resolveConfigEnvVars, o as EnvVarResolutionError } from "./preview-j9Kbx4-G.js";
2
+ import { r as loadConfig } from "./loader-BC0skbnH.js";
3
+ import { t as applyLocalDevVarsToConfig } from "./local-dev-vars-BFLS-HoI.js";
4
+ import { a as cyanBold, f as logLine, g as yellow, o as dim, r as createCliTheme } from "./ui-BhCdDC2N.js";
5
+ import { clearLocalSendEmailBindings, setLocalSendEmailBindings } from "../utils/send-email.js";
6
+ import { s as resolveServiceBindings, t as discoverRoutes } from "./routes-CRXB3-iw.js";
7
+ import { l as prepareComposedWorkerEntrypoint, m as hasWorkerSurfacePaths, n as resolveEffectiveViteProject } from "./vite-Dxuhm1BP.js";
8
+ import { t as bundleWorkerEntry } from "./worker-bundler-Isj5jiW6.js";
9
+ import { _ as createMiniflareLog, a as logWorkerHandlerDetection, c as resolveViteIntegration, d as createRuntimeStdioForwarder, f as buildMiniflareDevConfig, g as runD1Migrations, h as resolveMainWorkerSurfacePaths, i as logRemoteBindingRequirements, l as resolveWorkerConfigWatchPath, m as collectWorkerWatchRoots, n as logMiniflareBindingDiagnostics, o as maybeStartBrowserShim, p as resolveR2PresignOrigin, r as logMiniflareConfigDiagnostics, s as maybeStartDOBundler, t as startViteProcess } from "./vite-process-BxZ8pb7i.js";
10
+ import { n as bundleWorkflowEntrypointScript } from "./r2-presign-runtime-C75FkLMd.js";
11
+ import { r as stopSpawnedProcessTree, t as detectViteProject } from "./vite-utils-D8kPPxUy.js";
12
+ import { dirname, relative, resolve } from "pathe";
13
+ import { exec } from "node:child_process";
14
+ import { createConsola } from "consola";
15
+ import { promisify } from "node:util";
16
+ //#region src/bridge/miniflare.ts
17
+ function isIgnorableMiniflareDisposeError(error) {
18
+ if (!(error instanceof Error)) return false;
19
+ const details = error;
20
+ return details.code === "EBADF" && details.syscall === "kill";
21
+ }
22
+ //#endregion
23
+ //#region src/cli/wrangler-auth.ts
24
+ const execAsync = promisify(exec);
25
+ /**
26
+ * Check if the config contains any bindings that require remote access
27
+ */
28
+ function detectRemoteBindings(config) {
29
+ const remoteBindings = [];
30
+ const bindings = config.bindings;
31
+ if (!bindings) return remoteBindings;
32
+ if (bindings.ai) remoteBindings.push(`AI (binding: ${bindings.ai.binding})`);
33
+ if (bindings.vectorize) for (const [name] of Object.entries(bindings.vectorize)) remoteBindings.push(`Vectorize (binding: ${name})`);
34
+ return remoteBindings;
35
+ }
36
+ /**
37
+ * Check if wrangler is logged in by running `wrangler whoami`
38
+ *
39
+ * Returns:
40
+ * - loggedIn: true if user is authenticated
41
+ * - accountId: the account ID if available
42
+ * - email: the email if available
43
+ * - error: error message if check failed
44
+ */
45
+ async function checkWranglerAuth() {
46
+ try {
47
+ const { stdout, stderr } = await execAsync("bunx wrangler whoami", { timeout: 15e3 });
48
+ const output = stdout + stderr;
49
+ if (output.includes("not authenticated") || output.includes("Not logged in") || output.includes("wrangler login")) return {
50
+ loggedIn: false,
51
+ error: "Not logged in to Wrangler"
52
+ };
53
+ const emailMatch = output.match(/email[:\s]+([^\s!]+)/i);
54
+ const accountMatch = output.match(/Account\s+ID[:\s]+([a-f0-9]+)/i);
55
+ return {
56
+ loggedIn: true,
57
+ email: emailMatch?.[1],
58
+ accountId: accountMatch?.[1]
59
+ };
60
+ } catch (error) {
61
+ const msg = error instanceof Error ? error.message : String(error);
62
+ if (msg.includes("ENOENT") || msg.includes("not found")) return {
63
+ loggedIn: false,
64
+ error: "Wrangler not installed. Run: npm install -g wrangler"
65
+ };
66
+ return {
67
+ loggedIn: false,
68
+ error: msg
69
+ };
70
+ }
71
+ }
72
+ /**
73
+ * Check if remote bindings are properly configured
74
+ *
75
+ * Returns warnings if:
76
+ * - Config has remote-only bindings but no accountId
77
+ * - Config has remote-only bindings but user is not logged in to wrangler
78
+ */
79
+ async function checkRemoteBindingRequirements(config) {
80
+ const remoteBindings = detectRemoteBindings(config);
81
+ if (remoteBindings.length === 0) return {
82
+ hasRemoteBindings: false,
83
+ remoteBindings: [],
84
+ missingAccountId: false,
85
+ notLoggedIn: false
86
+ };
87
+ return {
88
+ hasRemoteBindings: true,
89
+ remoteBindings,
90
+ missingAccountId: !config.accountId,
91
+ notLoggedIn: !(await checkWranglerAuth()).loggedIn
92
+ };
93
+ }
94
+ //#endregion
95
+ //#region src/dev-server/dev-server-state.ts
96
+ /**
97
+ * Build a fresh `DevServerState` with all handles in their not-yet-started
98
+ * positions. `enableVite` is the user's request — `start()` may downgrade it
99
+ * later via `resolveViteIntegration`.
100
+ */
101
+ function createDevServerState(initial) {
102
+ return {
103
+ enableVite: initial.enableVite,
104
+ miniflare: null,
105
+ doBundler: null,
106
+ workerSourceWatcher: null,
107
+ workerWatchTargets: [],
108
+ viteProcess: null,
109
+ config: null,
110
+ serviceBindingResolution: null,
111
+ browserShim: null,
112
+ browserShimPort: initial.browserShimPort ?? 8788,
113
+ mainWorkerSurfacePaths: {
114
+ fetch: null,
115
+ queue: null,
116
+ scheduled: null,
117
+ email: null,
118
+ tail: null
119
+ },
120
+ resolvedWorkerConfigPath: null,
121
+ mainWorkerScriptPath: null,
122
+ bundledMainWorkerScriptPath: null,
123
+ workflowEntrypointScript: "",
124
+ currentDoResult: null,
125
+ mainWorkerRoutes: null,
126
+ generatedViteConfigPath: null
127
+ };
128
+ }
129
+ /**
130
+ * Tear down everything in `state` in the same order the legacy `stop()`
131
+ * function used. After this returns, every handle on `state` is `null` again
132
+ * and the local sendEmail bindings have been cleared.
133
+ *
134
+ * Order is important and intentionally mirrors the production sequence:
135
+ * 1. DO bundler (stop rebuilds before Miniflare goes away)
136
+ * 2. Worker source watcher (stop FS callbacks before Miniflare disposal)
137
+ * 3. Miniflare itself
138
+ * 4. Vite child process (after Miniflare so requests cannot race shutdown)
139
+ * 5. Browser shim
140
+ * 6. Local sendEmail registry reset
141
+ */
142
+ async function disposeDevServerState(state) {
143
+ if (state.doBundler) {
144
+ await state.doBundler.close();
145
+ state.doBundler = null;
146
+ }
147
+ if (state.workerSourceWatcher) {
148
+ await state.workerSourceWatcher.close();
149
+ state.workerSourceWatcher = null;
150
+ }
151
+ if (state.miniflare) {
152
+ try {
153
+ await state.miniflare.dispose();
154
+ } catch (error) {
155
+ if (!isIgnorableMiniflareDisposeError(error)) throw error;
156
+ }
157
+ state.miniflare = null;
158
+ }
159
+ if (state.viteProcess) {
160
+ await stopSpawnedProcessTree(state.viteProcess);
161
+ state.viteProcess = null;
162
+ }
163
+ if (state.browserShim) {
164
+ await state.browserShim.stop();
165
+ state.browserShim = null;
166
+ }
167
+ clearLocalSendEmailBindings();
168
+ }
169
+ //#endregion
170
+ //#region src/dev-server/reload-queue.ts
171
+ function createReloadQueue({ reload, logger }) {
172
+ let running = null;
173
+ let pending = null;
174
+ async function runOnce() {
175
+ try {
176
+ await reload();
177
+ } catch (error) {
178
+ logger?.error("[devflare dev] reload failed:", error);
179
+ }
180
+ }
181
+ function schedule() {
182
+ if (!running) {
183
+ running = runOnce().finally(() => {
184
+ running = null;
185
+ });
186
+ return running;
187
+ }
188
+ if (!pending) pending = running.then(() => {
189
+ pending = null;
190
+ running = runOnce().finally(() => {
191
+ running = null;
192
+ });
193
+ return running;
194
+ });
195
+ return pending;
196
+ }
197
+ async function drain() {
198
+ while (running || pending) if (pending) await pending;
199
+ else if (running) await running;
200
+ }
201
+ return {
202
+ schedule,
203
+ drain
204
+ };
205
+ }
206
+ //#endregion
207
+ //#region src/dev-server/worker-source-watcher.ts
208
+ async function startWorkerSourceWatcher(options) {
209
+ const { watchTargets, resolvedWorkerConfigPath, logger, onConfigChange, onWorkerChange } = options;
210
+ if (watchTargets.length === 0) return null;
211
+ const chokidar = await import("chokidar");
212
+ const isWindows = process.platform === "win32";
213
+ const ignoredSegments = [
214
+ "/node_modules/",
215
+ "/.git/",
216
+ "/.devflare/",
217
+ "/dist/"
218
+ ];
219
+ const normalizePath = (filePath) => filePath.replace(/\\/g, "/");
220
+ const isIgnoredPath = (filePath) => {
221
+ const normalizedPath = normalizePath(filePath);
222
+ return ignoredSegments.some((segment) => normalizedPath.includes(segment));
223
+ };
224
+ let reloadTimeout = null;
225
+ let reloadInProgress = false;
226
+ let pendingReloadPath = null;
227
+ const flushPendingReload = async () => {
228
+ if (!pendingReloadPath) return;
229
+ const nextPath = pendingReloadPath;
230
+ pendingReloadPath = null;
231
+ await triggerReload(nextPath);
232
+ };
233
+ const triggerReload = async (filePath) => {
234
+ if (reloadInProgress) {
235
+ pendingReloadPath = filePath;
236
+ return;
237
+ }
238
+ reloadInProgress = true;
239
+ try {
240
+ const normalizedConfigPath = resolvedWorkerConfigPath ? normalizePath(resolvedWorkerConfigPath) : null;
241
+ if (normalizedConfigPath && normalizePath(filePath) === normalizedConfigPath) {
242
+ logger?.info(`Devflare config changed: ${filePath}`);
243
+ await onConfigChange();
244
+ return;
245
+ }
246
+ logger?.info(`Worker source changed: ${filePath}`);
247
+ await onWorkerChange();
248
+ } catch (error) {
249
+ logger?.error("Worker source reload failed:", error);
250
+ } finally {
251
+ reloadInProgress = false;
252
+ await flushPendingReload();
253
+ }
254
+ };
255
+ const scheduleReload = (filePath) => {
256
+ if (reloadTimeout) clearTimeout(reloadTimeout);
257
+ reloadTimeout = setTimeout(() => {
258
+ reloadTimeout = null;
259
+ triggerReload(filePath);
260
+ }, 150);
261
+ };
262
+ const watcher = chokidar.watch(watchTargets, {
263
+ ignoreInitial: true,
264
+ usePolling: isWindows,
265
+ interval: isWindows ? 300 : void 0,
266
+ awaitWriteFinish: {
267
+ stabilityThreshold: 100,
268
+ pollInterval: 50
269
+ },
270
+ ignored: (filePath) => isIgnoredPath(filePath)
271
+ });
272
+ const onFileEvent = (filePath) => {
273
+ if (isIgnoredPath(filePath)) return;
274
+ scheduleReload(filePath);
275
+ };
276
+ watcher.on("change", onFileEvent);
277
+ watcher.on("add", onFileEvent);
278
+ watcher.on("unlink", onFileEvent);
279
+ watcher.on("error", (error) => {
280
+ logger?.error("Worker source watcher error:", error);
281
+ });
282
+ await new Promise((resolvePromise, rejectPromise) => {
283
+ const handleReady = () => {
284
+ watcher.off("error", handleInitialError);
285
+ logger?.info(`Worker source watcher ready (${watchTargets.length} target(s))`);
286
+ resolvePromise();
287
+ };
288
+ const handleInitialError = (error) => {
289
+ watcher.off("ready", handleReady);
290
+ rejectPromise(error instanceof Error ? error : new Error(String(error)));
291
+ };
292
+ watcher.once("ready", handleReady);
293
+ watcher.once("error", handleInitialError);
294
+ });
295
+ return watcher;
296
+ }
297
+ /**
298
+ * Compute the diff between current and next watch-target lists and apply it
299
+ * to the given watcher. Returns the deduped next-targets array which the
300
+ * caller should store as the new "current" state.
301
+ */
302
+ async function applyWatcherTargetDiff(watcher, currentTargets, nextTargets) {
303
+ const nextSet = new Set(nextTargets);
304
+ const targetsToRemove = currentTargets.filter((t) => !nextSet.has(t));
305
+ const targetsToAdd = nextTargets.filter((t) => !currentTargets.includes(t));
306
+ if (targetsToRemove.length > 0) await watcher.unwatch(targetsToRemove);
307
+ if (targetsToAdd.length > 0) watcher.add(targetsToAdd);
308
+ return nextTargets;
309
+ }
310
+ //#endregion
311
+ //#region src/dev-server/server.ts
312
+ function createDevServer(options) {
313
+ const { cwd, configPath, vitePort = 5173, miniflarePort = 8787, miniflareHost = "127.0.0.1", enableVite: enableViteRequested = true, persist = true, logger, verbose = false, debug = process.env.DEVFLARE_DEBUG === "true" } = options;
314
+ const state = createDevServerState({ enableVite: enableViteRequested });
315
+ const r2PresignSecret = `${crypto.randomUUID()}${crypto.randomUUID()}`;
316
+ const reloadQueue = createReloadQueue({
317
+ reload: async () => {
318
+ if (!state.miniflare) return;
319
+ const { Log, LogLevel } = await import("miniflare");
320
+ const mfConfig = buildMiniflareConfig(state.currentDoResult);
321
+ const log = createMiniflareLog(Log, LogLevel, "DEBUG", logger);
322
+ if (log) mfConfig.log = log;
323
+ mfConfig.handleRuntimeStdio = createRuntimeStdioForwarder(logger);
324
+ logger?.info("Reloading Miniflare...");
325
+ await state.miniflare.setOptions(mfConfig);
326
+ logger?.success("Miniflare reloaded");
327
+ },
328
+ logger
329
+ });
330
+ async function bundleMainWorker() {
331
+ if (!state.mainWorkerScriptPath || !state.config) {
332
+ state.bundledMainWorkerScriptPath = null;
333
+ return;
334
+ }
335
+ state.bundledMainWorkerScriptPath = await bundleWorkerEntry({
336
+ cwd,
337
+ inputFile: state.mainWorkerScriptPath,
338
+ outFile: resolve(cwd, ".devflare", "worker-entrypoints", "main.js"),
339
+ rolldownOptions: state.config.rolldown?.options,
340
+ sourcemap: state.config.rolldown?.sourcemap,
341
+ minify: state.config.rolldown?.minify,
342
+ logger
343
+ });
344
+ logger?.debug(`Bundled main worker → ${state.bundledMainWorkerScriptPath}`);
345
+ }
346
+ function buildMiniflareConfig(doResult) {
347
+ if (!state.config) throw new Error("Config not loaded");
348
+ return buildMiniflareDevConfig({
349
+ config: state.config,
350
+ cwd,
351
+ miniflarePort,
352
+ miniflareHost,
353
+ persist,
354
+ enableVite: state.enableVite,
355
+ debug,
356
+ mainWorkerSurfacePaths: state.mainWorkerSurfacePaths,
357
+ mainWorkerRoutes: state.mainWorkerRoutes,
358
+ mainWorkerScriptPath: state.mainWorkerScriptPath,
359
+ bundledMainWorkerScriptPath: state.bundledMainWorkerScriptPath,
360
+ workflowEntrypointScript: state.workflowEntrypointScript,
361
+ browserShimPort: state.browserShimPort,
362
+ doResult,
363
+ serviceBindingResolution: state.serviceBindingResolution,
364
+ r2PresignSecret,
365
+ logger
366
+ });
367
+ }
368
+ async function bundleWorkflowEntrypoints() {
369
+ if (!state.config) {
370
+ state.workflowEntrypointScript = "";
371
+ return;
372
+ }
373
+ state.workflowEntrypointScript = await bundleWorkflowEntrypointScript(state.config, cwd, { logger });
374
+ }
375
+ /**
376
+ * Start Miniflare with current config
377
+ */
378
+ async function startMiniflare(doResult) {
379
+ const { Miniflare, Log, LogLevel } = await import("miniflare");
380
+ const mfConfig = buildMiniflareConfig(doResult);
381
+ const log = createMiniflareLog(Log, LogLevel, "DEBUG", logger);
382
+ if (log) mfConfig.log = log;
383
+ mfConfig.handleRuntimeStdio = createRuntimeStdioForwarder(logger);
384
+ const shouldLogMiniflareDiagnostics = verbose || debug;
385
+ if (shouldLogMiniflareDiagnostics) logMiniflareConfigDiagnostics(logger, mfConfig);
386
+ state.miniflare = new Miniflare(mfConfig);
387
+ await state.miniflare.ready;
388
+ const displayHost = miniflareHost === "0.0.0.0" || miniflareHost === "::" ? "localhost" : miniflareHost;
389
+ logger?.success(`Miniflare ready on http://${displayHost}:${miniflarePort}`);
390
+ if (shouldLogMiniflareDiagnostics) await logMiniflareBindingDiagnostics(logger, state.miniflare, mfConfig);
391
+ }
392
+ /**
393
+ * Reload Miniflare with updated DO bundles
394
+ */
395
+ async function reloadMiniflare(doResult) {
396
+ state.currentDoResult = doResult;
397
+ await reloadQueue.schedule();
398
+ }
399
+ async function refreshWorkerOnlySurfaceState() {
400
+ if (!state.config) return;
401
+ state.mainWorkerSurfacePaths = await resolveMainWorkerSurfacePaths(cwd, state.config);
402
+ state.mainWorkerRoutes = await discoverRoutes(cwd, state.config);
403
+ const composedMainEntry = await prepareComposedWorkerEntrypoint(cwd, state.config, void 0, { devInternalEmail: true });
404
+ state.mainWorkerScriptPath = composedMainEntry ? composedMainEntry : null;
405
+ if (state.mainWorkerScriptPath) await bundleMainWorker();
406
+ else state.bundledMainWorkerScriptPath = null;
407
+ await syncWorkerWatchTargets();
408
+ }
409
+ function getWorkerWatchTargets() {
410
+ if (state.enableVite || !state.config) return [];
411
+ const targets = collectWorkerWatchRoots(cwd, state.config, state.mainWorkerSurfacePaths);
412
+ if (state.resolvedWorkerConfigPath) targets.push(state.resolvedWorkerConfigPath);
413
+ return [...new Set(targets)];
414
+ }
415
+ async function syncWorkerWatchTargets() {
416
+ if (!state.workerSourceWatcher) return;
417
+ state.workerWatchTargets = await applyWatcherTargetDiff(state.workerSourceWatcher, state.workerWatchTargets, getWorkerWatchTargets());
418
+ }
419
+ async function reloadWorkerOnlyConfig() {
420
+ await loadRuntimeConfigWhenEnvReady();
421
+ if (!state.config) return;
422
+ setLocalSendEmailBindings(state.config.bindings?.sendEmail ?? {});
423
+ await bundleWorkflowEntrypoints();
424
+ await refreshWorkerOnlySurfaceState();
425
+ await reloadMiniflare(state.currentDoResult);
426
+ }
427
+ async function loadRuntimeConfig() {
428
+ state.resolvedWorkerConfigPath = await resolveWorkerConfigWatchPath(cwd, configPath);
429
+ state.config = await applyLocalDevVarsToConfig(await resolveConfigEnvVars(await loadConfig({
430
+ cwd,
431
+ configFile: configPath
432
+ }), {
433
+ cwd,
434
+ configPath: state.resolvedWorkerConfigPath ?? configPath,
435
+ mode: "dev"
436
+ }), {
437
+ cwd,
438
+ configPath: state.resolvedWorkerConfigPath ?? void 0
439
+ });
440
+ state.serviceBindingResolution = state.config.bindings?.services ? await resolveServiceBindings(state.config, state.resolvedWorkerConfigPath ? dirname(state.resolvedWorkerConfigPath) : cwd) : null;
441
+ }
442
+ async function waitForDotenvChange() {
443
+ const configWatchPath = state.resolvedWorkerConfigPath ?? await resolveWorkerConfigWatchPath(cwd, configPath);
444
+ const watchPaths = getDevflareDotenvPaths(configWatchPath ? dirname(configWatchPath) : cwd);
445
+ const { watch } = await import("chokidar");
446
+ await new Promise((resolveWait, rejectWait) => {
447
+ const watcher = watch(watchPaths, {
448
+ ignoreInitial: true,
449
+ awaitWriteFinish: {
450
+ stabilityThreshold: 100,
451
+ pollInterval: 25
452
+ }
453
+ });
454
+ const finish = () => {
455
+ watcher.close().then(resolveWait, rejectWait);
456
+ };
457
+ watcher.on("add", finish);
458
+ watcher.on("change", finish);
459
+ watcher.on("unlink", finish);
460
+ watcher.on("error", rejectWait);
461
+ });
462
+ }
463
+ async function loadRuntimeConfigWhenEnvReady() {
464
+ while (true) try {
465
+ await loadRuntimeConfig();
466
+ return;
467
+ } catch (error) {
468
+ if (!(error instanceof EnvVarResolutionError)) throw error;
469
+ logger?.warn(error.message);
470
+ logger?.info("Devflare dev is waiting for .env or .env.dev to change before starting.");
471
+ await waitForDotenvChange();
472
+ }
473
+ }
474
+ async function startWorkerSourceWatcher$1() {
475
+ if (state.enableVite || !state.config) return;
476
+ const watchTargets = getWorkerWatchTargets();
477
+ if (watchTargets.length === 0) return;
478
+ state.workerWatchTargets = watchTargets;
479
+ state.workerSourceWatcher = await startWorkerSourceWatcher({
480
+ watchTargets,
481
+ resolvedWorkerConfigPath: state.resolvedWorkerConfigPath,
482
+ logger,
483
+ onConfigChange: reloadWorkerOnlyConfig,
484
+ onWorkerChange: async () => {
485
+ await refreshWorkerOnlySurfaceState();
486
+ await reloadMiniflare(state.currentDoResult);
487
+ }
488
+ });
489
+ }
490
+ /**
491
+ * Start the complete dev server
492
+ */
493
+ async function start() {
494
+ logger?.info("Starting unified dev server...");
495
+ await loadRuntimeConfigWhenEnvReady();
496
+ if (!state.config) throw new Error("Config not loaded");
497
+ setLocalSendEmailBindings(state.config.bindings?.sendEmail ?? {});
498
+ logger?.debug("Loaded config:", state.config.name);
499
+ await bundleWorkflowEntrypoints();
500
+ const viteIntegration = await resolveViteIntegration({
501
+ cwd,
502
+ configPath,
503
+ miniflarePort,
504
+ enableViteRequested: state.enableVite,
505
+ logger
506
+ });
507
+ state.enableVite = viteIntegration.enableVite;
508
+ state.generatedViteConfigPath = viteIntegration.generatedViteConfigPath;
509
+ await refreshWorkerOnlySurfaceState();
510
+ if (!state.enableVite && (hasWorkerSurfacePaths(state.mainWorkerSurfacePaths) || Boolean(state.mainWorkerRoutes?.routes.length))) logWorkerHandlerDetection(logger, state.enableVite, true, state.mainWorkerSurfacePaths, state.mainWorkerRoutes);
511
+ else if (!state.enableVite) logWorkerHandlerDetection(logger, state.enableVite, false, state.mainWorkerSurfacePaths, state.mainWorkerRoutes);
512
+ logRemoteBindingRequirements(logger, await checkRemoteBindingRequirements(state.config));
513
+ state.browserShim = await maybeStartBrowserShim(state.config, {
514
+ browserShimPort: state.browserShimPort,
515
+ logger,
516
+ verbose
517
+ });
518
+ const doInit = await maybeStartDOBundler(state.config, {
519
+ cwd,
520
+ logger,
521
+ onRebuild: async (result) => {
522
+ await reloadMiniflare(result);
523
+ }
524
+ });
525
+ state.doBundler = doInit.bundler;
526
+ const doResult = doInit.result;
527
+ state.currentDoResult = doResult;
528
+ await startMiniflare(doResult);
529
+ await startWorkerSourceWatcher$1();
530
+ if (state.enableVite) state.viteProcess = await startViteProcess({
531
+ cwd,
532
+ configPath,
533
+ vitePort,
534
+ miniflarePort,
535
+ generatedViteConfigPath: state.generatedViteConfigPath,
536
+ r2Presign: state.config.bindings?.r2 ? {
537
+ secret: r2PresignSecret,
538
+ origin: resolveR2PresignOrigin(state.config.server, miniflareHost, miniflarePort)
539
+ } : null,
540
+ logger
541
+ });
542
+ else logger?.info("Vite startup skipped (no effective Vite config found for this package)");
543
+ await new Promise((r) => setTimeout(r, 1e3));
544
+ await runD1Migrations({
545
+ cwd,
546
+ config: state.config,
547
+ miniflarePort,
548
+ logger
549
+ });
550
+ }
551
+ /**
552
+ * Stop the dev server
553
+ */
554
+ async function stop() {
555
+ await disposeDevServerState(state);
556
+ }
557
+ /**
558
+ * Get Miniflare instance
559
+ */
560
+ function getMiniflare() {
561
+ return state.miniflare;
562
+ }
563
+ return {
564
+ start,
565
+ stop,
566
+ getMiniflare
567
+ };
568
+ }
569
+ //#endregion
570
+ //#region src/cli/commands/dev.ts
571
+ function readStringOption(value) {
572
+ return typeof value === "string" ? value : void 0;
573
+ }
574
+ function parsePort(value, source) {
575
+ const port = Number(value);
576
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) throw new Error(`${source} must be an integer between 1 and 65535`);
577
+ return port;
578
+ }
579
+ function resolveSinglePort(runtimePort, bridgePort, source) {
580
+ if (runtimePort && bridgePort && runtimePort !== bridgePort) throw new Error(`Conflicting Devflare runtime ports: ${source} runtime port is ${runtimePort}, but bridge port is ${bridgePort}. Use one value for both.`);
581
+ const value = runtimePort ?? bridgePort;
582
+ return value ? parsePort(value, source) : void 0;
583
+ }
584
+ function resolveDevRuntimePort(options, env = process.env, configPort) {
585
+ return resolveSinglePort(readStringOption(options["runtime-port"]), readStringOption(options["bridge-port"]), "CLI") ?? resolveSinglePort(env.DEVFLARE_RUNTIME_PORT, env.DEVFLARE_BRIDGE_PORT, "environment") ?? configPort ?? 8787;
586
+ }
587
+ /**
588
+ * Resolve the host the local Miniflare runtime instance binds to.
589
+ *
590
+ * Precedence: `--runtime-host` CLI flag > `DEVFLARE_RUNTIME_HOST` env var >
591
+ * `server.host` config value > `127.0.0.1`.
592
+ */
593
+ function resolveDevRuntimeHost(options, env = process.env, configHost) {
594
+ const envHost = env.DEVFLARE_RUNTIME_HOST?.trim();
595
+ return readStringOption(options["runtime-host"]) ?? (envHost ? envHost : void 0) ?? configHost ?? "127.0.0.1";
596
+ }
597
+ /**
598
+ * Create a log writer that writes to both terminal and file
599
+ */
600
+ async function createLogWriter(cwd, options) {
601
+ if (!options.log && !options.logTemp) return null;
602
+ const fs = await import("node:fs");
603
+ let logPath;
604
+ if (options.logTemp) logPath = resolve(cwd, ".log");
605
+ else logPath = resolve(cwd, `.log-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19)}`);
606
+ const fileStream = fs.createWriteStream(logPath, { flags: "w" });
607
+ const ansiRegex = /\x1b\[[0-9;]*m/g;
608
+ return {
609
+ path: logPath,
610
+ write(data, source) {
611
+ const str = typeof data === "string" ? data : data.toString();
612
+ if (!str.trim()) return;
613
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
614
+ const prefix = source ? `[${timestamp}][${source.toUpperCase()}] ` : `[${timestamp}] `;
615
+ const cleanStr = str.replace(ansiRegex, "");
616
+ fileStream.write(prefix + cleanStr + (cleanStr.endsWith("\n") ? "" : "\n"));
617
+ },
618
+ close() {
619
+ fileStream.end();
620
+ }
621
+ };
622
+ }
623
+ async function runDevCommand(parsed, logger, options) {
624
+ const cwd = options.cwd || parsed.options.cwd || process.cwd();
625
+ const configPath = parsed.options.config;
626
+ const port = parsed.options.port;
627
+ const logEnabled = parsed.options.log === true;
628
+ const logTempEnabled = parsed.options["log-temp"] === true;
629
+ const persistEnabled = parsed.options.persist === true;
630
+ const debugEnabled = parsed.options.debug === true || process.env.DEVFLARE_DEBUG === "true";
631
+ const verbose = parsed.options.verbose === true || debugEnabled;
632
+ const theme = createCliTheme(parsed.options);
633
+ const config = await loadConfig({
634
+ cwd,
635
+ configFile: configPath
636
+ });
637
+ let miniflarePort;
638
+ let miniflareHost;
639
+ try {
640
+ miniflarePort = resolveDevRuntimePort(parsed.options, process.env, config.server?.port);
641
+ miniflareHost = resolveDevRuntimeHost(parsed.options, process.env, config.server?.host);
642
+ } catch (error) {
643
+ logger.error(error instanceof Error ? error.message : String(error));
644
+ return { exitCode: 1 };
645
+ }
646
+ const viteProject = resolveEffectiveViteProject(await detectViteProject(cwd), config);
647
+ const logWriter = await createLogWriter(cwd, {
648
+ log: logEnabled,
649
+ logTemp: logTempEnabled
650
+ });
651
+ if (logWriter) {
652
+ const logFile = relative(cwd, logWriter.path) || ".log";
653
+ logLine(logger, `${dim("logging", theme)} ${logFile}`);
654
+ }
655
+ const devLogger = createConsola({ level: verbose ? 4 : 3 });
656
+ if (logWriter) {
657
+ const wrapLog = (original, prefix = "") => {
658
+ return (message, ...args) => {
659
+ original(message, ...args);
660
+ const formatted = prefix ? `${prefix} ${[message, ...args].join(" ")}` : [message, ...args].join(" ");
661
+ logWriter.write(formatted);
662
+ };
663
+ };
664
+ Object.assign(devLogger.log, wrapLog(devLogger.log.bind(devLogger)));
665
+ Object.assign(devLogger.info, wrapLog(devLogger.info.bind(devLogger)));
666
+ Object.assign(devLogger.error, wrapLog(devLogger.error.bind(devLogger), "[ERROR]"));
667
+ Object.assign(devLogger.warn, wrapLog(devLogger.warn.bind(devLogger), "[WARN]"));
668
+ Object.assign(devLogger.success, wrapLog(devLogger.success.bind(devLogger), "[OK]"));
669
+ Object.assign(devLogger.debug, wrapLog(devLogger.debug.bind(devLogger), "[DEBUG]"));
670
+ }
671
+ try {
672
+ logLine(logger);
673
+ if (viteProject.shouldStartVite) {
674
+ logLine(logger, `${cyanBold("dev", theme)} ${dim("Unified Dev Server", theme)}`);
675
+ logLine(logger, " ├─ Vite: Full HMR for frontend");
676
+ logLine(logger, " ├─ Miniflare: All Cloudflare bindings");
677
+ logLine(logger, " ├─ Rolldown: Worker + DO bundling with watch");
678
+ logLine(logger, " └─ Bridge: WebSocket RPC connection");
679
+ } else {
680
+ logLine(logger, `${cyanBold("dev", theme)} ${dim("Worker Dev Server", theme)}`);
681
+ logLine(logger, " ├─ Miniflare: All Cloudflare bindings");
682
+ logLine(logger, " ├─ Rolldown: Worker + DO bundling with watch");
683
+ logLine(logger, " └─ Vite: Disabled (no effective Vite config found)");
684
+ if (viteProject.wantsViteIntegration) {
685
+ logger.warn("Vite-related settings were detected, but no effective Vite config was available");
686
+ logger.warn("Skipping Vite startup and running in worker-only mode");
687
+ }
688
+ }
689
+ logLine(logger);
690
+ const devServer = createDevServer({
691
+ cwd,
692
+ configPath,
693
+ vitePort: port ? Number.parseInt(port, 10) : 5173,
694
+ miniflarePort,
695
+ miniflareHost,
696
+ enableVite: viteProject.shouldStartVite,
697
+ persist: persistEnabled,
698
+ logger: devLogger,
699
+ verbose,
700
+ debug: debugEnabled
701
+ });
702
+ let isCleaningUp = false;
703
+ const cleanupHandlers = /* @__PURE__ */ new Map();
704
+ const removeCleanupHandlers = () => {
705
+ for (const [event, handler] of cleanupHandlers) process.off(event, handler);
706
+ cleanupHandlers.clear();
707
+ };
708
+ const cleanup = async (exitCode, reason) => {
709
+ if (isCleaningUp) return;
710
+ isCleaningUp = true;
711
+ removeCleanupHandlers();
712
+ if (reason) {
713
+ const message = reason instanceof Error ? reason.stack ?? reason.message : String(reason);
714
+ logger.error(message);
715
+ }
716
+ logLine(logger);
717
+ logLine(logger, `${yellow("dev", theme)} ${dim("Shutting down…", theme)}`);
718
+ try {
719
+ await devServer.stop();
720
+ } finally {
721
+ logWriter?.close();
722
+ process.exit(exitCode);
723
+ }
724
+ };
725
+ const registerCleanupHandler = (event, handler) => {
726
+ cleanupHandlers.set(event, handler);
727
+ process.on(event, handler);
728
+ };
729
+ registerCleanupHandler("SIGINT", () => {
730
+ cleanup(0);
731
+ });
732
+ registerCleanupHandler("SIGTERM", () => {
733
+ cleanup(0);
734
+ });
735
+ registerCleanupHandler("SIGHUP", () => {
736
+ cleanup(0);
737
+ });
738
+ registerCleanupHandler("uncaughtException", (error) => {
739
+ cleanup(1, error);
740
+ });
741
+ registerCleanupHandler("unhandledRejection", (reason) => {
742
+ cleanup(1, reason);
743
+ });
744
+ await devServer.start();
745
+ await new Promise(() => {});
746
+ return { exitCode: 0 };
747
+ } catch (error) {
748
+ logWriter?.close();
749
+ if (error instanceof Error) {
750
+ logger.error("Dev server failed:", error.message);
751
+ if (verbose) logger.error(error.stack);
752
+ }
753
+ return { exitCode: 1 };
754
+ }
755
+ }
756
+ //#endregion
757
+ export { runDevCommand };