void 0.26.1 → 0.28.0

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 (287) hide show
  1. package/README.md +3 -1
  2. package/dist/{account-cmd-CfUXh_nU.mjs → account-cmd-Ci00cncT.mjs} +5 -5
  3. package/dist/{agents-B_pX_HmQ.mjs → agents-CuNtT0D_.mjs} +5 -2
  4. package/dist/{application-analysis-DKy02UY0.mjs → application-analysis-D72ZQfMZ.mjs} +118 -135
  5. package/dist/{application-routing-DQyUh_5S.mjs → application-routing-Dy589Vgn.mjs} +1 -2
  6. package/dist/atomic-write-Dq6R0BPN.mjs +18 -0
  7. package/dist/{auth-B5_tZLxT.mjs → auth-B6iOCjKI.mjs} +23 -56
  8. package/dist/{auth-link-D5RmKnzh.mjs → auth-link-DWr6Afao.mjs} +10 -10
  9. package/dist/{auth-migrations-BwHgeEsD.mjs → auth-migrations-Cgs0k6lS.mjs} +28 -5
  10. package/dist/{auth-router-Bap-pvbn.mjs → auth-router-DP14wVkZ.mjs} +4 -4
  11. package/dist/auth-wn6F8dB8.mjs +2 -0
  12. package/dist/base64-DVrOndmI.mjs +16 -0
  13. package/dist/{better-auth-shared-CPvWD3nG.d.mts → better-auth-shared-B74NJIus.d.mts} +8 -13
  14. package/dist/{better-auth-shared-DPpj8zxc.mjs → better-auth-shared-CMXlZG7W.mjs} +56 -68
  15. package/dist/{build-cmd-B58c8iS8.mjs → build-cmd-Cr3FJxv0.mjs} +5 -5
  16. package/dist/{cache-Bo52trKF.mjs → cache-D0Tt28cn.mjs} +5 -5
  17. package/dist/{cancel-deploy-BZNz3Q26.mjs → cancel-deploy-CJKvk7AN.mjs} +4 -4
  18. package/dist/{cf-access-TWNzXltp.mjs → cf-access-BM9G0lhO.mjs} +4 -3
  19. package/dist/{cf-access-ngGqZpHZ.mjs → cf-access-CWP4avfP.mjs} +3 -7
  20. package/dist/cf-build-output-2u583yJK.mjs +2 -0
  21. package/dist/{cf-build-output-DjvB_872.mjs → cf-build-output-Bc8i3dzE.mjs} +2 -2
  22. package/dist/cf-user-agent-ChjQp2MU.mjs +33 -0
  23. package/dist/cli/cli.mjs +198 -311
  24. package/dist/cli/cloudflare-operation-process.mjs +46 -14
  25. package/dist/cli/env-schema-probe.d.mts +5 -43
  26. package/dist/cli/env-schema-probe.mjs +14 -80
  27. package/dist/client-C1BjRn3g.mjs +2 -0
  28. package/dist/{client-C2Prx_Bi.mjs → client-DM9Kcf4e.mjs} +161 -415
  29. package/dist/{cloudflare-auth-CuxfF6OA.mjs → cloudflare-auth-CqFBDo52.mjs} +12 -39
  30. package/dist/{cloudflare-cmd-lHqNlJGW.mjs → cloudflare-cmd-CTrn-lXJ.mjs} +6 -5
  31. package/dist/{cloudflare-config-BmhBv3HV.mjs → cloudflare-config-DV60XFFK.mjs} +16 -28
  32. package/dist/{cloudflare-connect-2G3rL2rP.mjs → cloudflare-connect-BosmCNlq.mjs} +3 -3
  33. package/dist/{cloudflare-operations-CwI1bChl.mjs → cloudflare-operations-BKdSfNBN.mjs} +26 -34
  34. package/dist/{cloudflare-operations-BlYLlowZ.mjs → cloudflare-operations-Ds-lX3Ts.mjs} +1 -1
  35. package/dist/{cloudflare-process-2-TfIEzB.mjs → cloudflare-process-CeDWJRhj.mjs} +12 -7
  36. package/dist/collect-NUlJMpLY.mjs +2 -0
  37. package/dist/{collect-DiTn9yr6.mjs → collect-xjffdCdk.mjs} +1 -1
  38. package/dist/{config-CSa15FvD.d.mts → config-7qoq2p9v.d.mts} +2 -2
  39. package/dist/{config-CLYAosnQ.mjs → config-BEm9vRvS.mjs} +11 -5
  40. package/dist/{config-DiowPT80.mjs → config-Bx_1SpnO.mjs} +87 -177
  41. package/dist/config-CNnrSMYm.mjs +2 -0
  42. package/dist/{config-DPByhVck.mjs → config-D0GY5ItR.mjs} +1 -1
  43. package/dist/config-entry.d.mts +1 -1
  44. package/dist/{connect-X0MUA1gk.mjs → connect-nukQSoEO.mjs} +8 -8
  45. package/dist/{create-project-DxiyW6iX.mjs → create-project-D4APeBwX.mjs} +7 -7
  46. package/dist/{create-project-iwkbqiBU.mjs → create-project-DL7YXpjj.mjs} +1 -1
  47. package/dist/{db-XnVMHzoV.mjs → db-27RztQLy.mjs} +173 -263
  48. package/dist/{delete-esYlMiSJ.mjs → delete-BbsZFZIw.mjs} +5 -5
  49. package/dist/deploy-B06EQq4t.mjs +6009 -0
  50. package/dist/deploy-Db2ALnQP.mjs +3 -0
  51. package/dist/deploy-managed-static-CGswbcay.mjs +2277 -0
  52. package/dist/deploy-transport-BiicfoQW.mjs +180 -0
  53. package/dist/deploy.d.mts +10 -0
  54. package/dist/deploy.mjs +82 -0
  55. package/dist/dev-inbox-Rd6j_8vM.mjs +217 -0
  56. package/dist/discover-CTUiSsfG.mjs +2 -0
  57. package/dist/{discover-CkCYv9T_.mjs → discover-D2PGT63u.mjs} +2 -2
  58. package/dist/{dist-BVqXeR_h.mjs → dist-ipQi0Q8y.mjs} +2 -3
  59. package/dist/{domain-CbbBWhWE.mjs → domain-Bfq9n03H.mjs} +7 -22
  60. package/dist/{dotenv-BfU6eqFt.mjs → dotenv-CNazY3-w.mjs} +4 -16
  61. package/dist/{email-d_Aux1vM.mjs → email-BcjSyAL0.mjs} +4 -20
  62. package/dist/{email-CpuT42g7.mjs → email-DIwaQU4P.mjs} +99 -191
  63. package/dist/email-cloudflare-CH053RxK.mjs +1096 -0
  64. package/dist/email-protocol-D9nTvhCp.mjs +82 -0
  65. package/dist/{entry-CMAkqBmU.mjs → entry-ehmBvs5w.mjs} +2 -2
  66. package/dist/{env-BxocywBB.mjs → env-BJjULvL0.mjs} +2 -2
  67. package/dist/{env-DMA3cXZt.mjs → env-BhdWMrNy.mjs} +2 -13
  68. package/dist/{env-w9jOyGZn.mjs → env-C6ipBLp3.mjs} +8 -8
  69. package/dist/{env-public-DR7ZM6d-.d.mts → env-public-CCdykygU.d.mts} +6 -23
  70. package/dist/{env-raw-jZpP5n47.mjs → env-raw-Cklzcde3.mjs} +3 -20
  71. package/dist/{env-types-CwXgeMgL.mjs → env-types-DS2_y4X8.mjs} +2 -2
  72. package/dist/{env-validation-CrVNjsPm.mjs → env-validation-CTn-HhG1.mjs} +1 -1
  73. package/dist/env-validation-cE0zcmzO.mjs +486 -0
  74. package/dist/{gen-DtkQhvQg.mjs → gen-CnN1Xidx.mjs} +131 -253
  75. package/dist/gen-F8pSZnhc.mjs +2 -0
  76. package/dist/{generate-GDbL7sUr.mjs → generate-Dat0UTry.mjs} +38 -4
  77. package/dist/generate-Rii9klHB.mjs +2 -0
  78. package/dist/generated-wrangler-config-DeWj5F9Y.mjs +18 -0
  79. package/dist/{github-cmd-D_FsOFxq.mjs → github-cmd-BdkU4Nhe.mjs} +27 -71
  80. package/dist/{handler-BHAn2fZJ.mjs → handler-BZ1t7-Rv.mjs} +2 -2
  81. package/dist/{handler-CNAwjRcW.d.mts → handler-_PqY82xe.d.mts} +1 -1
  82. package/dist/{headers-DlfR78Nu.mjs → headers-Di1FsD3e.mjs} +26 -81
  83. package/dist/{help-CVFBTmBx.mjs → help-Bug7pyK_.mjs} +18 -10
  84. package/dist/help-s9q7LTcu.mjs +2 -0
  85. package/dist/{inbound-PfQDLXc5.mjs → inbound-DmkTQeWp.mjs} +71 -222
  86. package/dist/{inbound-CH5Mksyy.d.mts → inbound-brj2-TM5.d.mts} +19 -58
  87. package/dist/index.d.mts +2 -18
  88. package/dist/index.mjs +200 -496
  89. package/dist/{info-PYWwkWyt.mjs → info-CujPwtMe.mjs} +5 -5
  90. package/dist/{init-Bw-fRKGp.mjs → init-ZWOJvc-Z.mjs} +179 -388
  91. package/dist/isr-config-DPjXM8J2.mjs +194 -0
  92. package/dist/{link-DlPG8iX3.mjs → link-UEc7bnF4.mjs} +8 -8
  93. package/dist/{list-2GN7_ueV.mjs → list-CuWeOnzk.mjs} +6 -17
  94. package/dist/{live-CgQuTpqD.mjs → live-BxLX-JqJ.mjs} +19 -17
  95. package/dist/{live-Du8jXwOW.d.mts → live-DJONVlQK.d.mts} +1 -1
  96. package/dist/load-user-env-DD5qveNU.mjs +49 -0
  97. package/dist/{local-d1-ga5O_idV.mjs → local-d1-BM3p74mm.mjs} +6 -6
  98. package/dist/{login-D-Q4xw3Q.mjs → login-CZ2Cqa-D.mjs} +11 -11
  99. package/dist/login-DhK7o1oJ.mjs +2 -0
  100. package/dist/{logs-rd8J-OFh.mjs → logs-TihRcLTk.mjs} +5 -5
  101. package/dist/migrate-BgT-hmu8.mjs +2 -0
  102. package/dist/{migrate-NLsjBbuM.mjs → migrate-uFwojusJ.mjs} +69 -21
  103. package/dist/migration-handler-Cg0Y4EPu.mjs +470 -0
  104. package/dist/{migration-handler-DM4clYj5.d.mts → migration-handler-CwlEQUoo.d.mts} +17 -26
  105. package/dist/{mime-DxCznlJq.mjs → mime-srOklVVv.mjs} +92 -395
  106. package/dist/{node-DJicPvPj.mjs → node-DoKbo1SZ.mjs} +8 -24
  107. package/dist/{operator-args-BNsW5gfp.mjs → operator-args-UnE8Y2eK.mjs} +27 -26
  108. package/dist/{operator-auth-AzVLbrr3.mjs → operator-auth-Cr2zCc-J.mjs} +2 -2
  109. package/dist/{operator-client-BQtzyG-g.mjs → operator-client-C-ROjk1I.mjs} +4 -4
  110. package/dist/{operator-cmd-B6f6Cg9Z.mjs → operator-cmd-C5lCctnc.mjs} +8 -8
  111. package/dist/operator-cmd-Cjk5DiRx.mjs +2 -0
  112. package/dist/{output-C50ag2r3.mjs → output-FNHKw5Ac.mjs} +36 -5
  113. package/dist/{package-json-DXfiT1jV.mjs → package-json-BjYqR-So.mjs} +1 -1
  114. package/dist/pages/client.d.mts +1 -1
  115. package/dist/pages/client.mjs +91 -5
  116. package/dist/pages/index.d.mts +1 -2
  117. package/dist/pages/index.mjs +6 -6
  118. package/dist/pages/islands-plugin.mjs +5 -83
  119. package/dist/pages/protocol.d.mts +2 -2
  120. package/dist/pages/protocol.mjs +1 -1
  121. package/dist/pages/serialize.mjs +1 -1
  122. package/dist/{platform-args-BoZkWQnJ.mjs → platform-args-C6Mb_I3a.mjs} +34 -58
  123. package/dist/platform-args-Cm7x-fot.mjs +2 -0
  124. package/dist/{platform-auth-config-n603OANg.mjs → platform-auth-config-CJmNkFT2.mjs} +10 -11
  125. package/dist/{platform-auth-protection-zxDr0hWW.mjs → platform-auth-protection-D5wvsI-Y.mjs} +36 -26
  126. package/dist/{platform-auth-recovery-tw9lgtgE.mjs → platform-auth-recovery-D_0I3n-1.mjs} +8 -9
  127. package/dist/{platform-cmd-C00YM14r.mjs → platform-cmd-Cuj4oUVo.mjs} +1 -1
  128. package/dist/{platform-cmd-B8-bCb7t.mjs → platform-cmd-DxxZO6e5.mjs} +17 -16
  129. package/dist/{platform-discovery-BC0HCKYt.mjs → platform-discovery-CHhNs8Nn.mjs} +1 -1
  130. package/dist/{platform-domain-p0kobLV8.mjs → platform-domain-zLXnW1G_.mjs} +5 -5
  131. package/dist/{platform-lifecycle-D_QEY32D.mjs → platform-lifecycle-DOW-wLfx.mjs} +1398 -2043
  132. package/dist/{platform-lifecycle-vFqtNPPa.mjs → platform-lifecycle-_gfnjUBr.mjs} +1 -1
  133. package/dist/{platform-management-oi-SvI3f.mjs → platform-management-CYrLvTGH.mjs} +1 -1
  134. package/dist/{platform-management-BdYblvvE.mjs → platform-management-t7HKy4-U.mjs} +632 -124
  135. package/dist/{platform-plans-config-CjiFv5J4.mjs → platform-plans-config-CNH6zbPX.mjs} +15 -11
  136. package/dist/{platform-recovery-PvNS9Nl2.mjs → platform-recovery-DeucI7A9.mjs} +6 -9
  137. package/dist/{platform-registry-P387v42A.mjs → platform-registry-B9mWX9LO.mjs} +18 -19
  138. package/dist/{platform-setup-DVe42Ws7.mjs → platform-setup-CTf6MMT9.mjs} +19 -26
  139. package/dist/{plugin-inference-BlQbOL0P.mjs → plugin-inference-DsARdQKE.mjs} +18 -33
  140. package/dist/{prepare-Dq9hTIbo.mjs → prepare-C4zfwks4.mjs} +3 -3
  141. package/dist/{prepare-Do96654b.mjs → prepare-CSH9dKTI.mjs} +11 -13
  142. package/dist/prepare-dr9uxQSV.mjs +2 -0
  143. package/dist/{prerender-render-Ct20jftc.mjs → prerender-render-Bfsumdkv.mjs} +8 -2
  144. package/dist/prerender-render.d.mts +2 -0
  145. package/dist/prerender-render.mjs +2 -2
  146. package/dist/project-DiiOBZPF.mjs +2 -0
  147. package/dist/{project-cmd-ti8_yTcA.mjs → project-cmd-Dq75AXzy.mjs} +19 -19
  148. package/dist/{project-paths-GgcMozYz.mjs → project-paths-DMvEeSau.mjs} +9 -14
  149. package/dist/{project-DQstYTxO.mjs → project-rqDnVsJN.mjs} +8 -4
  150. package/dist/{project-team-CDPE1Tsg.mjs → project-team-B3_QcStG.mjs} +6 -6
  151. package/dist/{project-token-CkZ1cN0Z.mjs → project-token-CvsRvv2I.mjs} +5 -5
  152. package/dist/{project-tsconfig-CVJhNMxs.mjs → project-tsconfig-bveqhlYA.mjs} +3 -5
  153. package/dist/{project-zero-trust-DdjLR61k.mjs → project-zero-trust-D1ICiwN_.mjs} +5 -5
  154. package/dist/{protocol-BPZaNqBo.d.mts → protocol-4H7V_3Os.d.mts} +1 -1
  155. package/dist/{protocol-CfaUwNs8.mjs → protocol-RNP0u7UY.mjs} +9 -3
  156. package/dist/{provision-BfaLiWbj.mjs → provision-DDh7ITVS.mjs} +118 -865
  157. package/dist/{requests-DFAqyYy8.mjs → requests-3CL3BuZh.mjs} +4 -4
  158. package/dist/resolve-project-B9Hr0D3n.mjs +2 -0
  159. package/dist/resolve-project-C_Cu7E8a.mjs +48 -0
  160. package/dist/{rollback-BKYXBduI.mjs → rollback-Ce1d_0J9.mjs} +7 -17
  161. package/dist/{route-types-CbtsIeFn.mjs → route-types-IrBuwJ7u.mjs} +35 -70
  162. package/dist/routing-assets-VkrKIbxE.mjs +139 -0
  163. package/dist/{runner-9wjsSZx-.mjs → runner-B5huK2eC.mjs} +1 -1
  164. package/dist/{runner-DMBKSrRk.mjs → runner-DcVDrzuR.mjs} +7 -28
  165. package/dist/{runner-mysql-CEzKtmFb.mjs → runner-mysql-CCtyI0p6.mjs} +4 -3
  166. package/dist/runner-mysql-CaJq-hd6.mjs +2 -0
  167. package/dist/runner-pg-BJhFZZG4.mjs +2 -0
  168. package/dist/{runner-pg-DV8p_zjA.mjs → runner-pg-CPd-CB_F.mjs} +1 -1
  169. package/dist/runtime/ai.mjs +9 -22
  170. package/dist/runtime/better-auth-mysql.d.mts +2 -5
  171. package/dist/runtime/better-auth-mysql.mjs +6 -31
  172. package/dist/runtime/better-auth-pg.d.mts +2 -5
  173. package/dist/runtime/better-auth-pg.mjs +6 -32
  174. package/dist/runtime/better-auth.d.mts +2 -4
  175. package/dist/runtime/better-auth.mjs +9 -19
  176. package/dist/runtime/db.mjs +1 -1
  177. package/dist/runtime/durable.d.mts +16 -0
  178. package/dist/runtime/durable.mjs +26 -2
  179. package/dist/runtime/email/testing.d.mts +1 -1
  180. package/dist/runtime/email/testing.mjs +9 -23
  181. package/dist/runtime/email-protocol.d.mts +12 -0
  182. package/dist/runtime/email-protocol.mjs +2 -70
  183. package/dist/runtime/email.d.mts +1 -1
  184. package/dist/runtime/email.mjs +79 -221
  185. package/dist/runtime/env-public.d.mts +1 -1
  186. package/dist/runtime/env-public.mjs +12 -38
  187. package/dist/runtime/env.d.mts +1 -12
  188. package/dist/runtime/env.mjs +1 -1
  189. package/dist/runtime/handler.d.mts +1 -1
  190. package/dist/runtime/handler.mjs +1 -1
  191. package/dist/runtime/isr.mjs +1 -1
  192. package/dist/runtime/kv.mjs +1 -1
  193. package/dist/runtime/live-client.d.mts +1 -1
  194. package/dist/runtime/live-server.mjs +1 -16
  195. package/dist/runtime/live.d.mts +1 -1
  196. package/dist/runtime/live.mjs +1 -1
  197. package/dist/runtime/migration-handler-mysql.d.mts +2 -2
  198. package/dist/runtime/migration-handler-mysql.mjs +3 -2
  199. package/dist/runtime/migration-handler-pg.d.mts +2 -2
  200. package/dist/runtime/migration-handler-pg.mjs +1 -1
  201. package/dist/runtime/migration-handler.d.mts +2 -2
  202. package/dist/runtime/migration-handler.mjs +2 -470
  203. package/dist/runtime/queues.mjs +1 -1
  204. package/dist/runtime/remote/binding-handler.mjs +62 -62
  205. package/dist/runtime/remote/index.mjs +16 -77
  206. package/dist/runtime/routing.d.mts +1 -4
  207. package/dist/runtime/routing.mjs +82 -218
  208. package/dist/runtime/sandbox-container.d.mts +2 -2
  209. package/dist/runtime/sandbox-container.mjs +2 -2
  210. package/dist/runtime/sandbox.d.mts +2 -2
  211. package/dist/runtime/sandbox.mjs +13 -6
  212. package/dist/runtime/storage.mjs +1 -1
  213. package/dist/runtime/validator.d.mts +1 -1
  214. package/dist/runtime/ws-server.d.mts +2 -3
  215. package/dist/runtime/ws-server.mjs +6 -28
  216. package/dist/runtime/ws.d.mts +1 -1
  217. package/dist/{sandbox-container-Bo0eiFKz.d.mts → sandbox-container-ChQRum-j.d.mts} +3 -1
  218. package/dist/{sandbox-container-C6ItmVuN.mjs → sandbox-container-DBKIqDEK.mjs} +12 -1
  219. package/dist/{sandbox-C5LFoJMf.d.mts → sandbox-q3waMc2T.d.mts} +1 -1
  220. package/dist/{scan-C3uDzz6J.mjs → scan-C0BMdmEd.mjs} +84 -7
  221. package/dist/{secret-BAt2KMmO.mjs → secret-B5J8bBsI.mjs} +62 -80
  222. package/dist/{serialize-BPvnNQuA.mjs → serialize-BtQyaYoC.mjs} +10 -4
  223. package/dist/{skills-BgVMvQdy.mjs → skills-BJsan98C.mjs} +2 -2
  224. package/dist/{subcommand-prompt-BSlnD1fI.mjs → subcommand-prompt-gQ-NDA-u.mjs} +38 -43
  225. package/dist/sveltekit.mjs +1 -1
  226. package/dist/validate-C9xtFOYR.mjs +2 -0
  227. package/dist/{validate-C9qJArv7.mjs → validate-CheZGRE7.mjs} +4 -8
  228. package/dist/version-metadata-DIrqEHQo.mjs +73 -0
  229. package/dist/{wrangler-D9KTuqnv.mjs → wrangler-BYs07Rxy.mjs} +119 -378
  230. package/dist/{ws-CChrrAU-.d.mts → ws-aKVYB-fA.d.mts} +1 -1
  231. package/dist/yarn-pnp-DLbEFMT0.mjs +92 -0
  232. package/docs/guide/auth.md +2 -0
  233. package/docs/guide/database/postgresql.md +2 -0
  234. package/docs/guide/database.md +2 -0
  235. package/docs/guide/deployment.md +2 -2
  236. package/docs/guide/durable-state.md +24 -0
  237. package/docs/guide/platform/administration/access.md +5 -1
  238. package/docs/guide/platform/administration/connected-apps.md +55 -0
  239. package/docs/guide/platform/installation/credentials.md +3 -1
  240. package/docs/guide/platform/installation/first-deployment.md +1 -1
  241. package/docs/guide/project-collaboration.md +1 -1
  242. package/docs/guide/quickstart.md +2 -0
  243. package/docs/guide/sandboxes.md +2 -0
  244. package/docs/reference/api/deploy.md +23 -0
  245. package/docs/reference/api/pages.md +2 -0
  246. package/docs/reference/api.md +2 -1
  247. package/docs/reference/cli/auth.md +1 -1
  248. package/docs/reference/cli/database.md +9 -3
  249. package/docs/reference/cli/deploy.md +1 -1
  250. package/docs/reference/cli/platform-installation.md +1 -1
  251. package/docs/reference/cli/setup.md +2 -0
  252. package/package.json +14 -17
  253. package/skills/void/SKILL.md +4 -2
  254. package/dist/auth-BgX2qYSY.mjs +0 -2
  255. package/dist/cf-build-output-H9a97vME.mjs +0 -2
  256. package/dist/client-By4JjMw_.mjs +0 -2
  257. package/dist/collect-DLyFDiKW.mjs +0 -2
  258. package/dist/config-5r-YpMC-.mjs +0 -2
  259. package/dist/config-B_ggY-IO.mjs +0 -89
  260. package/dist/config-BtSFvXZx.mjs +0 -2
  261. package/dist/config-write-AHGesphx.mjs +0 -61
  262. package/dist/deploy-BsEvnlJl.mjs +0 -10455
  263. package/dist/deploy-C1d-YTfp.mjs +0 -2
  264. package/dist/dev-inbox-CK-UuPob.mjs +0 -297
  265. package/dist/discover-EDUf6JJw.mjs +0 -2
  266. package/dist/dist-_UUlL6bg.mjs +0 -2
  267. package/dist/env-validation-Dxdx_5wo.mjs +0 -951
  268. package/dist/gen-dA9hW7AV.mjs +0 -2
  269. package/dist/head-client-DzmJGN4C.mjs +0 -90
  270. package/dist/help-DzKUrQvk.mjs +0 -2
  271. package/dist/login-Dc-wuTDq.mjs +0 -2
  272. package/dist/migrate-BHp07yJE.mjs +0 -2
  273. package/dist/operator-cmd-Cwn50lOV.mjs +0 -2
  274. package/dist/pages/head-client.d.mts +0 -13
  275. package/dist/pages/head-client.mjs +0 -2
  276. package/dist/platform-args-DYgITk0D.mjs +0 -2
  277. package/dist/prepare-DRUV-g3X.mjs +0 -2
  278. package/dist/project-BiaPQ7ur.mjs +0 -2
  279. package/dist/provision-Wu9ZBk9L.mjs +0 -2
  280. package/dist/resolve-project-BIeYs9Cw.mjs +0 -2
  281. package/dist/resolve-project-CP8rZs_p.mjs +0 -28
  282. package/dist/runner-mysql-CkFqZ6CE.mjs +0 -2
  283. package/dist/runner-pg-BGYRFdRp.mjs +0 -2
  284. package/dist/runtime/env-helpers.d.mts +0 -2
  285. package/dist/runtime/env-helpers.mjs +0 -2
  286. package/dist/validate-BJr9LfXy.mjs +0 -2
  287. package/dist/yarn-pnp-CUX_iTPA.mjs +0 -188
