devflare 1.0.0-next.5 → 1.0.0-next.50

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 (594) hide show
  1. package/LICENSE +21 -0
  2. package/LLM.md +17312 -2139
  3. package/README.md +394 -605
  4. package/bin/devflare.js +30 -14
  5. package/dist/_chunks/account-BI3WZMDy.js +462 -0
  6. package/dist/_chunks/account-resources-CsmVowJp.js +159 -0
  7. package/dist/_chunks/ai-CBEuLdoP.js +322 -0
  8. package/dist/_chunks/api-TzdliH-6.js +568 -0
  9. package/dist/_chunks/build-artifacts-DU5V0J0Z.js +483 -0
  10. package/dist/_chunks/build-e0KR38-4.js +24 -0
  11. package/dist/_chunks/cli-DuUz10Mn.js +1163 -0
  12. package/dist/_chunks/cloudflare-BaloqI3H.js +157 -0
  13. package/dist/_chunks/colors-DiK6wPJ5.js +13 -0
  14. package/dist/_chunks/command-utils-DA0Qx9_K.js +45 -0
  15. package/dist/_chunks/compiler-Zw16C9Pi.js +489 -0
  16. package/dist/_chunks/config-Brju62xy.js +78 -0
  17. package/dist/_chunks/config-path-DaL1ylZ_.js +42 -0
  18. package/dist/_chunks/context-CX50Y2Kb.js +2250 -0
  19. package/dist/_chunks/decorators-QmV57ixr.js +19 -0
  20. package/dist/_chunks/dependencies-C2oxFjU_.js +57 -0
  21. package/dist/_chunks/deploy-B-Kku1Vk.js +1032 -0
  22. package/dist/_chunks/dev-D4e_RRZn.js +3471 -0
  23. package/dist/_chunks/doctor-jm_K4dxL.js +219 -0
  24. package/dist/_chunks/durable-object-DMtH0XYX.js +234 -0
  25. package/dist/_chunks/env-S0_nMVz1.js +125 -0
  26. package/dist/_chunks/family-_9-mGSfO.js +143 -0
  27. package/dist/_chunks/generated-artifacts-dMJY4KAt.js +32 -0
  28. package/dist/_chunks/glob-CmQOvunB.js +56 -0
  29. package/dist/_chunks/init-DnkJOmpW.js +165 -0
  30. package/dist/_chunks/loader-Cr0_OWPy.js +1288 -0
  31. package/dist/_chunks/local-dev-vars-CTSa-wvF.js +81 -0
  32. package/dist/_chunks/local-hyperdrive-CJ90j46O.js +119 -0
  33. package/dist/_chunks/local-media-bindings-DsAW4AMF.js +335 -0
  34. package/dist/_chunks/local-secrets-DMPkmOn3.js +123 -0
  35. package/dist/_chunks/local-workflow-entrypoints-Cq_WJajd.js +90 -0
  36. package/dist/_chunks/login-FjTAAQbi.js +51 -0
  37. package/dist/_chunks/package-metadata-DjHBLB-z.js +39 -0
  38. package/dist/_chunks/preferences-BKp_7XJx.js +241 -0
  39. package/dist/_chunks/preview-C3Cdr8an.js +462 -0
  40. package/dist/_chunks/preview-registry-ClAbLTHH.js +1186 -0
  41. package/dist/_chunks/preview-resources-CGMBjsYB.js +359 -0
  42. package/dist/_chunks/previews-C7hs9-BT.js +1134 -0
  43. package/dist/_chunks/productions-T9Als4fE.js +411 -0
  44. package/dist/_chunks/ref-CYJNAAe_.js +178 -0
  45. package/dist/_chunks/remote-CZnP-HLJ.js +79 -0
  46. package/dist/_chunks/remote-config-D39BY9ji.js +132 -0
  47. package/dist/_chunks/resolve-package-qizBfCIm.js +152 -0
  48. package/dist/_chunks/rolldown-runtime-CvQ6eiug.js +14 -0
  49. package/dist/_chunks/routes-BFYCqHb-.js +833 -0
  50. package/dist/_chunks/runtime-DL-Nz3k8.js +550 -0
  51. package/dist/_chunks/schema-normalization-BCwTLTGp.js +660 -0
  52. package/dist/_chunks/secrets-DIpJGe6C.js +95 -0
  53. package/dist/_chunks/tail-IY-ijYmv.js +239 -0
  54. package/dist/_chunks/token-Dmi0KXyX.js +333 -0
  55. package/dist/_chunks/tokens-iVcgNXi-.js +221 -0
  56. package/dist/_chunks/types-DoKsMYfm.js +484 -0
  57. package/dist/_chunks/ui-BUoZApvE.js +103 -0
  58. package/dist/_chunks/usage-BBTGzjon.js +272 -0
  59. package/dist/_chunks/vite-CGtVNf5E.js +1267 -0
  60. package/dist/_chunks/vite-utils-OVLMV605.js +183 -0
  61. package/dist/_chunks/worker-BEZI4od1.js +410 -0
  62. package/dist/_chunks/worker-bundler-dTTnYU3n.js +458 -0
  63. package/dist/_chunks/worker-entrypoint-CQW77lG8.js +247 -0
  64. package/dist/_chunks/workerName-CFJsLZA-.js +23 -0
  65. package/dist/bridge/client.d.ts +49 -7
  66. package/dist/bridge/client.d.ts.map +1 -1
  67. package/dist/bridge/gateway-runtime.d.ts +8 -0
  68. package/dist/bridge/gateway-runtime.d.ts.map +1 -0
  69. package/dist/bridge/index.d.ts +6 -6
  70. package/dist/bridge/index.d.ts.map +1 -1
  71. package/dist/bridge/log.d.ts +5 -0
  72. package/dist/bridge/log.d.ts.map +1 -0
  73. package/dist/bridge/miniflare.d.ts +82 -2
  74. package/dist/bridge/miniflare.d.ts.map +1 -1
  75. package/dist/bridge/proxy.d.ts +12 -11
  76. package/dist/bridge/proxy.d.ts.map +1 -1
  77. package/dist/bridge/server.d.ts +2 -1
  78. package/dist/bridge/server.d.ts.map +1 -1
  79. package/dist/bridge/v2/body-streams.d.ts +53 -0
  80. package/dist/bridge/v2/body-streams.d.ts.map +1 -0
  81. package/dist/bridge/v2/codec.d.ts +106 -0
  82. package/dist/bridge/v2/codec.d.ts.map +1 -0
  83. package/dist/bridge/v2/control-messages.d.ts +46 -0
  84. package/dist/bridge/v2/control-messages.d.ts.map +1 -0
  85. package/dist/bridge/v2/frames.d.ts +109 -0
  86. package/dist/bridge/v2/frames.d.ts.map +1 -0
  87. package/dist/bridge/v2/index.d.ts +11 -0
  88. package/dist/bridge/v2/index.d.ts.map +1 -0
  89. package/dist/bridge/v2/serialization.d.ts +48 -0
  90. package/dist/bridge/v2/serialization.d.ts.map +1 -0
  91. package/dist/bridge/v2/transport.d.ts +31 -0
  92. package/dist/bridge/v2/transport.d.ts.map +1 -0
  93. package/dist/bridge/v2/value-codec.d.ts +61 -0
  94. package/dist/bridge/v2/value-codec.d.ts.map +1 -0
  95. package/dist/bridge/{serialization.d.ts → v2/value-serialization.d.ts} +40 -18
  96. package/dist/bridge/v2/value-serialization.d.ts.map +1 -0
  97. package/dist/bridge/{protocol.d.ts → v2/wire.d.ts} +32 -8
  98. package/dist/bridge/v2/wire.d.ts.map +1 -0
  99. package/dist/bridge/v2/ws-relay.d.ts +53 -0
  100. package/dist/bridge/v2/ws-relay.d.ts.map +1 -0
  101. package/dist/browser-shim/handler.d.ts +2 -2
  102. package/dist/browser-shim/handler.d.ts.map +1 -1
  103. package/dist/browser-shim/index.d.ts +2 -2
  104. package/dist/browser-shim/server.d.ts +63 -0
  105. package/dist/browser-shim/server.d.ts.map +1 -1
  106. package/dist/browser.d.ts +5411 -0
  107. package/dist/browser.d.ts.map +1 -0
  108. package/dist/browser.js +80 -0
  109. package/dist/bundler/defaults.d.ts +23 -0
  110. package/dist/bundler/defaults.d.ts.map +1 -0
  111. package/dist/bundler/do-bundler.d.ts +7 -0
  112. package/dist/bundler/do-bundler.d.ts.map +1 -1
  113. package/dist/bundler/index.d.ts +3 -1
  114. package/dist/bundler/index.d.ts.map +1 -1
  115. package/dist/bundler/rolldown-shared.d.ts +53 -0
  116. package/dist/bundler/rolldown-shared.d.ts.map +1 -0
  117. package/dist/bundler/worker-bundler.d.ts +13 -0
  118. package/dist/bundler/worker-bundler.d.ts.map +1 -0
  119. package/dist/bundler/worker-compat.d.ts +4 -0
  120. package/dist/bundler/worker-compat.d.ts.map +1 -0
  121. package/dist/cli/build-manifest.d.ts +66 -0
  122. package/dist/cli/build-manifest.d.ts.map +1 -0
  123. package/dist/cli/command-utils.d.ts +18 -0
  124. package/dist/cli/command-utils.d.ts.map +1 -0
  125. package/dist/cli/commands/account.d.ts +2 -2
  126. package/dist/cli/commands/account.d.ts.map +1 -1
  127. package/dist/cli/commands/ai.d.ts +1 -1
  128. package/dist/cli/commands/ai.d.ts.map +1 -1
  129. package/dist/cli/commands/build-artifacts.d.ts +40 -0
  130. package/dist/cli/commands/build-artifacts.d.ts.map +1 -0
  131. package/dist/cli/commands/build.d.ts +2 -2
  132. package/dist/cli/commands/build.d.ts.map +1 -1
  133. package/dist/cli/commands/config.d.ts +4 -0
  134. package/dist/cli/commands/config.d.ts.map +1 -0
  135. package/dist/cli/commands/deploy/metadata.d.ts +18 -0
  136. package/dist/cli/commands/deploy/metadata.d.ts.map +1 -0
  137. package/dist/cli/commands/deploy/prepare.d.ts +23 -0
  138. package/dist/cli/commands/deploy/prepare.d.ts.map +1 -0
  139. package/dist/cli/commands/deploy/runtime.d.ts +4 -0
  140. package/dist/cli/commands/deploy/runtime.d.ts.map +1 -0
  141. package/dist/cli/commands/deploy/verification.d.ts +36 -0
  142. package/dist/cli/commands/deploy/verification.d.ts.map +1 -0
  143. package/dist/cli/commands/deploy.d.ts +2 -2
  144. package/dist/cli/commands/deploy.d.ts.map +1 -1
  145. package/dist/cli/commands/dev.d.ts +9 -1
  146. package/dist/cli/commands/dev.d.ts.map +1 -1
  147. package/dist/cli/commands/doctor.d.ts +2 -2
  148. package/dist/cli/commands/doctor.d.ts.map +1 -1
  149. package/dist/cli/commands/init.d.ts +2 -2
  150. package/dist/cli/commands/init.d.ts.map +1 -1
  151. package/dist/cli/commands/login.d.ts +4 -0
  152. package/dist/cli/commands/login.d.ts.map +1 -0
  153. package/dist/cli/commands/previews-support/cleanup.d.ts +9 -0
  154. package/dist/cli/commands/previews-support/cleanup.d.ts.map +1 -0
  155. package/dist/cli/commands/previews-support/family.d.ts +10 -0
  156. package/dist/cli/commands/previews-support/family.d.ts.map +1 -0
  157. package/dist/cli/commands/previews-support/render.d.ts +8 -0
  158. package/dist/cli/commands/previews-support/render.d.ts.map +1 -0
  159. package/dist/cli/commands/previews-support/theme.d.ts +10 -0
  160. package/dist/cli/commands/previews-support/theme.d.ts.map +1 -0
  161. package/dist/cli/commands/previews-support/types.d.ts +70 -0
  162. package/dist/cli/commands/previews-support/types.d.ts.map +1 -0
  163. package/dist/cli/commands/previews.d.ts +4 -0
  164. package/dist/cli/commands/previews.d.ts.map +1 -0
  165. package/dist/cli/commands/productions.d.ts +4 -0
  166. package/dist/cli/commands/productions.d.ts.map +1 -0
  167. package/dist/cli/commands/remote.d.ts +1 -1
  168. package/dist/cli/commands/remote.d.ts.map +1 -1
  169. package/dist/cli/commands/secrets.d.ts +4 -0
  170. package/dist/cli/commands/secrets.d.ts.map +1 -0
  171. package/dist/cli/commands/tail.d.ts +44 -0
  172. package/dist/cli/commands/tail.d.ts.map +1 -0
  173. package/dist/cli/commands/token.d.ts +4 -0
  174. package/dist/cli/commands/token.d.ts.map +1 -0
  175. package/dist/cli/commands/type-generation/discovery.d.ts +7 -0
  176. package/dist/cli/commands/type-generation/discovery.d.ts.map +1 -0
  177. package/dist/cli/commands/type-generation/generator.d.ts +121 -0
  178. package/dist/cli/commands/type-generation/generator.d.ts.map +1 -0
  179. package/dist/cli/commands/type-generation/models.d.ts +27 -0
  180. package/dist/cli/commands/type-generation/models.d.ts.map +1 -0
  181. package/dist/cli/commands/types.d.ts +2 -2
  182. package/dist/cli/commands/types.d.ts.map +1 -1
  183. package/dist/cli/commands/worker.d.ts +4 -0
  184. package/dist/cli/commands/worker.d.ts.map +1 -0
  185. package/dist/cli/config-path.d.ts +6 -0
  186. package/dist/cli/config-path.d.ts.map +1 -0
  187. package/dist/cli/dependencies.d.ts +2 -1
  188. package/dist/cli/dependencies.d.ts.map +1 -1
  189. package/dist/cli/deploy-strategy.d.ts +17 -0
  190. package/dist/cli/deploy-strategy.d.ts.map +1 -0
  191. package/dist/cli/deploy-target.d.ts +17 -0
  192. package/dist/cli/deploy-target.d.ts.map +1 -0
  193. package/dist/cli/generated-artifacts.d.ts +12 -0
  194. package/dist/cli/generated-artifacts.d.ts.map +1 -0
  195. package/dist/cli/gradual-deploy.d.ts +49 -0
  196. package/dist/cli/gradual-deploy.d.ts.map +1 -0
  197. package/dist/cli/help-pages/pages/account.d.ts +3 -0
  198. package/dist/cli/help-pages/pages/account.d.ts.map +1 -0
  199. package/dist/cli/help-pages/pages/core.d.ts +4 -0
  200. package/dist/cli/help-pages/pages/core.d.ts.map +1 -0
  201. package/dist/cli/help-pages/pages/index.d.ts +3 -0
  202. package/dist/cli/help-pages/pages/index.d.ts.map +1 -0
  203. package/dist/cli/help-pages/pages/misc.d.ts +3 -0
  204. package/dist/cli/help-pages/pages/misc.d.ts.map +1 -0
  205. package/dist/cli/help-pages/pages/previews.d.ts +3 -0
  206. package/dist/cli/help-pages/pages/previews.d.ts.map +1 -0
  207. package/dist/cli/help-pages/pages/productions.d.ts +3 -0
  208. package/dist/cli/help-pages/pages/productions.d.ts.map +1 -0
  209. package/dist/cli/help-pages/render.d.ts +12 -0
  210. package/dist/cli/help-pages/render.d.ts.map +1 -0
  211. package/dist/cli/help-pages/shared.d.ts +15 -0
  212. package/dist/cli/help-pages/shared.d.ts.map +1 -0
  213. package/dist/cli/help-pages/types.d.ts +23 -0
  214. package/dist/cli/help-pages/types.d.ts.map +1 -0
  215. package/dist/cli/help.d.ts +6 -0
  216. package/dist/cli/help.d.ts.map +1 -0
  217. package/dist/cli/index.d.ts +1 -0
  218. package/dist/cli/index.d.ts.map +1 -1
  219. package/dist/cli/index.js +2 -0
  220. package/dist/cli/package-metadata.d.ts +16 -0
  221. package/dist/cli/package-metadata.d.ts.map +1 -0
  222. package/dist/cli/preview-bindings.d.ts +49 -0
  223. package/dist/cli/preview-bindings.d.ts.map +1 -0
  224. package/dist/cli/preview.d.ts +10 -0
  225. package/dist/cli/preview.d.ts.map +1 -0
  226. package/dist/cli/ui.d.ts +37 -0
  227. package/dist/cli/ui.d.ts.map +1 -0
  228. package/dist/cli/workspace-build-guard.d.ts +14 -0
  229. package/dist/cli/workspace-build-guard.d.ts.map +1 -0
  230. package/dist/cli/wrangler-auth.d.ts +1 -1
  231. package/dist/cloudflare/account-core.d.ts +6 -0
  232. package/dist/cloudflare/account-core.d.ts.map +1 -0
  233. package/dist/cloudflare/account-resources.d.ts +40 -0
  234. package/dist/cloudflare/account-resources.d.ts.map +1 -0
  235. package/dist/cloudflare/account-status.d.ts +11 -0
  236. package/dist/cloudflare/account-status.d.ts.map +1 -0
  237. package/dist/cloudflare/account-workers.d.ts +14 -0
  238. package/dist/cloudflare/account-workers.d.ts.map +1 -0
  239. package/dist/cloudflare/account.d.ts +7 -64
  240. package/dist/cloudflare/account.d.ts.map +1 -1
  241. package/dist/cloudflare/api.d.ts +71 -3
  242. package/dist/cloudflare/api.d.ts.map +1 -1
  243. package/dist/cloudflare/auth.d.ts +1 -1
  244. package/dist/cloudflare/auth.d.ts.map +1 -1
  245. package/dist/cloudflare/index.d.ts +61 -6
  246. package/dist/cloudflare/index.d.ts.map +1 -1
  247. package/dist/cloudflare/index.js +4 -13
  248. package/dist/cloudflare/known-permission-group-ids.generated.d.ts +10 -0
  249. package/dist/cloudflare/known-permission-group-ids.generated.d.ts.map +1 -0
  250. package/dist/cloudflare/kv-namespace.d.ts +4 -0
  251. package/dist/cloudflare/kv-namespace.d.ts.map +1 -0
  252. package/dist/cloudflare/preferences.d.ts +7 -0
  253. package/dist/cloudflare/preferences.d.ts.map +1 -1
  254. package/dist/cloudflare/preview-registry-cache.d.ts +6 -0
  255. package/dist/cloudflare/preview-registry-cache.d.ts.map +1 -0
  256. package/dist/cloudflare/preview-registry-inference.d.ts +14 -0
  257. package/dist/cloudflare/preview-registry-inference.d.ts.map +1 -0
  258. package/dist/cloudflare/preview-registry-records.d.ts +4 -0
  259. package/dist/cloudflare/preview-registry-records.d.ts.map +1 -0
  260. package/dist/cloudflare/preview-registry-shape.d.ts +47 -0
  261. package/dist/cloudflare/preview-registry-shape.d.ts.map +1 -0
  262. package/dist/cloudflare/preview-registry-store.d.ts +14 -0
  263. package/dist/cloudflare/preview-registry-store.d.ts.map +1 -0
  264. package/dist/cloudflare/preview-registry-transport.d.ts +4 -0
  265. package/dist/cloudflare/preview-registry-transport.d.ts.map +1 -0
  266. package/dist/cloudflare/preview-registry-types.d.ts +103 -0
  267. package/dist/cloudflare/preview-registry-types.d.ts.map +1 -0
  268. package/dist/cloudflare/preview-registry.d.ts +42 -0
  269. package/dist/cloudflare/preview-registry.d.ts.map +1 -0
  270. package/dist/cloudflare/preview-urls.d.ts +3 -0
  271. package/dist/cloudflare/preview-urls.d.ts.map +1 -0
  272. package/dist/cloudflare/registry-schema.d.ts +253 -0
  273. package/dist/cloudflare/registry-schema.d.ts.map +1 -0
  274. package/dist/cloudflare/remote-config.d.ts.map +1 -1
  275. package/dist/cloudflare/tokens.d.ts +44 -0
  276. package/dist/cloudflare/tokens.d.ts.map +1 -0
  277. package/dist/cloudflare/types.d.ts +124 -7
  278. package/dist/cloudflare/types.d.ts.map +1 -1
  279. package/dist/cloudflare/usage.d.ts +31 -3
  280. package/dist/cloudflare/usage.d.ts.map +1 -1
  281. package/dist/config/binding-resolution-helpers.d.ts +41 -0
  282. package/dist/config/binding-resolution-helpers.d.ts.map +1 -0
  283. package/dist/config/compatibility.d.ts +3 -0
  284. package/dist/config/compatibility.d.ts.map +1 -0
  285. package/dist/config/compiler/bindings.d.ts +14 -0
  286. package/dist/config/compiler/bindings.d.ts.map +1 -0
  287. package/dist/config/compiler/core-helpers.d.ts +6 -0
  288. package/dist/config/compiler/core-helpers.d.ts.map +1 -0
  289. package/dist/config/compiler/do-workers.d.ts +34 -0
  290. package/dist/config/compiler/do-workers.d.ts.map +1 -0
  291. package/dist/config/compiler/paths.d.ts +18 -0
  292. package/dist/config/compiler/paths.d.ts.map +1 -0
  293. package/dist/config/compiler/types.d.ts +298 -0
  294. package/dist/config/compiler/types.d.ts.map +1 -0
  295. package/dist/config/compiler.d.ts +22 -134
  296. package/dist/config/compiler.d.ts.map +1 -1
  297. package/dist/config/cron.d.ts +19 -0
  298. package/dist/config/cron.d.ts.map +1 -0
  299. package/dist/config/define.d.ts +8 -5
  300. package/dist/config/define.d.ts.map +1 -1
  301. package/dist/config/deploy-resources.d.ts +58 -0
  302. package/dist/config/deploy-resources.d.ts.map +1 -0
  303. package/dist/config/env-vars.d.ts +309 -0
  304. package/dist/config/env-vars.d.ts.map +1 -0
  305. package/dist/config/framework-providers.d.ts +9 -0
  306. package/dist/config/framework-providers.d.ts.map +1 -0
  307. package/dist/config/index.d.ts +12 -5
  308. package/dist/config/index.d.ts.map +1 -1
  309. package/dist/config/loader.d.ts +2 -1
  310. package/dist/config/loader.d.ts.map +1 -1
  311. package/dist/config/local-dev-vars.d.ts +15 -0
  312. package/dist/config/local-dev-vars.d.ts.map +1 -0
  313. package/dist/config/preview-resources.d.ts +83 -0
  314. package/dist/config/preview-resources.d.ts.map +1 -0
  315. package/dist/config/preview.d.ts +29 -0
  316. package/dist/config/preview.d.ts.map +1 -0
  317. package/dist/config/ref.d.ts +16 -25
  318. package/dist/config/ref.d.ts.map +1 -1
  319. package/dist/config/resolve-phased.d.ts +72 -0
  320. package/dist/config/resolve-phased.d.ts.map +1 -0
  321. package/dist/config/resolve.d.ts +11 -0
  322. package/dist/config/resolve.d.ts.map +1 -0
  323. package/dist/config/resource-resolution.d.ts +80 -0
  324. package/dist/config/resource-resolution.d.ts.map +1 -0
  325. package/dist/config/schema-bindings.d.ts +2025 -0
  326. package/dist/config/schema-bindings.d.ts.map +1 -0
  327. package/dist/config/schema-build.d.ts +67 -0
  328. package/dist/config/schema-build.d.ts.map +1 -0
  329. package/dist/config/schema-env.d.ts +3579 -0
  330. package/dist/config/schema-env.d.ts.map +1 -0
  331. package/dist/config/schema-normalization.d.ts +265 -0
  332. package/dist/config/schema-normalization.d.ts.map +1 -0
  333. package/dist/config/schema-runtime.d.ts +508 -0
  334. package/dist/config/schema-runtime.d.ts.map +1 -0
  335. package/dist/config/schema-types-bindings-platform.d.ts +508 -0
  336. package/dist/config/schema-types-bindings-platform.d.ts.map +1 -0
  337. package/dist/config/schema-types-bindings-resources.d.ts +767 -0
  338. package/dist/config/schema-types-bindings-resources.d.ts.map +1 -0
  339. package/dist/config/schema-types-bindings.d.ts +293 -0
  340. package/dist/config/schema-types-bindings.d.ts.map +1 -0
  341. package/dist/config/schema-types-build.d.ts +86 -0
  342. package/dist/config/schema-types-build.d.ts.map +1 -0
  343. package/dist/config/schema-types-runtime.d.ts +917 -0
  344. package/dist/config/schema-types-runtime.d.ts.map +1 -0
  345. package/dist/config/schema-types.d.ts +424 -0
  346. package/dist/config/schema-types.d.ts.map +1 -0
  347. package/dist/config/schema.d.ts +5324 -2011
  348. package/dist/config/schema.d.ts.map +1 -1
  349. package/dist/config/service-bindings-validation.d.ts +36 -0
  350. package/dist/config/service-bindings-validation.d.ts.map +1 -0
  351. package/dist/config-entry.d.ts +7 -0
  352. package/dist/config-entry.d.ts.map +1 -0
  353. package/dist/config-entry.js +3 -0
  354. package/dist/decorators/durable-object.d.ts.map +1 -1
  355. package/dist/decorators/index.d.ts +2 -2
  356. package/dist/decorators/index.js +2 -9
  357. package/dist/dev-server/d1-migrations.d.ts +36 -0
  358. package/dist/dev-server/d1-migrations.d.ts.map +1 -0
  359. package/dist/dev-server/dev-server-state.d.ts +59 -0
  360. package/dist/dev-server/dev-server-state.d.ts.map +1 -0
  361. package/dist/dev-server/gateway-script.d.ts +21 -0
  362. package/dist/dev-server/gateway-script.d.ts.map +1 -0
  363. package/dist/dev-server/index.d.ts +1 -1
  364. package/dist/dev-server/miniflare-bindings.d.ts +81 -0
  365. package/dist/dev-server/miniflare-bindings.d.ts.map +1 -0
  366. package/dist/dev-server/miniflare-dev-config.d.ts +36 -0
  367. package/dist/dev-server/miniflare-dev-config.d.ts.map +1 -0
  368. package/dist/dev-server/miniflare-log.d.ts +20 -0
  369. package/dist/dev-server/miniflare-log.d.ts.map +1 -0
  370. package/dist/dev-server/miniflare-worker-config.d.ts +82 -0
  371. package/dist/dev-server/miniflare-worker-config.d.ts.map +1 -0
  372. package/dist/dev-server/reload-queue.d.ts +13 -0
  373. package/dist/dev-server/reload-queue.d.ts.map +1 -0
  374. package/dist/dev-server/runtime-stdio.d.ts +8 -0
  375. package/dist/dev-server/runtime-stdio.d.ts.map +1 -0
  376. package/dist/dev-server/server-startup-helpers.d.ts +84 -0
  377. package/dist/dev-server/server-startup-helpers.d.ts.map +1 -0
  378. package/dist/dev-server/server.d.ts +2 -0
  379. package/dist/dev-server/server.d.ts.map +1 -1
  380. package/dist/dev-server/vite-process.d.ts +15 -0
  381. package/dist/dev-server/vite-process.d.ts.map +1 -0
  382. package/dist/dev-server/vite-utils.d.ts +19 -1
  383. package/dist/dev-server/vite-utils.d.ts.map +1 -1
  384. package/dist/dev-server/worker-source-watcher.d.ts +17 -0
  385. package/dist/dev-server/worker-source-watcher.d.ts.map +1 -0
  386. package/dist/dev-server/worker-surface-paths.d.ts +6 -0
  387. package/dist/dev-server/worker-surface-paths.d.ts.map +1 -0
  388. package/dist/env.d.ts +10 -0
  389. package/dist/env.d.ts.map +1 -1
  390. package/dist/index.d.ts +8 -12
  391. package/dist/index.d.ts.map +1 -1
  392. package/dist/index.js +9 -298
  393. package/dist/runtime/context-events.d.ts +13 -0
  394. package/dist/runtime/context-events.d.ts.map +1 -0
  395. package/dist/runtime/context-types.d.ts +82 -0
  396. package/dist/runtime/context-types.d.ts.map +1 -0
  397. package/dist/runtime/context.d.ts +20 -44
  398. package/dist/runtime/context.d.ts.map +1 -1
  399. package/dist/runtime/exports.d.ts +70 -55
  400. package/dist/runtime/exports.d.ts.map +1 -1
  401. package/dist/runtime/index.d.ts +10 -3
  402. package/dist/runtime/index.d.ts.map +1 -1
  403. package/dist/runtime/index.js +5 -111
  404. package/dist/runtime/middleware.d.ts +119 -67
  405. package/dist/runtime/middleware.d.ts.map +1 -1
  406. package/dist/runtime/router/index.d.ts +7 -0
  407. package/dist/runtime/router/index.d.ts.map +1 -0
  408. package/dist/runtime/router/types.d.ts +24 -0
  409. package/dist/runtime/router/types.d.ts.map +1 -0
  410. package/dist/runtime/validation.d.ts +18 -4
  411. package/dist/runtime/validation.d.ts.map +1 -1
  412. package/dist/secrets/local-secrets.d.ts +46 -0
  413. package/dist/secrets/local-secrets.d.ts.map +1 -0
  414. package/dist/shims/local-hyperdrive.d.ts +12 -0
  415. package/dist/shims/local-hyperdrive.d.ts.map +1 -0
  416. package/dist/shims/local-media-bindings.d.ts +19 -0
  417. package/dist/shims/local-media-bindings.d.ts.map +1 -0
  418. package/dist/shims/local-worker-loader.d.ts +3 -0
  419. package/dist/shims/local-worker-loader.d.ts.map +1 -0
  420. package/dist/sveltekit/index.d.ts +1 -1
  421. package/dist/sveltekit/index.d.ts.map +1 -1
  422. package/dist/sveltekit/index.js +313 -167
  423. package/dist/sveltekit/local-bindings.d.ts +4 -0
  424. package/dist/sveltekit/local-bindings.d.ts.map +1 -0
  425. package/dist/sveltekit/platform.d.ts +20 -2
  426. package/dist/sveltekit/platform.d.ts.map +1 -1
  427. package/dist/test/ai-search.d.ts +39 -0
  428. package/dist/test/ai-search.d.ts.map +1 -0
  429. package/dist/test/alarm.d.ts +55 -0
  430. package/dist/test/alarm.d.ts.map +1 -0
  431. package/dist/test/binding-hints.d.ts +10 -0
  432. package/dist/test/binding-hints.d.ts.map +1 -0
  433. package/dist/test/cf.d.ts +71 -35
  434. package/dist/test/cf.d.ts.map +1 -1
  435. package/dist/test/containers.d.ts +87 -0
  436. package/dist/test/containers.d.ts.map +1 -0
  437. package/dist/test/email.d.ts +16 -7
  438. package/dist/test/email.d.ts.map +1 -1
  439. package/dist/test/index.d.ts +20 -21
  440. package/dist/test/index.d.ts.map +1 -1
  441. package/dist/test/index.js +4971 -70
  442. package/dist/test/offline-bindings.d.ts +115 -0
  443. package/dist/test/offline-bindings.d.ts.map +1 -0
  444. package/dist/test/queue.d.ts.map +1 -1
  445. package/dist/test/remote-ai.d.ts.map +1 -1
  446. package/dist/test/remote-cloudflare.d.ts +13 -0
  447. package/dist/test/remote-cloudflare.d.ts.map +1 -0
  448. package/dist/test/remote-vectorize.d.ts.map +1 -1
  449. package/dist/test/resolve-service-bindings.d.ts +64 -4
  450. package/dist/test/resolve-service-bindings.d.ts.map +1 -1
  451. package/dist/test/scheduled.d.ts.map +1 -1
  452. package/dist/test/should-skip.d.ts +14 -18
  453. package/dist/test/should-skip.d.ts.map +1 -1
  454. package/dist/test/simple-context-bindings.d.ts +13 -0
  455. package/dist/test/simple-context-bindings.d.ts.map +1 -0
  456. package/dist/test/simple-context-durable-objects.d.ts +6 -0
  457. package/dist/test/simple-context-durable-objects.d.ts.map +1 -0
  458. package/dist/test/simple-context-env.d.ts +35 -0
  459. package/dist/test/simple-context-env.d.ts.map +1 -0
  460. package/dist/test/simple-context-gateway-script.d.ts +2 -0
  461. package/dist/test/simple-context-gateway-script.d.ts.map +1 -0
  462. package/dist/test/simple-context-handlers.d.ts +12 -0
  463. package/dist/test/simple-context-handlers.d.ts.map +1 -0
  464. package/dist/test/simple-context-lifecycle.d.ts +32 -0
  465. package/dist/test/simple-context-lifecycle.d.ts.map +1 -0
  466. package/dist/test/simple-context-mfconfig.d.ts +10 -0
  467. package/dist/test/simple-context-mfconfig.d.ts.map +1 -0
  468. package/dist/test/simple-context-multi-worker.d.ts +16 -0
  469. package/dist/test/simple-context-multi-worker.d.ts.map +1 -0
  470. package/dist/test/simple-context-paths.d.ts +40 -0
  471. package/dist/test/simple-context-paths.d.ts.map +1 -0
  472. package/dist/test/simple-context-runtime.d.ts +19 -0
  473. package/dist/test/simple-context-runtime.d.ts.map +1 -0
  474. package/dist/test/simple-context-startup.d.ts +11 -0
  475. package/dist/test/simple-context-startup.d.ts.map +1 -0
  476. package/dist/test/simple-context-transport.d.ts +14 -0
  477. package/dist/test/simple-context-transport.d.ts.map +1 -0
  478. package/dist/test/simple-context.d.ts +8 -27
  479. package/dist/test/simple-context.d.ts.map +1 -1
  480. package/dist/test/tail.d.ts +3 -2
  481. package/dist/test/tail.d.ts.map +1 -1
  482. package/dist/test/utilities/analytics-engine.d.ts +22 -0
  483. package/dist/test/utilities/analytics-engine.d.ts.map +1 -0
  484. package/dist/test/utilities/artifacts.d.ts +11 -0
  485. package/dist/test/utilities/artifacts.d.ts.map +1 -0
  486. package/dist/test/utilities/context.d.ts +39 -0
  487. package/dist/test/utilities/context.d.ts.map +1 -0
  488. package/dist/test/utilities/d1.d.ts +21 -0
  489. package/dist/test/utilities/d1.d.ts.map +1 -0
  490. package/dist/test/utilities/env.d.ts +43 -0
  491. package/dist/test/utilities/env.d.ts.map +1 -0
  492. package/dist/test/utilities/kv.d.ts +11 -0
  493. package/dist/test/utilities/kv.d.ts.map +1 -0
  494. package/dist/test/utilities/media.d.ts +16 -0
  495. package/dist/test/utilities/media.d.ts.map +1 -0
  496. package/dist/test/utilities/platform.d.ts +41 -0
  497. package/dist/test/utilities/platform.d.ts.map +1 -0
  498. package/dist/test/utilities/queue.d.ts +5 -0
  499. package/dist/test/utilities/queue.d.ts.map +1 -0
  500. package/dist/test/utilities/r2.d.ts +2 -0
  501. package/dist/test/utilities/r2.d.ts.map +1 -0
  502. package/dist/test/utilities/stream-flagship.d.ts +27 -0
  503. package/dist/test/utilities/stream-flagship.d.ts.map +1 -0
  504. package/dist/test/utilities/vectorize.d.ts +38 -0
  505. package/dist/test/utilities/vectorize.d.ts.map +1 -0
  506. package/dist/test/utilities/workflows.d.ts +26 -0
  507. package/dist/test/utilities/workflows.d.ts.map +1 -0
  508. package/dist/test/utilities.d.ts +13 -98
  509. package/dist/test/utilities.d.ts.map +1 -1
  510. package/dist/test/worker.d.ts +6 -0
  511. package/dist/test/worker.d.ts.map +1 -1
  512. package/dist/transform/durable-object.d.ts.map +1 -1
  513. package/dist/transform/index.d.ts +2 -2
  514. package/dist/transform/worker-entrypoint.d.ts +5 -0
  515. package/dist/transform/worker-entrypoint.d.ts.map +1 -1
  516. package/dist/utils/entrypoint-discovery.d.ts +6 -3
  517. package/dist/utils/entrypoint-discovery.d.ts.map +1 -1
  518. package/dist/utils/glob.d.ts.map +1 -1
  519. package/dist/utils/resolve-package.d.ts.map +1 -1
  520. package/dist/utils/send-email.d.ts +15 -0
  521. package/dist/utils/send-email.d.ts.map +1 -0
  522. package/dist/utils/send-email.js +163 -0
  523. package/dist/vite/config-file.d.ts +25 -0
  524. package/dist/vite/config-file.d.ts.map +1 -0
  525. package/dist/vite/index.d.ts +3 -2
  526. package/dist/vite/index.d.ts.map +1 -1
  527. package/dist/vite/index.js +2 -339
  528. package/dist/vite/plugin-config-hook.d.ts +28 -0
  529. package/dist/vite/plugin-config-hook.d.ts.map +1 -0
  530. package/dist/vite/plugin-context.d.ts +27 -0
  531. package/dist/vite/plugin-context.d.ts.map +1 -0
  532. package/dist/vite/plugin-durable-objects.d.ts +18 -0
  533. package/dist/vite/plugin-durable-objects.d.ts.map +1 -0
  534. package/dist/vite/plugin-programmatic.d.ts +44 -0
  535. package/dist/vite/plugin-programmatic.d.ts.map +1 -0
  536. package/dist/vite/plugin-service-bindings.d.ts +13 -0
  537. package/dist/vite/plugin-service-bindings.d.ts.map +1 -0
  538. package/dist/vite/plugin-transform.d.ts +41 -0
  539. package/dist/vite/plugin-transform.d.ts.map +1 -0
  540. package/dist/vite/plugin.d.ts +10 -41
  541. package/dist/vite/plugin.d.ts.map +1 -1
  542. package/dist/worker-entry/composed-worker.d.ts +7 -0
  543. package/dist/worker-entry/composed-worker.d.ts.map +1 -0
  544. package/dist/worker-entry/durable-object-discovery.d.ts +20 -0
  545. package/dist/worker-entry/durable-object-discovery.d.ts.map +1 -0
  546. package/dist/worker-entry/extensions.d.ts +5 -0
  547. package/dist/worker-entry/extensions.d.ts.map +1 -0
  548. package/dist/worker-entry/routes.d.ts +22 -0
  549. package/dist/worker-entry/routes.d.ts.map +1 -0
  550. package/dist/worker-entry/surface-paths.d.ts +26 -0
  551. package/dist/worker-entry/surface-paths.d.ts.map +1 -0
  552. package/dist/worker-entry/validate-fetch-style.d.ts +12 -0
  553. package/dist/worker-entry/validate-fetch-style.d.ts.map +1 -0
  554. package/dist/workflows/local-workflow-entrypoints.d.ts +7 -0
  555. package/dist/workflows/local-workflow-entrypoints.d.ts.map +1 -0
  556. package/package.json +159 -116
  557. package/R2.md +0 -170
  558. package/dist/account-rvrj687w.js +0 -397
  559. package/dist/ai-dx4fr9jh.js +0 -107
  560. package/dist/bridge/protocol.d.ts.map +0 -1
  561. package/dist/bridge/serialization.d.ts.map +0 -1
  562. package/dist/browser-shim/worker.d.ts +0 -14
  563. package/dist/browser-shim/worker.d.ts.map +0 -1
  564. package/dist/build-mnf6v8gd.js +0 -53
  565. package/dist/deploy-nhceck39.js +0 -70
  566. package/dist/dev-b9dmrj7b.js +0 -2417
  567. package/dist/doctor-fmgb3d28.js +0 -190
  568. package/dist/durable-object-t4kbb0yt.js +0 -13
  569. package/dist/index-07q6yxyc.js +0 -168
  570. package/dist/index-18hvb6gb.js +0 -194
  571. package/dist/index-1xpj0m4r.js +0 -57
  572. package/dist/index-37x76zdn.js +0 -4
  573. package/dist/index-3t6rypgc.js +0 -13
  574. package/dist/index-67qcae0f.js +0 -183
  575. package/dist/index-a855bdsx.js +0 -18
  576. package/dist/index-d8bdkx2h.js +0 -109
  577. package/dist/index-ep3445yc.js +0 -2225
  578. package/dist/index-gz1gndna.js +0 -307
  579. package/dist/index-hcex3rgh.js +0 -266
  580. package/dist/index-m2q41jwa.js +0 -462
  581. package/dist/index-n7rs26ft.js +0 -77
  582. package/dist/index-pf5s73n9.js +0 -1413
  583. package/dist/index-rbht7m9r.js +0 -36
  584. package/dist/index-tfyxa77h.js +0 -850
  585. package/dist/index-tk6ej9dj.js +0 -94
  586. package/dist/index-z14anrqp.js +0 -226
  587. package/dist/init-f9mgmew3.js +0 -186
  588. package/dist/remote-q59qk463.js +0 -97
  589. package/dist/test/bridge-context.d.ts +0 -76
  590. package/dist/test/bridge-context.d.ts.map +0 -1
  591. package/dist/test/multi-worker-context.d.ts +0 -114
  592. package/dist/test/multi-worker-context.d.ts.map +0 -1
  593. package/dist/types-5nyrz1sz.js +0 -454
  594. package/dist/worker-entrypoint-m9th0rg0.js +0 -13
