devflare 1.0.0-next.6 → 1.0.0-next.61

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