@@ -0,0 +1,1096 @@
1
+ import { n as __toESM } from "./rolldown-runtime-DXIUcv95.mjs";
2
+ import { a as join, n as dirname, s as relative } from "./dist-ipQi0Q8y.mjs";
3
+ import { t as require_picocolors } from "./picocolors-BTps1_gs.mjs";
4
+ import { d as readConfig } from "./config-Bx_1SpnO.mjs";
5
+ import { i as collectFiles } from "./isr-config-DPjXM8J2.mjs";
6
+ import { i as voidWarn, r as voidLog } from "./log-vqY3pqwI.mjs";
7
+ import { i as readProjectIdField } from "./project-rqDnVsJN.mjs";
8
+ import { p as findBindingNameOwner, u as ensureWranglerCompat } from "./wrangler-BYs07Rxy.mjs";
9
+ import { n as scopedAccessCredentials } from "./cf-access-BM9G0lhO.mjs";
10
+ import { n as ensurePlatformProxyAccess } from "./cf-access-CWP4avfP.mjs";
11
+ import { a as getApiUrl, c as getToken, o as getProxyUrl } from "./auth-B6iOCjKI.mjs";
12
+ import { l as stripInternalEnvKeys } from "./env-BJjULvL0.mjs";
13
+ import { n as readPrerenderPathsResponse, r as renderPrerenderPaths, t as outputFileForPath } from "./prerender-render-Bfsumdkv.mjs";
14
+ import { a as buildEnv } from "./version-metadata-DIrqEHQo.mjs";
15
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
16
+ import { isRecord } from "@void/deploy-core";
17
+ import { execFile } from "node:child_process";
18
+ //#region src/prerender.ts
19
+ var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
20
+ const PRERENDER_PATHS_ENDPOINT = "/__void/prerender-paths";
21
+ const nodeRendererUrl = new URL(import.meta.url.endsWith(".ts") ? "./prerender-render.ts" : "./prerender-render.mjs", import.meta.url).href;
22
+ function normalizeWorkerModulePath(path) {
23
+ return path.replace(/\\/g, "/").replace(/^\.\//, "");
24
+ }
25
+ function resolveWorkerModuleType(relativePath) {
26
+ const normalized = normalizeWorkerModulePath(relativePath);
27
+ if (normalized.includes("/.vite/") || normalized.startsWith(".vite/")) return null;
28
+ if (normalized === "wrangler.json" || normalized === "wrangler.jsonc") return null;
29
+ if (normalized.endsWith(".mjs") || normalized.endsWith(".js")) return "ESModule";
30
+ if (normalized.endsWith(".cjs")) return "CommonJS";
31
+ if (normalized.endsWith(".wasm")) return "CompiledWasm";
32
+ if (normalized.endsWith(".txt")) return "Text";
33
+ if (normalized.endsWith(".bin")) return "Data";
34
+ return null;
35
+ }
36
+ function collectPrerenderWorkerModules(workerDir, workerMain) {
37
+ const normalizedEntry = normalizeWorkerModulePath(workerMain);
38
+ const modules = collectFiles(workerDir).flatMap((filePath) => {
39
+ const relativePath = normalizeWorkerModulePath(relative(workerDir, filePath));
40
+ const type = resolveWorkerModuleType(relativePath);
41
+ if (!type) return [];
42
+ const file = readFileSync(filePath);
43
+ return [{
44
+ path: filePath,
45
+ relativePath,
46
+ type,
47
+ contents: type === "CompiledWasm" || type === "Data" ? new Uint8Array(file) : file.toString("utf-8")
48
+ }];
49
+ });
50
+ modules.sort((a, b) => {
51
+ if (a.relativePath === normalizedEntry) return -1;
52
+ if (b.relativePath === normalizedEntry) return 1;
53
+ return a.relativePath.localeCompare(b.relativePath);
54
+ });
55
+ if (!modules.some((module) => module.relativePath === normalizedEntry)) throw new Error(`prerender: Worker entry '${normalizedEntry}' not found in '${workerDir}'.`);
56
+ return modules.map(({ relativePath: _relativePath, ...module }) => module);
57
+ }
58
+ function buildPrerenderBindings(input) {
59
+ const { sanitized: wranglerVars, stripped } = stripInternalEnvKeys(input.wranglerVars);
60
+ if (stripped.length > 0) voidWarn(`Ignoring internal env keys in prerender inputs: ${stripped.join(", ")}. These are reserved for Void runtime use.`);
61
+ const voidBindings = {};
62
+ if (input.voidToken && input.voidProjectId) {
63
+ voidBindings.__VOID_TOKEN = input.voidToken;
64
+ voidBindings.__VOID_PROJECT_ID = input.voidProjectId;
65
+ if (!input.voidProxyUrl) throw new Error("prerender: No connected platform proxy is available. Run `void connect <url>` first.");
66
+ voidBindings.__VOID_PROXY_URL = input.voidProxyUrl;
67
+ const access = scopedAccessCredentials(input.accessEnv ?? {}, voidBindings.__VOID_PROXY_URL);
68
+ if (access) voidBindings.__VOID_ACCESS_CREDENTIALS = access;
69
+ if (input.remoteMode) voidBindings.__VOID_REMOTE = "1";
70
+ }
71
+ return {
72
+ ...wranglerVars,
73
+ ...voidBindings
74
+ };
75
+ }
76
+ async function createCloudflarePrerenderRunner(options) {
77
+ const { root, workerDir, persistDir, clientDir, remoteMode } = options;
78
+ const wranglerPath = join(workerDir, "wrangler.json");
79
+ if (!existsSync(wranglerPath)) throw new Error(`prerender: wrangler.json not found in '${workerDir}'.`);
80
+ const wranglerConfig = JSON.parse(readFileSync(wranglerPath, "utf-8"));
81
+ const wranglerCompat = ensureWranglerCompat(root, readConfig(root), workerDir);
82
+ const workerMain = wranglerConfig.main ?? "index.js";
83
+ let voidProjectId = options.projectId;
84
+ if (!voidProjectId) voidProjectId = process.env.VOID_DEPLOY_PROJECT_ID || void 0;
85
+ if (!voidProjectId) voidProjectId = readProjectIdField(root);
86
+ const voidToken = voidProjectId ? options.voidToken ?? getToken(root) : null;
87
+ const voidProxyUrl = voidToken && voidProjectId ? getProxyUrl(root) : void 0;
88
+ if (voidProxyUrl) ensurePlatformProxyAccess(getApiUrl(root), voidProxyUrl);
89
+ const bindings = buildPrerenderBindings({
90
+ accessEnv: process.env,
91
+ wranglerVars: wranglerConfig.vars,
92
+ voidToken,
93
+ voidProjectId,
94
+ voidProxyUrl,
95
+ remoteMode
96
+ });
97
+ const { Miniflare, convertV4MiniflareOptions } = await import("miniflare");
98
+ const mfOptions = {
99
+ modules: collectPrerenderWorkerModules(workerDir, workerMain),
100
+ modulesRoot: workerDir,
101
+ compatibilityDate: wranglerCompat.compatibilityDate,
102
+ compatibilityFlags: wranglerCompat.compatibilityFlags ?? [],
103
+ bindings: {
104
+ ...bindings,
105
+ __VOID_PRERENDER: "1"
106
+ }
107
+ };
108
+ if (clientDir) mfOptions.serviceBindings = { [wranglerConfig.assets?.binding ?? "ASSETS"](request) {
109
+ const url = new URL(request.url);
110
+ const filePath = join(clientDir, url.pathname);
111
+ try {
112
+ const content = readFileSync(filePath);
113
+ return new Response(content);
114
+ } catch {
115
+ return new Response("Not Found", { status: 404 });
116
+ }
117
+ } };
118
+ const d1 = wranglerConfig.d1_databases;
119
+ if (Array.isArray(d1) && d1.length > 0) {
120
+ const databases = {};
121
+ for (const db of d1) databases[db.binding] = db.database_id;
122
+ mfOptions.d1Databases = databases;
123
+ }
124
+ const kv = wranglerConfig.kv_namespaces;
125
+ if (Array.isArray(kv) && kv.length > 0) {
126
+ const namespaces = {};
127
+ for (const ns of kv) namespaces[ns.binding] = ns.id;
128
+ mfOptions.kvNamespaces = namespaces;
129
+ }
130
+ const r2 = wranglerConfig.r2_buckets;
131
+ if (Array.isArray(r2) && r2.length > 0) {
132
+ const buckets = {};
133
+ for (const bucket of r2) buckets[bucket.binding] = bucket.bucket_name;
134
+ mfOptions.r2Buckets = buckets;
135
+ }
136
+ if (mfOptions.d1Databases || mfOptions.kvNamespaces || mfOptions.r2Buckets) mfOptions.resourcePersistencePath = join(persistDir, "v3");
137
+ const mf = new Miniflare(convertV4MiniflareOptions(mfOptions));
138
+ return {
139
+ async collectPaths() {
140
+ return readPrerenderPathsResponse(await mf.dispatchFetch(`http://localhost${PRERENDER_PATHS_ENDPOINT}`, { method: "POST" }));
141
+ },
142
+ async fetchPath(path) {
143
+ return mf.dispatchFetch(`http://localhost${path}`);
144
+ },
145
+ async fetchPageData(path) {
146
+ return mf.dispatchFetch(`http://localhost${path}`, { headers: {
147
+ Accept: "application/json",
148
+ Purpose: "prefetch",
149
+ "X-VoidPages": "true"
150
+ } });
151
+ },
152
+ async close() {
153
+ await mf.dispose();
154
+ }
155
+ };
156
+ }
157
+ async function collectPrerenderPaths(options) {
158
+ const runner = await createCloudflarePrerenderRunner(options);
159
+ try {
160
+ return await runner.collectPaths();
161
+ } finally {
162
+ await runner.close();
163
+ }
164
+ }
165
+ /**
166
+ * Prerender pages by spinning up Miniflare with the built worker.
167
+ * Collects paths through the built worker's internal endpoint and then
168
+ * fetches each page, writing HTML responses to `clientDir`.
169
+ */
170
+ async function prerenderPages(options) {
171
+ const runner = await createCloudflarePrerenderRunner(options);
172
+ try {
173
+ const paths = await runner.collectPaths();
174
+ if (paths.length === 0) return {
175
+ paths,
176
+ rendered: [],
177
+ failed: []
178
+ };
179
+ return {
180
+ paths,
181
+ ...await renderPrerenderPaths(paths, options.clientDir, (path) => runner.fetchPath(path), (path) => runner.fetchPageData(path), (path, outputFile, error) => {
182
+ if (error !== void 0) voidWarn(`prerender ${path} failed: ${error}`);
183
+ else voidLog(`prerender ${import_picocolors.default.blue(outputFile)}`);
184
+ })
185
+ };
186
+ } finally {
187
+ await runner.close();
188
+ }
189
+ }
190
+ async function runNodePrerenderRunner(root, config) {
191
+ const runnerPath = join(root, ".void", "prerender-runner.mjs");
192
+ mkdirSync(dirname(runnerPath), { recursive: true });
193
+ writeFileSync(runnerPath, `
194
+ import { pathToFileURL } from "node:url";
195
+ import { dirname, join } from "node:path";
196
+ import { readPrerenderPathsResponse, renderPrerenderPaths } from ${JSON.stringify(nodeRendererUrl)};
197
+
198
+ const config = JSON.parse(process.argv[2]);
199
+ const { mode, ssrDir, clientDir } = config;
200
+
201
+ const appUrl = pathToFileURL(join(ssrDir, "app.js")).href;
202
+ const app = await import(appUrl);
203
+ const fetch = app.default?.fetch;
204
+ if (!fetch) {
205
+ console.error("[void] No default export with fetch() found in app.js");
206
+ process.exit(1);
207
+ }
208
+
209
+ // The second argument is the Hono env. Its marker is what lets the built app
210
+ // answer path discovery; HTTP servers never pass it.
211
+ async function collectPaths() {
212
+ return readPrerenderPathsResponse(
213
+ await fetch(
214
+ new Request("http://localhost${PRERENDER_PATHS_ENDPOINT}", { method: "POST" }),
215
+ { __VOID_PRERENDER: "1" },
216
+ ),
217
+ );
218
+ }
219
+
220
+ async function renderPaths(paths) {
221
+ if (!clientDir) return { rendered: [], failed: [] };
222
+ return renderPrerenderPaths(
223
+ paths, clientDir,
224
+ (path) => fetch(new Request("http://localhost" + path)),
225
+ (path) => fetch(new Request("http://localhost" + path, {
226
+ headers: { "Accept": "application/json", "Purpose": "prefetch", "X-VoidPages": "true" },
227
+ })),
228
+ );
229
+ }
230
+
231
+ if (mode === "collect") {
232
+ process.stdout.write(JSON.stringify({ paths: await collectPaths() }));
233
+ } else {
234
+ const paths = await collectPaths();
235
+ const result = await renderPaths(paths);
236
+ process.stdout.write(JSON.stringify({ paths, ...result }));
237
+ }
238
+ `);
239
+ try {
240
+ return await new Promise((resolve, reject) => {
241
+ execFile(process.execPath, [runnerPath, JSON.stringify(config)], {
242
+ env: buildEnv(),
243
+ cwd: root,
244
+ maxBuffer: 10485760
245
+ }, (error, stdout, stderr) => {
246
+ if (stderr) process.stderr.write(stderr);
247
+ if (error) {
248
+ reject(/* @__PURE__ */ new Error(`Prerender child process failed: ${error.message}`));
249
+ return;
250
+ }
251
+ try {
252
+ resolve(JSON.parse(stdout));
253
+ } catch {
254
+ reject(/* @__PURE__ */ new Error(`Failed to parse prerender output: ${stdout}`));
255
+ }
256
+ });
257
+ });
258
+ } finally {
259
+ try {
260
+ unlinkSync(runnerPath);
261
+ } catch {}
262
+ }
263
+ }
264
+ async function collectPrerenderPathsNode(options) {
265
+ return (await runNodePrerenderRunner(options.root, {
266
+ mode: "collect",
267
+ ssrDir: options.ssrDir,
268
+ clientDir: options.clientDir
269
+ })).paths;
270
+ }
271
+ /**
272
+ * Prerender pages for Node.js targets by spawning a child process.
273
+ *
274
+ * Uses a child process to avoid mutating `process.env` in the parent Vite
275
+ * build process. The child imports the built app, collects prerender paths
276
+ * from the internal endpoint, renders each page, and returns the result.
277
+ */
278
+ async function prerenderPagesNode(options) {
279
+ const result = await runNodePrerenderRunner(options.root, {
280
+ mode: "collect-and-render",
281
+ ssrDir: options.ssrDir,
282
+ clientDir: options.clientDir
283
+ });
284
+ for (const path of result.rendered) voidLog(`prerender ${import_picocolors.default.blue(outputFileForPath(path))}`);
285
+ for (const { path, error } of result.failed) voidWarn(`prerender ${path} failed: ${error}`);
286
+ return result;
287
+ }
288
+ //#endregion
289
+ //#region src/cli/email-cloudflare.ts
290
+ /** The `send_email` binding name Void provisions and the runtime reads. */
291
+ const SEND_EMAIL_BINDING = "SEND_EMAIL";
292
+ /** The root-config `vars` key carrying `void.config.ts` `email.from` into the worker. */
293
+ const EMAIL_FROM_VAR = "__VOID_EMAIL_FROM";
294
+ const CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
295
+ /** OAuth scopes the session needs (`whoami` `tokenPermissions`). */
296
+ const REQUIRED_EMAIL_SCOPES = ["email_routing:write", "email_sending:write"];
297
+ /** Cloudflare Email API error codes with a known meaning; others are reported verbatim. */
298
+ const CF_EMAIL_ERROR_CODES = {
299
+ 2062: "Email Routing is not enabled on this zone",
300
+ 2040: "already onboarded for Email Sending",
301
+ 2036: "account is not entitled to Email Sending",
302
+ 10105: "account is not entitled to Email Sending"
303
+ };
304
+ /** The one-time dashboard step printed when the subdomain routing call cannot be made. */
305
+ function subdomainDashboardFallback(apex, domain) {
306
+ return `Cloudflare dashboard → ${apex} → Email → Settings → Subdomains → add ${domain}, then redeploy`;
307
+ }
308
+ /** The Workers Free hint printed when Email Sending onboarding fails. */
309
+ const SENDING_FREE_PLAN_HINT = "Workers Paid needed for arbitrary recipients. Inbound works; sendEmail() to verified destinations only.";
310
+ /**
311
+ * Config shapes the writer would refuse for `SEND_EMAIL` and `__VOID_EMAIL_FROM`, checked
312
+ * before anything is asked. The writer keeps the same guard, since the file can change first.
313
+ */
314
+ function findEmailConfigConflicts(config) {
315
+ const conflicts = /* @__PURE__ */ new Set();
316
+ const check = (field, name) => {
317
+ let owner;
318
+ try {
319
+ owner = findBindingNameOwner(config, field, name);
320
+ } catch (err) {
321
+ conflicts.add(`Cloudflare config: ${err instanceof Error ? err.message : String(err)}`);
322
+ return;
323
+ }
324
+ if (owner !== void 0) conflicts.add(`Cloudflare config already uses ${name} as a ${owner} binding; rename it in void.config.ts (Void writes ${name} into ${field})`);
325
+ };
326
+ check("send_email", SEND_EMAIL_BINDING);
327
+ check("vars", EMAIL_FROM_VAR);
328
+ return [...conflicts];
329
+ }
330
+ /** The `EmailCloudflareInputs` fields read from the parsed root config. */
331
+ function emailConfigInputs(rootConfig) {
332
+ return {
333
+ existingSendEmail: Array.isArray(rootConfig.send_email) ? rootConfig.send_email : void 0,
334
+ existingAddresses: rootConfig.addresses,
335
+ existingVars: isRecord(rootConfig.vars) ? rootConfig.vars : void 0,
336
+ configConflicts: findEmailConfigConflicts(rootConfig)
337
+ };
338
+ }
339
+ /**
340
+ * Host part of `email.from`, lowercased; `null` when it is not `local@host`. `void.config.ts`
341
+ * also accepts the display-name form (`Acme <noreply@mail.acme.com>`, see `config.ts`),
342
+ * so the address inside `<…>` is read when present.
343
+ *
344
+ * Validated per label, so an empty label or one that starts with `-` never reaches a
345
+ * Cloudflare command argument.
346
+ */
347
+ function emailFromDomain(from) {
348
+ const bracketed = /<([^<>]*)>\s*$/.exec(from.trim());
349
+ const address = bracketed ? bracketed[1] : from;
350
+ const at = address.lastIndexOf("@");
351
+ if (at <= 0 || at === address.length - 1) return null;
352
+ const host = address.slice(at + 1).trim().toLowerCase();
353
+ const labels = host.split(".");
354
+ if (labels.length < 2 || !labels.every((l) => /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/.test(l))) return null;
355
+ return host;
356
+ }
357
+ /**
358
+ * `email/<literal>.ts` and `email/<literal>+[tag].ts` → `<literal>@<domain>`.
359
+ * `email/_default.ts` and any dynamic local part (`[name].ts`, `[name]+[tag].ts`)
360
+ * need a catch-all, which Cloudflare offers only on a zone apex: `*@<apex>` on the apex
361
+ * path, refused with a message on a subdomain.
362
+ *
363
+ * Reuses the scanner's compiled pattern kinds rather than re-parsing filenames — the
364
+ * grammar lives in `router/email.ts` and this must never disagree with the dispatcher.
365
+ */
366
+ function deriveAddresses(handlers, domain, mode) {
367
+ const literals = [];
368
+ const routes = [];
369
+ const refused = [];
370
+ let needsCatchAll = false;
371
+ const catchAllHandlers = [];
372
+ for (const def of handlers.addresses) {
373
+ const handler = `email/${def.filePath}`;
374
+ if (def.pattern.kind === "static" || def.pattern.kind === "static-tag") {
375
+ const address = `${def.address.split("+")[0] ?? def.address}@${domain}`;
376
+ if (!literals.includes(address)) literals.push(address);
377
+ routes.push({
378
+ address,
379
+ handler
380
+ });
381
+ continue;
382
+ }
383
+ if (mode === "subdomain") {
384
+ refused.push({
385
+ handler,
386
+ reason: `a dynamic local part needs a catch-all, and a catch-all exists only on a zone apex; nothing at @${domain} reaches it`
387
+ });
388
+ continue;
389
+ }
390
+ needsCatchAll = true;
391
+ catchAllHandlers.push(handler);
392
+ }
393
+ if (handlers.defaultHandler) {
394
+ const handler = `email/${handlers.defaultHandler}`;
395
+ if (mode === "subdomain") refused.push({
396
+ handler,
397
+ reason: `a catch-all exists only on an apex; other @${domain} mail bounces`
398
+ });
399
+ else {
400
+ needsCatchAll = true;
401
+ catchAllHandlers.push(handler);
402
+ }
403
+ }
404
+ const addresses = [...literals];
405
+ if (needsCatchAll) {
406
+ const catchAll = `*@${domain}`;
407
+ addresses.push(catchAll);
408
+ for (const handler of catchAllHandlers) routes.push({
409
+ address: catchAll,
410
+ handler
411
+ });
412
+ }
413
+ return {
414
+ addresses,
415
+ refused,
416
+ routes
417
+ };
418
+ }
419
+ function parseWhoamiJson(data) {
420
+ if (!isRecord(data) || typeof data.loggedIn !== "boolean") return null;
421
+ if (data.tokenPermissions !== void 0 && (!Array.isArray(data.tokenPermissions) || data.tokenPermissions.some((scope) => typeof scope !== "string"))) return null;
422
+ if (data.accounts !== void 0 && (!Array.isArray(data.accounts) || data.accounts.some((account) => !isRecord(account) || typeof account.id !== "string" || typeof account.name !== "string"))) return null;
423
+ return data;
424
+ }
425
+ function readRoutingSettings(data) {
426
+ return isRecord(data) && typeof data.name === "string" && data.name.length > 0 && typeof data.enabled === "boolean" && typeof data.status === "string" && data.status.length > 0 && typeof data.tag === "string" && /^[0-9a-f]{32}$/.test(data.tag) ? {
427
+ name: data.name,
428
+ enabled: data.enabled,
429
+ status: data.status,
430
+ tag: data.tag
431
+ } : null;
432
+ }
433
+ function readRoutingRules(data) {
434
+ if (!Array.isArray(data)) return null;
435
+ const rules = [];
436
+ let catchAll = null;
437
+ for (const entry of data) {
438
+ if (!isRecord(entry) || typeof entry.enabled !== "boolean" || !Array.isArray(entry.actions) || entry.actions.length === 0 || !Array.isArray(entry.matchers) || entry.matchers.length === 0) return null;
439
+ const actions = [];
440
+ for (const action of entry.actions) {
441
+ if (!isRecord(action) || typeof action.type !== "string") return null;
442
+ if (action.type === "drop") actions.push("drop");
443
+ else if (action.type === "worker" || action.type === "forward") {
444
+ const values = typeof action.value === "string" ? [action.value] : action.value;
445
+ if (!Array.isArray(values) || values.length === 0 || !values.every((value) => typeof value === "string" && value.length > 0)) return null;
446
+ if (action.type === "worker") actions.push("worker:" + values.join(","));
447
+ else actions.push("forward:" + values.join(","));
448
+ } else return null;
449
+ }
450
+ const to = [];
451
+ let all = false;
452
+ for (const matcher of entry.matchers) {
453
+ if (!isRecord(matcher)) return null;
454
+ if (matcher.type === "all") all = true;
455
+ else if (matcher.type === "literal" && matcher.field === "to" || matcher.type === "to") {
456
+ if (typeof matcher.value !== "string" || matcher.value.length === 0) return null;
457
+ to.push(matcher.value.toLowerCase());
458
+ } else return null;
459
+ }
460
+ if (all) {
461
+ if (catchAll || entry.matchers.length !== 1) return null;
462
+ catchAll = {
463
+ enabled: entry.enabled,
464
+ actions
465
+ };
466
+ } else {
467
+ if (typeof entry.id !== "string" || entry.id.length === 0 || entry.name !== void 0 && typeof entry.name !== "string") return null;
468
+ rules.push({
469
+ id: entry.id,
470
+ name: entry.name ?? "",
471
+ enabled: entry.enabled,
472
+ to,
473
+ actions
474
+ });
475
+ }
476
+ }
477
+ return {
478
+ rules,
479
+ catchAll
480
+ };
481
+ }
482
+ function readSendingSettings(data) {
483
+ if (!isRecord(data)) return null;
484
+ const tag = data.tag ?? data.id;
485
+ return typeof data.name === "string" && data.name.length > 0 && typeof data.enabled === "boolean" && typeof tag === "string" && /^[0-9a-f]{32}$/.test(tag) ? {
486
+ name: data.name,
487
+ enabled: data.enabled,
488
+ tag
489
+ } : null;
490
+ }
491
+ function providerErrorCode(result) {
492
+ return result.errorCode ?? result.errors?.find((error) => error.code !== void 0)?.code ?? null;
493
+ }
494
+ function isHardFailure(result) {
495
+ return Boolean(result.signal) || Boolean(result.error);
496
+ }
497
+ function mxState(exchanges) {
498
+ if (exchanges.length === 0) return "empty";
499
+ return exchanges.every((x) => /\.mx\.cloudflare\.net\.?$/i.test(x)) ? "cloudflare" : "foreign";
500
+ }
501
+ async function probeMx(dns, name) {
502
+ try {
503
+ const exchanges = (await dns.resolveMx(name)).slice().sort((a, b) => a.priority - b.priority).map((r) => r.exchange.toLowerCase());
504
+ return {
505
+ state: mxState(exchanges),
506
+ exchanges
507
+ };
508
+ } catch (err) {
509
+ if (isNoRecord(err)) return {
510
+ state: "empty",
511
+ exchanges: []
512
+ };
513
+ return {
514
+ state: "unknown",
515
+ exchanges: []
516
+ };
517
+ }
518
+ }
519
+ async function probeTxt(dns, name) {
520
+ try {
521
+ return (await dns.resolveTxt(name)).length > 0 ? "present" : "absent";
522
+ } catch (err) {
523
+ return isNoRecord(err) ? "absent" : "unknown";
524
+ }
525
+ }
526
+ function isNoRecord(err) {
527
+ const code = err?.code;
528
+ return code === "ENODATA" || code === "ENOTFOUND";
529
+ }
530
+ /** Candidate zone names for `mail.acme.com`: `mail.acme.com`, `acme.com`. */
531
+ function zoneCandidates(domain) {
532
+ const labels = domain.split(".");
533
+ const out = [];
534
+ for (let i = 0; i <= labels.length - 2; i += 1) out.push(labels.slice(i).join("."));
535
+ return out;
536
+ }
537
+ function shortId(id) {
538
+ return `${id.slice(0, 8)}…`;
539
+ }
540
+ /**
541
+ * Read-only preflight: session, zone, public DNS, the provider reads and, when the bearer
542
+ * can be borrowed, the two REST reads that have no command. Nothing here writes.
543
+ */
544
+ async function preflightEmail(inputs) {
545
+ const domain = emailFromDomain(inputs.from);
546
+ if (!domain) throw new Error(`email.from must be an address like you@mail.example.com (got ${JSON.stringify(inputs.from)})`);
547
+ const apiBaseUrl = inputs.apiBaseUrl ?? CLOUDFLARE_API_BASE_URL;
548
+ const run = async (operation) => inputs.runWrangler({
549
+ ...operation,
550
+ ...inputs.configPath ? { configPath: inputs.configPath } : {}
551
+ }, { capture: true });
552
+ const usageHandlers = [...inputs.handlers.addresses.map((d) => `email/${d.filePath}`), ...inputs.handlers.defaultHandler ? [`email/${inputs.handlers.defaultHandler}`] : []];
553
+ const pre = {
554
+ from: inputs.from,
555
+ domain,
556
+ apex: null,
557
+ mode: null,
558
+ workerName: inputs.workerName,
559
+ accountId: inputs.accountId,
560
+ usage: {
561
+ handlers: usageHandlers,
562
+ sendEmailFiles: inputs.sendEmailFiles
563
+ },
564
+ session: {
565
+ state: "unknown",
566
+ missingScopes: []
567
+ },
568
+ zone: { state: "unknown" },
569
+ apexMx: {
570
+ state: "unknown",
571
+ exchanges: []
572
+ },
573
+ domainMx: {
574
+ state: "unknown",
575
+ exchanges: []
576
+ },
577
+ routing: { state: "unknown" },
578
+ subaddressing: { state: "unknown" },
579
+ sending: {
580
+ state: "unknown",
581
+ dnsConflicts: []
582
+ },
583
+ rules: {
584
+ state: "unknown",
585
+ mine: [],
586
+ foreign: [],
587
+ absent: []
588
+ },
589
+ binding: { state: "absent" },
590
+ sender: { state: "absent" },
591
+ addressesOwnership: {
592
+ state: "absent",
593
+ foreign: []
594
+ },
595
+ addressesStale: false,
596
+ derived: {
597
+ addresses: [],
598
+ refused: [],
599
+ routes: []
600
+ },
601
+ addresses: [],
602
+ rulesPending: [],
603
+ blockers: [],
604
+ sendingRetryable: false,
605
+ ready: false
606
+ };
607
+ const block = (scope, message) => {
608
+ pre.blockers.push({
609
+ scope,
610
+ message
611
+ });
612
+ };
613
+ for (const conflict of inputs.configConflicts ?? []) block("all", conflict);
614
+ const whoami = await run({ type: "auth.whoami" });
615
+ const who = isHardFailure(whoami) || whoami.status !== 0 ? null : parseWhoamiJson(whoami.data);
616
+ if (!who) {
617
+ pre.session.state = "unknown";
618
+ block("all", "could not read the Cloudflare session — run `void cloudflare login`");
619
+ } else if (!who.loggedIn) {
620
+ pre.session.state = "not-logged-in";
621
+ block("all", "not logged in to Cloudflare — run `void cloudflare login`");
622
+ } else {
623
+ pre.session.authType = who.authType;
624
+ pre.session.email = who.email;
625
+ pre.session.accountName = who.accounts?.find((a) => a.id === inputs.accountId)?.name;
626
+ if (who.authType === "Global API Key") {
627
+ pre.session.state = "api-key";
628
+ block("all", "the Cloudflare session is a Global API Key (CLOUDFLARE_API_KEY), which has no bearer to borrow for the subaddressing calls — unset CLOUDFLARE_API_KEY, then run `void cloudflare login` or set CLOUDFLARE_API_TOKEN to a token with Email Routing Edit + Email Sending Edit");
629
+ } else if (who.authType === void 0 || who.authType === "OAuth Token") {
630
+ const scopes = new Set(who.tokenPermissions ?? []);
631
+ pre.session.missingScopes = REQUIRED_EMAIL_SCOPES.filter((s) => !scopes.has(s));
632
+ if (pre.session.missingScopes.length > 0) {
633
+ pre.session.state = "missing-scopes";
634
+ block("all", `the Cloudflare session lacks ${pre.session.missingScopes.join(" + ")} — run \`void cloudflare logout\`, then \`void cloudflare login\` (one browser Allow)`);
635
+ } else pre.session.state = "ok";
636
+ } else pre.session.state = "ok";
637
+ }
638
+ const tokenHint = pre.session.state === "ok" && pre.session.authType !== "OAuth Token" ? " (an API token needs Email Routing Edit + Email Sending Edit on the zone and account)" : "";
639
+ let settings = null;
640
+ let settingsMiss = false;
641
+ if (pre.session.state === "ok") for (const candidate of zoneCandidates(domain)) {
642
+ const result = await run({
643
+ type: "email.routing.settings",
644
+ domain: candidate
645
+ });
646
+ if (isHardFailure(result)) {
647
+ settingsMiss = true;
648
+ break;
649
+ }
650
+ if (result.status === 0 && result.data === null) continue;
651
+ if (result.status !== 0) {
652
+ settingsMiss = true;
653
+ break;
654
+ }
655
+ settings = readRoutingSettings(result.data);
656
+ if (!settings) settingsMiss = true;
657
+ break;
658
+ }
659
+ if (settings) {
660
+ pre.apex = settings.name.toLowerCase();
661
+ pre.mode = pre.apex === domain ? "apex" : "subdomain";
662
+ pre.zone = {
663
+ state: "found",
664
+ zoneId: settings.tag
665
+ };
666
+ } else if (settingsMiss) block("all", `could not read Email Routing settings — unknown — run \`void email setup --platform cloudflare\`${tokenHint}`);
667
+ else if (pre.session.state === "ok") pre.zone.state = "not-found";
668
+ pre.domainMx = await probeMx(inputs.dns, domain);
669
+ pre.apexMx = pre.apex && pre.apex !== domain ? await probeMx(inputs.dns, pre.apex) : pre.domainMx;
670
+ const dmarc = await probeTxt(inputs.dns, `_dmarc.${domain}`);
671
+ const bounce = await probeTxt(inputs.dns, `cf-bounce.${domain}`);
672
+ if (dmarc === "present") pre.sending.dnsConflicts.push(`_dmarc.${domain}`);
673
+ if (bounce === "present") pre.sending.dnsConflicts.push(`cf-bounce.${domain}`);
674
+ if (dmarc === "unknown" || bounce === "unknown") pre.sending.dnsConflicts.push(`(DNS lookup failed for _dmarc.${domain} / cf-bounce.${domain})`);
675
+ let bearer = null;
676
+ if (pre.session.state === "ok") bearer = await inputs.getSessionBearer().catch(() => null);
677
+ if (bearer) {
678
+ const candidates = pre.apex ? [pre.apex] : zoneCandidates(domain);
679
+ for (const candidate of candidates) {
680
+ const zones = await fetchWithSessionBearer(inputs.fetchImpl, bearer, "GET", `/zones?name=${encodeURIComponent(candidate)}`, void 0, apiBaseUrl);
681
+ if (!zones.ok) break;
682
+ const zone = (Array.isArray(zones.result) ? zones.result : [])[0];
683
+ if (!zone) continue;
684
+ pre.zone.type = zone.type;
685
+ if (zone.account?.id && zone.account.id !== inputs.accountId) pre.zone = {
686
+ state: "other-account",
687
+ zoneId: zone.id,
688
+ type: zone.type
689
+ };
690
+ else if (zone.type && zone.type !== "full") pre.zone = {
691
+ state: "partial",
692
+ zoneId: zone.id,
693
+ type: zone.type
694
+ };
695
+ else {
696
+ pre.zone = {
697
+ state: "found",
698
+ zoneId: zone.id,
699
+ type: zone.type
700
+ };
701
+ if (!pre.apex) {
702
+ pre.apex = zone.name.toLowerCase();
703
+ pre.mode = pre.apex === domain ? "apex" : "subdomain";
704
+ }
705
+ }
706
+ break;
707
+ }
708
+ if (pre.zone.state === "found" && pre.zone.zoneId) {
709
+ const routing = await fetchWithSessionBearer(inputs.fetchImpl, bearer, "GET", `/zones/${pre.zone.zoneId}/email/routing`, void 0, apiBaseUrl);
710
+ const flag = routing.result?.support_subaddress;
711
+ if (routing.ok && typeof flag === "boolean") pre.subaddressing.state = flag ? "on" : "off";
712
+ }
713
+ }
714
+ bearer = null;
715
+ switch (pre.zone.state) {
716
+ case "not-found":
717
+ block("all", `no zone for ${domain} in Cloudflare account ${inputs.accountId} — add the domain to this account first`);
718
+ break;
719
+ case "other-account":
720
+ block("all", `${pre.apex ?? domain} is a zone in a different Cloudflare account than ${inputs.accountId}`);
721
+ break;
722
+ case "partial": block("all", `${pre.apex ?? domain} is a ${pre.zone.type} zone; Email Routing needs a full zone on Cloudflare DNS`);
723
+ }
724
+ if (!pre.apex || !pre.mode) {
725
+ pre.ready = false;
726
+ return pre;
727
+ }
728
+ const apex = pre.apex;
729
+ const mode = pre.mode;
730
+ if (settings) {
731
+ if (mode === "apex") {
732
+ pre.routing = {
733
+ state: settings.enabled && settings.status === "ready" ? "ready" : "pending",
734
+ status: settings.status,
735
+ enabled: settings.enabled
736
+ };
737
+ if (/^(unlocked|misconfigured)/.test(settings.status)) block("all", `Email Routing on ${apex} is ${settings.status} — fix it in the Cloudflare dashboard; Void never re-locks records you unlocked`);
738
+ } else {
739
+ pre.routing = {
740
+ state: pre.domainMx.state === "cloudflare" ? "ready" : pre.domainMx.state === "unknown" ? "unknown" : "pending",
741
+ status: settings.status,
742
+ enabled: settings.enabled
743
+ };
744
+ if (settings.status === "unlocked") block("all", `Email Routing on ${apex} is ${settings.status} — fix it in the Cloudflare dashboard; Void never re-locks records you unlocked`);
745
+ }
746
+ }
747
+ if (pre.routing.state !== "ready") {
748
+ if (pre.domainMx.state === "foreign") block("all", `${domain} already receives mail (${pre.domainMx.exchanges.join(", ")}). Void never enables routing over live mail.${mode === "apex" ? ` Use something@mail.${apex}.` : ""}`);
749
+ else if (pre.domainMx.state === "unknown") block("all", `could not read the MX records of ${domain} — unknown — run \`void email setup --platform cloudflare\``);
750
+ }
751
+ pre.derived = deriveAddresses(inputs.handlers, domain, mode);
752
+ const rulesResult = await run({
753
+ type: "email.routing.rules.list",
754
+ domain: apex
755
+ });
756
+ const rules = isHardFailure(rulesResult) || rulesResult.status !== 0 ? null : readRoutingRules(rulesResult.data);
757
+ if (!rules) {
758
+ pre.rules.state = "unknown";
759
+ block("all", `could not read Email Routing rules — unknown — run \`void email setup --platform cloudflare\`${tokenHint}`);
760
+ } else {
761
+ const workerToken = `worker:${inputs.workerName}`;
762
+ for (const address of pre.derived.addresses) {
763
+ if (address.startsWith("*@")) {
764
+ if (!rules.catchAll || !rules.catchAll.enabled || rules.catchAll.actions.includes("drop")) pre.rules.absent.push(address);
765
+ else if (rules.catchAll.actions.includes(workerToken)) pre.rules.mine.push(address);
766
+ else pre.rules.foreign.push({
767
+ address,
768
+ owner: rules.catchAll.actions.join(", ")
769
+ });
770
+ continue;
771
+ }
772
+ const rule = rules.rules.find((r) => r.enabled && r.to.includes(address)) ?? rules.rules.find((r) => r.to.includes(address));
773
+ if (!rule) pre.rules.absent.push(address);
774
+ else if (rule.actions.includes(workerToken)) {
775
+ if (rule.enabled) pre.rules.mine.push(address);
776
+ else pre.rules.absent.push(address);
777
+ } else pre.rules.foreign.push({
778
+ address,
779
+ owner: rule.actions.join(", ")
780
+ });
781
+ }
782
+ pre.rules.state = pre.rules.absent.length === 0 ? "ready" : "pending";
783
+ }
784
+ const foreign = new Set(pre.rules.foreign.map((f) => f.address));
785
+ pre.addresses = pre.derived.addresses.filter((a) => !foreign.has(a));
786
+ const sendingResult = await run({
787
+ type: "email.sending.settings",
788
+ domain
789
+ });
790
+ if (isHardFailure(sendingResult)) pre.sending.state = "unknown";
791
+ else if (sendingResult.status === 0 && sendingResult.data === null) pre.sending.state = "pending";
792
+ else if (sendingResult.status === 0) {
793
+ const parsed = readSendingSettings(sendingResult.data);
794
+ pre.sending.state = parsed ? parsed.enabled ? "ready" : "pending" : "unknown";
795
+ } else pre.sending.state = "unknown";
796
+ if (pre.sending.state === "unknown") block("sending", `could not read Email Sending settings — unknown — run \`void email setup --platform cloudflare\`${tokenHint}`);
797
+ else if (pre.sending.state === "pending" && pre.sending.dnsConflicts.length > 0) block("sending", `${pre.sending.dnsConflicts.join(", ")} already exists; onboarding writes its own _dmarc (p=reject) and DKIM records and would conflict — remove them or keep sending off`);
798
+ const names = (inputs.existingSendEmail ?? []).map((e) => typeof e?.name === "string" ? e.name : "");
799
+ if (names.includes("SEND_EMAIL")) pre.binding = { state: "present" };
800
+ else if (names.length > 0) {
801
+ pre.binding = {
802
+ state: "foreign",
803
+ foreignName: names[0]
804
+ };
805
+ block("all", `Cloudflare config already has a send_email binding named ${JSON.stringify(names[0])}; rename it to ${SEND_EMAIL_BINDING} in void.config.ts (a second binding would be silent)`);
806
+ }
807
+ if (inputs.existingAddresses !== void 0 && inputs.existingAddresses !== null) {
808
+ const existing = Array.isArray(inputs.existingAddresses) ? inputs.existingAddresses.map((a) => typeof a === "string" ? a.toLowerCase() : String(a)) : ["(not an array)"];
809
+ const candidates = new Set(pre.derived.addresses);
810
+ const unknown = existing.filter((a) => !candidates.has(a));
811
+ if (unknown.length > 0) {
812
+ pre.addressesOwnership = {
813
+ state: "foreign",
814
+ foreign: unknown
815
+ };
816
+ block("all", `Cloudflare addresses holds entries Void did not derive (${unknown.join(", ")}); remove ${unknown.map((a) => JSON.stringify(a)).join(", ")} from cloudflare.addresses in void.config.ts, or manage addresses by hand and unset email.from — skipping email setup this deploy`);
817
+ } else {
818
+ pre.addressesOwnership = {
819
+ state: "mine",
820
+ foreign: []
821
+ };
822
+ const pruned = new Set(pre.addresses);
823
+ pre.addressesStale = existing.length !== pre.addresses.length || existing.some((a) => !pruned.has(a));
824
+ }
825
+ } else pre.addressesStale = pre.addresses.length > 0;
826
+ const senderVar = inputs.existingVars?.[EMAIL_FROM_VAR];
827
+ pre.sender.state = senderVar === inputs.from ? "ready" : senderVar === void 0 ? "absent" : "stale";
828
+ pre.rulesPending = pre.rules.absent.filter((a) => pre.addresses.includes(a));
829
+ const sendingBlocked = pre.blockers.some((b) => b.scope === "sending");
830
+ pre.sendingRetryable = pre.sending.state === "pending" && !sendingBlocked;
831
+ const sendingSettled = pre.sending.state === "ready" || sendingBlocked || pre.sending.state === "pending" && pre.binding.state === "present";
832
+ pre.ready = !pre.blockers.some((b) => b.scope === "all") && pre.routing.state === "ready" && pre.subaddressing.state === "on" && sendingSettled && pre.binding.state === "present";
833
+ return pre;
834
+ }
835
+ /** Column padding for the checklist; an overflowing cell gets three spaces. */
836
+ function pad(text, width) {
837
+ return text.length >= width ? `${text} ` : text.padEnd(width);
838
+ }
839
+ /**
840
+ * The block shown before the confirmation: status rows, then the `+`/`!` change list.
841
+ * Blockers render as `✘` rows at the end so the developer sees why nothing will be asked.
842
+ */
843
+ function renderEmailChecklist(pre) {
844
+ const apex = pre.apex ?? "?";
845
+ const lines = [];
846
+ const inUse = [...pre.usage.handlers, ...pre.usage.sendEmailFiles ? [`sendEmail() in ${pre.usage.sendEmailFiles} file${pre.usage.sendEmailFiles === 1 ? "" : "s"}`] : []];
847
+ lines.push(` Email in use ${inUse.join(" · ")}`);
848
+ const row = (label, value, note = "") => {
849
+ lines.push(` ${pad(label, 10)}${note ? `${pad(value, 23)}${note}` : value}`);
850
+ };
851
+ row("domain", pre.domain, "(void.config.ts email.from)");
852
+ const session = [pre.session.accountName ? `account ${pre.session.accountName} (${shortId(pre.accountId)})` : `account ${shortId(pre.accountId)}`, pre.session.authType && pre.session.authType !== "OAuth Token" ? `session ${pre.session.email ? `${pre.session.email} ` : ""}(${pre.session.authType})` : pre.session.email ? `session ${pre.session.email}` : null].filter(Boolean).join(" · ");
853
+ row("zone", pre.zone.state === "found" ? apex : `${apex} ${pre.zone.state}`, session);
854
+ if (pre.mode === "subdomain") row("apex MX", pre.apexMx.exchanges[0] ?? (pre.apexMx.state === "unknown" ? "unknown" : "none"), "left alone — mail lives on the subdomain");
855
+ else row("apex MX", pre.apexMx.exchanges[0] ?? (pre.apexMx.state === "unknown" ? "unknown" : "none"), pre.apexMx.state === "cloudflare" ? "Cloudflare Email Routing" : pre.apexMx.state === "empty" ? "empty — the apex can take routing" : "");
856
+ const routingWord = pre.routing.state === "ready" ? "enabled" : pre.routing.state === "unknown" ? "unknown — run void email setup --platform cloudflare" : `not enabled${pre.mode === "apex" && pre.routing.status && pre.routing.status !== "unconfigured" ? ` (${pre.routing.status})` : ""}`;
857
+ const subWord = pre.subaddressing.state === "on" ? "subaddressing on" : pre.subaddressing.state === "off" ? "subaddressing off" : "subaddressing unknown";
858
+ row("routing", `${pre.domain} ${routingWord} · ${subWord}`);
859
+ const sendingWord = pre.sending.state === "ready" ? "onboarded" : pre.sending.state === "unknown" ? "unknown — run void email setup --platform cloudflare" : pre.sendingRetryable && pre.binding.state === "present" ? "not onboarded — verified destinations only; after upgrading to Workers Paid run void email setup --platform cloudflare" : "not onboarded";
860
+ row("sending", `${pre.domain} ${sendingWord}`);
861
+ if (pre.derived.addresses.length === 0) row("rules", "(none — no email/ handlers)");
862
+ for (const address of pre.derived.addresses) {
863
+ const foreignRule = pre.rules.foreign.find((f) => f.address === address);
864
+ const status = pre.rules.mine.includes(address) ? "ready" : foreignRule ? `owned elsewhere: ${foreignRule.owner} — pruned` : pre.rules.state === "unknown" ? "unknown" : "absent";
865
+ row("rules", `${address} → ${pre.workerName}`, `(${status})`);
866
+ }
867
+ row("binding", SEND_EMAIL_BINDING, `(${pre.binding.state === "present" ? "present" : pre.binding.state === "foreign" ? `found ${pre.binding.foreignName} instead` : "absent"})`);
868
+ row("sender", pre.from, `(vars.${EMAIL_FROM_VAR} ${pre.sender.state})`);
869
+ lines.push("");
870
+ const changes = [];
871
+ const change = (what, why) => {
872
+ changes.push(` + ${pad(what, 44)}${why}`);
873
+ };
874
+ const warn = (what, why) => {
875
+ changes.push(` ! ${pad(what, 44)}${why}`);
876
+ };
877
+ const subdomainNeedsApex = pre.mode === "subdomain" && pre.routing.enabled !== true;
878
+ if (pre.routing.state !== "ready" && subdomainNeedsApex) warn(`enable Email Routing on ${pre.domain}`, `Email Routing is not enabled on ${apex}; Void never enables it on the apex from the subdomain path — ${subdomainDashboardFallback(apex, pre.domain)}`);
879
+ else if (pre.routing.state !== "ready") change(`enable Email Routing on ${pre.domain}`, `Cloudflare writes and locks 3 MX + 1 SPF record ${pre.mode === "subdomain" ? "there" : "on the apex"}`);
880
+ if (pre.subaddressing.state !== "on") change(`turn on subaddressing for ${apex}`, "support+anything@ reaches support@");
881
+ if (pre.sending.state !== "ready" && !pre.blockers.some((b) => b.scope === "sending")) change(`onboard ${pre.domain} for Email Sending`, `MX/SPF/DKIM on cf-bounce.${pre.domain}, _dmarc.${pre.domain} (p=reject)`);
882
+ const parts = [];
883
+ if (pre.binding.state !== "present") parts.push(`send_email: [{ name: "${SEND_EMAIL_BINDING}" }]`);
884
+ if (pre.addressesStale) parts.push(pre.addresses.length > 0 ? `addresses: ${JSON.stringify(pre.addresses)}` : "addresses: (removed)");
885
+ if (pre.sender.state !== "ready") parts.push(`vars.${EMAIL_FROM_VAR}: ${JSON.stringify(pre.from)}`);
886
+ if (parts.length > 0) change("Cloudflare config", parts.join(", "));
887
+ for (const address of pre.rules.absent) change("routing rule (created on deploy)", `${address} → ${pre.workerName}`);
888
+ for (const foreign of pre.rules.foreign) warn(foreign.address, `already routed to ${foreign.owner}; left alone, not in addresses`);
889
+ for (const refused of pre.derived.refused) warn(refused.handler, refused.reason);
890
+ for (const blocker of pre.blockers) changes.push(` ✘ ${blocker.message}`);
891
+ if (changes.length === 0) lines.push(" Nothing to change — email is set up.");
892
+ else lines.push(` This changes YOUR Cloudflare account.${pre.mode === "subdomain" && !subdomainNeedsApex ? ` ${apex} itself is not touched.` : ""}`, ...changes);
893
+ return lines.join("\n");
894
+ }
895
+ /**
896
+ * The ONLY function that receives the session bearer. It puts the token in the
897
+ * `Authorization` header and nowhere else: not in the URL, not in a log line, not in
898
+ * the returned object, not in an error. A transport failure is returned as
899
+ * `{ ok: false, status: 0 }` with the underlying message (which names the URL at
900
+ * most) — never thrown, so a caller can never leak the token through a stack trace.
901
+ */
902
+ async function fetchWithSessionBearer(fetchImpl, bearer, method, path, body, apiBaseUrl = CLOUDFLARE_API_BASE_URL) {
903
+ const headers = { Authorization: `Bearer ${bearer}` };
904
+ if (body !== void 0) headers["Content-Type"] = "application/json";
905
+ const scrub = (text) => text.split(bearer).join("[redacted]");
906
+ let response;
907
+ try {
908
+ response = await fetchImpl(`${apiBaseUrl}${path}`, {
909
+ method,
910
+ headers,
911
+ body: body === void 0 ? void 0 : JSON.stringify(body)
912
+ });
913
+ } catch (err) {
914
+ return {
915
+ ok: false,
916
+ status: 0,
917
+ result: null,
918
+ errors: [{
919
+ code: 0,
920
+ message: scrub(err instanceof Error ? err.message : String(err))
921
+ }]
922
+ };
923
+ }
924
+ let payload = {};
925
+ try {
926
+ payload = await response.json();
927
+ } catch {
928
+ payload = {};
929
+ }
930
+ const errors = (payload.errors ?? []).map((e) => ({
931
+ code: typeof e?.code === "number" ? e.code : 0,
932
+ message: typeof e?.message === "string" ? scrub(e.message) : ""
933
+ }));
934
+ return {
935
+ ok: response.ok && payload.success !== false,
936
+ status: response.status,
937
+ result: payload.result ?? null,
938
+ errors
939
+ };
940
+ }
941
+ /**
942
+ * Run only the rows preflight found not ready:
943
+ *
944
+ * routing apex → `email routing enable <apex>`
945
+ * subdomain → bearer POST /zones/{z}/email/routing/dns { name }
946
+ * subaddr bearer PATCH /zones/{z}/email/routing { support_subaddress: true }
947
+ * sending `email sending enable <domain>`; code 2040 (already onboarded) is ok;
948
+ * any other failure prints a hint and continues without sending
949
+ *
950
+ * A failed bearer call prints the dashboard fallback and withholds `addresses` this run;
951
+ * it never fails the deploy. An `all` blocker applies nothing and returns an empty patch.
952
+ */
953
+ async function applyEmailSetup(pre, inputs) {
954
+ const lines = [];
955
+ const result = {
956
+ patch: {},
957
+ lines,
958
+ routingReady: pre.routing.state === "ready",
959
+ subaddressingReady: pre.subaddressing.state === "on",
960
+ sendingReady: pre.sending.state === "ready",
961
+ inboundReady: false
962
+ };
963
+ const blocked = pre.blockers.filter((b) => b.scope === "all");
964
+ if (blocked.length > 0 || !pre.apex || !pre.mode) {
965
+ for (const b of blocked) lines.push(` ✘ ${b.message}`);
966
+ if (blocked.length === 0) lines.push(" ✘ email: the zone could not be resolved — unknown — run `void email setup --platform cloudflare`");
967
+ return result;
968
+ }
969
+ const apex = pre.apex;
970
+ const apiBaseUrl = inputs.apiBaseUrl ?? CLOUDFLARE_API_BASE_URL;
971
+ const run = async (operation) => inputs.runWrangler({
972
+ ...operation,
973
+ ...inputs.configPath ? { configPath: inputs.configPath } : {}
974
+ }, { capture: true });
975
+ let bearer;
976
+ let bearerFailed = false;
977
+ let bearerObtained = false;
978
+ const borrow = async () => {
979
+ if (bearer === void 0) {
980
+ bearer = await inputs.getSessionBearer().catch(() => null);
981
+ if (bearer) bearerObtained = true;
982
+ else bearerFailed = true;
983
+ }
984
+ return bearer;
985
+ };
986
+ const withBearer = async (method, path, body) => {
987
+ const token = await borrow();
988
+ if (!token) return null;
989
+ const response = await fetchWithSessionBearer(inputs.fetchImpl, token, method, path, body, apiBaseUrl);
990
+ if (!response.ok) bearerFailed = true;
991
+ return response;
992
+ };
993
+ const describeFailure = (response) => {
994
+ if (!response) return "could not borrow the Cloudflare session (session-bearer step failed)";
995
+ const first = response.errors[0];
996
+ const code = first?.code ? ` [code: ${first.code}]` : "";
997
+ const known = first?.code ? CF_EMAIL_ERROR_CODES[first.code] : void 0;
998
+ return `Cloudflare answered ${response.status}${code}${known ? ` — ${known}` : first?.message ? ` — ${first.message}` : ""}`;
999
+ };
1000
+ const apexRoutingOff = pre.mode === "subdomain" && pre.routing.enabled !== true;
1001
+ if (!result.routingReady) {
1002
+ if (pre.mode === "apex") {
1003
+ const enable = await run({
1004
+ type: "email.routing.enable",
1005
+ domain: apex
1006
+ });
1007
+ if (!isHardFailure(enable) && enable.status === 0) {
1008
+ result.routingReady = true;
1009
+ lines.push(` ✔ routing ${apex} enabled`);
1010
+ } else {
1011
+ const code = providerErrorCode(enable);
1012
+ lines.push(` ✘ routing could not enable Email Routing on ${apex}${code ? ` [code: ${code}]${CF_EMAIL_ERROR_CODES[code] ? ` — ${CF_EMAIL_ERROR_CODES[code]}` : ""}` : ""}`);
1013
+ }
1014
+ } else if (apexRoutingOff) {
1015
+ lines.push(` ✘ routing ${pre.domain}: Email Routing is not enabled on ${apex} — a subdomain can only be added to a zone that already routes; Void never enables routing on the apex from the subdomain path`);
1016
+ lines.push(` ${subdomainDashboardFallback(apex, pre.domain)}`);
1017
+ } else {
1018
+ const response = await withBearer("POST", `/zones/${pre.zone.zoneId}/email/routing/dns`, { name: pre.domain });
1019
+ if (response?.ok) {
1020
+ result.routingReady = true;
1021
+ lines.push(` ✔ routing ${pre.domain} enabled`);
1022
+ } else {
1023
+ lines.push(` ✘ routing ${pre.domain}: ${describeFailure(response)}`);
1024
+ lines.push(` ${subdomainDashboardFallback(apex, pre.domain)}`);
1025
+ }
1026
+ }
1027
+ }
1028
+ if (!result.subaddressingReady && !bearerFailed && !apexRoutingOff) {
1029
+ const response = await withBearer("PATCH", `/zones/${pre.zone.zoneId}/email/routing`, { support_subaddress: true });
1030
+ if (response?.ok) {
1031
+ result.subaddressingReady = true;
1032
+ lines.push(` ✔ routing subaddressing on for ${apex}`);
1033
+ } else {
1034
+ lines.push(` ✘ routing subaddressing on ${apex}: ${describeFailure(response)}`);
1035
+ lines.push(` turn it on in the Cloudflare dashboard → ${apex} → Email → Settings → Subaddressing`);
1036
+ }
1037
+ } else if (!result.subaddressingReady) lines.push(` ✘ routing subaddressing on ${apex}: skipped (${apexRoutingOff ? `Email Routing is not enabled on ${apex}` : bearerObtained ? "the subdomain routing call failed" : "session bearer unavailable"})`);
1038
+ bearer = null;
1039
+ const sendingBlocked = pre.blockers.filter((b) => b.scope === "sending");
1040
+ if (!result.sendingReady && sendingBlocked.length > 0) for (const b of sendingBlocked) lines.push(` ! sending ${b.message}`);
1041
+ else if (!result.sendingReady) {
1042
+ const enable = await run({
1043
+ type: "email.sending.enable",
1044
+ domain: pre.domain
1045
+ });
1046
+ const code = isHardFailure(enable) ? null : providerErrorCode(enable);
1047
+ if (!isHardFailure(enable) && (enable.status === 0 || code === 2040)) {
1048
+ result.sendingReady = true;
1049
+ lines.push(` ✔ sending ${pre.domain} onboarded`);
1050
+ } else {
1051
+ const known = code ? CF_EMAIL_ERROR_CODES[code] : void 0;
1052
+ lines.push(` ! sending ${pre.domain} not onboarded${code ? ` [code: ${code}]` : ""}${known ? ` — ${known}` : ""}`);
1053
+ lines.push(` ${SENDING_FREE_PLAN_HINT}`);
1054
+ }
1055
+ }
1056
+ if (result.routingReady || result.sendingReady) {
1057
+ result.patch.sendEmail = { name: SEND_EMAIL_BINDING };
1058
+ result.patch.vars = { [EMAIL_FROM_VAR]: pre.from };
1059
+ }
1060
+ const addressesWithheld = bearerFailed || !result.routingReady;
1061
+ if (!addressesWithheld && pre.addresses.length > 0) result.patch.addresses = pre.addresses;
1062
+ else {
1063
+ result.patch.addresses = null;
1064
+ if (addressesWithheld && pre.addresses.length > 0) lines.push(` ! config addresses withheld this run (${bearerFailed ? "session-bearer step failed" : "routing not ready"}); no routing rule is created for ${pre.addresses.join(", ")}`);
1065
+ }
1066
+ lines.push(" ✔ config void.lock.json updated");
1067
+ result.inboundReady = result.routingReady && result.subaddressingReady && !addressesWithheld;
1068
+ return result;
1069
+ }
1070
+ /**
1071
+ * The post-deploy block:
1072
+ *
1073
+ * inbound support@mail.acme.com → email/support+[ticket].ts
1074
+ * outbound sendEmail() from support@mail.acme.com
1075
+ */
1076
+ function renderAddressMap(map) {
1077
+ const lines = [];
1078
+ const width = Math.max(0, ...map.routes.map((r) => r.address.length));
1079
+ const pending = map.pendingLabel ?? "(no rule this deploy)";
1080
+ for (const route of map.routes) {
1081
+ const marker = !map.addresses.includes(route.address) ? ` ${pending}` : map.creating?.includes(route.address) ? " (rule created by this deploy)" : "";
1082
+ lines.push(` inbound ${pad(route.address, width + 2)}→ ${route.handler}${marker}`);
1083
+ }
1084
+ lines.push(` outbound sendEmail() from ${map.from}${map.sendingReady ? "" : " (verified destinations only)"}`);
1085
+ return lines.join("\n");
1086
+ }
1087
+ /** The production `EmailDns`: `node:dns/promises` against the system resolver. */
1088
+ async function createNodeEmailDns() {
1089
+ const dns = await import("node:dns/promises");
1090
+ return {
1091
+ resolveMx: (name) => dns.resolveMx(name),
1092
+ resolveTxt: (name) => dns.resolveTxt(name)
1093
+ };
1094
+ }
1095
+ //#endregion
1096
+ export { emailConfigInputs as a, renderAddressMap as c, collectPrerenderPathsNode as d, prerenderPages as f, createNodeEmailDns as i, renderEmailChecklist as l, SEND_EMAIL_BINDING as n, preflightEmail as o, prerenderPagesNode as p, applyEmailSetup as r, probeMx as s, EMAIL_FROM_VAR as t, collectPrerenderPaths as u };