@@ -0,0 +1,3471 @@
1
+ import { c as getDevflareDotenvPaths, d as resolveConfigEnvVars, o as EnvVarResolutionError } from "./preview-C3Cdr8an.js";
2
+ import { i as resolveConfigPath, r as loadConfig } from "./loader-Cr0_OWPy.js";
3
+ import { g as normalizeR2Binding, n as getLocalKVNamespaceIdentifier, r as getSingleBrowserBindingName, t as getLocalD1DatabaseIdentifier } from "./schema-normalization-BCwTLTGp.js";
4
+ import { t as applyLocalDevVarsToConfig } from "./local-dev-vars-CTSa-wvF.js";
5
+ import { a as cyanBold, f as logLine, g as yellow, o as dim, r as createCliTheme } from "./ui-BUoZApvE.js";
6
+ import { clearLocalSendEmailBindings, setLocalSendEmailBindings } from "../utils/send-email.js";
7
+ import { C as buildSendEmailConfig, D as buildWorkerLoadersConfig, E as buildVersionMetadataConfig, O as buildWorkflowsConfig, S as buildSecretsStoreConfig, T as buildTailConsumersConfig, _ as buildMtlsCertificatesConfig, b as buildQueueProducers, c as buildAiSearchInstancesConfig, d as buildArtifactsConfig, f as buildDispatchNamespacesConfig, g as buildMediaConfig, h as buildImagesConfig, l as buildAiSearchNamespacesConfig, m as buildHyperdrivesConfig, n as getRouteDirectoryCandidate, p as buildFlagshipConfig, s as resolveServiceBindings, t as discoverRoutes, u as buildAnalyticsEngineConfig, v as buildPipelinesConfig, w as buildStreamConfig, x as buildRateLimitsConfig, y as buildQueueConsumers } from "./routes-BFYCqHb-.js";
8
+ import { r as transformDurableObject } from "./durable-object-DMtH0XYX.js";
9
+ import { i as findFiles } from "./glob-CmQOvunB.js";
10
+ import { d as DEFAULT_FETCH_ENTRY_FILES, f as DEFAULT_QUEUE_ENTRY_FILES, g as discoverDurableObjectFiles, h as resolveWorkerSurfacePaths, i as writeGeneratedViteConfig, l as prepareComposedWorkerEntrypoint, m as hasWorkerSurfacePaths, n as resolveEffectiveViteProject, p as DEFAULT_SCHEDULED_ENTRY_FILES, u as DEFAULT_EMAIL_ENTRY_FILES } from "./vite-CGtVNf5E.js";
11
+ import { a as createWorkerdBundlerDefaults, i as writeWorkerCompatibleBundle, n as ensureDebugShim, r as resolveWorkerCompatibleRolldownConfig, t as bundleWorkerEntry } from "./worker-bundler-dTTnYU3n.js";
12
+ import { t as bundleWorkflowEntrypointScript } from "./local-workflow-entrypoints-Cq_WJajd.js";
13
+ import { n as buildLocalSecretWrappedBindingConfig } from "./local-secrets-DMPkmOn3.js";
14
+ import { t as buildLocalBindingShimServiceConfig } from "./local-media-bindings-DsAW4AMF.js";
15
+ import { i as waitForViteReady, n as resolveViteMode, r as stopSpawnedProcessTree, t as detectViteProject } from "./vite-utils-OVLMV605.js";
16
+ import { existsSync } from "node:fs";
17
+ import { dirname, relative, resolve } from "pathe";
18
+ import { exec, spawn } from "node:child_process";
19
+ import { homedir } from "node:os";
20
+ import { join as join$1 } from "node:path";
21
+ import { createConsola } from "consola";
22
+ import picomatch from "picomatch";
23
+ import { createHash } from "node:crypto";
24
+ import { promisify } from "node:util";
25
+ import { createInterface } from "node:readline";
26
+ import { createServer } from "node:http";
27
+ import { Browser, detectBrowserPlatform, install, resolveBuildId } from "@puppeteer/browsers";
28
+ import puppeteerCore from "puppeteer-core";
29
+ //#region src/bundler/do-bundler.ts
30
+ /**
31
+ * Convert PascalCase class name to SCREAMING_SNAKE_CASE binding name
32
+ */
33
+ function classToBindingName(className) {
34
+ return className.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").toUpperCase();
35
+ }
36
+ /**
37
+ * Discover DO classes from a glob pattern.
38
+ * Respects .gitignore automatically.
39
+ */
40
+ async function discoverDOs(cwd, pattern) {
41
+ const discovered = [];
42
+ const files = await discoverDurableObjectFiles(cwd, pattern);
43
+ for (const [filePath, classNames] of files) for (const className of classNames) discovered.push({
44
+ filePath,
45
+ className,
46
+ bindingName: classToBindingName(className)
47
+ });
48
+ return discovered;
49
+ }
50
+ /**
51
+ * Strip @durableObject decorator and its import from source code
52
+ * For dev mode, we just need to remove the decorator syntax - the DO class works as-is
53
+ */
54
+ function stripDecoratorSyntax(code) {
55
+ let result = code;
56
+ result = result.replace(/@durableObject\s*\([^)]*\)\s*\n?\s*(?=export\s+class)/g, "");
57
+ result = result.replace(/import\s*\{([^}]*)\bdurableObject\b[^}]*\}\s*from\s*['"]devflare\/runtime['"]\s*;?/g, (match, imports) => {
58
+ const cleanedImports = imports.split(",").map((s) => s.trim()).filter((s) => !s.startsWith("durableObject")).join(", ");
59
+ if (cleanedImports.trim() === "") return "";
60
+ return `import { ${cleanedImports} } from 'devflare/runtime'`;
61
+ });
62
+ return result;
63
+ }
64
+ /**
65
+ * Bundle a single DO file using Rolldown
66
+ *
67
+ * Strategy:
68
+ * 1. Read the source file
69
+ * 2. Strip @durableObject decorator (not needed at runtime - just a marker)
70
+ * 3. Feed the cleaned code to Rolldown through a virtual-entry plugin whose id
71
+ * sits in the source directory, so relative imports resolve identically to
72
+ * the original Durable Object module — without ever writing a temp file
73
+ * next to user source.
74
+ * 4. Bundle with Rolldown.
75
+ */
76
+ async function bundleDOFile(sourcePath, className, outDir, cwd, bundleOptions) {
77
+ const fs = await import("node:fs/promises");
78
+ await fs.mkdir(outDir, { recursive: true });
79
+ const sourceCode = await fs.readFile(sourcePath, "utf-8");
80
+ const entryCode = `${(await transformDurableObject(sourceCode, sourcePath))?.code ?? stripDecoratorSyntax(sourceCode)}
81
+
82
+ // Default export for worker (required by Miniflare)
83
+ export default {
84
+ async fetch(request) {
85
+ return new Response('DO Worker for ${className}', { status: 200 });
86
+ }
87
+ };
88
+ `;
89
+ const virtualEntryId = resolve(dirname(sourcePath), `.devflare-do-${className}.virtual.ts`);
90
+ const classOutDir = resolve(outDir, className);
91
+ try {
92
+ await fs.rm(classOutDir, {
93
+ recursive: true,
94
+ force: true
95
+ });
96
+ } catch {}
97
+ await fs.mkdir(classOutDir, { recursive: true });
98
+ const debugShimPath = await ensureDebugShim(outDir);
99
+ const virtualEntryPlugin = {
100
+ name: "devflare-do-virtual-entry",
101
+ resolveId(id) {
102
+ if (id === virtualEntryId) return virtualEntryId;
103
+ return null;
104
+ },
105
+ load(id) {
106
+ if (id === virtualEntryId) return entryCode;
107
+ return null;
108
+ }
109
+ };
110
+ const userRolldownOptions = bundleOptions?.rolldownOptions;
111
+ const userPlugins = userRolldownOptions?.plugins;
112
+ const mergedPlugins = userPlugins === void 0 ? [virtualEntryPlugin] : Array.isArray(userPlugins) ? [virtualEntryPlugin, ...userPlugins] : [virtualEntryPlugin, userPlugins];
113
+ const outFile = resolve(classOutDir, "index.js");
114
+ const defaults = createWorkerdBundlerDefaults();
115
+ const { inputOptions, outputOptions } = resolveWorkerCompatibleRolldownConfig({
116
+ cwd,
117
+ inputFile: virtualEntryId,
118
+ outFile,
119
+ ...defaults,
120
+ platform: "neutral",
121
+ alias: { debug: debugShimPath },
122
+ rolldownOptions: {
123
+ ...userRolldownOptions,
124
+ plugins: mergedPlugins
125
+ },
126
+ sourcemap: bundleOptions?.sourcemap ?? defaults.sourcemap,
127
+ minify: bundleOptions?.minify ?? defaults.minify,
128
+ inlineDynamicImports: true,
129
+ defaultTsconfigMode: "always"
130
+ });
131
+ await writeWorkerCompatibleBundle({
132
+ inputOptions,
133
+ outputOptions,
134
+ outFile
135
+ });
136
+ return resolve(classOutDir, "index.js");
137
+ }
138
+ /**
139
+ * Bundle all discovered DOs
140
+ */
141
+ async function bundleAllDOs(discovered, outDir, cwd, logger, bundleOptions) {
142
+ await import("node:fs/promises");
143
+ const bundles = /* @__PURE__ */ new Map();
144
+ const classes = /* @__PURE__ */ new Map();
145
+ const sourceFiles = /* @__PURE__ */ new Map();
146
+ const errors = [];
147
+ for (const do_ of discovered) {
148
+ const existing = sourceFiles.get(do_.filePath) || [];
149
+ existing.push(do_.className);
150
+ sourceFiles.set(do_.filePath, existing);
151
+ }
152
+ for (const do_ of discovered) try {
153
+ logger?.debug(`Bundling ${do_.className} from ${do_.filePath}`);
154
+ const outFile = await bundleDOFile(do_.filePath, do_.className, outDir, cwd, bundleOptions);
155
+ bundles.set(do_.bindingName, outFile);
156
+ classes.set(do_.bindingName, do_.className);
157
+ logger?.debug(` → ${outFile}`);
158
+ } catch (error) {
159
+ const err = error instanceof Error ? error : new Error(String(error));
160
+ errors.push(err);
161
+ logger?.error(`Failed to bundle ${do_.className}:`, err.message);
162
+ }
163
+ return {
164
+ bundles,
165
+ classes,
166
+ sourceFiles,
167
+ errors
168
+ };
169
+ }
170
+ /**
171
+ * Create a DO bundler with watch support
172
+ */
173
+ function createDOBundler(options) {
174
+ const { cwd, pattern, outDir, logger, onRebuild, rolldownOptions, sourcemap, minify } = options;
175
+ let result = {
176
+ bundles: /* @__PURE__ */ new Map(),
177
+ classes: /* @__PURE__ */ new Map(),
178
+ sourceFiles: /* @__PURE__ */ new Map(),
179
+ errors: []
180
+ };
181
+ let watcher = null;
182
+ let chokidarWatcher = null;
183
+ /**
184
+ * Perform initial build
185
+ */
186
+ async function build() {
187
+ const discovered = await discoverDOs(cwd, pattern);
188
+ if (discovered.length === 0) {
189
+ logger?.debug("No DOs found matching pattern:", pattern);
190
+ return result;
191
+ }
192
+ logger?.info(`Found ${discovered.length} Durable Object(s)`);
193
+ for (const do_ of discovered) logger?.info(` • ${do_.className} → ${do_.bindingName}`);
194
+ result = await bundleAllDOs(discovered, outDir, cwd, logger, {
195
+ rolldownOptions,
196
+ sourcemap,
197
+ minify
198
+ });
199
+ if (result.errors.length === 0) logger?.success(`Bundled ${result.bundles.size} DO(s) to ${outDir}`);
200
+ return result;
201
+ }
202
+ /**
203
+ * Watch for changes and rebuild
204
+ *
205
+ * Strategy: Watch parent directories of DO files with a filter for matching files.
206
+ * This allows detection of new DO files created during dev.
207
+ * Uses compiled picomatch for fast pattern matching instead of re-globbing on every event.
208
+ */
209
+ async function watch() {
210
+ const chokidar = await import("chokidar");
211
+ const files = await findFiles(pattern, { cwd });
212
+ let dirsToWatch;
213
+ if (files.length > 0) dirsToWatch = [...new Set(files.map((f) => dirname(f)))];
214
+ else {
215
+ const patternDir = dirname(pattern);
216
+ const absolutePatternDir = resolve(cwd, patternDir === "." ? "" : patternDir) || cwd;
217
+ dirsToWatch = [absolutePatternDir];
218
+ logger?.debug(`No DO files yet, watching pattern directory: ${absolutePatternDir}`);
219
+ }
220
+ logger?.info(`Watching ${files.length} DO file(s) in ${dirsToWatch.length} director(ies)...`);
221
+ const isWindows = process.platform === "win32";
222
+ chokidarWatcher = chokidar.watch(dirsToWatch, {
223
+ ignoreInitial: true,
224
+ usePolling: isWindows,
225
+ interval: isWindows ? 300 : void 0,
226
+ awaitWriteFinish: {
227
+ stabilityThreshold: 100,
228
+ pollInterval: 50
229
+ },
230
+ depth: 0
231
+ });
232
+ const normalizePath = (p) => {
233
+ let normalized = p.replace(/\\/g, "/");
234
+ if (isWindows && /^[a-zA-Z]:/.test(normalized)) normalized = normalized[0].toLowerCase() + normalized.slice(1);
235
+ return normalized;
236
+ };
237
+ const isMatch = picomatch(pattern, {
238
+ dot: true,
239
+ matchBase: false
240
+ });
241
+ const matchesPattern = (filePath) => {
242
+ const normalizedPath = normalizePath(filePath);
243
+ return isMatch(relative(normalizePath(cwd), normalizedPath));
244
+ };
245
+ let isRebuilding = false;
246
+ let pendingRebuild = null;
247
+ let rebuildTimeout = null;
248
+ const scheduleRebuild = (changedPath) => {
249
+ if (rebuildTimeout) clearTimeout(rebuildTimeout);
250
+ rebuildTimeout = setTimeout(() => {
251
+ triggerRebuild(changedPath);
252
+ }, 150);
253
+ };
254
+ const triggerRebuild = async (changedPath) => {
255
+ if (isRebuilding) {
256
+ pendingRebuild = changedPath;
257
+ logger?.debug(`Rebuild already in progress, queuing: ${changedPath}`);
258
+ return;
259
+ }
260
+ isRebuilding = true;
261
+ try {
262
+ logger?.info(`DO file changed: ${changedPath}`);
263
+ logger?.info("Rebuilding DOs...");
264
+ const startTime = Date.now();
265
+ result = await build();
266
+ const elapsed = Date.now() - startTime;
267
+ logger?.success(`DO rebuild complete (${elapsed}ms)`);
268
+ await onRebuild?.(result);
269
+ } catch (error) {
270
+ logger?.error("DO rebuild failed:", error);
271
+ } finally {
272
+ isRebuilding = false;
273
+ if (pendingRebuild) {
274
+ const nextPath = pendingRebuild;
275
+ pendingRebuild = null;
276
+ triggerRebuild(nextPath);
277
+ }
278
+ }
279
+ };
280
+ chokidarWatcher.on("change", (filePath) => {
281
+ if (matchesPattern(filePath)) {
282
+ logger?.debug(`File changed: ${filePath}`);
283
+ scheduleRebuild(filePath);
284
+ }
285
+ });
286
+ chokidarWatcher.on("add", (filePath) => {
287
+ if (matchesPattern(filePath)) {
288
+ logger?.debug(`File added: ${filePath}`);
289
+ scheduleRebuild(filePath);
290
+ }
291
+ });
292
+ chokidarWatcher.on("unlink", (filePath) => {
293
+ if (matchesPattern(filePath)) {
294
+ logger?.debug(`File removed: ${filePath}`);
295
+ scheduleRebuild(filePath);
296
+ }
297
+ });
298
+ chokidarWatcher.on("ready", () => {
299
+ logger?.info("DO file watcher ready");
300
+ });
301
+ chokidarWatcher.on("error", (error) => {
302
+ logger?.error("DO file watcher error:", error);
303
+ });
304
+ }
305
+ /**
306
+ * Stop watching
307
+ */
308
+ async function close() {
309
+ if (watcher) {
310
+ await watcher.close();
311
+ watcher = null;
312
+ }
313
+ if (chokidarWatcher) {
314
+ await chokidarWatcher.close();
315
+ chokidarWatcher = null;
316
+ }
317
+ }
318
+ /**
319
+ * Get the latest result
320
+ */
321
+ function getResult() {
322
+ return result;
323
+ }
324
+ return {
325
+ build,
326
+ watch,
327
+ close,
328
+ getResult
329
+ };
330
+ }
331
+ //#endregion
332
+ //#region src/dev-server/miniflare-log.ts
333
+ const ANSI_ESCAPE_REGEX = /\u001B\[[0-9;]*m/g;
334
+ const COMPATIBILITY_DATE_FALLBACK_REGEX = /^The latest compatibility date supported by the installed Cloudflare Workers Runtime is "([^"]+)", but you've requested "([^"]+)"\. Falling back to "([^"]+)"\.\.\.$/;
335
+ const MINIFLARE_LOG_LEVEL_FALLBACKS = {
336
+ WARN: 2,
337
+ DEBUG: 4
338
+ };
339
+ function normalizeMiniflareMessage(message) {
340
+ return message.replace(ANSI_ESCAPE_REGEX, "").replace(/\s+/g, " ").trim();
341
+ }
342
+ function formatCompatibilityDateFallbackNotice(message) {
343
+ const normalizedMessage = normalizeMiniflareMessage(message);
344
+ const match = COMPATIBILITY_DATE_FALLBACK_REGEX.exec(normalizedMessage);
345
+ if (!match) return null;
346
+ const [, _supportedDate, requestedDate, fallbackDate] = match;
347
+ return `Using latest supported Cloudflare Workers Runtime compatibility date ${fallbackDate} (requested ${requestedDate})`;
348
+ }
349
+ function resolveMiniflareLogLevel(logLevel, levelName) {
350
+ return logLevel?.[levelName] ?? MINIFLARE_LOG_LEVEL_FALLBACKS[levelName];
351
+ }
352
+ function createMiniflareLog(BaseLog, logLevel, levelName, logger) {
353
+ if (!BaseLog) return;
354
+ return createCompatibilityAwareMiniflareLog(BaseLog, resolveMiniflareLogLevel(logLevel, levelName), logger);
355
+ }
356
+ function createCompatibilityAwareMiniflareLog(BaseLog, level, logger) {
357
+ const log = new BaseLog(level);
358
+ const originalWarn = log.warn.bind(log);
359
+ const originalInfo = log.info.bind(log);
360
+ log.warn = (message) => {
361
+ const notice = formatCompatibilityDateFallbackNotice(message);
362
+ if (!notice) {
363
+ originalWarn(message);
364
+ return;
365
+ }
366
+ if (logger) {
367
+ logger.info(notice);
368
+ return;
369
+ }
370
+ originalInfo(notice);
371
+ };
372
+ return log;
373
+ }
374
+ //#endregion
375
+ //#region src/bridge/gateway-runtime.ts
376
+ /**
377
+ * Shared gateway helpers (base64, R2 serializers, email) and the RPC method
378
+ * dispatcher. Designed to be embedded verbatim at the top level of a worker
379
+ * module. All symbols are declared with `function`/`const` so they are
380
+ * hoisted in both embedding sites.
381
+ */
382
+ const GATEWAY_RUNTIME_JS = `
383
+ const RAW_EMAIL = 'EmailMessage::raw'
384
+
385
+ // Inline body cap for proxied DO/service-fetch responses. Matches
386
+ // HTTP_TRANSFER_THRESHOLD in wire.ts (512 KB). A larger body would exceed
387
+ // workerd's ~1 MB WebSocket message limit once base64-encoded into the rpc.ok
388
+ // frame, so it is rejected with a clear error rather than silently truncated.
389
+ const HTTP_TRANSFER_THRESHOLD = 512 * 1024
390
+
391
+ function arrayBufferToBase64(buffer) {
392
+ const bytes = new Uint8Array(buffer)
393
+ let binary = ''
394
+ for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i])
395
+ return btoa(binary)
396
+ }
397
+
398
+ function base64ToArrayBuffer(base64) {
399
+ const binary = atob(base64)
400
+ const bytes = new Uint8Array(binary.length)
401
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)
402
+ return bytes.buffer
403
+ }
404
+
405
+ // Binary WsData frame codec (10-byte header: kind:u8, id:u32 LE, seq:u32 LE,
406
+ // flags:u8 + payload). MUST match wire.ts encodeBinaryFrame/decodeBinaryFrame so
407
+ // the bridge client and this gateway agree on the WS-data wire format
408
+ // (BinaryKind.WsData = 2, BinaryFlags.TEXT = 2).
409
+ function encodeWsDataFrame(wid, flags, payload) {
410
+ const frame = new Uint8Array(10 + payload.byteLength)
411
+ const view = new DataView(frame.buffer)
412
+ view.setUint8(0, 2)
413
+ view.setUint32(1, wid, true)
414
+ view.setUint32(5, 0, true)
415
+ view.setUint8(9, flags)
416
+ frame.set(payload, 10)
417
+ return frame
418
+ }
419
+
420
+ function decodeWsDataFrame(buffer) {
421
+ const bytes = new Uint8Array(buffer)
422
+ if (bytes.byteLength < 10) return null
423
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
424
+ return {
425
+ kind: view.getUint8(0),
426
+ id: view.getUint32(1, true),
427
+ flags: view.getUint8(9),
428
+ payload: bytes.subarray(10)
429
+ }
430
+ }
431
+
432
+ function serializeR2Object(obj) {
433
+ if (!obj) return null
434
+ return {
435
+ __type: 'R2Object',
436
+ key: obj.key,
437
+ version: obj.version,
438
+ size: obj.size,
439
+ etag: obj.etag,
440
+ httpEtag: obj.httpEtag,
441
+ checksums: obj.checksums,
442
+ uploaded: obj.uploaded?.toISOString(),
443
+ httpMetadata: obj.httpMetadata,
444
+ customMetadata: obj.customMetadata,
445
+ range: obj.range,
446
+ storageClass: obj.storageClass
447
+ }
448
+ }
449
+
450
+ function serializeR2ObjectBody(obj, bodyData) {
451
+ if (!obj) return null
452
+ return {
453
+ __type: 'R2ObjectBody',
454
+ key: obj.key,
455
+ version: obj.version,
456
+ size: obj.size,
457
+ etag: obj.etag,
458
+ httpEtag: obj.httpEtag,
459
+ checksums: obj.checksums,
460
+ uploaded: obj.uploaded?.toISOString(),
461
+ httpMetadata: obj.httpMetadata,
462
+ customMetadata: obj.customMetadata,
463
+ range: obj.range,
464
+ storageClass: obj.storageClass,
465
+ bodyData
466
+ }
467
+ }
468
+
469
+ function serializeR2Objects(result) {
470
+ if (!result) return null
471
+ return {
472
+ objects: result.objects.map(serializeR2Object),
473
+ truncated: result.truncated,
474
+ cursor: result.cursor,
475
+ delimitedPrefixes: result.delimitedPrefixes
476
+ }
477
+ }
478
+
479
+ async function serializeResponse(response) {
480
+ let body = null
481
+ if (response.body) {
482
+ const bytes = await response.arrayBuffer()
483
+ if (bytes.byteLength > HTTP_TRANSFER_THRESHOLD) {
484
+ throw new Error(
485
+ '[devflare][bridge] Response body (' + bytes.byteLength + ' bytes) exceeds the '
486
+ + HTTP_TRANSFER_THRESHOLD + '-byte inline limit. DO and service-binding fetch responses '
487
+ + 'are delivered inline over the bridge WebSocket and cannot be streamed locally; keep '
488
+ + 'proxied response bodies under the limit (large R2 objects are exempt — read them through '
489
+ + 'the R2 binding, which uses the HTTP transfer side-channel).'
490
+ )
491
+ }
492
+ if (bytes.byteLength > 0) {
493
+ body = { type: 'bytes', data: arrayBufferToBase64(bytes) }
494
+ }
495
+ }
496
+ return {
497
+ status: response.status,
498
+ statusText: response.statusText,
499
+ headers: [...response.headers.entries()],
500
+ body
501
+ }
502
+ }
503
+
504
+ function deserializeRequest(serializedReq) {
505
+ return new Request(serializedReq.url, {
506
+ method: serializedReq.method,
507
+ headers: serializedReq.headers,
508
+ body: serializedReq.body?.type === 'bytes'
509
+ ? base64ToArrayBuffer(serializedReq.body.data)
510
+ : undefined,
511
+ redirect: serializedReq.redirect
512
+ })
513
+ }
514
+
515
+ function createEmailMessageRaw(raw) {
516
+ if (typeof raw === 'string' || raw instanceof ReadableStream) {
517
+ return raw
518
+ }
519
+ if (raw instanceof ArrayBuffer || raw instanceof Uint8Array) {
520
+ return new Response(raw).body
521
+ }
522
+ throw new Error('Unsupported EmailMessage raw payload')
523
+ }
524
+
525
+ function isDurableObjectNamespace(binding) {
526
+ return !!binding
527
+ && typeof binding.idFromName === 'function'
528
+ && typeof binding.idFromString === 'function'
529
+ && typeof binding.newUniqueId === 'function'
530
+ }
531
+
532
+ /**
533
+ * Resolve a (possibly jurisdiction-scoped) DurableObjectNamespace.
534
+ * Forwards the jurisdiction faithfully when the binding supports it
535
+ * (real Cloudflare), and degrades to the plain binding otherwise
536
+ * (miniflare/workerd local emulation, which does not pin jurisdictions).
537
+ */
538
+ function resolveDoNamespace(binding, jurisdiction) {
539
+ if (jurisdiction && typeof binding.jurisdiction === 'function') {
540
+ return binding.jurisdiction(jurisdiction)
541
+ }
542
+ return binding
543
+ }
544
+
545
+ /**
546
+ * Execute an RPC method against the gateway's bindings.
547
+ *
548
+ * Method format: "binding.operation". Operations must be namespaced by
549
+ * binding kind (e.g. "kv.get", "r2.head", "d1.stmt.first", "do.fetch",
550
+ * "service.fetch", "queue.send", "email.send", "ai.run"). Bare verbs and the legacy
551
+ * "stmt.*" / "stub.*" sub-prefixes were removed in B3-final and now throw.
552
+ * Method vocabulary must stay in sync with the canonical server in
553
+ * src/bridge/server.ts.
554
+ */
555
+ async function executeRpcMethod(method, params, env, _ctx) {
556
+ const parts = method.split('.')
557
+ if (parts.length < 2) throw new Error('Invalid method format: ' + method)
558
+
559
+ const bindingName = parts[0]
560
+ const operation = parts.slice(1).join('.')
561
+ const binding = env[bindingName]
562
+
563
+ if (!binding) throw new Error('Binding not found: ' + bindingName)
564
+
565
+ const isNamespaced =
566
+ operation.indexOf('kv.') === 0 ||
567
+ operation.indexOf('r2.') === 0 ||
568
+ operation.indexOf('d1.') === 0 ||
569
+ operation.indexOf('do.') === 0 ||
570
+ operation.indexOf('service.') === 0 ||
571
+ operation.indexOf('queue.') === 0 ||
572
+ operation.indexOf('email.') === 0 ||
573
+ operation.indexOf('ai.') === 0 ||
574
+ operation.indexOf('workflow.') === 0 ||
575
+ operation.indexOf('var.') === 0
576
+ if (!isNamespaced) {
577
+ throw new Error(createUnsupportedBridgeOperationErrorMessage(bindingName, operation))
578
+ }
579
+
580
+ // KV
581
+ if (operation === 'kv.get') return binding.get(params[0], params[1])
582
+ if (operation === 'kv.put') return binding.put(params[0], params[1], params[2])
583
+ if (operation === 'kv.delete') return binding.delete(params[0])
584
+ if (operation === 'kv.list') return binding.list(params[0])
585
+ if (operation === 'kv.getWithMetadata') return binding.getWithMetadata(params[0], params[1])
586
+
587
+ // DO get (returns DOStub reference)
588
+ if (operation === 'do.get') {
589
+ return { __type: 'DOStub', binding: bindingName, id: params[0] }
590
+ }
591
+
592
+ // R2
593
+ if (operation === 'r2.head') return serializeR2Object(await binding.head(params[0]))
594
+ if (operation === 'r2.get') {
595
+ const obj = await binding.get(params[0], params[1])
596
+ if (!obj) return null
597
+ const body = await obj.arrayBuffer()
598
+ return serializeR2ObjectBody(obj, arrayBufferToBase64(body))
599
+ }
600
+ if (operation === 'r2.put') {
601
+ let value = params[1]
602
+ if (value && typeof value === 'object') {
603
+ if (value.__type === 'ArrayBuffer' || value.__type === 'Uint8Array') {
604
+ value = base64ToArrayBuffer(value.data)
605
+ }
606
+ }
607
+ return serializeR2Object(await binding.put(params[0], value, params[2]))
608
+ }
609
+ if (operation === 'r2.delete') return binding.delete(params[0])
610
+ if (operation === 'r2.list') return serializeR2Objects(await binding.list(params[0]))
611
+
612
+ // D1
613
+ if (operation === 'd1.exec') return binding.exec(params[0])
614
+ if (operation === 'd1.batch') {
615
+ const statements = params[0].map((s) => binding.prepare(s.sql).bind(...(s.bindings || [])))
616
+ return binding.batch(statements)
617
+ }
618
+ if (operation.indexOf('d1.stmt.') === 0) {
619
+ const mode = operation.split('.')[2]
620
+ const [sql, ...rest] = params
621
+ let bindings = rest
622
+ let extraParam
623
+ if (mode === 'first' || mode === 'raw') {
624
+ extraParam = rest[rest.length - 1]
625
+ bindings = rest.slice(0, -1)
626
+ }
627
+ let stmt = binding.prepare(sql)
628
+ if (bindings.length > 0) stmt = stmt.bind(...bindings)
629
+ if (mode === 'first') {
630
+ if (typeof extraParam === 'string' && extraParam.length > 0) return stmt.first(extraParam)
631
+ return stmt.first()
632
+ }
633
+ if (mode === 'all') return stmt.all()
634
+ if (mode === 'run') return stmt.run()
635
+ if (mode === 'raw') return stmt.raw(extraParam)
636
+ throw new Error('Unknown stmt mode: ' + mode)
637
+ }
638
+
639
+ // Durable Objects
640
+ if (operation === 'do.idFromName') {
641
+ const ns = resolveDoNamespace(binding, params[1])
642
+ const id = ns.idFromName(params[0])
643
+ return { __type: 'DOId', hex: id.toString() }
644
+ }
645
+ if (operation === 'do.idFromString') {
646
+ const id = binding.idFromString(params[0])
647
+ return { __type: 'DOId', hex: id.toString() }
648
+ }
649
+ if (operation === 'do.newUniqueId') {
650
+ const ns = resolveDoNamespace(binding, params[1])
651
+ const id = ns.newUniqueId(params[0])
652
+ return { __type: 'DOId', hex: id.toString() }
653
+ }
654
+ if (operation === 'do.fetch') {
655
+ const [, serializedId, serializedReq] = params
656
+ const id = binding.idFromString(serializedId.hex)
657
+ const stub = binding.get(id)
658
+ const response = await stub.fetch(new Request(serializedReq.url, {
659
+ method: serializedReq.method,
660
+ headers: serializedReq.headers,
661
+ body: serializedReq.body?.type === 'bytes'
662
+ ? base64ToArrayBuffer(serializedReq.body.data)
663
+ : undefined
664
+ }))
665
+ return serializeResponse(response)
666
+ }
667
+ if (operation === 'do.rpc') {
668
+ const [, serializedId, methodName, args] = params
669
+ const id = binding.idFromString(serializedId.hex)
670
+ const stub = binding.get(id)
671
+ const response = await stub.fetch(new Request('http://do/_rpc', {
672
+ method: 'POST',
673
+ headers: { 'Content-Type': 'application/json' },
674
+ body: JSON.stringify({ method: methodName, params: args })
675
+ }))
676
+ const result = await response.json()
677
+ if (!result.ok) throw new Error(result.error?.message || 'RPC failed')
678
+ return result.result
679
+ }
680
+
681
+ // Service Bindings
682
+ if (operation === 'service.fetch') {
683
+ if (!binding || typeof binding.fetch !== 'function') {
684
+ throw new Error('Service binding ' + bindingName + ' does not support fetch()')
685
+ }
686
+ const response = await binding.fetch(deserializeRequest(params[0]))
687
+ return serializeResponse(response)
688
+ }
689
+ if (operation === 'service.rpc') {
690
+ const methodName = params[0]
691
+ if (typeof methodName !== 'string') {
692
+ throw new Error('Service binding ' + bindingName + ' RPC method name must be a string')
693
+ }
694
+ const args = Array.isArray(params[1]) ? params[1] : []
695
+ const method = binding && binding[methodName]
696
+ if (typeof method !== 'function') {
697
+ throw new Error('Service binding ' + bindingName + ' does not support ' + methodName + '()')
698
+ }
699
+ return method.apply(binding, args)
700
+ }
701
+
702
+ // Queues
703
+ if (operation === 'queue.send') return binding.send(params[0], params[1])
704
+ if (operation === 'queue.sendBatch') return binding.sendBatch(params[0], params[1])
705
+
706
+ // Send Email
707
+ if (operation === 'email.send') {
708
+ if (binding && typeof binding.send === 'function') {
709
+ const message = params[0]
710
+ if (message && typeof message === 'object' && 'from' in message && 'to' in message && 'raw' in message) {
711
+ return binding.send({
712
+ from: message.from,
713
+ to: message.to,
714
+ [RAW_EMAIL]: createEmailMessageRaw(message.raw)
715
+ })
716
+ }
717
+ return binding.send(message)
718
+ }
719
+ return { ok: true, simulated: true }
720
+ }
721
+
722
+ // Workflows
723
+ if (operation === 'workflow.create') {
724
+ return serializeWorkflowInstance(await binding.create(params[0]))
725
+ }
726
+ if (operation === 'workflow.get') {
727
+ return serializeWorkflowInstance(await binding.get(params[0]))
728
+ }
729
+ if (operation === 'workflow.status') {
730
+ return (await binding.get(params[0])).status()
731
+ }
732
+ if (operation === 'workflow.pause') {
733
+ return (await binding.get(params[0])).pause()
734
+ }
735
+ if (operation === 'workflow.resume') {
736
+ return (await binding.get(params[0])).resume()
737
+ }
738
+ if (operation === 'workflow.terminate') {
739
+ return (await binding.get(params[0])).terminate()
740
+ }
741
+ if (operation === 'workflow.restart') {
742
+ return (await binding.get(params[0])).restart()
743
+ }
744
+ if (operation === 'workflow.sendEvent') {
745
+ return (await binding.get(params[0])).sendEvent(params[1])
746
+ }
747
+
748
+ // AI / generic run()
749
+ if (operation === 'ai.run') {
750
+ if (typeof binding.run !== 'function') {
751
+ throw new Error('Binding ' + bindingName + ' does not support run(): ' + method)
752
+ }
753
+ return binding.run(params[0], params[1])
754
+ }
755
+
756
+ throw new Error('Unknown operation: ' + method)
757
+ }
758
+
759
+ function createUnsupportedBridgeOperationErrorMessage(bindingName, operation) {
760
+ const base = "[devflare][bridge] Unsupported bridge operation '" + operation + "' for binding '" + bindingName + "'."
761
+ if (operation === 'fetch') {
762
+ return base + ' Devflare could not dispatch fetch() for this binding through the local bridge. '
763
+ + 'Expected Cloudflare API: env.' + bindingName + '.fetch(request). '
764
+ + 'If this came from SvelteKit platform.env, make sure the binding is declared as a service binding; '
765
+ + 'this is a Devflare local bridge issue when service bindings fall back to a bare fetch operation.'
766
+ }
767
+ if (operation === 'toString') {
768
+ return base + ' A platform.env value was coerced to a string through the bridge. '
769
+ + 'For SvelteKit local dev, declared vars should be plain string values and missing env names should read as undefined.'
770
+ }
771
+ return base + ' Bare verbs and the legacy stmt.*/stub.* sub-prefixes are not supported; '
772
+ + 'use the namespaced form (e.g. kv.get, r2.put, d1.stmt.first, do.fetch, service.fetch).'
773
+ }
774
+
775
+ function serializeWorkflowInstance(instance) {
776
+ return {
777
+ __type: 'WorkflowInstance',
778
+ id: instance.id
779
+ }
780
+ }
781
+
782
+ // ---------------------------------------------------------------------------
783
+ // WebSocket bridge (shared with src/bridge/server.ts in shape)
784
+ // ---------------------------------------------------------------------------
785
+ // NOTE: wsProxies is intentionally created per handleBridgeWebSocket call so
786
+ // state never leaks across connections or across gateway-script regenerations.
787
+
788
+ async function handleBridgeRpcCall(msg, ws, env, ctx) {
789
+ try {
790
+ const result = await executeRpcMethod(msg.method, msg.params, env, ctx)
791
+ ws.send(JSON.stringify({ t: 'rpc.ok', id: msg.id, result }))
792
+ } catch (error) {
793
+ ws.send(JSON.stringify({
794
+ t: 'rpc.err',
795
+ id: msg.id,
796
+ error: {
797
+ code: error?.code || 'INTERNAL_ERROR',
798
+ message: error?.message || String(error)
799
+ }
800
+ }))
801
+ }
802
+ }
803
+
804
+ async function handleBridgeWsOpen(msg, ws, env, wsProxies) {
805
+ try {
806
+ const binding = env[msg.target.binding]
807
+ const id = binding.idFromString(msg.target.id)
808
+ const stub = binding.get(id)
809
+
810
+ const headers = new Headers(msg.target.headers || [])
811
+ headers.set('Upgrade', 'websocket')
812
+
813
+ const response = await stub.fetch(new Request(msg.target.url, { method: 'GET', headers }))
814
+ const doWs = response.webSocket
815
+
816
+ if (!doWs) {
817
+ ws.send(JSON.stringify({
818
+ t: 'rpc.err',
819
+ id: 'ws_' + msg.wid,
820
+ error: { code: 'WS_FAILED', message: 'No WebSocket returned' }
821
+ }))
822
+ return
823
+ }
824
+
825
+ doWs.accept()
826
+ wsProxies.set(msg.wid, { doWs })
827
+
828
+ doWs.addEventListener('message', (event) => {
829
+ const isText = typeof event.data === 'string'
830
+ const payload = isText
831
+ ? new TextEncoder().encode(event.data)
832
+ : new Uint8Array(event.data)
833
+ const flags = isText ? 2 : 0
834
+ ws.send(encodeWsDataFrame(msg.wid, flags, payload))
835
+ })
836
+
837
+ doWs.addEventListener('close', (event) => {
838
+ ws.send(JSON.stringify({ t: 'ws.close', wid: msg.wid, code: event.code, reason: event.reason }))
839
+ wsProxies.delete(msg.wid)
840
+ })
841
+
842
+ ws.send(JSON.stringify({ t: 'ws.opened', wid: msg.wid }))
843
+ } catch (error) {
844
+ ws.send(JSON.stringify({
845
+ t: 'rpc.err',
846
+ id: 'ws_' + msg.wid,
847
+ error: { code: 'WS_FAILED', message: error.message }
848
+ }))
849
+ }
850
+ }
851
+
852
+ function handleBridgeWsClose(msg, wsProxies) {
853
+ const proxy = wsProxies.get(msg.wid)
854
+ if (proxy) {
855
+ proxy.doWs.close(msg.code, msg.reason)
856
+ wsProxies.delete(msg.wid)
857
+ }
858
+ }
859
+
860
+ // Relay an inbound binary WsData frame (client -> DO). Mirrors server.ts
861
+ // handleBinaryMessage: decode the frame, look up the proxy by ws id, and forward
862
+ // the payload to the DO socket honoring the TEXT flag.
863
+ function handleBridgeBinaryMessage(buffer, wsProxies) {
864
+ const frame = decodeWsDataFrame(buffer)
865
+ if (!frame || frame.kind !== 2) return
866
+ const proxy = wsProxies.get(frame.id)
867
+ if (!proxy) return
868
+ if ((frame.flags & 2) !== 0) {
869
+ proxy.doWs.send(new TextDecoder().decode(frame.payload))
870
+ } else {
871
+ proxy.doWs.send(frame.payload)
872
+ }
873
+ }
874
+
875
+ async function handleBridgeJsonMessage(data, ws, env, ctx, wsProxies) {
876
+ const msg = JSON.parse(data)
877
+ switch (msg.t) {
878
+ case 'hello':
879
+ // v2 handshake — acknowledge with welcome echoing the negotiated
880
+ // capability intersection. The gateway advertises only what it
881
+ // implements end-to-end: 'ws-relay' (binary WsData relay to/from DO
882
+ // sockets) and 'http-transfer' (the R2 transfer HTTP side-channel).
883
+ // 'streams' is intentionally NOT advertised — large DO/service-fetch
884
+ // responses are inlined, not streamed, in this gateway.
885
+ ws.send(JSON.stringify({
886
+ t: 'welcome',
887
+ protocolVersion: 2,
888
+ capabilities: ['ws-relay', 'http-transfer']
889
+ .filter((c) => Array.isArray(msg.capabilities) && msg.capabilities.includes(c))
890
+ .sort()
891
+ }))
892
+ break
893
+ case 'rpc.call':
894
+ await handleBridgeRpcCall(msg, ws, env, ctx)
895
+ break
896
+ case 'ws.open':
897
+ await handleBridgeWsOpen(msg, ws, env, wsProxies)
898
+ break
899
+ case 'ws.close':
900
+ handleBridgeWsClose(msg, wsProxies)
901
+ break
902
+ }
903
+ }
904
+
905
+ function handleBridgeWebSocket(request, env, ctx) {
906
+ const { 0: client, 1: server } = new WebSocketPair()
907
+ server.accept()
908
+
909
+ // Per-connection state: recreated for every bridge client so reloads and
910
+ // concurrent clients never share WS proxy entries.
911
+ const wsProxies = new Map()
912
+
913
+ server.addEventListener('message', async (event) => {
914
+ try {
915
+ if (typeof event.data === 'string') {
916
+ await handleBridgeJsonMessage(event.data, server, env, ctx, wsProxies)
917
+ } else {
918
+ handleBridgeBinaryMessage(event.data, wsProxies)
919
+ }
920
+ } catch (error) {
921
+ console.error('[Gateway] Error:', error)
922
+ }
923
+ })
924
+
925
+ server.addEventListener('close', () => {
926
+ for (const proxy of wsProxies.values()) {
927
+ // Best-effort cleanup: the DO-side WS may already be closed or in an
928
+ // invalid state; any throw here would abort sibling closes. Surface
929
+ // the swallowed error when DEVFLARE_DEBUG_BRIDGE is enabled.
930
+ try { proxy.doWs.close() } catch (error) {
931
+ if (globalThis.DEVFLARE_DEBUG_BRIDGE) {
932
+ console.warn('[devflare:bridge] proxy.doWs.close() failed', error)
933
+ }
934
+ }
935
+ }
936
+ wsProxies.clear()
937
+ })
938
+
939
+ return new Response(null, { status: 101, webSocket: client })
940
+ }
941
+
942
+ // ---------------------------------------------------------------------------
943
+ // HTTP transfer for R2 bodies (shared with src/bridge/server.ts in shape)
944
+ // ---------------------------------------------------------------------------
945
+
946
+ async function handleHttpTransfer(request, env, url) {
947
+ const transferIdEncoded = url.pathname.split('/').pop()
948
+ const transferId = decodeURIComponent(transferIdEncoded || '')
949
+ const [binding, ...keyParts] = transferId.split(':')
950
+ const key = keyParts.join(':')
951
+ const bucket = env[binding]
952
+
953
+ if (!bucket) return new Response('Bucket not found: ' + binding, { status: 404 })
954
+
955
+ if (request.method === 'PUT' || request.method === 'POST') {
956
+ const result = await bucket.put(key, request.body)
957
+ return new Response(JSON.stringify(serializeR2Object(result)), {
958
+ headers: { 'Content-Type': 'application/json' }
959
+ })
960
+ }
961
+
962
+ if (request.method === 'GET') {
963
+ const object = await bucket.get(key)
964
+ if (!object) return new Response('Not found', { status: 404 })
965
+ return new Response(object.body, {
966
+ headers: {
967
+ 'Content-Type': object.httpMetadata?.contentType || 'application/octet-stream',
968
+ 'Content-Length': String(object.size)
969
+ }
970
+ })
971
+ }
972
+
973
+ return new Response('Method not allowed', { status: 405 })
974
+ }
975
+ `;
976
+ //#endregion
977
+ //#region src/bridge/miniflare.ts
978
+ function isIgnorableMiniflareDisposeError(error) {
979
+ if (!(error instanceof Error)) return false;
980
+ const details = error;
981
+ return details.code === "EBADF" && details.syscall === "kill";
982
+ }
983
+ //#endregion
984
+ //#region src/cli/wrangler-auth.ts
985
+ const execAsync = promisify(exec);
986
+ /**
987
+ * Check if the config contains any bindings that require remote access
988
+ */
989
+ function detectRemoteBindings(config) {
990
+ const remoteBindings = [];
991
+ const bindings = config.bindings;
992
+ if (!bindings) return remoteBindings;
993
+ if (bindings.ai) remoteBindings.push(`AI (binding: ${bindings.ai.binding})`);
994
+ if (bindings.vectorize) for (const [name] of Object.entries(bindings.vectorize)) remoteBindings.push(`Vectorize (binding: ${name})`);
995
+ return remoteBindings;
996
+ }
997
+ /**
998
+ * Check if wrangler is logged in by running `wrangler whoami`
999
+ *
1000
+ * Returns:
1001
+ * - loggedIn: true if user is authenticated
1002
+ * - accountId: the account ID if available
1003
+ * - email: the email if available
1004
+ * - error: error message if check failed
1005
+ */
1006
+ async function checkWranglerAuth() {
1007
+ try {
1008
+ const { stdout, stderr } = await execAsync("bunx wrangler whoami", { timeout: 15e3 });
1009
+ const output = stdout + stderr;
1010
+ if (output.includes("not authenticated") || output.includes("Not logged in") || output.includes("wrangler login")) return {
1011
+ loggedIn: false,
1012
+ error: "Not logged in to Wrangler"
1013
+ };
1014
+ const emailMatch = output.match(/email[:\s]+([^\s!]+)/i);
1015
+ const accountMatch = output.match(/Account\s+ID[:\s]+([a-f0-9]+)/i);
1016
+ return {
1017
+ loggedIn: true,
1018
+ email: emailMatch?.[1],
1019
+ accountId: accountMatch?.[1]
1020
+ };
1021
+ } catch (error) {
1022
+ const msg = error instanceof Error ? error.message : String(error);
1023
+ if (msg.includes("ENOENT") || msg.includes("not found")) return {
1024
+ loggedIn: false,
1025
+ error: "Wrangler not installed. Run: npm install -g wrangler"
1026
+ };
1027
+ return {
1028
+ loggedIn: false,
1029
+ error: msg
1030
+ };
1031
+ }
1032
+ }
1033
+ /**
1034
+ * Check if remote bindings are properly configured
1035
+ *
1036
+ * Returns warnings if:
1037
+ * - Config has remote-only bindings but no accountId
1038
+ * - Config has remote-only bindings but user is not logged in to wrangler
1039
+ */
1040
+ async function checkRemoteBindingRequirements(config) {
1041
+ const remoteBindings = detectRemoteBindings(config);
1042
+ if (remoteBindings.length === 0) return {
1043
+ hasRemoteBindings: false,
1044
+ remoteBindings: [],
1045
+ missingAccountId: false,
1046
+ notLoggedIn: false
1047
+ };
1048
+ return {
1049
+ hasRemoteBindings: true,
1050
+ remoteBindings,
1051
+ missingAccountId: !config.accountId,
1052
+ notLoggedIn: !(await checkWranglerAuth()).loggedIn
1053
+ };
1054
+ }
1055
+ //#endregion
1056
+ //#region src/dev-server/d1-migrations.ts
1057
+ const MIGRATION_RETRY_DELAYS_MS = [
1058
+ 500,
1059
+ 1e3,
1060
+ 1500,
1061
+ 2e3
1062
+ ];
1063
+ function collectMigrationStatements(sql) {
1064
+ return sql.split("\n").filter((line) => !line.trim().startsWith("--")).join("\n").split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
1065
+ }
1066
+ function hashSql(sql) {
1067
+ return createHash("sha256").update(sql).digest("hex");
1068
+ }
1069
+ function getErrorMessage(error) {
1070
+ return error instanceof Error ? error.message : String(error);
1071
+ }
1072
+ async function waitForRetry(delayMs) {
1073
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, delayMs));
1074
+ }
1075
+ async function applyMigrationsToBinding(options) {
1076
+ const { bindingName, statements, files, miniflarePort, logger } = options;
1077
+ let lastError;
1078
+ for (let attempt = 0; attempt <= MIGRATION_RETRY_DELAYS_MS.length; attempt++) {
1079
+ if (attempt > 0) await waitForRetry(MIGRATION_RETRY_DELAYS_MS[attempt - 1]);
1080
+ try {
1081
+ const response = await fetch(`http://127.0.0.1:${miniflarePort}/_devflare/migrate`, {
1082
+ method: "POST",
1083
+ headers: { "Content-Type": "application/json" },
1084
+ body: JSON.stringify({
1085
+ bindingName,
1086
+ statements,
1087
+ files
1088
+ })
1089
+ });
1090
+ if (!response.ok) {
1091
+ const text = await response.text();
1092
+ throw new Error(`HTTP ${response.status}: ${text}`);
1093
+ }
1094
+ const result = await response.json();
1095
+ if (result.success) {
1096
+ if (Array.isArray(result.warnings)) for (const warning of result.warnings) console.warn(`[devflare] D1 migration file "${warning.filename}" for binding ${bindingName} has changed since it was applied; skipping re-apply to protect existing data.`);
1097
+ const appliedCount = result.applied?.length ?? 0;
1098
+ const skippedCount = result.skipped?.length ?? 0;
1099
+ if (appliedCount > 0 || skippedCount > 0) logger?.success(`D1 migrations for ${bindingName}: ${appliedCount} applied, ${skippedCount} skipped`);
1100
+ else logger?.success(`D1 migrations applied to ${bindingName}`);
1101
+ return;
1102
+ }
1103
+ throw new Error(result.error || "Unknown error");
1104
+ } catch (error) {
1105
+ lastError = error;
1106
+ }
1107
+ }
1108
+ logger?.warn(`Failed to apply migrations to ${bindingName}: ${getErrorMessage(lastError)}`);
1109
+ }
1110
+ /**
1111
+ * Run D1 migrations from migrations/ directory.
1112
+ *
1113
+ * Resolution per D1 binding (in order):
1114
+ * 1. `<cwd>/migrations/<BINDING_NAME>/*.sql` — per-binding directory.
1115
+ * NOTE: if the per-binding directory EXISTS but contains no .sql files,
1116
+ * the binding is skipped — the shared fallback is NOT used.
1117
+ * 2. `<cwd>/migrations/*.sql` — shared fallback, used ONLY when the
1118
+ * per-binding directory does not exist.
1119
+ * 3. Otherwise, skip the binding with a debug log.
1120
+ *
1121
+ * Applied-migration ledger:
1122
+ * The gateway maintains a `_devflare_migrations` table per D1 binding with
1123
+ * columns (filename TEXT PRIMARY KEY, applied_at TEXT NOT NULL,
1124
+ * sha256 TEXT NOT NULL). On each run the gateway, for every file we send:
1125
+ * - if filename is present AND sha256 matches — skip silently
1126
+ * - if filename is present but sha256 differs — surface a warning that
1127
+ * the client turns into a `console.warn`; the file is skipped to
1128
+ * protect existing data (there is no force-reapply flag today)
1129
+ * - if filename is absent — apply the file's SQL, then record the entry
1130
+ * We pass each file (filename + sha256 + statements) in a single
1131
+ * /_devflare/migrate request so ledger read/write stays in-process inside
1132
+ * workerd.
1133
+ *
1134
+ * Uses the gateway worker HTTP endpoint to run migrations inside workerd.
1135
+ */
1136
+ async function runD1Migrations(options) {
1137
+ const { cwd, config, miniflarePort, logger } = options;
1138
+ if (!config?.bindings?.d1) return;
1139
+ const { existsSync, readdirSync, readFileSync, statSync } = await import("node:fs");
1140
+ const migrationsDir = resolve(cwd, "migrations");
1141
+ if (!existsSync(migrationsDir)) {
1142
+ logger?.debug("No migrations/ directory found, skipping D1 migrations");
1143
+ return;
1144
+ }
1145
+ const sharedFiles = readdirSync(migrationsDir).filter((file) => file.endsWith(".sql")).sort();
1146
+ let sharedFileEntries = null;
1147
+ if (sharedFiles.length > 0) {
1148
+ sharedFileEntries = [];
1149
+ for (const file of sharedFiles) {
1150
+ const sql = readFileSync(resolve(migrationsDir, file), "utf-8");
1151
+ const fileStatements = collectMigrationStatements(sql);
1152
+ sharedFileEntries.push({
1153
+ filename: file,
1154
+ sha256: hashSql(sql),
1155
+ statements: fileStatements
1156
+ });
1157
+ logger?.debug(`Shared file ${file}: ${fileStatements.length} statement(s)`);
1158
+ }
1159
+ }
1160
+ for (const [bindingName] of Object.entries(config.bindings.d1)) {
1161
+ const perBindingDir = resolve(migrationsDir, bindingName);
1162
+ const hasPerBindingDir = existsSync(perBindingDir) && statSync(perBindingDir).isDirectory();
1163
+ let files = [];
1164
+ let sourceLabel = "";
1165
+ if (hasPerBindingDir) {
1166
+ const perBindingFiles = readdirSync(perBindingDir).filter((file) => file.endsWith(".sql")).sort();
1167
+ if (perBindingFiles.length === 0) {
1168
+ logger?.debug(`No SQL migration files in migrations/${bindingName}/, skipping ${bindingName}`);
1169
+ continue;
1170
+ }
1171
+ for (const file of perBindingFiles) {
1172
+ const sql = readFileSync(resolve(perBindingDir, file), "utf-8");
1173
+ const fileStatements = collectMigrationStatements(sql);
1174
+ files.push({
1175
+ filename: file,
1176
+ sha256: hashSql(sql),
1177
+ statements: fileStatements
1178
+ });
1179
+ logger?.debug(`File ${bindingName}/${file}: ${fileStatements.length} statement(s)`);
1180
+ }
1181
+ sourceLabel = `migrations/${bindingName}/`;
1182
+ } else if (sharedFileEntries !== null) {
1183
+ files = sharedFileEntries;
1184
+ sourceLabel = "migrations/ [shared fallback]";
1185
+ } else {
1186
+ logger?.debug(`No migrations found for ${bindingName}, skipping`);
1187
+ continue;
1188
+ }
1189
+ const statements = files.flatMap((file) => file.statements);
1190
+ logger?.info(`Running ${files.length} D1 migration(s) for ${bindingName} (from ${sourceLabel})`);
1191
+ if (statements.length === 0) {
1192
+ logger?.debug(`No executable D1 migration statements for ${bindingName}`);
1193
+ continue;
1194
+ }
1195
+ await applyMigrationsToBinding({
1196
+ bindingName,
1197
+ statements,
1198
+ files,
1199
+ miniflarePort,
1200
+ logger
1201
+ });
1202
+ }
1203
+ }
1204
+ //#endregion
1205
+ //#region src/dev-server/dev-server-state.ts
1206
+ /**
1207
+ * Build a fresh `DevServerState` with all handles in their not-yet-started
1208
+ * positions. `enableVite` is the user's request — `start()` may downgrade it
1209
+ * later via `resolveViteIntegration`.
1210
+ */
1211
+ function createDevServerState(initial) {
1212
+ return {
1213
+ enableVite: initial.enableVite,
1214
+ miniflare: null,
1215
+ doBundler: null,
1216
+ workerSourceWatcher: null,
1217
+ workerWatchTargets: [],
1218
+ viteProcess: null,
1219
+ config: null,
1220
+ serviceBindingResolution: null,
1221
+ browserShim: null,
1222
+ browserShimPort: initial.browserShimPort ?? 8788,
1223
+ mainWorkerSurfacePaths: {
1224
+ fetch: null,
1225
+ queue: null,
1226
+ scheduled: null,
1227
+ email: null,
1228
+ tail: null
1229
+ },
1230
+ resolvedWorkerConfigPath: null,
1231
+ mainWorkerScriptPath: null,
1232
+ bundledMainWorkerScriptPath: null,
1233
+ workflowEntrypointScript: "",
1234
+ currentDoResult: null,
1235
+ mainWorkerRoutes: null,
1236
+ generatedViteConfigPath: null
1237
+ };
1238
+ }
1239
+ /**
1240
+ * Tear down everything in `state` in the same order the legacy `stop()`
1241
+ * function used. After this returns, every handle on `state` is `null` again
1242
+ * and the local sendEmail bindings have been cleared.
1243
+ *
1244
+ * Order is important and intentionally mirrors the production sequence:
1245
+ * 1. DO bundler (stop rebuilds before Miniflare goes away)
1246
+ * 2. Worker source watcher (stop FS callbacks before Miniflare disposal)
1247
+ * 3. Miniflare itself
1248
+ * 4. Vite child process (after Miniflare so requests cannot race shutdown)
1249
+ * 5. Browser shim
1250
+ * 6. Local sendEmail registry reset
1251
+ */
1252
+ async function disposeDevServerState(state) {
1253
+ if (state.doBundler) {
1254
+ await state.doBundler.close();
1255
+ state.doBundler = null;
1256
+ }
1257
+ if (state.workerSourceWatcher) {
1258
+ await state.workerSourceWatcher.close();
1259
+ state.workerSourceWatcher = null;
1260
+ }
1261
+ if (state.miniflare) {
1262
+ try {
1263
+ await state.miniflare.dispose();
1264
+ } catch (error) {
1265
+ if (!isIgnorableMiniflareDisposeError(error)) throw error;
1266
+ }
1267
+ state.miniflare = null;
1268
+ }
1269
+ if (state.viteProcess) {
1270
+ await stopSpawnedProcessTree(state.viteProcess);
1271
+ state.viteProcess = null;
1272
+ }
1273
+ if (state.browserShim) {
1274
+ await state.browserShim.stop();
1275
+ state.browserShim = null;
1276
+ }
1277
+ clearLocalSendEmailBindings();
1278
+ }
1279
+ //#endregion
1280
+ //#region src/browser-shim/binding-worker.ts
1281
+ const MAX_CHUNK_SIZE = 1048575;
1282
+ /**
1283
+ * Generate the browser binding worker script
1284
+ * @param browserShimUrl - URL of the external browser shim server (e.g., http://127.0.0.1:8788)
1285
+ * @param debug - Enable debug logging (default: false)
1286
+ */
1287
+ function getBrowserBindingScript(browserShimUrl, debug = false) {
1288
+ return `
1289
+ // Browser Binding Worker — Proxies puppeteer requests to external browser shim
1290
+ // Handles WebSocket upgrades using WebSocketPair for @cloudflare/puppeteer compatibility
1291
+
1292
+ const BROWSER_SHIM_URL = ${JSON.stringify(browserShimUrl)}
1293
+ const MAX_CHUNK_SIZE = ${MAX_CHUNK_SIZE}
1294
+ const DEBUG = ${debug}
1295
+ const log = (...args) => DEBUG && console.log('[BrowserBinding]', ...args)
1296
+
1297
+ export default {
1298
+ async fetch(request, env, ctx) {
1299
+ const url = new URL(request.url)
1300
+ const upgradeHeader = request.headers.get('Upgrade')
1301
+ const isWebSocket = upgradeHeader && upgradeHeader.toLowerCase() === 'websocket'
1302
+
1303
+ log('Request:', url.pathname, isWebSocket ? '(WebSocket)' : '(HTTP)')
1304
+
1305
+ // Handle WebSocket upgrade for DevTools connection
1306
+ if (url.pathname === '/v1/connectDevtools' && isWebSocket) {
1307
+ return handleDevToolsWebSocket(request, url)
1308
+ }
1309
+
1310
+ // Proxy all other requests to the browser shim server
1311
+ return proxyToBrowserShim(request, url)
1312
+ }
1313
+ }
1314
+
1315
+ // Proxy HTTP requests to the external browser shim server
1316
+ async function proxyToBrowserShim(request, url) {
1317
+ const shimUrl = new URL(url.pathname + url.search, BROWSER_SHIM_URL)
1318
+
1319
+ log('Proxying to:', shimUrl.toString())
1320
+
1321
+ const response = await fetch(shimUrl.toString(), {
1322
+ method: request.method,
1323
+ headers: request.headers,
1324
+ body: request.method !== 'GET' && request.method !== 'HEAD' ? request.body : undefined
1325
+ })
1326
+
1327
+ log('Response:', response.status)
1328
+
1329
+ // Return the response as-is
1330
+ return new Response(response.body, {
1331
+ status: response.status,
1332
+ statusText: response.statusText,
1333
+ headers: response.headers
1334
+ })
1335
+ }
1336
+
1337
+ // Validate WebSocket close code to be in valid range
1338
+ function validateCloseCode(code) {
1339
+ if (typeof code !== 'number' || isNaN(code)) return 1000
1340
+ if (code < 1000 || code > 4999) return 1000
1341
+ return code
1342
+ }
1343
+
1344
+ // Split a message into chunks following @cloudflare/puppeteer protocol
1345
+ // First chunk has 4-byte LE length header, subsequent chunks are raw payload
1346
+ function messageToChunks(message) {
1347
+ const data = typeof message === 'string'
1348
+ ? new TextEncoder().encode(message)
1349
+ : new Uint8Array(message)
1350
+
1351
+ const chunks = []
1352
+ const totalLength = data.length
1353
+ let offset = 0
1354
+ let isFirst = true
1355
+
1356
+ while (offset < totalLength) {
1357
+ const remaining = totalLength - offset
1358
+ let chunkSize
1359
+
1360
+ if (isFirst) {
1361
+ // First chunk: 4-byte header + payload
1362
+ chunkSize = Math.min(remaining, MAX_CHUNK_SIZE - 4)
1363
+ const chunk = new Uint8Array(chunkSize + 4)
1364
+ new DataView(chunk.buffer).setUint32(0, totalLength, true) // little-endian
1365
+ chunk.set(data.subarray(offset, offset + chunkSize), 4)
1366
+ chunks.push(chunk)
1367
+ isFirst = false
1368
+ } else {
1369
+ // Subsequent chunks: raw payload only
1370
+ chunkSize = Math.min(remaining, MAX_CHUNK_SIZE)
1371
+ const chunk = data.subarray(offset, offset + chunkSize)
1372
+ chunks.push(chunk)
1373
+ }
1374
+
1375
+ offset += chunkSize
1376
+ }
1377
+
1378
+ return chunks
1379
+ }
1380
+
1381
+ // Reassemble chunks back into a complete message
1382
+ // Returns null if more chunks are needed
1383
+ function chunksToMessage(chunks) {
1384
+ if (chunks.length === 0) return null
1385
+
1386
+ // First chunk must have 4-byte header
1387
+ const firstChunk = chunks[0]
1388
+ if (firstChunk.length < 4) return null
1389
+
1390
+ const expectedLength = new DataView(firstChunk.buffer, firstChunk.byteOffset).getUint32(0, true)
1391
+
1392
+ // Calculate total received payload
1393
+ let totalReceived = firstChunk.length - 4 // first chunk payload (minus header)
1394
+ for (let i = 1; i < chunks.length; i++) {
1395
+ totalReceived += chunks[i].length
1396
+ }
1397
+
1398
+ if (totalReceived < expectedLength) {
1399
+ return null // Need more chunks
1400
+ }
1401
+
1402
+ // Reassemble the message
1403
+ const assembled = new Uint8Array(expectedLength)
1404
+ let offset = 0
1405
+
1406
+ // Copy first chunk payload (skip 4-byte header)
1407
+ const firstPayload = firstChunk.subarray(4)
1408
+ assembled.set(firstPayload, offset)
1409
+ offset += firstPayload.length
1410
+
1411
+ // Copy remaining chunks
1412
+ for (let i = 1; i < chunks.length; i++) {
1413
+ const chunk = chunks[i]
1414
+ const toCopy = Math.min(chunk.length, expectedLength - offset)
1415
+ assembled.set(chunk.subarray(0, toCopy), offset)
1416
+ offset += toCopy
1417
+ }
1418
+
1419
+ return new TextDecoder().decode(assembled)
1420
+ }
1421
+
1422
+ // Handle WebSocket upgrade for DevTools connection
1423
+ // Creates a WebSocketPair and proxies to Chrome's DevTools WebSocket
1424
+ async function handleDevToolsWebSocket(request, url) {
1425
+ const sessionId = url.searchParams.get('browser_session')
1426
+ if (!sessionId) {
1427
+ return new Response('browser_session parameter required', { status: 400 })
1428
+ }
1429
+
1430
+ log('DevTools WebSocket request for session:', sessionId)
1431
+
1432
+ // Get session info from browser shim (includes Chrome's wsEndpoint)
1433
+ const sessionUrl = new URL('/v1/session/' + sessionId, BROWSER_SHIM_URL)
1434
+
1435
+ // Add timeout for session fetch
1436
+ const controller = new AbortController()
1437
+ const timeout = setTimeout(() => controller.abort(), 5000)
1438
+
1439
+ let sessionRes
1440
+ try {
1441
+ sessionRes = await fetch(sessionUrl.toString(), { signal: controller.signal })
1442
+ } catch (e) {
1443
+ DEBUG && console.error('[BrowserBinding] Session fetch timeout or error:', e.message)
1444
+ return new Response('Session fetch timeout', { status: 504 })
1445
+ } finally {
1446
+ clearTimeout(timeout)
1447
+ }
1448
+
1449
+ if (!sessionRes.ok) {
1450
+ DEBUG && console.error('[BrowserBinding] Session not found:', sessionId)
1451
+ return new Response('Session not found', { status: 404 })
1452
+ }
1453
+
1454
+ const sessionInfo = await sessionRes.json()
1455
+ const wsEndpoint = sessionInfo.wsEndpoint
1456
+
1457
+ if (!wsEndpoint) {
1458
+ DEBUG && console.error('[BrowserBinding] No wsEndpoint in session info')
1459
+ return new Response('No wsEndpoint for session', { status: 500 })
1460
+ }
1461
+
1462
+ log('Connecting to Chrome DevTools:', wsEndpoint)
1463
+
1464
+ // Connect to Chrome's DevTools WebSocket
1465
+ // Chrome uses ws:// but fetch expects http:// for WebSocket upgrade
1466
+ const chromeUrl = wsEndpoint.replace('ws://', 'http://').replace('wss://', 'https://')
1467
+
1468
+ const chromeRes = await fetch(chromeUrl, {
1469
+ headers: { Upgrade: 'websocket' }
1470
+ })
1471
+
1472
+ if (!chromeRes.webSocket) {
1473
+ DEBUG && console.error('[BrowserBinding] Failed to connect to Chrome DevTools')
1474
+ return new Response('Failed to connect to Chrome DevTools', { status: 502 })
1475
+ }
1476
+
1477
+ const chromeWs = chromeRes.webSocket
1478
+ chromeWs.accept()
1479
+
1480
+ log('Connected to Chrome DevTools')
1481
+
1482
+ // Create WebSocketPair for client connection
1483
+ const { 0: client, 1: server } = new WebSocketPair()
1484
+ server.accept()
1485
+
1486
+ // Chunk buffer for reassembling multi-chunk messages from puppeteer
1487
+ let chunks = []
1488
+ const MAX_BUFFER_SIZE = 50 * 1024 * 1024 // 50MB max buffer
1489
+ let bufferSize = 0
1490
+
1491
+ // Proxy messages from client (puppeteer) to Chrome
1492
+ // Handle multi-chunk framing protocol
1493
+ server.addEventListener('message', (event) => {
1494
+ // Keep-alive ping from puppeteer
1495
+ if (event.data === 'ping') {
1496
+ return
1497
+ }
1498
+
1499
+ // Handle binary data (chunked protocol)
1500
+ if (event.data instanceof ArrayBuffer) {
1501
+ const chunk = new Uint8Array(event.data)
1502
+ bufferSize += chunk.length
1503
+
1504
+ // Prevent unbounded buffering
1505
+ if (bufferSize > MAX_BUFFER_SIZE) {
1506
+ DEBUG && console.error('[BrowserBinding] Buffer overflow, closing connection')
1507
+ server.close(1009, 'Message too big')
1508
+ chromeWs.close(1009, 'Message too big')
1509
+ return
1510
+ }
1511
+
1512
+ chunks.push(chunk)
1513
+
1514
+ // Try to reassemble complete message
1515
+ const message = chunksToMessage(chunks)
1516
+ if (message !== null) {
1517
+ // Send complete message to Chrome
1518
+ if (chromeWs.readyState === 1) { // OPEN
1519
+ chromeWs.send(message)
1520
+ }
1521
+ // Clear buffer
1522
+ chunks = []
1523
+ bufferSize = 0
1524
+ }
1525
+ } else if (typeof event.data === 'string') {
1526
+ // Shouldn't happen in normal protocol, but handle it
1527
+ if (chromeWs.readyState === 1) {
1528
+ chromeWs.send(event.data)
1529
+ }
1530
+ }
1531
+ })
1532
+
1533
+ // Proxy messages from Chrome to client (puppeteer)
1534
+ // Split into chunks following the multi-chunk protocol
1535
+ chromeWs.addEventListener('message', (event) => {
1536
+ if (server.readyState !== 1) return // Not OPEN
1537
+
1538
+ // Split message into chunks
1539
+ const outChunks = messageToChunks(event.data)
1540
+ for (const chunk of outChunks) {
1541
+ server.send(chunk)
1542
+ }
1543
+ })
1544
+
1545
+ // Handle close events with validated codes
1546
+ server.addEventListener('close', (event) => {
1547
+ log('Client WebSocket closed:', event.code)
1548
+ const code = validateCloseCode(event.code)
1549
+ try {
1550
+ if (chromeWs.readyState === 1 || chromeWs.readyState === 0) {
1551
+ chromeWs.close(code, event.reason || '')
1552
+ }
1553
+ } catch {}
1554
+ })
1555
+
1556
+ chromeWs.addEventListener('close', (event) => {
1557
+ log('Chrome WebSocket closed:', event.code)
1558
+ const code = validateCloseCode(event.code)
1559
+ try {
1560
+ if (server.readyState === 1 || server.readyState === 0) {
1561
+ server.close(code, event.reason || '')
1562
+ }
1563
+ } catch {}
1564
+ })
1565
+
1566
+ // Handle errors
1567
+ server.addEventListener('error', (event) => {
1568
+ DEBUG && console.error('[BrowserBinding] Client WebSocket error')
1569
+ try { chromeWs.close(1011, 'Client error') } catch {}
1570
+ })
1571
+
1572
+ chromeWs.addEventListener('error', (event) => {
1573
+ DEBUG && console.error('[BrowserBinding] Chrome WebSocket error')
1574
+ try { server.close(1011, 'Chrome error') } catch {}
1575
+ })
1576
+
1577
+ log('WebSocket proxy established')
1578
+
1579
+ // Return Cloudflare-style WebSocket response
1580
+ return new Response(null, {
1581
+ status: 101,
1582
+ webSocket: client
1583
+ })
1584
+ }
1585
+ `;
1586
+ }
1587
+ //#endregion
1588
+ //#region src/dev-server/gateway-script.ts
1589
+ /**
1590
+ * Generates the dev-server gateway worker script inline.
1591
+ *
1592
+ * All in-sandbox RPC behavior (method dispatch, error envelope, serialization,
1593
+ * WebSocket bridge, HTTP transfer) lives in `GATEWAY_RUNTIME_JS` and is shared
1594
+ * with `src/bridge/miniflare.ts`. The canonical TypeScript equivalent lives in
1595
+ * `src/bridge/server.ts`.
1596
+ *
1597
+ * This file only owns the pieces that are genuinely dev-server-specific:
1598
+ * - WebSocket route matching & DO WebSocket forwarding (`WS_ROUTES`)
1599
+ * - D1 migration endpoint
1600
+ * - Inbound email ingestion endpoint
1601
+ * - Service-binding fallthrough to the app worker
1602
+ *
1603
+ * @param wsRoutes - WebSocket routes for DO proxying
1604
+ * @param debug - Enable debug logging in gateway
1605
+ * @param appServiceBindingName - Service binding name for the app worker (if any)
1606
+ */
1607
+ function getGatewayScript(wsRoutes = [], debug = false, appServiceBindingName = null) {
1608
+ return `
1609
+ ${GATEWAY_RUNTIME_JS}
1610
+
1611
+ // Bridge Gateway Worker — Dev Server
1612
+ // Dev-server-specific overlay on top of the shared GATEWAY_RUNTIME_JS:
1613
+ // WS route DO forwarding, D1 migration, email ingest, app-worker fallthrough.
1614
+
1615
+ const DEBUG = ${debug}
1616
+ const log = (...args) => DEBUG && console.log('[Gateway]', ...args)
1617
+
1618
+ const WS_ROUTES = ${JSON.stringify(wsRoutes)}
1619
+ const APP_SERVICE_BINDING = ${JSON.stringify(appServiceBindingName)}
1620
+
1621
+ export default {
1622
+ async fetch(request, env, ctx) {
1623
+ const url = new URL(request.url)
1624
+ const isWebSocket = request.headers.get('Upgrade') === 'websocket'
1625
+
1626
+ if (isWebSocket) {
1627
+ const matchedRoute = matchWsRoute(url.pathname)
1628
+ if (matchedRoute) {
1629
+ return handleDoWebSocket(request, env, url, matchedRoute)
1630
+ }
1631
+ return handleBridgeWebSocket(request, env, ctx)
1632
+ }
1633
+
1634
+ if (url.pathname.startsWith('/_devflare/transfer/')) {
1635
+ return handleHttpTransfer(request, env, url)
1636
+ }
1637
+
1638
+ if (url.pathname === '/_devflare/migrate' && request.method === 'POST') {
1639
+ return handleMigration(request, env)
1640
+ }
1641
+
1642
+ if (url.pathname === '/cdn-cgi/handler/email' && request.method === 'POST') {
1643
+ return handleEmailIncoming(request, env, ctx, url)
1644
+ }
1645
+
1646
+ if (url.pathname === '/_devflare/health') {
1647
+ return new Response(JSON.stringify({
1648
+ ok: true,
1649
+ bindings: Object.keys(env),
1650
+ wsRoutes: WS_ROUTES
1651
+ }), { headers: { 'Content-Type': 'application/json' } })
1652
+ }
1653
+
1654
+ if (APP_SERVICE_BINDING) {
1655
+ const appWorker = env[APP_SERVICE_BINDING]
1656
+ if (appWorker && typeof appWorker.fetch === 'function') {
1657
+ return appWorker.fetch(request)
1658
+ }
1659
+ }
1660
+
1661
+ return new Response('Devflare Bridge Gateway', { status: 200 })
1662
+ }
1663
+ }
1664
+
1665
+ async function handleMigration(request, env) {
1666
+ try {
1667
+ const { bindingName, statements, files } = await request.json()
1668
+ log('Migration request for binding:', bindingName, 'statements count:', statements?.length, 'files:', files?.length, 'bindings:', Object.keys(env))
1669
+ const db = env[bindingName]
1670
+ if (!db) {
1671
+ return Response.json({ error: 'Binding not found: ' + bindingName }, { status: 404 })
1672
+ }
1673
+
1674
+ // Ledger-aware path: when the client sends per-file metadata, we track
1675
+ // applied migrations in a \`_devflare_migrations\` table and skip files
1676
+ // whose filename+sha256 is already recorded. Files with a drifting hash
1677
+ // are reported as warnings and skipped — we refuse to re-apply to avoid
1678
+ // stomping on user data.
1679
+ if (Array.isArray(files) && files.length > 0) {
1680
+ try {
1681
+ await db.prepare(
1682
+ 'CREATE TABLE IF NOT EXISTS _devflare_migrations (filename TEXT PRIMARY KEY, applied_at TEXT NOT NULL, sha256 TEXT NOT NULL)'
1683
+ ).run()
1684
+ } catch (error) {
1685
+ const msg = error?.message || String(error)
1686
+ log('Failed to ensure migration ledger:', msg)
1687
+ return Response.json({ error: 'Failed to ensure migration ledger: ' + msg }, { status: 500 })
1688
+ }
1689
+
1690
+ let ledgerRows = []
1691
+ try {
1692
+ const ledger = await db.prepare('SELECT filename, sha256 FROM _devflare_migrations').all()
1693
+ ledgerRows = ledger?.results || []
1694
+ } catch (error) {
1695
+ log('Failed to read migration ledger:', error?.message || String(error))
1696
+ }
1697
+ const ledgerByFilename = new Map()
1698
+ for (const row of ledgerRows) {
1699
+ ledgerByFilename.set(row.filename, row.sha256)
1700
+ }
1701
+
1702
+ const applied = []
1703
+ const skipped = []
1704
+ const warnings = []
1705
+ const results = []
1706
+
1707
+ for (const file of files) {
1708
+ const existingHash = ledgerByFilename.get(file.filename)
1709
+ if (existingHash === file.sha256) {
1710
+ skipped.push(file.filename)
1711
+ continue
1712
+ }
1713
+ if (existingHash && existingHash !== file.sha256) {
1714
+ warnings.push({
1715
+ filename: file.filename,
1716
+ message: 'sha256 drifted since last apply; skipped'
1717
+ })
1718
+ skipped.push(file.filename)
1719
+ continue
1720
+ }
1721
+
1722
+ let fileFailed = false
1723
+ for (const sql of file.statements || []) {
1724
+ try {
1725
+ log('Running migration SQL:', sql.slice(0, 80))
1726
+ await db.prepare(sql).run()
1727
+ results.push({ sql: sql.slice(0, 50), success: true })
1728
+ } catch (error) {
1729
+ const msg = error?.message || String(error)
1730
+ log('Migration SQL error:', msg)
1731
+ if (msg.includes('already exists')) {
1732
+ results.push({ sql: sql.slice(0, 50), success: true, skipped: true })
1733
+ } else {
1734
+ results.push({ sql: sql.slice(0, 50), success: false, error: msg })
1735
+ fileFailed = true
1736
+ }
1737
+ }
1738
+ }
1739
+
1740
+ if (!fileFailed) {
1741
+ try {
1742
+ await db.prepare(
1743
+ 'INSERT OR REPLACE INTO _devflare_migrations (filename, applied_at, sha256) VALUES (?, ?, ?)'
1744
+ ).bind(file.filename, new Date().toISOString(), file.sha256).run()
1745
+ applied.push(file.filename)
1746
+ } catch (error) {
1747
+ log('Failed to record migration in ledger:', error?.message || String(error))
1748
+ }
1749
+ }
1750
+ }
1751
+
1752
+ return Response.json({ success: true, results, applied, skipped, warnings })
1753
+ }
1754
+
1755
+ // Legacy path: flat statement list, no ledger tracking.
1756
+ const results = []
1757
+ for (const sql of statements) {
1758
+ try {
1759
+ log('Running migration SQL:', sql.slice(0, 80))
1760
+ await db.prepare(sql).run()
1761
+ results.push({ sql: sql.slice(0, 50), success: true })
1762
+ log('Migration SQL succeeded')
1763
+ } catch (error) {
1764
+ const msg = error?.message || String(error)
1765
+ log('Migration SQL error:', msg)
1766
+ if (msg.includes('already exists')) {
1767
+ results.push({ sql: sql.slice(0, 50), success: true, skipped: true })
1768
+ } else {
1769
+ results.push({ sql: sql.slice(0, 50), success: false, error: msg })
1770
+ }
1771
+ }
1772
+ }
1773
+
1774
+ try {
1775
+ const tables = await db.prepare(\"SELECT name FROM sqlite_master WHERE type='table'\").all()
1776
+ log('Tables after migration:', JSON.stringify(tables))
1777
+ } catch (e) {
1778
+ log('Error listing tables:', e.message)
1779
+ }
1780
+
1781
+ return Response.json({ success: true, results })
1782
+ } catch (error) {
1783
+ return Response.json({ error: error?.message || String(error) }, { status: 500 })
1784
+ }
1785
+ }
1786
+
1787
+ async function handleEmailIncoming(request, env, ctx, url) {
1788
+ try {
1789
+ const from = url.searchParams.get('from') || 'unknown@example.com'
1790
+ const to = url.searchParams.get('to') || 'worker@example.com'
1791
+ const rawBody = await request.text()
1792
+
1793
+ log('Email incoming:', { from, to, bodyLength: rawBody.length })
1794
+
1795
+ if (APP_SERVICE_BINDING) {
1796
+ const appWorker = env[APP_SERVICE_BINDING]
1797
+ if (appWorker && typeof appWorker.fetch === 'function') {
1798
+ const response = await appWorker.fetch(new Request('http://devflare.internal/_devflare/internal/email', {
1799
+ method: 'POST',
1800
+ headers: {
1801
+ 'x-devflare-event': 'email',
1802
+ 'x-devflare-email-from': from,
1803
+ 'x-devflare-email-to': to,
1804
+ 'content-type': request.headers.get('content-type') || 'text/plain'
1805
+ },
1806
+ body: rawBody
1807
+ }))
1808
+
1809
+ if (!response.ok) {
1810
+ return response
1811
+ }
1812
+ }
1813
+ }
1814
+
1815
+ return new Response(JSON.stringify({ ok: true, from, to }), {
1816
+ headers: { 'Content-Type': 'application/json' }
1817
+ })
1818
+ } catch (error) {
1819
+ console.error('[Gateway] Email handler error:', error)
1820
+ return Response.json({ error: error?.message || String(error) }, { status: 500 })
1821
+ }
1822
+ }
1823
+
1824
+ function matchWsRoute(pathname) {
1825
+ for (const route of WS_ROUTES) {
1826
+ if (pathname === route.pattern || pathname.startsWith(route.pattern + '?')) {
1827
+ return route
1828
+ }
1829
+ }
1830
+ return null
1831
+ }
1832
+
1833
+ async function handleDoWebSocket(request, env, url, route) {
1834
+ try {
1835
+ const namespace = env[route.doNamespace]
1836
+ if (!namespace) {
1837
+ console.error('[Gateway] DO namespace not found:', route.doNamespace)
1838
+ return new Response('DO namespace not found: ' + route.doNamespace, { status: 500 })
1839
+ }
1840
+
1841
+ const idValue = url.searchParams.get(route.idParam) || 'default'
1842
+ const doId = namespace.idFromName(idValue)
1843
+ const stub = namespace.get(doId)
1844
+
1845
+ const forwardUrl = new URL(route.forwardPath, url.origin)
1846
+ url.searchParams.forEach((v, k) => forwardUrl.searchParams.set(k, v))
1847
+
1848
+ log('Forwarding WebSocket to DO:', route.doNamespace, 'id:', idValue, 'path:', forwardUrl.pathname)
1849
+
1850
+ return stub.fetch(forwardUrl.toString(), {
1851
+ method: request.method,
1852
+ headers: request.headers
1853
+ })
1854
+ } catch (error) {
1855
+ console.error('[Gateway] Error forwarding to DO:', error)
1856
+ return new Response('Error forwarding to DO: ' + error.message, { status: 500 })
1857
+ }
1858
+ }
1859
+ `;
1860
+ }
1861
+ //#endregion
1862
+ //#region src/dev-server/miniflare-worker-config.ts
1863
+ const DEFAULT_MODULE_RULES = [
1864
+ {
1865
+ type: "ESModule",
1866
+ include: [
1867
+ "**/*.ts",
1868
+ "**/*.tsx",
1869
+ "**/*.mts",
1870
+ "**/*.mjs"
1871
+ ]
1872
+ },
1873
+ {
1874
+ type: "CommonJS",
1875
+ include: ["**/*.js", "**/*.cjs"]
1876
+ },
1877
+ {
1878
+ type: "ESModule",
1879
+ include: ["**/*.jsx"]
1880
+ }
1881
+ ];
1882
+ function toMiniflareModuleRule(rule) {
1883
+ return {
1884
+ type: rule.type,
1885
+ include: rule.globs,
1886
+ ...rule.fallthrough !== void 0 && { fallthrough: rule.fallthrough }
1887
+ };
1888
+ }
1889
+ /**
1890
+ * Build the per-worker `serviceBindings` map. Combines user-declared
1891
+ * `bindings.services` (config) with any extra bindings the caller wants to
1892
+ * inject (e.g. internal gateway -> app routing).
1893
+ */
1894
+ function buildServiceBindings(bindings, extraBindings = {}) {
1895
+ const serviceBindings = {};
1896
+ if (bindings.services) for (const [bindingName, serviceConfig] of Object.entries(bindings.services)) serviceBindings[bindingName] = {
1897
+ name: serviceConfig.service,
1898
+ ...serviceConfig.entrypoint && { entrypoint: serviceConfig.entrypoint }
1899
+ };
1900
+ for (const [bindingName, target] of Object.entries(extraBindings)) serviceBindings[bindingName] = target;
1901
+ return Object.keys(serviceBindings).length > 0 ? serviceBindings : void 0;
1902
+ }
1903
+ /**
1904
+ * Build a single worker config object for Miniflare's `workers` array.
1905
+ * All inputs are passed explicitly (no closures over the caller's locals).
1906
+ */
1907
+ function makeMiniflareWorker(context, options) {
1908
+ const { cwd, loadedConfig, bindings, sendEmailConfig, rateLimitsConfig, versionMetadataConfig, workerLoadersConfig, mtlsCertificatesConfig, dispatchNamespacesConfig, workflowsConfig, pipelinesConfig, hyperdrivesConfig, imagesConfig, mediaConfig, streamConfig, flagshipConfig, analyticsEngineConfig, tailConsumersConfig, artifactsConfig, aiSearchNamespacesConfig, aiSearchInstancesConfig, secretsStoreConfig, localSecretWrappedBindingConfig, queueProducers } = context;
1909
+ const baseFlags = loadedConfig.compatibilityFlags ?? [];
1910
+ const compatFlags = baseFlags.includes("nodejs_compat") ? baseFlags : [...baseFlags, "nodejs_compat"];
1911
+ const workerBindings = loadedConfig.vars ?? {};
1912
+ const localWrappedBindings = { ...localSecretWrappedBindingConfig?.wrappedBindings ?? {} };
1913
+ const workerConfig = {
1914
+ name: options.name,
1915
+ modules: true,
1916
+ compatibilityDate: loadedConfig.compatibilityDate,
1917
+ compatibilityFlags: compatFlags,
1918
+ ...bindings.kv && { kvNamespaces: Object.fromEntries(Object.entries(bindings.kv).map(([bindingName, bindingConfig]) => {
1919
+ return [bindingName, getLocalKVNamespaceIdentifier(bindingConfig)];
1920
+ })) },
1921
+ ...bindings.r2 && { r2Buckets: Object.fromEntries(Object.entries(bindings.r2).map(([bindingName, bindingConfig]) => {
1922
+ return [bindingName, normalizeR2Binding(bindingConfig).bucketName];
1923
+ })) },
1924
+ ...bindings.d1 && { d1Databases: Object.fromEntries(Object.entries(bindings.d1).map(([bindingName, bindingConfig]) => {
1925
+ return [bindingName, getLocalD1DatabaseIdentifier(bindingConfig)];
1926
+ })) },
1927
+ ...Object.keys(workerBindings).length > 0 && { bindings: workerBindings },
1928
+ ...sendEmailConfig && { email: sendEmailConfig },
1929
+ ...rateLimitsConfig && { ratelimits: rateLimitsConfig },
1930
+ ...versionMetadataConfig && { versionMetadata: versionMetadataConfig },
1931
+ ...workerLoadersConfig && { workerLoaders: workerLoadersConfig },
1932
+ ...mtlsCertificatesConfig && { mtlsCertificates: mtlsCertificatesConfig },
1933
+ ...dispatchNamespacesConfig && { dispatchNamespaces: dispatchNamespacesConfig },
1934
+ ...workflowsConfig && { workflows: workflowsConfig },
1935
+ ...pipelinesConfig && { pipelines: pipelinesConfig },
1936
+ ...hyperdrivesConfig && { hyperdrives: hyperdrivesConfig },
1937
+ ...imagesConfig && { images: imagesConfig },
1938
+ ...mediaConfig && { media: mediaConfig },
1939
+ ...streamConfig && { stream: streamConfig },
1940
+ ...flagshipConfig && { flagship: flagshipConfig },
1941
+ ...analyticsEngineConfig && { analyticsEngineDatasets: analyticsEngineConfig },
1942
+ ...tailConsumersConfig && { tails: tailConsumersConfig },
1943
+ ...artifactsConfig && { artifacts: artifactsConfig },
1944
+ ...aiSearchNamespacesConfig && { aiSearchNamespaces: aiSearchNamespacesConfig },
1945
+ ...aiSearchInstancesConfig && { aiSearchInstances: aiSearchInstancesConfig },
1946
+ ...secretsStoreConfig && { secretsStoreSecrets: secretsStoreConfig },
1947
+ ...Object.keys(localWrappedBindings).length > 0 && { wrappedBindings: localWrappedBindings },
1948
+ ...queueProducers && { queueProducers },
1949
+ ...options.queueConsumers && { queueConsumers: options.queueConsumers },
1950
+ ...options.triggers && { triggers: options.triggers }
1951
+ };
1952
+ if (options.scriptPath) {
1953
+ workerConfig.scriptPath = options.scriptPath;
1954
+ workerConfig.modulesRoot = loadedConfig.baseDir ? resolve(cwd, loadedConfig.baseDir) : cwd;
1955
+ workerConfig.modulesRules = [...loadedConfig.rules?.map(toMiniflareModuleRule) ?? [], ...DEFAULT_MODULE_RULES];
1956
+ }
1957
+ if (options.script) workerConfig.script = options.script;
1958
+ if (options.durableObjects && Object.keys(options.durableObjects).length > 0) workerConfig.durableObjects = options.durableObjects;
1959
+ if (options.serviceBindings && Object.keys(options.serviceBindings).length > 0) workerConfig.serviceBindings = options.serviceBindings;
1960
+ return workerConfig;
1961
+ }
1962
+ //#endregion
1963
+ //#region src/dev-server/worker-surface-paths.ts
1964
+ const DEFAULT_TRANSPORT_ENTRY_FILES = [
1965
+ "src/transport.ts",
1966
+ "src/transport.js",
1967
+ "src/transport.mts",
1968
+ "src/transport.mjs"
1969
+ ];
1970
+ const resolveMainWorkerSurfacePaths = resolveWorkerSurfacePaths;
1971
+ function addWorkerWatchRoots(roots, cwd, configuredPath, defaultEntries) {
1972
+ if (configuredPath === false || configuredPath === null) return;
1973
+ if (typeof configuredPath === "string" && configuredPath) {
1974
+ roots.add(dirname(resolve(cwd, configuredPath)));
1975
+ return;
1976
+ }
1977
+ for (const defaultEntry of defaultEntries) roots.add(dirname(resolve(cwd, defaultEntry)));
1978
+ }
1979
+ function collectWorkerWatchRoots(cwd, config, mainWorkerSurfacePaths) {
1980
+ const roots = /* @__PURE__ */ new Set();
1981
+ for (const surfacePath of Object.values(mainWorkerSurfacePaths)) if (surfacePath) roots.add(dirname(surfacePath));
1982
+ addWorkerWatchRoots(roots, cwd, config.files?.fetch, DEFAULT_FETCH_ENTRY_FILES);
1983
+ addWorkerWatchRoots(roots, cwd, config.files?.queue, DEFAULT_QUEUE_ENTRY_FILES);
1984
+ addWorkerWatchRoots(roots, cwd, config.files?.scheduled, DEFAULT_SCHEDULED_ENTRY_FILES);
1985
+ addWorkerWatchRoots(roots, cwd, config.files?.email, DEFAULT_EMAIL_ENTRY_FILES);
1986
+ addWorkerWatchRoots(roots, cwd, config.files?.transport, DEFAULT_TRANSPORT_ENTRY_FILES);
1987
+ const routeDirectory = getRouteDirectoryCandidate(cwd, config);
1988
+ if (routeDirectory) roots.add(routeDirectory.absoluteDir);
1989
+ return [...roots];
1990
+ }
1991
+ //#endregion
1992
+ //#region src/dev-server/miniflare-dev-config.ts
1993
+ const INTERNAL_APP_SERVICE_BINDING = "__DEVFLARE_APP";
1994
+ /**
1995
+ * Build the complete Miniflare configuration for the dev server.
1996
+ *
1997
+ * IMPORTANT: When using multi-worker setup, ALL workers must go in the
1998
+ * `workers` array. The FIRST worker is the entrypoint and receives all
1999
+ * HTTP requests. Top-level script/modules options are NOT used when
2000
+ * workers array is present.
2001
+ */
2002
+ function buildMiniflareDevConfig(input) {
2003
+ const { config: loadedConfig, cwd, miniflarePort, miniflareHost = "127.0.0.1", persist, enableVite, debug, mainWorkerSurfacePaths, mainWorkerRoutes, mainWorkerScriptPath, bundledMainWorkerScriptPath, workflowEntrypointScript, browserShimPort, doResult, serviceBindingResolution, logger } = input;
2004
+ const bindings = loadedConfig.bindings ?? {};
2005
+ const persistPath = resolve(cwd, ".devflare/data");
2006
+ const appWorkerName = loadedConfig.name;
2007
+ const shouldRunMainWorker = !enableVite && (hasWorkerSurfacePaths(mainWorkerSurfacePaths) || Boolean(mainWorkerRoutes?.routes.length));
2008
+ const queueProducers = buildQueueProducers(bindings);
2009
+ const queueConsumers = buildQueueConsumers(bindings);
2010
+ const sharedOptions = {
2011
+ port: miniflarePort,
2012
+ host: miniflareHost,
2013
+ kvPersist: persist ? `${persistPath}/kv` : void 0,
2014
+ r2Persist: persist ? `${persistPath}/r2` : void 0,
2015
+ d1Persist: persist ? `${persistPath}/d1` : void 0,
2016
+ durableObjectsPersist: persist ? `${persistPath}/do` : void 0,
2017
+ workflowsPersist: persist ? `${persistPath}/workflows` : void 0,
2018
+ imagesPersist: persist ? `${persistPath}/images` : void 0,
2019
+ streamPersist: persist ? `${persistPath}/stream` : void 0
2020
+ };
2021
+ const localBindingShimServiceConfig = buildLocalBindingShimServiceConfig(loadedConfig);
2022
+ const createServiceBindings = (extraBindings = {}) => buildServiceBindings(bindings, {
2023
+ ...serviceBindingResolution?.primaryServiceBindings ?? {},
2024
+ ...localBindingShimServiceConfig.serviceBindings,
2025
+ ...extraBindings
2026
+ });
2027
+ const sendEmailConfig = buildSendEmailConfig(bindings);
2028
+ const rateLimitsConfig = buildRateLimitsConfig(bindings);
2029
+ const versionMetadataConfig = buildVersionMetadataConfig(bindings);
2030
+ const workerLoadersConfig = buildWorkerLoadersConfig(bindings);
2031
+ const mtlsCertificatesConfig = buildMtlsCertificatesConfig(bindings);
2032
+ const dispatchNamespacesConfig = buildDispatchNamespacesConfig(bindings);
2033
+ const workflowsConfig = buildWorkflowsConfig(bindings);
2034
+ const pipelinesConfig = buildPipelinesConfig(bindings);
2035
+ const hyperdrivesConfig = buildHyperdrivesConfig(bindings);
2036
+ const imagesConfig = bindings.images ? void 0 : buildImagesConfig(bindings);
2037
+ const mediaConfig = bindings.media ? void 0 : buildMediaConfig(bindings);
2038
+ const streamConfig = buildStreamConfig(bindings);
2039
+ const flagshipConfig = buildFlagshipConfig(bindings);
2040
+ const analyticsEngineConfig = buildAnalyticsEngineConfig(bindings);
2041
+ const tailConsumersConfig = buildTailConsumersConfig(loadedConfig);
2042
+ const artifactsConfig = buildArtifactsConfig(bindings);
2043
+ const aiSearchNamespacesConfig = buildAiSearchNamespacesConfig(bindings);
2044
+ const aiSearchInstancesConfig = buildAiSearchInstancesConfig(bindings);
2045
+ const localSecretWrappedBindingConfig = buildLocalSecretWrappedBindingConfig(loadedConfig, cwd);
2046
+ const localSecretBindingNames = new Set(localSecretWrappedBindingConfig.localBindingNames);
2047
+ const workerContext = {
2048
+ cwd,
2049
+ loadedConfig,
2050
+ bindings,
2051
+ sendEmailConfig,
2052
+ rateLimitsConfig,
2053
+ versionMetadataConfig,
2054
+ workerLoadersConfig,
2055
+ mtlsCertificatesConfig,
2056
+ dispatchNamespacesConfig,
2057
+ workflowsConfig,
2058
+ pipelinesConfig,
2059
+ hyperdrivesConfig,
2060
+ imagesConfig,
2061
+ mediaConfig,
2062
+ streamConfig,
2063
+ flagshipConfig,
2064
+ analyticsEngineConfig,
2065
+ tailConsumersConfig,
2066
+ artifactsConfig,
2067
+ aiSearchNamespacesConfig,
2068
+ aiSearchInstancesConfig,
2069
+ secretsStoreConfig: buildSecretsStoreConfig(bindings, loadedConfig.secretsStoreId, localSecretBindingNames),
2070
+ localSecretWrappedBindingConfig,
2071
+ queueProducers
2072
+ };
2073
+ const createWorkerConfig = (options) => makeMiniflareWorker(workerContext, options);
2074
+ const gatewayWorker = createWorkerConfig({
2075
+ name: "gateway",
2076
+ script: [workflowEntrypointScript, getGatewayScript(loadedConfig.wsRoutes, debug, shouldRunMainWorker ? INTERNAL_APP_SERVICE_BINDING : null)].filter(Boolean).join("\n\n"),
2077
+ serviceBindings: shouldRunMainWorker ? createServiceBindings({ [INTERNAL_APP_SERVICE_BINDING]: { name: appWorkerName } }) : createServiceBindings()
2078
+ });
2079
+ gatewayWorker.routes = ["*"];
2080
+ const hasDurableObjectBundles = !!doResult && doResult.bundles.size > 0;
2081
+ const browserBindingName = getSingleBrowserBindingName(bindings.browser);
2082
+ const needsBrowserWorker = Boolean(browserBindingName && (hasDurableObjectBundles || shouldRunMainWorker));
2083
+ const workers = [];
2084
+ const durableObjects = {};
2085
+ const browserShimUrl = `http://127.0.0.1:${browserShimPort}`;
2086
+ const browserWorkerName = "browser-binding";
2087
+ if (shouldRunMainWorker && mainWorkerScriptPath) {
2088
+ const mainWorkerServiceBindings = createServiceBindings(browserBindingName ? { [browserBindingName]: { name: browserWorkerName } } : {});
2089
+ const mainWorkerConfig = createWorkerConfig({
2090
+ name: appWorkerName,
2091
+ scriptPath: bundledMainWorkerScriptPath ?? mainWorkerScriptPath,
2092
+ serviceBindings: mainWorkerServiceBindings,
2093
+ queueConsumers,
2094
+ triggers: loadedConfig.triggers?.crons?.length ? { crons: loadedConfig.triggers.crons } : void 0
2095
+ });
2096
+ workers.push(mainWorkerConfig);
2097
+ }
2098
+ if (doResult) for (const [bindingName, bundlePath] of doResult.bundles) {
2099
+ const className = doResult.classes.get(bindingName);
2100
+ if (!className) continue;
2101
+ const workerName = `do-${bindingName.toLowerCase()}`;
2102
+ const workerConfig = createWorkerConfig({
2103
+ name: workerName,
2104
+ scriptPath: bundlePath,
2105
+ durableObjects: { [bindingName]: className },
2106
+ serviceBindings: createServiceBindings(browserBindingName ? { [browserBindingName]: { name: browserWorkerName } } : {})
2107
+ });
2108
+ if (browserBindingName) logger?.debug(`DO ${workerName} has browser service binding: ${browserBindingName} → ${browserWorkerName}`);
2109
+ logger?.debug(`DO ${workerName} config:`, JSON.stringify(workerConfig, null, 2));
2110
+ workers.push(workerConfig);
2111
+ durableObjects[bindingName] = {
2112
+ className,
2113
+ scriptName: workerName
2114
+ };
2115
+ }
2116
+ if (needsBrowserWorker) {
2117
+ const browserWorker = createWorkerConfig({
2118
+ name: browserWorkerName,
2119
+ script: getBrowserBindingScript(browserShimUrl, debug)
2120
+ });
2121
+ workers.push(browserWorker);
2122
+ logger?.info(`Browser binding worker configured: ${browserBindingName} → ${browserShimUrl}`);
2123
+ }
2124
+ if (Object.keys(durableObjects).length > 0) {
2125
+ gatewayWorker.durableObjects = durableObjects;
2126
+ if (shouldRunMainWorker) {
2127
+ const mainWorker = workers.find((worker) => worker.name === appWorkerName);
2128
+ if (mainWorker) mainWorker.durableObjects = durableObjects;
2129
+ }
2130
+ }
2131
+ return {
2132
+ ...sharedOptions,
2133
+ workers: [
2134
+ gatewayWorker,
2135
+ ...workers,
2136
+ ...serviceBindingResolution?.workers ?? [],
2137
+ ...localSecretWrappedBindingConfig.workers,
2138
+ ...localBindingShimServiceConfig.workers
2139
+ ]
2140
+ };
2141
+ }
2142
+ //#endregion
2143
+ //#region src/dev-server/reload-queue.ts
2144
+ function createReloadQueue({ reload, logger }) {
2145
+ let running = null;
2146
+ let pending = null;
2147
+ async function runOnce() {
2148
+ try {
2149
+ await reload();
2150
+ } catch (error) {
2151
+ logger?.error("[devflare dev] reload failed:", error);
2152
+ }
2153
+ }
2154
+ function schedule() {
2155
+ if (!running) {
2156
+ running = runOnce().finally(() => {
2157
+ running = null;
2158
+ });
2159
+ return running;
2160
+ }
2161
+ if (!pending) pending = running.then(() => {
2162
+ pending = null;
2163
+ running = runOnce().finally(() => {
2164
+ running = null;
2165
+ });
2166
+ return running;
2167
+ });
2168
+ return pending;
2169
+ }
2170
+ async function drain() {
2171
+ while (running || pending) if (pending) await pending;
2172
+ else if (running) await running;
2173
+ }
2174
+ return {
2175
+ schedule,
2176
+ drain
2177
+ };
2178
+ }
2179
+ //#endregion
2180
+ //#region src/dev-server/runtime-stdio.ts
2181
+ function writeStdout(logger, message) {
2182
+ if (typeof logger?.log === "function") {
2183
+ logger.log(message);
2184
+ return;
2185
+ }
2186
+ if (typeof logger?.info === "function") {
2187
+ logger.info(message);
2188
+ return;
2189
+ }
2190
+ console.log(message);
2191
+ }
2192
+ function writeStderr(logger, message) {
2193
+ if (typeof logger?.error === "function") {
2194
+ logger.error(message);
2195
+ return;
2196
+ }
2197
+ console.error(message);
2198
+ }
2199
+ function createRuntimeStdioForwarder(logger) {
2200
+ return (stdout, stderr) => {
2201
+ createInterface({ input: stdout }).on("line", (data) => {
2202
+ writeStdout(logger, data);
2203
+ });
2204
+ createInterface({ input: stderr }).on("line", (data) => {
2205
+ writeStderr(logger, data);
2206
+ });
2207
+ };
2208
+ }
2209
+ //#endregion
2210
+ //#region src/browser-shim/server.ts
2211
+ /**
2212
+ * Devflare's **local browser-rendering shim**.
2213
+ *
2214
+ * Accepts only loopback browser origins (e.g. `http://127.0.0.1:*`,
2215
+ * `http://localhost:*`) plus origin-less tool traffic (Puppeteer, curl, and
2216
+ * other non-browser clients that do not send an `Origin` header). Cross-origin
2217
+ * browser traffic is rejected at the request boundary.
2218
+ *
2219
+ * This is NOT a user-facing app route — it is devflare's protected helper
2220
+ * endpoint used by the local Browser Rendering binding to satisfy the
2221
+ * `@cloudflare/puppeteer` contract during local dev. The loopback-only posture
2222
+ * applies to this shim only and does not apply to the user's normal worker
2223
+ * routes.
2224
+ */
2225
+ let cachedExecutablePath = null;
2226
+ /**
2227
+ * Default Chrome flags used when launching headless Chrome for local
2228
+ * browser-rendering emulation. Each flag is included for a deliberate reason;
2229
+ * edit cautiously.
2230
+ *
2231
+ * NOTE: `--no-sandbox` / `--disable-setuid-sandbox` are intentionally NOT part
2232
+ * of the defaults. Disabling the sandbox removes the primary boundary between
2233
+ * untrusted web content and the host and must be opted into explicitly via
2234
+ * `BrowserShimOptions.allowNoSandbox`.
2235
+ */
2236
+ const DEFAULT_CHROME_FLAGS = [
2237
+ "--disable-dev-shm-usage",
2238
+ "--disable-gpu",
2239
+ "--disable-software-rasterizer",
2240
+ "--disable-extensions",
2241
+ "--disable-background-networking",
2242
+ "--disable-background-timer-throttling",
2243
+ "--disable-backgrounding-occluded-windows",
2244
+ "--disable-renderer-backgrounding",
2245
+ "--disable-features=TranslateUI",
2246
+ "--disable-ipc-flooding-protection",
2247
+ "--disable-default-apps",
2248
+ "--mute-audio",
2249
+ "--js-flags=--max-old-space-size=4096"
2250
+ ];
2251
+ /**
2252
+ * Flags appended only when `allowNoSandbox` is explicitly enabled. Kept in a
2253
+ * separate constant so callers and tests can assert they are opt-in.
2254
+ */
2255
+ const NO_SANDBOX_FLAGS = ["--no-sandbox", "--disable-setuid-sandbox"];
2256
+ /**
2257
+ * Resolve the Chrome argv for a shim launch. Exported for testability.
2258
+ */
2259
+ function resolveChromeFlags(options = {}) {
2260
+ const flags = [...DEFAULT_CHROME_FLAGS];
2261
+ if (options.allowNoSandbox) flags.unshift(...NO_SANDBOX_FLAGS);
2262
+ return flags;
2263
+ }
2264
+ /**
2265
+ * Create a download progress logger that emits at most one "start" line and
2266
+ * exactly one "complete" line per download. Avoids the previous heuristic
2267
+ * percent-spam which could log the same bucket multiple times.
2268
+ *
2269
+ * The returned callback matches `@puppeteer/browsers` `downloadProgressCallback`.
2270
+ */
2271
+ function createDownloadProgressLogger(logger, label = "Chrome") {
2272
+ const state = {
2273
+ started: false,
2274
+ completed: false,
2275
+ progress: {
2276
+ bytesReceived: 0,
2277
+ totalBytes: 0
2278
+ }
2279
+ };
2280
+ return {
2281
+ onProgress(downloadedBytes, totalBytes) {
2282
+ if (state.completed) return;
2283
+ state.progress.bytesReceived = downloadedBytes;
2284
+ state.progress.totalBytes = totalBytes;
2285
+ if (!state.started) {
2286
+ state.started = true;
2287
+ logger?.info(`[BrowserShim] Downloading ${label}...`);
2288
+ }
2289
+ if (totalBytes > 0 && downloadedBytes >= totalBytes) {
2290
+ state.completed = true;
2291
+ logger?.info(`[BrowserShim] ${label} download complete`);
2292
+ }
2293
+ },
2294
+ finalize() {
2295
+ if (!state.started || state.completed) return;
2296
+ state.completed = true;
2297
+ logger?.info(`[BrowserShim] ${label} download complete`);
2298
+ },
2299
+ get progress() {
2300
+ return state.progress;
2301
+ },
2302
+ get started() {
2303
+ return state.started;
2304
+ },
2305
+ get completed() {
2306
+ return state.completed;
2307
+ }
2308
+ };
2309
+ }
2310
+ /**
2311
+ * Get or install Chrome Headless Shell
2312
+ * Uses a shared cache directory so Chrome is only installed once globally
2313
+ */
2314
+ async function ensureChrome(cacheDir, logger) {
2315
+ if (cachedExecutablePath && existsSync(cachedExecutablePath)) return cachedExecutablePath;
2316
+ const platform = detectBrowserPlatform();
2317
+ if (!platform) throw new Error("Could not detect browser platform");
2318
+ const buildId = await resolveBuildId(Browser.CHROMEHEADLESSSHELL, platform, "stable");
2319
+ logger?.debug(`[BrowserShim] Resolved Chrome Headless Shell build: ${buildId}`);
2320
+ const progressLogger = createDownloadProgressLogger(logger, "Chrome");
2321
+ const installedBrowser = await install({
2322
+ browser: Browser.CHROMEHEADLESSSHELL,
2323
+ buildId,
2324
+ cacheDir,
2325
+ downloadProgressCallback: (downloadedBytes, totalBytes) => {
2326
+ progressLogger.onProgress(downloadedBytes, totalBytes);
2327
+ }
2328
+ });
2329
+ progressLogger.finalize();
2330
+ cachedExecutablePath = installedBrowser.executablePath;
2331
+ logger?.success(`[BrowserShim] Chrome ready: ${installedBrowser.executablePath}`);
2332
+ return installedBrowser.executablePath;
2333
+ }
2334
+ function createBrowserShim(options = {}) {
2335
+ const { port = 8788, host = "127.0.0.1", logger, verbose = false, keepAlive = 6e4, cacheDir = join$1(homedir(), ".devflare", "chrome"), allowNoSandbox = false } = options;
2336
+ const chromeLaunchArgs = resolveChromeFlags({ allowNoSandbox });
2337
+ if (allowNoSandbox) logger?.warn("[BrowserShim] Launching Chrome with --no-sandbox (allowNoSandbox=true). Only use this in trusted CI/rootless environments.");
2338
+ let server = null;
2339
+ let executablePath = null;
2340
+ const sessions = /* @__PURE__ */ new Map();
2341
+ const history = [];
2342
+ let WebSocketServerClass = null;
2343
+ let WebSocketClass = null;
2344
+ const maxRequestBodyBytes = 1024 * 1024;
2345
+ function getRequestOrigin(req) {
2346
+ const origin = req.headers.origin;
2347
+ if (typeof origin === "string") return origin;
2348
+ if (Array.isArray(origin) && origin[0]) return origin[0];
2349
+ return null;
2350
+ }
2351
+ function isLoopbackOrigin(origin) {
2352
+ try {
2353
+ const url = new URL(origin);
2354
+ return url.hostname === "127.0.0.1" || url.hostname === "localhost" || url.hostname === "::1" || url.hostname === "[::1]";
2355
+ } catch {
2356
+ return false;
2357
+ }
2358
+ }
2359
+ function applyCorsHeaders(req, res) {
2360
+ const origin = getRequestOrigin(req);
2361
+ if (!origin) return true;
2362
+ if (!isLoopbackOrigin(origin)) {
2363
+ res.writeHead(403, { "Content-Type": "application/json" });
2364
+ res.end(JSON.stringify({ error: "Forbidden origin" }));
2365
+ return false;
2366
+ }
2367
+ res.setHeader("Access-Control-Allow-Origin", origin);
2368
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
2369
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
2370
+ res.setHeader("Vary", "Origin");
2371
+ return true;
2372
+ }
2373
+ /**
2374
+ * Launch a new browser and create a session
2375
+ */
2376
+ async function acquireSession(acquireOptions) {
2377
+ if (!executablePath) throw new Error("Chrome not initialized");
2378
+ const browser = await puppeteerCore.launch({
2379
+ executablePath,
2380
+ headless: true,
2381
+ protocolTimeout: 12e4,
2382
+ args: chromeLaunchArgs
2383
+ });
2384
+ const wsEndpoint = browser.wsEndpoint();
2385
+ const sessionId = crypto.randomUUID();
2386
+ const session = {
2387
+ sessionId,
2388
+ browser,
2389
+ wsEndpoint,
2390
+ startTime: Date.now()
2391
+ };
2392
+ sessions.set(sessionId, session);
2393
+ const timeout = acquireOptions?.keep_alive ?? keepAlive;
2394
+ if (timeout > 0) session.idleTimeout = setTimeout(async () => {
2395
+ const s = sessions.get(sessionId);
2396
+ if (s && !s.connectionId) await closeSession(sessionId, 2, "BrowserIdle");
2397
+ }, timeout);
2398
+ if (verbose) logger?.debug(`[BrowserShim] Acquired session ${sessionId}`);
2399
+ return { sessionId };
2400
+ }
2401
+ /**
2402
+ * Close a browser session
2403
+ */
2404
+ async function closeSession(sessionId, closeReason = 1, closeReasonText = "NormalClosure") {
2405
+ const session = sessions.get(sessionId);
2406
+ if (!session) return;
2407
+ if (session.idleTimeout) clearTimeout(session.idleTimeout);
2408
+ try {
2409
+ await session.browser.close();
2410
+ } catch {}
2411
+ sessions.delete(sessionId);
2412
+ history.unshift({
2413
+ sessionId,
2414
+ startTime: session.startTime,
2415
+ endTime: Date.now(),
2416
+ closeReason,
2417
+ closeReasonText
2418
+ });
2419
+ if (history.length > 100) history.pop();
2420
+ if (verbose) logger?.debug(`[BrowserShim] Closed session ${sessionId}: ${closeReasonText}`);
2421
+ }
2422
+ /**
2423
+ * Handle HTTP requests
2424
+ */
2425
+ async function handleRequest(req, res) {
2426
+ const url = new URL(req.url || "/", `http://${host}:${port}`);
2427
+ const method = req.method || "GET";
2428
+ logger?.debug(`[BrowserShim] ${method} ${url.pathname}${url.search ? url.search : ""}`);
2429
+ if (!applyCorsHeaders(req, res)) return;
2430
+ if (method === "OPTIONS") {
2431
+ res.writeHead(204);
2432
+ res.end();
2433
+ return;
2434
+ }
2435
+ if (url.pathname === "/v1/acquire" && (method === "POST" || method === "GET")) {
2436
+ try {
2437
+ let acquireOptions = {};
2438
+ if (method === "GET") {
2439
+ const keepAlive = url.searchParams.get("keep_alive");
2440
+ if (keepAlive) acquireOptions.keep_alive = Number.parseInt(keepAlive, 10);
2441
+ } else try {
2442
+ const body = await readBody(req);
2443
+ acquireOptions = JSON.parse(body);
2444
+ } catch {}
2445
+ sendJson(res, 200, await acquireSession(acquireOptions));
2446
+ } catch (error) {
2447
+ const msg = error instanceof Error ? error.message : "Failed to acquire browser";
2448
+ logger?.error(`[BrowserShim] Acquire failed: ${msg}`);
2449
+ sendJson(res, 500, { error: msg });
2450
+ }
2451
+ return;
2452
+ }
2453
+ if (url.pathname === "/v1/sessions" && method === "GET") {
2454
+ sendJson(res, 200, Array.from(sessions.values()).map((s) => ({
2455
+ sessionId: s.sessionId,
2456
+ startTime: s.startTime,
2457
+ connectionId: s.connectionId,
2458
+ connectionStartTime: s.connectionStartTime
2459
+ })));
2460
+ return;
2461
+ }
2462
+ if (url.pathname === "/v1/history" && method === "GET") {
2463
+ sendJson(res, 200, history.slice(0, 50));
2464
+ return;
2465
+ }
2466
+ if (url.pathname === "/v1/limits" && method === "GET") {
2467
+ sendJson(res, 200, {
2468
+ activeSessions: Array.from(sessions.keys()).map((id) => ({ id })),
2469
+ allowedBrowserAcquisitions: 10,
2470
+ maxConcurrentSessions: 10,
2471
+ timeUntilNextAllowedBrowserAcquisition: 0
2472
+ });
2473
+ return;
2474
+ }
2475
+ if (url.pathname.startsWith("/v1/session/") && method === "GET") {
2476
+ const sessionId = url.pathname.slice(12);
2477
+ const session = sessions.get(sessionId);
2478
+ if (!session) {
2479
+ sendJson(res, 404, { error: "Session not found" });
2480
+ return;
2481
+ }
2482
+ sendJson(res, 200, {
2483
+ sessionId: session.sessionId,
2484
+ wsEndpoint: session.wsEndpoint,
2485
+ startTime: session.startTime,
2486
+ connectionId: session.connectionId,
2487
+ connectionStartTime: session.connectionStartTime
2488
+ });
2489
+ return;
2490
+ }
2491
+ if (url.pathname === "/_devflare/browser/health") {
2492
+ sendJson(res, 200, {
2493
+ ok: true,
2494
+ activeSessions: sessions.size,
2495
+ historySize: history.length,
2496
+ executablePath
2497
+ });
2498
+ return;
2499
+ }
2500
+ if (url.pathname === "/v1/connectDevtools") {
2501
+ res.writeHead(426, { "Content-Type": "text/plain" });
2502
+ res.end("WebSocket upgrade required");
2503
+ return;
2504
+ }
2505
+ res.writeHead(404, { "Content-Type": "text/plain" });
2506
+ res.end("Not found");
2507
+ }
2508
+ /**
2509
+ * Read request body as string
2510
+ */
2511
+ function readBody(req) {
2512
+ return new Promise((resolve, reject) => {
2513
+ const chunks = [];
2514
+ let totalBytes = 0;
2515
+ req.on("data", (chunk) => {
2516
+ totalBytes += chunk.length;
2517
+ if (totalBytes > maxRequestBodyBytes) {
2518
+ req.destroy();
2519
+ reject(/* @__PURE__ */ new Error(`Request body exceeds ${maxRequestBodyBytes} bytes`));
2520
+ return;
2521
+ }
2522
+ chunks.push(chunk);
2523
+ });
2524
+ req.on("end", () => resolve(Buffer.concat(chunks).toString()));
2525
+ req.on("error", reject);
2526
+ });
2527
+ }
2528
+ /**
2529
+ * Send JSON response
2530
+ */
2531
+ function sendJson(res, status, data) {
2532
+ const body = JSON.stringify(data);
2533
+ res.writeHead(status, {
2534
+ "Content-Type": "application/json",
2535
+ "Content-Length": Buffer.byteLength(body)
2536
+ });
2537
+ res.end(body);
2538
+ }
2539
+ /**
2540
+ * Start the browser shim server
2541
+ */
2542
+ async function start() {
2543
+ logger?.info("[BrowserShim] Ensuring Chrome Headless Shell is available...");
2544
+ executablePath = await ensureChrome(cacheDir, logger);
2545
+ try {
2546
+ const wsModule = await import("ws");
2547
+ WebSocketServerClass = wsModule.WebSocketServer || wsModule.default?.WebSocketServer;
2548
+ WebSocketClass = wsModule.WebSocket || wsModule.default?.WebSocket || wsModule.default;
2549
+ } catch {
2550
+ logger?.warn("[BrowserShim] ws package not found, WebSocket proxy disabled");
2551
+ logger?.warn("[BrowserShim] Install with: npm install ws");
2552
+ }
2553
+ server = createServer((req, res) => {
2554
+ handleRequest(req, res).catch((error) => {
2555
+ logger?.error("[BrowserShim] Request error:", error);
2556
+ res.writeHead(500);
2557
+ res.end("Internal server error");
2558
+ });
2559
+ });
2560
+ if (WebSocketServerClass) {
2561
+ const wss = new WebSocketServerClass({ noServer: true });
2562
+ server.on("upgrade", (request, socket, head) => {
2563
+ const origin = getRequestOrigin(request);
2564
+ if (origin && !isLoopbackOrigin(origin)) {
2565
+ socket.write("HTTP/1.1 403 Forbidden\r\n\r\n");
2566
+ socket.destroy();
2567
+ return;
2568
+ }
2569
+ const url = new URL(request.url || "/", `http://${host}:${port}`);
2570
+ if (url.pathname !== "/v1/connectDevtools") {
2571
+ socket.destroy();
2572
+ return;
2573
+ }
2574
+ const sessionId = url.searchParams.get("browser_session");
2575
+ if (!sessionId) {
2576
+ socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
2577
+ socket.destroy();
2578
+ return;
2579
+ }
2580
+ const session = sessions.get(sessionId);
2581
+ if (!session) {
2582
+ socket.write("HTTP/1.1 404 Not Found\r\n\r\n");
2583
+ socket.destroy();
2584
+ return;
2585
+ }
2586
+ const connectionId = crypto.randomUUID();
2587
+ session.connectionId = connectionId;
2588
+ session.connectionStartTime = Date.now();
2589
+ if (session.idleTimeout) {
2590
+ clearTimeout(session.idleTimeout);
2591
+ session.idleTimeout = void 0;
2592
+ }
2593
+ wss.handleUpgrade(request, socket, head, (ws) => {
2594
+ if (verbose) logger?.debug(`[BrowserShim] WebSocket connected for session ${sessionId}`);
2595
+ const chromeWs = new WebSocketClass(session.wsEndpoint);
2596
+ let chromeConnected = false;
2597
+ const connectTimeout = setTimeout(() => {
2598
+ if (!chromeConnected) {
2599
+ logger?.error("[BrowserShim] Chrome connection timeout");
2600
+ try {
2601
+ ws.close(1011, "Chrome connection timeout");
2602
+ chromeWs.close();
2603
+ } catch {}
2604
+ closeSession(sessionId, 5, "ChromeConnectionTimeout").catch(() => {});
2605
+ }
2606
+ }, 1e4);
2607
+ chromeWs.on("open", () => {
2608
+ chromeConnected = true;
2609
+ clearTimeout(connectTimeout);
2610
+ if (verbose) logger?.debug("[BrowserShim] Connected to Chrome DevTools");
2611
+ });
2612
+ chromeWs.on("message", (data) => {
2613
+ if (ws.readyState === 1) ws.send(data);
2614
+ });
2615
+ chromeWs.on("close", (code, reason) => {
2616
+ if (verbose) logger?.debug(`[BrowserShim] Chrome WS closed: ${code}`);
2617
+ const validCode = typeof code === "number" && code >= 1e3 && code <= 4999 ? code : 1e3;
2618
+ try {
2619
+ ws.close(validCode, reason?.toString?.() || "");
2620
+ } catch {}
2621
+ closeSession(sessionId, 2, "ChromeDisconnected").catch((err) => {
2622
+ logger?.error("[BrowserShim] Error closing session after Chrome disconnect:", err);
2623
+ });
2624
+ });
2625
+ chromeWs.on("error", (error) => {
2626
+ logger?.error("[BrowserShim] Chrome WS error:", error.message);
2627
+ try {
2628
+ ws.close(1011, "Chrome WebSocket error");
2629
+ } catch {}
2630
+ closeSession(sessionId, 4, "ChromeError").catch((err) => {
2631
+ logger?.error("[BrowserShim] Error closing session after Chrome error:", err);
2632
+ });
2633
+ });
2634
+ ws.on("message", (data) => {
2635
+ if (chromeWs.readyState === 1) chromeWs.send(data);
2636
+ });
2637
+ ws.on("close", (code, reason) => {
2638
+ if (verbose) logger?.debug(`[BrowserShim] Client WS closed for session ${sessionId}`);
2639
+ const validCode = typeof code === "number" && code >= 1e3 && code <= 4999 ? code : 1e3;
2640
+ try {
2641
+ chromeWs.close(validCode, reason?.toString?.() || "");
2642
+ } catch {}
2643
+ const s = sessions.get(sessionId);
2644
+ if (s && s.connectionId === connectionId) {
2645
+ s.connectionId = void 0;
2646
+ s.connectionStartTime = void 0;
2647
+ closeSession(sessionId, 1, "ClientDisconnected").catch((err) => {
2648
+ logger?.error("[BrowserShim] Error closing session after disconnect:", err);
2649
+ });
2650
+ }
2651
+ });
2652
+ ws.on("error", (error) => {
2653
+ logger?.error("[BrowserShim] Client WS error:", error.message);
2654
+ try {
2655
+ chromeWs.close();
2656
+ } catch {}
2657
+ });
2658
+ });
2659
+ });
2660
+ }
2661
+ await new Promise((resolve, reject) => {
2662
+ server.on("error", reject);
2663
+ server.listen(port, host, () => {
2664
+ resolve();
2665
+ });
2666
+ });
2667
+ logger?.success(`Browser shim server ready on http://${host}:${port}`);
2668
+ }
2669
+ /**
2670
+ * Stop the server and close all browsers
2671
+ */
2672
+ async function stop() {
2673
+ for (const sessionId of Array.from(sessions.keys())) await closeSession(sessionId, 3, "ServerShutdown");
2674
+ if (server) {
2675
+ await new Promise((resolve) => {
2676
+ server.close(() => resolve());
2677
+ });
2678
+ server = null;
2679
+ }
2680
+ logger?.info("Browser shim server stopped");
2681
+ }
2682
+ /**
2683
+ * Get the server URL
2684
+ */
2685
+ function getUrl() {
2686
+ return `http://${host}:${port}`;
2687
+ }
2688
+ return {
2689
+ start,
2690
+ stop,
2691
+ getUrl
2692
+ };
2693
+ }
2694
+ //#endregion
2695
+ //#region src/dev-server/server-startup-helpers.ts
2696
+ /**
2697
+ * Emit informational/warning lines about detected worker handlers in
2698
+ * worker-only (no-Vite) mode.
2699
+ */
2700
+ function logWorkerHandlerDetection(logger, enableVite, hasSurface, mainWorkerSurfacePaths, mainWorkerRoutes) {
2701
+ if (enableVite) return;
2702
+ if (hasSurface) {
2703
+ const detectedWorkerHandlers = Object.entries(mainWorkerSurfacePaths).filter(([, surfacePath]) => !!surfacePath).map(([surfaceName, surfacePath]) => `${surfaceName}=${surfacePath}`);
2704
+ const detectedRouteHandlers = mainWorkerRoutes?.routes.map((route) => `route=${route.filePath}`) ?? [];
2705
+ logger?.info(`Worker handlers detected: ${[...detectedWorkerHandlers, ...detectedRouteHandlers].join(", ")}`);
2706
+ } else logger?.warn("No local worker handler entry was found for worker-only mode");
2707
+ }
2708
+ /**
2709
+ * Emit warnings about remote-only bindings (AI, Vectorize) and any missing
2710
+ * prerequisites (accountId, wrangler login).
2711
+ */
2712
+ function logRemoteBindingRequirements(logger, remoteCheck) {
2713
+ if (!remoteCheck.hasRemoteBindings) return;
2714
+ logger?.info("");
2715
+ logger?.warn("⚠️ Remote-only bindings detected:");
2716
+ for (const binding of remoteCheck.remoteBindings) logger?.warn(` • ${binding}`);
2717
+ logger?.info("");
2718
+ if (remoteCheck.missingAccountId) {
2719
+ logger?.warn("⚠️ WARN: accountId is not set in devflare.config.ts");
2720
+ logger?.warn(" Remote bindings (AI, Vectorize) require accountId to charge the correct account.");
2721
+ logger?.warn(" Add: accountId: 'your-cloudflare-account-id'");
2722
+ logger?.info("");
2723
+ }
2724
+ if (remoteCheck.notLoggedIn) {
2725
+ logger?.warn("⚠️ WARN: Not logged in to Wrangler");
2726
+ logger?.warn(" Remote bindings require authentication.");
2727
+ logger?.warn(" Run: bunx wrangler login");
2728
+ logger?.info("");
2729
+ }
2730
+ if (!remoteCheck.missingAccountId && !remoteCheck.notLoggedIn) {
2731
+ logger?.success("✓ Remote binding requirements met");
2732
+ logger?.info("");
2733
+ }
2734
+ }
2735
+ function formatErrorMessage(error) {
2736
+ return error instanceof Error ? error.message : String(error);
2737
+ }
2738
+ /**
2739
+ * Resolve the path to watch for config changes. Prefers an explicit
2740
+ * `configPath` if it's directly accessible on disk, otherwise falls back to
2741
+ * the auto-discovered config path under `cwd`. Returns `null` when neither
2742
+ * is available.
2743
+ */
2744
+ async function resolveWorkerConfigWatchPath(cwd, configPath) {
2745
+ if (configPath) {
2746
+ const explicitPath = resolve(cwd, configPath);
2747
+ const fs = await import("node:fs/promises");
2748
+ try {
2749
+ await fs.access(explicitPath);
2750
+ return explicitPath;
2751
+ } catch {}
2752
+ }
2753
+ return await resolveConfigPath(cwd) ?? null;
2754
+ }
2755
+ /**
2756
+ * Pretty-print the resolved Miniflare config (truncating long inline scripts)
2757
+ * before `Miniflare` is constructed. Only emits when verbose/debug logging is
2758
+ * requested by the caller.
2759
+ */
2760
+ function logMiniflareConfigDiagnostics(logger, mfConfig) {
2761
+ logger?.info("=== MINIFLARE CONFIG DEBUG ===");
2762
+ logger?.info("Full config:", JSON.stringify(mfConfig, (key, value) => {
2763
+ if (key === "script" && typeof value === "string" && value.length > 200) return value.substring(0, 200) + "...[truncated]";
2764
+ return value;
2765
+ }, 2));
2766
+ if (mfConfig.workers) {
2767
+ logger?.info("Workers order:");
2768
+ for (const w of mfConfig.workers) {
2769
+ logger?.info(` → ${w.name}:`);
2770
+ logger?.info(` script: ${w.script ? "inline" : w.scriptPath}`);
2771
+ logger?.info(` browserRendering: ${JSON.stringify(w.browserRendering)}`);
2772
+ logger?.info(` durableObjects: ${JSON.stringify(w.durableObjects)}`);
2773
+ }
2774
+ }
2775
+ }
2776
+ /**
2777
+ * After `Miniflare` is `ready`, query each declared worker's bindings and
2778
+ * log them. Best-effort: any per-worker failure is logged at debug and does
2779
+ * not abort the rest of the diagnostics.
2780
+ */
2781
+ async function logMiniflareBindingDiagnostics(logger, miniflare, mfConfig) {
2782
+ try {
2783
+ const gatewayBindings = await miniflare.getBindings("gateway");
2784
+ logger?.info("Gateway worker bindings:", Object.keys(gatewayBindings));
2785
+ if (mfConfig.workers) {
2786
+ for (const w of mfConfig.workers) if (w.name !== "gateway") try {
2787
+ const doBindings = await miniflare.getBindings(w.name);
2788
+ logger?.info(`${w.name} worker bindings:`, Object.keys(doBindings));
2789
+ if ("BROWSER" in doBindings) logger?.success(`${w.name} has BROWSER binding!`);
2790
+ else logger?.warn(`${w.name} is MISSING BROWSER binding`);
2791
+ } catch (error) {
2792
+ logger?.debug(`Skipping binding diagnostics for ${w.name}: ${formatErrorMessage(error)}`);
2793
+ }
2794
+ }
2795
+ } catch (error) {
2796
+ logger?.debug(`Skipping Miniflare binding diagnostics: ${formatErrorMessage(error)}`);
2797
+ }
2798
+ }
2799
+ /**
2800
+ * If the config declares a single browser-rendering binding, construct and
2801
+ * start a `BrowserShim` listening on `browserShimPort`. Returns `null` when
2802
+ * no browser binding is configured (no shim needed).
2803
+ */
2804
+ async function maybeStartBrowserShim(config, options) {
2805
+ const browserBinding = getSingleBrowserBindingName(config.bindings?.browser);
2806
+ if (!browserBinding) return null;
2807
+ options.logger?.info(`Starting Browser Rendering shim (binding: ${browserBinding})...`);
2808
+ const shim = createBrowserShim({
2809
+ port: options.browserShimPort,
2810
+ host: "127.0.0.1",
2811
+ logger: options.logger,
2812
+ verbose: options.verbose
2813
+ });
2814
+ await shim.start();
2815
+ return shim;
2816
+ }
2817
+ /**
2818
+ * If the config declares a `files.durableObjects` glob, construct a
2819
+ * `DOBundler`, run an initial build, and start watching. Returns
2820
+ * `{ bundler, result }` (`{ null, null }` when no DO pattern is configured).
2821
+ * The `onRebuild` callback fires on each subsequent rebuild — typically used
2822
+ * to schedule a Miniflare reload.
2823
+ */
2824
+ async function maybeStartDOBundler(config, options) {
2825
+ const doPattern = config.files?.durableObjects;
2826
+ if (typeof doPattern !== "string" || !doPattern) return {
2827
+ bundler: null,
2828
+ result: null
2829
+ };
2830
+ const outDir = resolve(options.cwd, ".devflare/do-bundles");
2831
+ const bundler = createDOBundler({
2832
+ cwd: options.cwd,
2833
+ pattern: doPattern,
2834
+ outDir,
2835
+ rolldownOptions: config.rolldown?.options,
2836
+ sourcemap: config.rolldown?.sourcemap,
2837
+ minify: config.rolldown?.minify,
2838
+ logger: options.logger,
2839
+ onRebuild: options.onRebuild
2840
+ });
2841
+ const result = await bundler.build();
2842
+ await bundler.watch();
2843
+ return {
2844
+ bundler,
2845
+ result
2846
+ };
2847
+ }
2848
+ /**
2849
+ * Resolve whether Vite should run for this package, and if so, write the
2850
+ * generated Vite config under `.devflare/`. Returns:
2851
+ * - `{ enableVite: false, generatedViteConfigPath: null }` when Vite is
2852
+ * not requested or no Vite config exists for this package
2853
+ * - `{ enableVite: true, generatedViteConfigPath }` after writing the
2854
+ * generated config
2855
+ */
2856
+ async function resolveViteIntegration(options) {
2857
+ if (!options.enableViteRequested) return {
2858
+ enableVite: false,
2859
+ generatedViteConfigPath: null
2860
+ };
2861
+ const viteMode = await resolveViteMode(options.cwd, { requested: true });
2862
+ if (!viteMode.enableVite) {
2863
+ options.logger?.info("Vite disabled: no vite config found for this package");
2864
+ return {
2865
+ enableVite: false,
2866
+ generatedViteConfigPath: null
2867
+ };
2868
+ }
2869
+ const generatedViteConfigPath = await writeGeneratedViteConfig({
2870
+ cwd: options.cwd,
2871
+ configPath: options.configPath,
2872
+ localConfigPath: viteMode.viteConfigPath,
2873
+ bridgePort: options.miniflarePort
2874
+ });
2875
+ options.logger?.debug(`Generated Vite config → ${generatedViteConfigPath}`);
2876
+ return {
2877
+ enableVite: true,
2878
+ generatedViteConfigPath
2879
+ };
2880
+ }
2881
+ //#endregion
2882
+ //#region src/dev-server/vite-process.ts
2883
+ /**
2884
+ * Start the Vite dev server process.
2885
+ */
2886
+ async function startViteProcess(options) {
2887
+ const { cwd, configPath, vitePort, miniflarePort, generatedViteConfigPath, logger } = options;
2888
+ const args = [
2889
+ "vite",
2890
+ "dev",
2891
+ "--port",
2892
+ String(vitePort)
2893
+ ];
2894
+ if (generatedViteConfigPath) args.push("--config", generatedViteConfigPath);
2895
+ const viteProcess = spawn("bunx", args, {
2896
+ cwd,
2897
+ stdio: [
2898
+ "inherit",
2899
+ "pipe",
2900
+ "pipe"
2901
+ ],
2902
+ windowsHide: true,
2903
+ env: {
2904
+ ...process.env,
2905
+ DEVFLARE_DEV: "true",
2906
+ DEVFLARE_BRIDGE_PORT: String(miniflarePort),
2907
+ ...configPath ? { DEVFLARE_CONFIG_PATH: configPath } : {},
2908
+ FORCE_COLOR: "1"
2909
+ }
2910
+ });
2911
+ const readyUrl = await waitForViteReady(viteProcess, {
2912
+ onStdout(chunk) {
2913
+ process.stdout.write(chunk);
2914
+ },
2915
+ onStderr(chunk) {
2916
+ process.stderr.write(chunk);
2917
+ }
2918
+ });
2919
+ if (readyUrl) {
2920
+ logger?.success(`Vite dev server started on ${readyUrl}`);
2921
+ return viteProcess;
2922
+ }
2923
+ logger?.warn("Vite process started, but the final local URL could not be confirmed yet");
2924
+ return viteProcess;
2925
+ }
2926
+ //#endregion
2927
+ //#region src/dev-server/worker-source-watcher.ts
2928
+ async function startWorkerSourceWatcher(options) {
2929
+ const { watchTargets, resolvedWorkerConfigPath, logger, onConfigChange, onWorkerChange } = options;
2930
+ if (watchTargets.length === 0) return null;
2931
+ const chokidar = await import("chokidar");
2932
+ const isWindows = process.platform === "win32";
2933
+ const ignoredSegments = [
2934
+ "/node_modules/",
2935
+ "/.git/",
2936
+ "/.devflare/",
2937
+ "/dist/"
2938
+ ];
2939
+ const normalizePath = (filePath) => filePath.replace(/\\/g, "/");
2940
+ const isIgnoredPath = (filePath) => {
2941
+ const normalizedPath = normalizePath(filePath);
2942
+ return ignoredSegments.some((segment) => normalizedPath.includes(segment));
2943
+ };
2944
+ let reloadTimeout = null;
2945
+ let reloadInProgress = false;
2946
+ let pendingReloadPath = null;
2947
+ const flushPendingReload = async () => {
2948
+ if (!pendingReloadPath) return;
2949
+ const nextPath = pendingReloadPath;
2950
+ pendingReloadPath = null;
2951
+ await triggerReload(nextPath);
2952
+ };
2953
+ const triggerReload = async (filePath) => {
2954
+ if (reloadInProgress) {
2955
+ pendingReloadPath = filePath;
2956
+ return;
2957
+ }
2958
+ reloadInProgress = true;
2959
+ try {
2960
+ const normalizedConfigPath = resolvedWorkerConfigPath ? normalizePath(resolvedWorkerConfigPath) : null;
2961
+ if (normalizedConfigPath && normalizePath(filePath) === normalizedConfigPath) {
2962
+ logger?.info(`Devflare config changed: ${filePath}`);
2963
+ await onConfigChange();
2964
+ return;
2965
+ }
2966
+ logger?.info(`Worker source changed: ${filePath}`);
2967
+ await onWorkerChange();
2968
+ } catch (error) {
2969
+ logger?.error("Worker source reload failed:", error);
2970
+ } finally {
2971
+ reloadInProgress = false;
2972
+ await flushPendingReload();
2973
+ }
2974
+ };
2975
+ const scheduleReload = (filePath) => {
2976
+ if (reloadTimeout) clearTimeout(reloadTimeout);
2977
+ reloadTimeout = setTimeout(() => {
2978
+ reloadTimeout = null;
2979
+ triggerReload(filePath);
2980
+ }, 150);
2981
+ };
2982
+ const watcher = chokidar.watch(watchTargets, {
2983
+ ignoreInitial: true,
2984
+ usePolling: isWindows,
2985
+ interval: isWindows ? 300 : void 0,
2986
+ awaitWriteFinish: {
2987
+ stabilityThreshold: 100,
2988
+ pollInterval: 50
2989
+ },
2990
+ ignored: (filePath) => isIgnoredPath(filePath)
2991
+ });
2992
+ const onFileEvent = (filePath) => {
2993
+ if (isIgnoredPath(filePath)) return;
2994
+ scheduleReload(filePath);
2995
+ };
2996
+ watcher.on("change", onFileEvent);
2997
+ watcher.on("add", onFileEvent);
2998
+ watcher.on("unlink", onFileEvent);
2999
+ watcher.on("error", (error) => {
3000
+ logger?.error("Worker source watcher error:", error);
3001
+ });
3002
+ await new Promise((resolvePromise, rejectPromise) => {
3003
+ const handleReady = () => {
3004
+ watcher.off("error", handleInitialError);
3005
+ logger?.info(`Worker source watcher ready (${watchTargets.length} target(s))`);
3006
+ resolvePromise();
3007
+ };
3008
+ const handleInitialError = (error) => {
3009
+ watcher.off("ready", handleReady);
3010
+ rejectPromise(error instanceof Error ? error : new Error(String(error)));
3011
+ };
3012
+ watcher.once("ready", handleReady);
3013
+ watcher.once("error", handleInitialError);
3014
+ });
3015
+ return watcher;
3016
+ }
3017
+ /**
3018
+ * Compute the diff between current and next watch-target lists and apply it
3019
+ * to the given watcher. Returns the deduped next-targets array which the
3020
+ * caller should store as the new "current" state.
3021
+ */
3022
+ async function applyWatcherTargetDiff(watcher, currentTargets, nextTargets) {
3023
+ const nextSet = new Set(nextTargets);
3024
+ const targetsToRemove = currentTargets.filter((t) => !nextSet.has(t));
3025
+ const targetsToAdd = nextTargets.filter((t) => !currentTargets.includes(t));
3026
+ if (targetsToRemove.length > 0) await watcher.unwatch(targetsToRemove);
3027
+ if (targetsToAdd.length > 0) watcher.add(targetsToAdd);
3028
+ return nextTargets;
3029
+ }
3030
+ //#endregion
3031
+ //#region src/dev-server/server.ts
3032
+ function createDevServer(options) {
3033
+ 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;
3034
+ const state = createDevServerState({ enableVite: enableViteRequested });
3035
+ const reloadQueue = createReloadQueue({
3036
+ reload: async () => {
3037
+ if (!state.miniflare) return;
3038
+ const { Log, LogLevel } = await import("miniflare");
3039
+ const mfConfig = buildMiniflareConfig(state.currentDoResult);
3040
+ const log = createMiniflareLog(Log, LogLevel, "DEBUG", logger);
3041
+ if (log) mfConfig.log = log;
3042
+ mfConfig.handleRuntimeStdio = createRuntimeStdioForwarder(logger);
3043
+ logger?.info("Reloading Miniflare...");
3044
+ await state.miniflare.setOptions(mfConfig);
3045
+ logger?.success("Miniflare reloaded");
3046
+ },
3047
+ logger
3048
+ });
3049
+ async function bundleMainWorker() {
3050
+ if (!state.mainWorkerScriptPath || !state.config) {
3051
+ state.bundledMainWorkerScriptPath = null;
3052
+ return;
3053
+ }
3054
+ state.bundledMainWorkerScriptPath = await bundleWorkerEntry({
3055
+ cwd,
3056
+ inputFile: state.mainWorkerScriptPath,
3057
+ outFile: resolve(cwd, ".devflare", "worker-entrypoints", "main.js"),
3058
+ rolldownOptions: state.config.rolldown?.options,
3059
+ sourcemap: state.config.rolldown?.sourcemap,
3060
+ minify: state.config.rolldown?.minify,
3061
+ logger
3062
+ });
3063
+ logger?.debug(`Bundled main worker → ${state.bundledMainWorkerScriptPath}`);
3064
+ }
3065
+ function buildMiniflareConfig(doResult) {
3066
+ if (!state.config) throw new Error("Config not loaded");
3067
+ return buildMiniflareDevConfig({
3068
+ config: state.config,
3069
+ cwd,
3070
+ miniflarePort,
3071
+ miniflareHost,
3072
+ persist,
3073
+ enableVite: state.enableVite,
3074
+ debug,
3075
+ mainWorkerSurfacePaths: state.mainWorkerSurfacePaths,
3076
+ mainWorkerRoutes: state.mainWorkerRoutes,
3077
+ mainWorkerScriptPath: state.mainWorkerScriptPath,
3078
+ bundledMainWorkerScriptPath: state.bundledMainWorkerScriptPath,
3079
+ workflowEntrypointScript: state.workflowEntrypointScript,
3080
+ browserShimPort: state.browserShimPort,
3081
+ doResult,
3082
+ serviceBindingResolution: state.serviceBindingResolution,
3083
+ logger
3084
+ });
3085
+ }
3086
+ async function bundleWorkflowEntrypoints() {
3087
+ if (!state.config) {
3088
+ state.workflowEntrypointScript = "";
3089
+ return;
3090
+ }
3091
+ state.workflowEntrypointScript = await bundleWorkflowEntrypointScript(state.config, cwd, { logger });
3092
+ }
3093
+ /**
3094
+ * Start Miniflare with current config
3095
+ */
3096
+ async function startMiniflare(doResult) {
3097
+ const { Miniflare, Log, LogLevel } = await import("miniflare");
3098
+ const mfConfig = buildMiniflareConfig(doResult);
3099
+ const log = createMiniflareLog(Log, LogLevel, "DEBUG", logger);
3100
+ if (log) mfConfig.log = log;
3101
+ mfConfig.handleRuntimeStdio = createRuntimeStdioForwarder(logger);
3102
+ const shouldLogMiniflareDiagnostics = verbose || debug;
3103
+ if (shouldLogMiniflareDiagnostics) logMiniflareConfigDiagnostics(logger, mfConfig);
3104
+ state.miniflare = new Miniflare(mfConfig);
3105
+ await state.miniflare.ready;
3106
+ const displayHost = miniflareHost === "0.0.0.0" || miniflareHost === "::" ? "localhost" : miniflareHost;
3107
+ logger?.success(`Miniflare ready on http://${displayHost}:${miniflarePort}`);
3108
+ if (shouldLogMiniflareDiagnostics) await logMiniflareBindingDiagnostics(logger, state.miniflare, mfConfig);
3109
+ }
3110
+ /**
3111
+ * Reload Miniflare with updated DO bundles
3112
+ */
3113
+ async function reloadMiniflare(doResult) {
3114
+ state.currentDoResult = doResult;
3115
+ await reloadQueue.schedule();
3116
+ }
3117
+ async function refreshWorkerOnlySurfaceState() {
3118
+ if (!state.config) return;
3119
+ state.mainWorkerSurfacePaths = await resolveMainWorkerSurfacePaths(cwd, state.config);
3120
+ state.mainWorkerRoutes = await discoverRoutes(cwd, state.config);
3121
+ const composedMainEntry = await prepareComposedWorkerEntrypoint(cwd, state.config, void 0, { devInternalEmail: true });
3122
+ state.mainWorkerScriptPath = composedMainEntry ? composedMainEntry : null;
3123
+ if (state.mainWorkerScriptPath) await bundleMainWorker();
3124
+ else state.bundledMainWorkerScriptPath = null;
3125
+ await syncWorkerWatchTargets();
3126
+ }
3127
+ function getWorkerWatchTargets() {
3128
+ if (state.enableVite || !state.config) return [];
3129
+ const targets = collectWorkerWatchRoots(cwd, state.config, state.mainWorkerSurfacePaths);
3130
+ if (state.resolvedWorkerConfigPath) targets.push(state.resolvedWorkerConfigPath);
3131
+ return [...new Set(targets)];
3132
+ }
3133
+ async function syncWorkerWatchTargets() {
3134
+ if (!state.workerSourceWatcher) return;
3135
+ state.workerWatchTargets = await applyWatcherTargetDiff(state.workerSourceWatcher, state.workerWatchTargets, getWorkerWatchTargets());
3136
+ }
3137
+ async function reloadWorkerOnlyConfig() {
3138
+ await loadRuntimeConfigWhenEnvReady();
3139
+ if (!state.config) return;
3140
+ setLocalSendEmailBindings(state.config.bindings?.sendEmail ?? {});
3141
+ await bundleWorkflowEntrypoints();
3142
+ await refreshWorkerOnlySurfaceState();
3143
+ await reloadMiniflare(state.currentDoResult);
3144
+ }
3145
+ async function loadRuntimeConfig() {
3146
+ state.resolvedWorkerConfigPath = await resolveWorkerConfigWatchPath(cwd, configPath);
3147
+ state.config = await applyLocalDevVarsToConfig(await resolveConfigEnvVars(await loadConfig({
3148
+ cwd,
3149
+ configFile: configPath
3150
+ }), {
3151
+ cwd,
3152
+ configPath: state.resolvedWorkerConfigPath ?? configPath,
3153
+ mode: "dev"
3154
+ }), {
3155
+ cwd,
3156
+ configPath: state.resolvedWorkerConfigPath ?? void 0
3157
+ });
3158
+ state.serviceBindingResolution = state.config.bindings?.services ? await resolveServiceBindings(state.config, state.resolvedWorkerConfigPath ? dirname(state.resolvedWorkerConfigPath) : cwd) : null;
3159
+ }
3160
+ async function waitForDotenvChange() {
3161
+ const configWatchPath = state.resolvedWorkerConfigPath ?? await resolveWorkerConfigWatchPath(cwd, configPath);
3162
+ const watchPaths = getDevflareDotenvPaths(configWatchPath ? dirname(configWatchPath) : cwd);
3163
+ const { watch } = await import("chokidar");
3164
+ await new Promise((resolveWait, rejectWait) => {
3165
+ const watcher = watch(watchPaths, {
3166
+ ignoreInitial: true,
3167
+ awaitWriteFinish: {
3168
+ stabilityThreshold: 100,
3169
+ pollInterval: 25
3170
+ }
3171
+ });
3172
+ const finish = () => {
3173
+ watcher.close().then(resolveWait, rejectWait);
3174
+ };
3175
+ watcher.on("add", finish);
3176
+ watcher.on("change", finish);
3177
+ watcher.on("unlink", finish);
3178
+ watcher.on("error", rejectWait);
3179
+ });
3180
+ }
3181
+ async function loadRuntimeConfigWhenEnvReady() {
3182
+ while (true) try {
3183
+ await loadRuntimeConfig();
3184
+ return;
3185
+ } catch (error) {
3186
+ if (!(error instanceof EnvVarResolutionError)) throw error;
3187
+ logger?.warn(error.message);
3188
+ logger?.info("Devflare dev is waiting for .env or .env.dev to change before starting.");
3189
+ await waitForDotenvChange();
3190
+ }
3191
+ }
3192
+ async function startWorkerSourceWatcher$1() {
3193
+ if (state.enableVite || !state.config) return;
3194
+ const watchTargets = getWorkerWatchTargets();
3195
+ if (watchTargets.length === 0) return;
3196
+ state.workerWatchTargets = watchTargets;
3197
+ state.workerSourceWatcher = await startWorkerSourceWatcher({
3198
+ watchTargets,
3199
+ resolvedWorkerConfigPath: state.resolvedWorkerConfigPath,
3200
+ logger,
3201
+ onConfigChange: reloadWorkerOnlyConfig,
3202
+ onWorkerChange: async () => {
3203
+ await refreshWorkerOnlySurfaceState();
3204
+ await reloadMiniflare(state.currentDoResult);
3205
+ }
3206
+ });
3207
+ }
3208
+ /**
3209
+ * Start the complete dev server
3210
+ */
3211
+ async function start() {
3212
+ logger?.info("Starting unified dev server...");
3213
+ await loadRuntimeConfigWhenEnvReady();
3214
+ if (!state.config) throw new Error("Config not loaded");
3215
+ setLocalSendEmailBindings(state.config.bindings?.sendEmail ?? {});
3216
+ logger?.debug("Loaded config:", state.config.name);
3217
+ await bundleWorkflowEntrypoints();
3218
+ const viteIntegration = await resolveViteIntegration({
3219
+ cwd,
3220
+ configPath,
3221
+ miniflarePort,
3222
+ enableViteRequested: state.enableVite,
3223
+ logger
3224
+ });
3225
+ state.enableVite = viteIntegration.enableVite;
3226
+ state.generatedViteConfigPath = viteIntegration.generatedViteConfigPath;
3227
+ await refreshWorkerOnlySurfaceState();
3228
+ if (!state.enableVite && (hasWorkerSurfacePaths(state.mainWorkerSurfacePaths) || Boolean(state.mainWorkerRoutes?.routes.length))) logWorkerHandlerDetection(logger, state.enableVite, true, state.mainWorkerSurfacePaths, state.mainWorkerRoutes);
3229
+ else if (!state.enableVite) logWorkerHandlerDetection(logger, state.enableVite, false, state.mainWorkerSurfacePaths, state.mainWorkerRoutes);
3230
+ logRemoteBindingRequirements(logger, await checkRemoteBindingRequirements(state.config));
3231
+ state.browserShim = await maybeStartBrowserShim(state.config, {
3232
+ browserShimPort: state.browserShimPort,
3233
+ logger,
3234
+ verbose
3235
+ });
3236
+ const doInit = await maybeStartDOBundler(state.config, {
3237
+ cwd,
3238
+ logger,
3239
+ onRebuild: async (result) => {
3240
+ await reloadMiniflare(result);
3241
+ }
3242
+ });
3243
+ state.doBundler = doInit.bundler;
3244
+ const doResult = doInit.result;
3245
+ state.currentDoResult = doResult;
3246
+ await startMiniflare(doResult);
3247
+ await startWorkerSourceWatcher$1();
3248
+ if (state.enableVite) state.viteProcess = await startViteProcess({
3249
+ cwd,
3250
+ configPath,
3251
+ vitePort,
3252
+ miniflarePort,
3253
+ generatedViteConfigPath: state.generatedViteConfigPath,
3254
+ logger
3255
+ });
3256
+ else logger?.info("Vite startup skipped (no effective Vite config found for this package)");
3257
+ await new Promise((r) => setTimeout(r, 1e3));
3258
+ await runD1Migrations({
3259
+ cwd,
3260
+ config: state.config,
3261
+ miniflarePort,
3262
+ logger
3263
+ });
3264
+ }
3265
+ /**
3266
+ * Stop the dev server
3267
+ */
3268
+ async function stop() {
3269
+ await disposeDevServerState(state);
3270
+ }
3271
+ /**
3272
+ * Get Miniflare instance
3273
+ */
3274
+ function getMiniflare() {
3275
+ return state.miniflare;
3276
+ }
3277
+ return {
3278
+ start,
3279
+ stop,
3280
+ getMiniflare
3281
+ };
3282
+ }
3283
+ //#endregion
3284
+ //#region src/cli/commands/dev.ts
3285
+ function readStringOption(value) {
3286
+ return typeof value === "string" ? value : void 0;
3287
+ }
3288
+ function parsePort(value, source) {
3289
+ const port = Number(value);
3290
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) throw new Error(`${source} must be an integer between 1 and 65535`);
3291
+ return port;
3292
+ }
3293
+ function resolveSinglePort(runtimePort, bridgePort, source) {
3294
+ 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.`);
3295
+ const value = runtimePort ?? bridgePort;
3296
+ return value ? parsePort(value, source) : void 0;
3297
+ }
3298
+ function resolveDevRuntimePort(options, env = process.env, configPort) {
3299
+ return resolveSinglePort(readStringOption(options["runtime-port"]), readStringOption(options["bridge-port"]), "CLI") ?? resolveSinglePort(env.DEVFLARE_RUNTIME_PORT, env.DEVFLARE_BRIDGE_PORT, "environment") ?? configPort ?? 8787;
3300
+ }
3301
+ /**
3302
+ * Resolve the host the local Miniflare runtime instance binds to.
3303
+ *
3304
+ * Precedence: `--runtime-host` CLI flag > `DEVFLARE_RUNTIME_HOST` env var >
3305
+ * `server.host` config value > `127.0.0.1`.
3306
+ */
3307
+ function resolveDevRuntimeHost(options, env = process.env, configHost) {
3308
+ const envHost = env.DEVFLARE_RUNTIME_HOST?.trim();
3309
+ return readStringOption(options["runtime-host"]) ?? (envHost ? envHost : void 0) ?? configHost ?? "127.0.0.1";
3310
+ }
3311
+ /**
3312
+ * Create a log writer that writes to both terminal and file
3313
+ */
3314
+ async function createLogWriter(cwd, options) {
3315
+ if (!options.log && !options.logTemp) return null;
3316
+ const fs = await import("node:fs");
3317
+ let logPath;
3318
+ if (options.logTemp) logPath = resolve(cwd, ".log");
3319
+ else logPath = resolve(cwd, `.log-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19)}`);
3320
+ const fileStream = fs.createWriteStream(logPath, { flags: "w" });
3321
+ const ansiRegex = /\x1b\[[0-9;]*m/g;
3322
+ return {
3323
+ path: logPath,
3324
+ write(data, source) {
3325
+ const str = typeof data === "string" ? data : data.toString();
3326
+ if (!str.trim()) return;
3327
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
3328
+ const prefix = source ? `[${timestamp}][${source.toUpperCase()}] ` : `[${timestamp}] `;
3329
+ const cleanStr = str.replace(ansiRegex, "");
3330
+ fileStream.write(prefix + cleanStr + (cleanStr.endsWith("\n") ? "" : "\n"));
3331
+ },
3332
+ close() {
3333
+ fileStream.end();
3334
+ }
3335
+ };
3336
+ }
3337
+ async function runDevCommand(parsed, logger, options) {
3338
+ const cwd = options.cwd || parsed.options.cwd || process.cwd();
3339
+ const configPath = parsed.options.config;
3340
+ const port = parsed.options.port;
3341
+ const logEnabled = parsed.options.log === true;
3342
+ const logTempEnabled = parsed.options["log-temp"] === true;
3343
+ const persistEnabled = parsed.options.persist === true;
3344
+ const debugEnabled = parsed.options.debug === true || process.env.DEVFLARE_DEBUG === "true";
3345
+ const verbose = parsed.options.verbose === true || debugEnabled;
3346
+ const theme = createCliTheme(parsed.options);
3347
+ const config = await loadConfig({
3348
+ cwd,
3349
+ configFile: configPath
3350
+ });
3351
+ let miniflarePort;
3352
+ let miniflareHost;
3353
+ try {
3354
+ miniflarePort = resolveDevRuntimePort(parsed.options, process.env, config.server?.port);
3355
+ miniflareHost = resolveDevRuntimeHost(parsed.options, process.env, config.server?.host);
3356
+ } catch (error) {
3357
+ logger.error(error instanceof Error ? error.message : String(error));
3358
+ return { exitCode: 1 };
3359
+ }
3360
+ const viteProject = resolveEffectiveViteProject(await detectViteProject(cwd), config);
3361
+ const logWriter = await createLogWriter(cwd, {
3362
+ log: logEnabled,
3363
+ logTemp: logTempEnabled
3364
+ });
3365
+ if (logWriter) {
3366
+ const logFile = relative(cwd, logWriter.path) || ".log";
3367
+ logLine(logger, `${dim("logging", theme)} ${logFile}`);
3368
+ }
3369
+ const devLogger = createConsola({ level: verbose ? 4 : 3 });
3370
+ if (logWriter) {
3371
+ const wrapLog = (original, prefix = "") => {
3372
+ return (message, ...args) => {
3373
+ original(message, ...args);
3374
+ const formatted = prefix ? `${prefix} ${[message, ...args].join(" ")}` : [message, ...args].join(" ");
3375
+ logWriter.write(formatted);
3376
+ };
3377
+ };
3378
+ Object.assign(devLogger.log, wrapLog(devLogger.log.bind(devLogger)));
3379
+ Object.assign(devLogger.info, wrapLog(devLogger.info.bind(devLogger)));
3380
+ Object.assign(devLogger.error, wrapLog(devLogger.error.bind(devLogger), "[ERROR]"));
3381
+ Object.assign(devLogger.warn, wrapLog(devLogger.warn.bind(devLogger), "[WARN]"));
3382
+ Object.assign(devLogger.success, wrapLog(devLogger.success.bind(devLogger), "[OK]"));
3383
+ Object.assign(devLogger.debug, wrapLog(devLogger.debug.bind(devLogger), "[DEBUG]"));
3384
+ }
3385
+ try {
3386
+ logLine(logger);
3387
+ if (viteProject.shouldStartVite) {
3388
+ logLine(logger, `${cyanBold("dev", theme)} ${dim("Unified Dev Server", theme)}`);
3389
+ logLine(logger, " ├─ Vite: Full HMR for frontend");
3390
+ logLine(logger, " ├─ Miniflare: All Cloudflare bindings");
3391
+ logLine(logger, " ├─ Rolldown: Worker + DO bundling with watch");
3392
+ logLine(logger, " └─ Bridge: WebSocket RPC connection");
3393
+ } else {
3394
+ logLine(logger, `${cyanBold("dev", theme)} ${dim("Worker Dev Server", theme)}`);
3395
+ logLine(logger, " ├─ Miniflare: All Cloudflare bindings");
3396
+ logLine(logger, " ├─ Rolldown: Worker + DO bundling with watch");
3397
+ logLine(logger, " └─ Vite: Disabled (no effective Vite config found)");
3398
+ if (viteProject.wantsViteIntegration) {
3399
+ logger.warn("Vite-related settings were detected, but no effective Vite config was available");
3400
+ logger.warn("Skipping Vite startup and running in worker-only mode");
3401
+ }
3402
+ }
3403
+ logLine(logger);
3404
+ const devServer = createDevServer({
3405
+ cwd,
3406
+ configPath,
3407
+ vitePort: port ? Number.parseInt(port, 10) : 5173,
3408
+ miniflarePort,
3409
+ miniflareHost,
3410
+ enableVite: viteProject.shouldStartVite,
3411
+ persist: persistEnabled,
3412
+ logger: devLogger,
3413
+ verbose,
3414
+ debug: debugEnabled
3415
+ });
3416
+ let isCleaningUp = false;
3417
+ const cleanupHandlers = /* @__PURE__ */ new Map();
3418
+ const removeCleanupHandlers = () => {
3419
+ for (const [event, handler] of cleanupHandlers) process.off(event, handler);
3420
+ cleanupHandlers.clear();
3421
+ };
3422
+ const cleanup = async (exitCode, reason) => {
3423
+ if (isCleaningUp) return;
3424
+ isCleaningUp = true;
3425
+ removeCleanupHandlers();
3426
+ if (reason) {
3427
+ const message = reason instanceof Error ? reason.stack ?? reason.message : String(reason);
3428
+ logger.error(message);
3429
+ }
3430
+ logLine(logger);
3431
+ logLine(logger, `${yellow("dev", theme)} ${dim("Shutting down…", theme)}`);
3432
+ try {
3433
+ await devServer.stop();
3434
+ } finally {
3435
+ logWriter?.close();
3436
+ process.exit(exitCode);
3437
+ }
3438
+ };
3439
+ const registerCleanupHandler = (event, handler) => {
3440
+ cleanupHandlers.set(event, handler);
3441
+ process.on(event, handler);
3442
+ };
3443
+ registerCleanupHandler("SIGINT", () => {
3444
+ cleanup(0);
3445
+ });
3446
+ registerCleanupHandler("SIGTERM", () => {
3447
+ cleanup(0);
3448
+ });
3449
+ registerCleanupHandler("SIGHUP", () => {
3450
+ cleanup(0);
3451
+ });
3452
+ registerCleanupHandler("uncaughtException", (error) => {
3453
+ cleanup(1, error);
3454
+ });
3455
+ registerCleanupHandler("unhandledRejection", (reason) => {
3456
+ cleanup(1, reason);
3457
+ });
3458
+ await devServer.start();
3459
+ await new Promise(() => {});
3460
+ return { exitCode: 0 };
3461
+ } catch (error) {
3462
+ logWriter?.close();
3463
+ if (error instanceof Error) {
3464
+ logger.error("Dev server failed:", error.message);
3465
+ if (verbose) logger.error(error.stack);
3466
+ }
3467
+ return { exitCode: 1 };
3468
+ }
3469
+ }
3470
+ //#endregion
3471
+ export { runDevCommand };