void 0.22.0 → 0.23.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 (276) hide show
  1. package/README.md +5 -1
  2. package/dist/{account-cmd-CjyxcGkM.mjs → account-cmd-C84Ee8cO.mjs} +4 -4
  3. package/dist/{scan-ClYmX3sa.mjs → application-analysis-BVsVqc11.mjs} +135 -18
  4. package/dist/application-routing-B7QEjqSR.mjs +27 -0
  5. package/dist/{auth-CZuiVsFh.mjs → auth-CCjt0hcq.mjs} +1 -1
  6. package/dist/{auth-DLNN0D3Z.mjs → auth-CSkdO2Bb.mjs} +4 -47
  7. package/dist/{auth-link-BLO4ptzo.mjs → auth-link-CioEg6uY.mjs} +4 -4
  8. package/dist/{auth-router-ZUg9MF_U.mjs → auth-router-BsR981d4.mjs} +4 -4
  9. package/dist/{better-auth-shared-rsBGBvWJ.mjs → better-auth-shared-hy6RPh9W.mjs} +13 -2
  10. package/dist/{build-cmd-BxR5FROK.mjs → build-cmd-LzvNJORH.mjs} +17 -5
  11. package/dist/{cache-D0sWhgKI.mjs → cache-C4MvnrMH.mjs} +2 -2
  12. package/dist/{cancel-deploy-D4NUqFbi.mjs → cancel-deploy-abUxpP2n.mjs} +2 -2
  13. package/dist/{cf-build-output-BJ6yGEIS.mjs → cf-build-output-BPqOT964.mjs} +2 -1
  14. package/dist/cf-build-output-_0HNWysu.mjs +2 -0
  15. package/dist/cli/cli.mjs +80 -455
  16. package/dist/cli/{cf-compat.mjs → cloudflare-operation-process.mjs} +442 -421
  17. package/dist/cli/env-schema-probe.d.mts +2 -1
  18. package/dist/cli/env-schema-probe.mjs +3 -3
  19. package/dist/client-Cu7jWiF1.mjs +2 -0
  20. package/dist/{client-BTZ3XkrB.mjs → client-RV8NVeB8.mjs} +140 -21
  21. package/dist/{cloudflare-auth-DQkqoMYa.mjs → cloudflare-auth-Qdc7tw8F.mjs} +41 -38
  22. package/dist/{cloudflare-cmd-BKlfGHAy.mjs → cloudflare-cmd-BcrVyTmJ.mjs} +5 -5
  23. package/dist/cloudflare-config-Bktvwtpf.mjs +182 -0
  24. package/dist/{cloudflare-connect-Ctmrw579.mjs → cloudflare-connect-B8uPZ4nx.mjs} +3 -3
  25. package/dist/{cloudflare-operations-BT6OWFBk.mjs → cloudflare-operations-AiWashgg.mjs} +1 -1
  26. package/dist/{cloudflare-operations-B9tzgjmf.mjs → cloudflare-operations-fxHb-byx.mjs} +116 -172
  27. package/dist/{preset-UHj9ARyP.mjs → cloudflare-process-B-wekeR6.mjs} +93 -6
  28. package/dist/{config-VavjpDnp.d.mts → config-BMHb8RCj.d.mts} +1 -0
  29. package/dist/config-C_XRIPx2.mjs +89 -0
  30. package/dist/config-entry.d.mts +1 -1
  31. package/dist/{connect-Dg-WkW-C.mjs → connect-Bd4kJd9U.mjs} +5 -5
  32. package/dist/{create-project-DdoqwFLF.mjs → create-project-Boczwj5r.mjs} +1 -1
  33. package/dist/{create-project-l9J7pbtm.mjs → create-project-bMf6ffLZ.mjs} +2 -2
  34. package/dist/{db-gp2sCXyN.mjs → db-8uG64XLl.mjs} +21 -21
  35. package/dist/{delete-TTedbH6B.mjs → delete-NvbiyeJf.mjs} +2 -2
  36. package/dist/{deploy-CH-o2CaZ.mjs → deploy-DzAIwqNU.mjs} +908 -582
  37. package/dist/{deploy-B19L2Bra.mjs → deploy-bjXdFCtn.mjs} +1 -1
  38. package/dist/{domain-JRF59P_r.mjs → domain-y5Tvydvo.mjs} +3 -3
  39. package/dist/{email-Bo6G9LOZ.mjs → email-B3umsW75.mjs} +13 -28
  40. package/dist/{env-BYOrWQnv.mjs → env-BS6qYHDb.mjs} +4 -4
  41. package/dist/{env-public-BxU_0yTL.d.mts → env-public-BX_r8HR6.d.mts} +1 -1
  42. package/dist/{env-validation-BB4GkLxn.mjs → env-validation-BPn7vk-V.mjs} +18 -71
  43. package/dist/{env-validation-BXge7uyK.mjs → env-validation-DbTg7-ar.mjs} +1 -1
  44. package/dist/{fetch-CXDChK7B.mjs → fetch-BIZJh7vR.mjs} +2 -2
  45. package/dist/{fetch-stream-AOByI7Ki.mjs → fetch-stream-IvCYKyQL.mjs} +24 -4
  46. package/dist/{gen-sCtlCOdA.mjs → gen-B580--vC.mjs} +2 -2
  47. package/dist/gen-BVaUUumi.mjs +2 -0
  48. package/dist/{github-cmd-0-7PexDq.mjs → github-cmd-v45BdfKX.mjs} +2 -2
  49. package/dist/{handler-HEcZsaij.d.mts → handler-CZ4nAylQ.d.mts} +1 -1
  50. package/dist/help-DofyZuY7.mjs +2 -0
  51. package/dist/{help-GKtwl07I.mjs → help-daGKjXGk.mjs} +229 -747
  52. package/dist/index.d.mts +1 -1
  53. package/dist/index.mjs +288 -1502
  54. package/dist/info-B97bTX9N.mjs +113 -0
  55. package/dist/{init-FZx3Elvz.mjs → init-Bvy7zrBo.mjs} +46 -15
  56. package/dist/limits-Bq5LG8Id.d.mts +27 -0
  57. package/dist/limits-Cjuk2VPm.mjs +68 -0
  58. package/dist/{link-BrQfb_CU.mjs → link-CDqqCjFl.mjs} +3 -3
  59. package/dist/{list-D44jmIAM.mjs → list-CZj0dzKY.mjs} +3 -3
  60. package/dist/{live-CKJlvlNp.d.mts → live-Chw1eIMv.d.mts} +1 -1
  61. package/dist/{local-d1-Bg9OzEEO.mjs → local-d1-2CMnpuW_.mjs} +2 -2
  62. package/dist/login-DYt_An22.mjs +2 -0
  63. package/dist/{login-CAsAsQ-_.mjs → login-UZKFM_u7.mjs} +3 -3
  64. package/dist/{logs-BjKvFnVM.mjs → logs-CUZ6t9t3.mjs} +3 -3
  65. package/dist/migrate-8_2u55MD.mjs +2 -0
  66. package/dist/{migrate-BPITvDJN.mjs → migrate-DHul7PRV.mjs} +2 -1
  67. package/dist/{node-Dt7z256D.mjs → node-BkyRWRx8.mjs} +1 -1
  68. package/dist/operator-args-CLgKGlwU.mjs +690 -0
  69. package/dist/{operator-cmd-BK5CCiT9.mjs → operator-cmd-DBA6dl0m.mjs} +68 -22
  70. package/dist/pages/client.d.mts +34 -2
  71. package/dist/pages/client.mjs +59 -3
  72. package/dist/pages/index.d.mts +1 -1
  73. package/dist/pages/index.mjs +1 -1
  74. package/dist/pages/islands-plugin.mjs +1 -1
  75. package/dist/pages/protocol.d.mts +2 -2
  76. package/dist/pages/protocol.mjs +2 -308
  77. package/dist/{parse-filename-DioPHiR9.mjs → parse-filename-CUbj-1MP.mjs} +35 -1
  78. package/dist/plan-D1Q5rf-r.mjs +2 -0
  79. package/dist/plan-NPpwZ_kc.mjs +58 -0
  80. package/dist/platform-args-BJdRtlLq.mjs +506 -0
  81. package/dist/platform-args-D3RXyR6h.mjs +2 -0
  82. package/dist/{platform-auth-config-BlN8xTdD.mjs → platform-auth-config-B56E9YP1.mjs} +3 -3
  83. package/dist/{platform-auth-protection-7d5aV2Jg.mjs → platform-auth-protection-BDWO_aER.mjs} +2 -2
  84. package/dist/{platform-auth-recovery-Dxij8ZbR.mjs → platform-auth-recovery-1gg4CSRe.mjs} +3 -3
  85. package/dist/{platform-cmd-E0FuL212.mjs → platform-cmd-Bt-1w6pY.mjs} +1 -1
  86. package/dist/{platform-cmd-B3hwKrFK.mjs → platform-cmd-DMcQStSc.mjs} +24 -3
  87. package/dist/{platform-domain-D6Xcy9ZX.mjs → platform-domain-BNkcz0OB.mjs} +2 -2
  88. package/dist/{platform-lifecycle-k0E0xoxx.mjs → platform-lifecycle-9OyBALhH.mjs} +327 -215
  89. package/dist/{platform-lifecycle-DMh_qrry.mjs → platform-lifecycle-BE6C_jxh.mjs} +1 -1
  90. package/dist/{platform-management-Brz_BDiT.mjs → platform-management-CjwLVQwN.mjs} +6 -3
  91. package/dist/{platform-management-DOtss0BN.mjs → platform-management-CnyTdcWX.mjs} +1 -1
  92. package/dist/platform-plans-config-BNGKGr4P.mjs +359 -0
  93. package/dist/{platform-recovery-BvtcXON5.mjs → platform-recovery-D6KSpuFm.mjs} +2 -2
  94. package/dist/{plugin-inference-BMfKRSqE.mjs → plugin-inference-CXWnn79A.mjs} +174 -64
  95. package/dist/{prepare-C-6YZyyg.mjs → prepare-B5Mkic5u.mjs} +1 -1
  96. package/dist/{prepare-CRhJbVrG.mjs → prepare-DOsL0CC9.mjs} +4 -20
  97. package/dist/prepare-cgvDMtSb.mjs +2 -0
  98. package/dist/{project-cmd-BZLCqOqw.mjs → project-cmd-CNzrqvBv.mjs} +16 -16
  99. package/dist/{project-team-BvgM0WoX.mjs → project-team-DQOWPfQT.mjs} +2 -2
  100. package/dist/{project-token-l5DqK6Az.mjs → project-token-CTDYU0Cc.mjs} +2 -2
  101. package/dist/{project-zero-trust-v6Mvpp8y.mjs → project-zero-trust-BNAkW-_0.mjs} +2 -2
  102. package/dist/{protocol-BBa6cstI.d.mts → protocol-CjF_iI9X.d.mts} +2 -2
  103. package/dist/protocol-U7bfjHmA.mjs +331 -0
  104. package/dist/{provision-Fr9pRqce.mjs → provision-C4ORE7G7.mjs} +1 -1
  105. package/dist/{provision-C4IGqkBf.mjs → provision-CJFgTZY8.mjs} +89 -198
  106. package/dist/{requests-DN-BbNiM.mjs → requests-MhxYnau8.mjs} +2 -2
  107. package/dist/resource-name-C7LVpcRm.mjs +11 -0
  108. package/dist/{rollback-D7rzSaZM.mjs → rollback-CJ6iSDoU.mjs} +3 -3
  109. package/dist/route-url-CG7U-cRN.mjs +15 -0
  110. package/dist/{runner-B8wXwWlo.mjs → runner-CXA9Fh8h.mjs} +1 -1
  111. package/dist/{runner-p-dMs2UN.mjs → runner-Ol0TNjk6.mjs} +2 -2
  112. package/dist/runtime/ai.d.mts +12 -8
  113. package/dist/runtime/ai.mjs +84 -17
  114. package/dist/runtime/better-auth-mysql.mjs +1 -1
  115. package/dist/runtime/better-auth-pg.mjs +1 -1
  116. package/dist/runtime/better-auth.mjs +1 -1
  117. package/dist/runtime/client-react.mjs +2 -2
  118. package/dist/runtime/client-solid.mjs +2 -2
  119. package/dist/runtime/client-svelte.mjs +2 -2
  120. package/dist/runtime/client-vue.mjs +2 -2
  121. package/dist/runtime/client.mjs +2 -2
  122. package/dist/runtime/durable.d.mts +3 -1
  123. package/dist/runtime/durable.mjs +4 -1
  124. package/dist/runtime/email/testing.mjs +1 -1
  125. package/dist/runtime/env-public.d.mts +1 -1
  126. package/dist/runtime/fetch-stream.mjs +1 -1
  127. package/dist/runtime/fetch.mjs +1 -1
  128. package/dist/runtime/handler.d.mts +1 -1
  129. package/dist/runtime/kv.mjs +0 -1
  130. package/dist/runtime/limits.d.mts +2 -0
  131. package/dist/runtime/limits.mjs +2 -0
  132. package/dist/runtime/live-client.d.mts +1 -1
  133. package/dist/runtime/live-server.mjs +25 -16
  134. package/dist/runtime/live.d.mts +1 -1
  135. package/dist/runtime/migration-handler.mjs +62 -42
  136. package/dist/runtime/route-url.d.mts +4 -0
  137. package/dist/runtime/route-url.mjs +2 -0
  138. package/dist/runtime/routing.d.mts +180 -0
  139. package/dist/runtime/routing.mjs +1082 -0
  140. package/dist/runtime/sandbox-container.d.mts +1 -1
  141. package/dist/runtime/sandbox-container.mjs +1 -1
  142. package/dist/runtime/sandbox.d.mts +3 -3
  143. package/dist/runtime/sandbox.mjs +75 -45
  144. package/dist/runtime/sse.mjs +1 -1
  145. package/dist/runtime/validator.d.mts +1 -1
  146. package/dist/runtime/ws-server.d.mts +4 -2
  147. package/dist/runtime/ws-server.mjs +27 -2
  148. package/dist/runtime/ws.d.mts +2 -2
  149. package/dist/runtime/ws.mjs +8 -6
  150. package/dist/{sandbox-qpNBT8a3.d.mts → sandbox-XZAqzFlG.d.mts} +11 -8
  151. package/dist/{sandbox-container-DdNEBfCc.d.mts → sandbox-container-Bo0eiFKz.d.mts} +2 -1
  152. package/dist/{sandbox-container-4fdqLnyb.mjs → sandbox-container-C6ItmVuN.mjs} +4 -2
  153. package/dist/{scan-4tfN-PSn.mjs → scan-C7okrLyM.mjs} +4 -35
  154. package/dist/{secret-BOtOl_cb.mjs → secret-Dli5fP0B.mjs} +4 -4
  155. package/dist/{sse-BaC1jXko.mjs → sse-CQNaDFFV.mjs} +6 -3
  156. package/dist/validate-Dq_L3s0S.mjs +2 -0
  157. package/dist/{validate-CIUwFpjB.mjs → validate-ctOrgiS3.mjs} +2 -1
  158. package/dist/{wrangler-DQF1vKyf.mjs → wrangler-7K-bW_DL.mjs} +8 -239
  159. package/dist/{ws-BoY7vQML.d.mts → ws-CL1w7GXU.d.mts} +13 -2
  160. package/package.json +15 -8
  161. package/skills/migrate-vite-cloudflare-to-void/SKILL.md +34 -157
  162. package/skills/void/SKILL.md +50 -135
  163. package/skills/void/docs/guide/ai.md +94 -84
  164. package/skills/void/docs/guide/app-types.md +3 -32
  165. package/skills/void/docs/guide/auth.md +12 -116
  166. package/skills/void/docs/guide/database/d1.md +9 -54
  167. package/skills/void/docs/guide/database/mysql.md +1 -1
  168. package/skills/void/docs/guide/database/postgresql.md +5 -26
  169. package/skills/void/docs/guide/database.md +23 -75
  170. package/skills/void/docs/guide/deployment.md +27 -113
  171. package/skills/void/docs/guide/durable-state.md +43 -18
  172. package/skills/void/docs/guide/edge/headers.md +3 -47
  173. package/skills/void/docs/guide/edge/prerendering.md +5 -20
  174. package/skills/void/docs/guide/edge/redirects.md +11 -64
  175. package/skills/void/docs/guide/edge/revalidation.md +5 -18
  176. package/skills/void/docs/guide/edge/rewrites.md +56 -284
  177. package/skills/void/docs/guide/edge/static-assets.md +22 -72
  178. package/skills/void/docs/guide/email/domains.md +112 -0
  179. package/skills/void/docs/guide/email/receiving.md +139 -0
  180. package/skills/void/docs/guide/email/sending.md +231 -0
  181. package/skills/void/docs/guide/email.md +13 -619
  182. package/skills/void/docs/guide/env-migration.md +11 -11
  183. package/skills/void/docs/guide/env-vars.md +9 -29
  184. package/skills/void/docs/guide/index.md +0 -15
  185. package/skills/void/docs/guide/jobs.md +3 -18
  186. package/skills/void/docs/guide/kv.md +5 -11
  187. package/skills/void/docs/guide/live.md +5 -56
  188. package/skills/void/docs/guide/pages-routing/actions-and-forms.md +78 -125
  189. package/skills/void/docs/guide/pages-routing/head.md +10 -10
  190. package/skills/void/docs/guide/pages-routing/islands.md +6 -36
  191. package/skills/void/docs/guide/pages-routing/layouts.md +6 -128
  192. package/skills/void/docs/guide/pages-routing/loaders.md +3 -19
  193. package/skills/void/docs/guide/pages-routing/markdown.md +13 -171
  194. package/skills/void/docs/guide/pages-routing/overview.md +7 -17
  195. package/skills/void/docs/guide/pages-routing/view-transitions.md +1 -1
  196. package/skills/void/docs/guide/platform/administration/access.md +1 -4
  197. package/skills/void/docs/guide/platform/administration/email.md +35 -8
  198. package/skills/void/docs/guide/platform/administration/operations.md +26 -4
  199. package/skills/void/docs/guide/platform/administration/plans.md +126 -0
  200. package/skills/void/docs/guide/platform/administration/projects.md +5 -2
  201. package/skills/void/docs/guide/platform/administration/zero-trust.md +23 -152
  202. package/skills/void/docs/guide/platform/development/runtime.md +3 -13
  203. package/skills/void/docs/guide/platform/development/schema-ci.md +0 -58
  204. package/skills/void/docs/guide/platform/installation/credentials.md +6 -4
  205. package/skills/void/docs/guide/platform/installation/domains.md +30 -2
  206. package/skills/void/docs/guide/platform/installation/first-deployment.md +2 -0
  207. package/skills/void/docs/guide/platform/installation/maintenance.md +3 -1
  208. package/skills/void/docs/guide/platform/installation/prerequisites.md +20 -15
  209. package/skills/void/docs/guide/platform/installation/setup.md +9 -5
  210. package/skills/void/docs/guide/platform-administration.md +1 -0
  211. package/skills/void/docs/guide/queues.md +7 -9
  212. package/skills/void/docs/guide/quickstart.md +38 -37
  213. package/skills/void/docs/guide/remote-dev.md +4 -9
  214. package/skills/void/docs/guide/sandboxes.md +29 -21
  215. package/skills/void/docs/guide/server-routing.md +9 -72
  216. package/skills/void/docs/guide/sse.md +4 -18
  217. package/skills/void/docs/guide/ssg.md +3 -15
  218. package/skills/void/docs/guide/ssr.md +14 -62
  219. package/skills/void/docs/guide/storage.md +9 -4
  220. package/skills/void/docs/guide/type-safety.md +3 -14
  221. package/skills/void/docs/guide/typed-fetch.md +3 -7
  222. package/skills/void/docs/guide/websockets.md +68 -40
  223. package/skills/void/docs/integrations/agents.md +3 -3
  224. package/skills/void/docs/integrations/cloudflare.md +85 -316
  225. package/skills/void/docs/integrations/frameworks/analog.md +5 -64
  226. package/skills/void/docs/integrations/frameworks/astro.md +4 -73
  227. package/skills/void/docs/integrations/frameworks/nuxt.md +5 -62
  228. package/skills/void/docs/integrations/frameworks/overview.md +11 -54
  229. package/skills/void/docs/integrations/frameworks/react-router.md +5 -60
  230. package/skills/void/docs/integrations/frameworks/sveltekit.md +6 -65
  231. package/skills/void/docs/integrations/frameworks/tanstack-start.md +4 -62
  232. package/skills/void/docs/integrations/nodejs-bun-deno.md +5 -69
  233. package/skills/void/docs/reference/api/auth.md +156 -0
  234. package/skills/void/docs/reference/api/client.md +87 -0
  235. package/skills/void/docs/reference/api/database.md +95 -0
  236. package/skills/void/docs/reference/api/durable.md +46 -0
  237. package/skills/void/docs/reference/api/env.md +50 -0
  238. package/skills/void/docs/reference/api/handlers.md +254 -0
  239. package/skills/void/docs/reference/api/pages.md +241 -0
  240. package/skills/void/docs/reference/api/plugin.md +39 -0
  241. package/skills/void/docs/reference/api/resources.md +109 -0
  242. package/skills/void/docs/reference/api/rewrites.md +76 -0
  243. package/skills/void/docs/reference/api/types.md +92 -0
  244. package/skills/void/docs/reference/api.md +56 -1218
  245. package/skills/void/docs/reference/cli/auth.md +88 -0
  246. package/skills/void/docs/reference/cli/database.md +128 -0
  247. package/skills/void/docs/reference/cli/deploy.md +85 -0
  248. package/skills/void/docs/reference/cli/domains.md +41 -0
  249. package/skills/void/docs/reference/cli/email.md +129 -0
  250. package/skills/void/docs/reference/cli/generate.md +116 -0
  251. package/skills/void/docs/reference/cli/github.md +189 -0
  252. package/skills/void/docs/reference/cli/platform-config.md +92 -0
  253. package/skills/void/docs/reference/cli/platform-email.md +81 -0
  254. package/skills/void/docs/reference/cli/platform-installation.md +127 -0
  255. package/skills/void/docs/reference/cli/platform-operations.md +90 -0
  256. package/skills/void/docs/reference/cli/platform-users.md +89 -0
  257. package/skills/void/docs/reference/cli/platform-zero-trust.md +45 -0
  258. package/skills/void/docs/reference/cli/platform.md +70 -0
  259. package/skills/void/docs/reference/cli/project.md +214 -0
  260. package/skills/void/docs/reference/cli/secrets.md +76 -0
  261. package/skills/void/docs/reference/cli/setup.md +70 -0
  262. package/skills/void/docs/reference/cli.md +32 -1682
  263. package/skills/void/docs/reference/config.md +12 -18
  264. package/skills/void/docs/reference/resource-inference.md +3 -58
  265. package/skills/void/docs/reference/structure.md +14 -41
  266. package/dist/canonical-json-DuDiiUsQ.mjs +0 -13
  267. package/dist/client-Czz8o5jP.mjs +0 -2
  268. package/dist/gen-CNJ62MM7.mjs +0 -2
  269. package/dist/help-CmZzxUba.mjs +0 -2
  270. package/dist/login-CGcRKEoi.mjs +0 -2
  271. package/dist/migrate-CYfbKkXh.mjs +0 -2
  272. package/dist/plan-BEZ8VJW0.mjs +0 -256
  273. package/dist/plan-DpuOr14e.mjs +0 -2
  274. package/dist/prepare-Bm3iq-u4.mjs +0 -2
  275. package/dist/validate-EKmJWxmy.mjs +0 -2
  276. /package/dist/cli/{cf-compat.d.mts → cloudflare-operation-process.d.mts} +0 -0
@@ -1,51 +1,52 @@
1
1
  import { n as __toESM, t as __commonJSMin } from "./rolldown-runtime-DXIUcv95.mjs";
2
2
  import { a as join, l as relative, n as dirname, o as normalize, r as extname, t as basename, u as resolve } from "./dist-BVqXeR_h.mjs";
3
3
  import { t as require_picocolors } from "./picocolors-BTps1_gs.mjs";
4
- import { i as resolveProjectPaths, n as readProjectPaths, t as assertNoRootSourceConventionConflicts } from "./project-paths-GgcMozYz.mjs";
5
- import { t as findVoidAuthConfig } from "./config-DPByhVck.mjs";
4
+ import { m as needsApplicationDatabase, r as detectFramework } from "./plugin-inference-CXWnn79A.mjs";
5
+ import { n as readProjectPaths, t as assertNoRootSourceConventionConflicts } from "./project-paths-GgcMozYz.mjs";
6
+ import { d as isNodeTarget, h as resolveBindingNames, m as readConfig } from "./config-Br_JZD6u.mjs";
7
+ import { n as isIdentifier, r as isLiteral } from "./ast-CPGPjb0V.mjs";
8
+ import { o as scanDurableObjectModules, t as analyzeApplicationSync } from "./application-analysis-BVsVqc11.mjs";
9
+ import { t as collectMigrations } from "./collect-DiTn9yr6.mjs";
10
+ import { o as validateSsrEntry } from "./entry-CF5wvH4w.mjs";
11
+ import { r as hasEmailHandlers } from "./email--tEtMoj_.mjs";
12
+ import { a as hasSandboxSqliteMigration, c as resolveSandboxConfig, i as assertSandboxNamespaceConfig, l as sandboxContainerConfig, r as SANDBOX_MIGRATION_TAG, s as requiresSandboxPlatformImage } from "./config-C_XRIPx2.mjs";
6
13
  import { i as trustedCloudflareWorkerAccessHeaders, r as scopedAccessCredentials, t as cfAccessHeaders } from "./cf-access-DRsQRe6k.mjs";
7
- import { a as getApiUrl, c as getToken, l as getTokenSource, o as getProxyUrl, u as isStagingMode } from "./auth-DLNN0D3Z.mjs";
14
+ import { a as getApiUrl, c as getToken, l as getTokenSource, o as getProxyUrl, u as isStagingMode } from "./auth-CSkdO2Bb.mjs";
8
15
  import { r as ensurePlatformProxyAccess, t as ensureCloudflaredToken } from "./cf-access-P0iZnu0q.mjs";
9
16
  import { d as writeProjectConfig, i as readDeploymentPlatform, l as writeDeploymentPlatform, o as readProjectConfig, t as attachSelectedPlatform } from "./project-C0Fqu05O.mjs";
10
- import { d as isNodeTarget, h as resolveBindingNames, m as readConfig, u as getDatabaseDialect } from "./config-Br_JZD6u.mjs";
11
- import { A as writeWranglerPublishedTrafficSnapshot, B as materializeCloudflareConfig, D as syncWranglerWebSockets, E as syncWranglerDurableObjects, F as requiresSandboxPlatformImage, I as resolveSandboxConfig, L as sandboxContainerConfig, M as SANDBOX_MIGRATION_TAG, P as isSandboxEnabled, S as readWranglerPublishedTrafficSnapshot, T as syncWranglerBindings, V as persistCloudflareConfig, W as stashCloudflareDeployPointer, a as LIVE_MIGRATION_TAG, c as bindingEntriesForField, f as findBindingNameOwner, g as readExistingLiveBindingConfig, h as normalizeWranglerCompatibilityFlags, k as writeProvisionedWranglerConfig, l as ensureWranglerCompat, m as nextDurableObjectMigrationTag, o as acknowledgeWranglerManagedQueueRemovals, p as hasDurableObjectSqliteMigration, r as FRIENDLY_BINDING_NAMES, w as resolveLiveBindingConfig, x as readWranglerManagedQueueCleanupCandidates } from "./wrangler-DQF1vKyf.mjs";
17
+ import { i as persistCloudflareConfig, r as materializeCloudflareConfig, s as stashCloudflareDeployPointer } from "./cloudflare-config-Bktvwtpf.mjs";
18
+ import { A as writeProvisionedWranglerConfig, C as readWranglerPublishedTrafficSnapshot, D as syncWranglerDurableObjects, E as syncWranglerBindings, O as syncWranglerWebSockets, S as readWranglerManagedQueueCleanupCandidates, T as resolveLiveBindingConfig, _ as readExistingLiveBindingConfig, a as LIVE_MIGRATION_TAG, c as bindingEntriesForField, g as normalizeWranglerCompatibilityFlags, h as nextDurableObjectMigrationTag, j as writeWranglerPublishedTrafficSnapshot, l as bindingNamesForField, m as hasDurableObjectSqliteMigration, o as acknowledgeWranglerManagedQueueRemovals, p as findBindingNameOwner, r as FRIENDLY_BINDING_NAMES, u as ensureWranglerCompat } from "./wrangler-7K-bW_DL.mjs";
12
19
  import { i as voidWarn, r as voidLog } from "./log-ChfPKsVd.mjs";
13
20
  import { n as lintDuplicateSources, r as mergeRoutingRules, t as lintDestinationSplats } from "./headers-DWi2IXWx.mjs";
14
- import { n as isIdentifier, r as isLiteral } from "./ast-CPGPjb0V.mjs";
15
- import { t as scanPages } from "./scan-4tfN-PSn.mjs";
16
- import { a as scanJobsSync, i as scanQueuesSync, r as scanRoutes } from "./scan-ClYmX3sa.mjs";
17
- import { i as inferProjectBindings, n as FRAMEWORK_SCAN_DIRS, r as detectFramework, s as scanWebSocketRoutesSync } from "./plugin-inference-BMfKRSqE.mjs";
18
- import { t as collectMigrations } from "./collect-DiTn9yr6.mjs";
19
- import { r as buildMigrationPlanBundle } from "./plan-BEZ8VJW0.mjs";
21
+ import { n as buildMigrationPlanBundle } from "./plan-NPpwZ_kc.mjs";
20
22
  import { r as scanWranglerDotenvKeys } from "./dotenv-VQxupEUv.mjs";
21
23
  import { t as discoverSchema } from "./discover-BcPPiF1o.mjs";
22
24
  import { t as MagicString } from "./dist-AoCzRTJE.mjs";
23
25
  import { t as findEnvFile } from "./env-types-C2_bIk_b.mjs";
24
- import { d as formatEnvReport, f as listSelfHostSecretNames, g as validateProdEnv, h as selfHostSchemaFailureMessage, i as SelfHostSecretListError, l as dischargeSelfHostEnvContext, n as SelfHostFirstDeployError, p as readEmittedWorkerVars, r as SelfHostSchemaError, s as deriveSelfHostEnvContextFromVars, t as SelfHostEnvValidationError, u as fetchRemoteSecretNames, v as readGeneratedWranglerConfigFile } from "./env-validation-BB4GkLxn.mjs";
26
+ import { d as formatEnvReport, f as listSelfHostSecretNames, g as validateProdEnv, h as selfHostSchemaFailureMessage, i as SelfHostSecretListError, l as dischargeSelfHostEnvContext, n as SelfHostFirstDeployError, p as readEmittedWorkerVars, r as SelfHostSchemaError, s as deriveSelfHostEnvContextFromVars, t as SelfHostEnvValidationError, u as fetchRemoteSecretNames, v as readGeneratedWranglerConfigFile } from "./env-validation-BPn7vk-V.mjs";
25
27
  import { n as CLOUDFLARE_DEPLOY_BUILD_ENV, o as resolveRemoteMode, s as stripInternalEnvKeys } from "./env-_v349JBd.mjs";
26
- import { o as validateSsrEntry } from "./entry-CF5wvH4w.mjs";
27
28
  import { E as confirm, I as select, N as note, O as intro, P as outro, W as isCancel, j as log$1 } from "./dist-CTBk70IR.mjs";
29
+ import { n as completeApplicationAnalysis, t as analyzeApplication } from "./application-routing-B7QEjqSR.mjs";
28
30
  import { t as writeDrizzleConfig } from "./config-D8MOgiQV.mjs";
29
31
  import { i as runMySqlMigrations } from "./runner-mysql-DmWchSWJ.mjs";
30
32
  import { i as runPgMigrations } from "./runner-pg-D4OPlil-.mjs";
31
33
  import { n as openMigrationValidationDatabase, t as SqliteMigrationError } from "./sqlite-validation-BzKMWnO4.mjs";
32
- import { i as validateMigrations, t as assertJournalCoherence } from "./validate-CIUwFpjB.mjs";
34
+ import { i as validateMigrations, t as assertJournalCoherence } from "./validate-ctOrgiS3.mjs";
33
35
  import { t as renderPrerenderPaths } from "./prerender-render-D9qbLs2f.mjs";
34
- import { a as scanEmailHandlersSync, r as hasEmailHandlers } from "./email--tEtMoj_.mjs";
35
- import { i as migrateBuiltWranglerConfig, n as prepareCfFrameworkBuildOutput, r as prepareCfStaticBuildOutput, t as prepareCfBuildOutput } from "./cf-build-output-BJ6yGEIS.mjs";
36
+ import { i as migrateBuiltWranglerConfig, n as prepareCfFrameworkBuildOutput, r as prepareCfStaticBuildOutput, t as prepareCfBuildOutput } from "./cf-build-output-BPqOT964.mjs";
36
37
  import { n as getPackageDir } from "./agents-B_pX_HmQ.mjs";
37
- import { i as isExpiredTokenError, n as PlatformClient, o as parsePlatformErrorBody, r as isCliOutdatedError, s as platformErrorMessage, t as PlatformApiError } from "./client-BTZ3XkrB.mjs";
38
- import { a as resolveProjectCommand, i as hasBackendFiles, n as detectPreset, o as resolveWranglerArgv, r as formatProjectCommand, t as FRAMEWORK_PRESETS } from "./preset-UHj9ARyP.mjs";
39
- import { a as ensureCloudflareAuthentication, n as cloudflareAuthNeedsTerminal, o as parseCloudflareAuthSession, r as cloudflareCredentialOverrides, t as CloudflareAuthenticationError } from "./cloudflare-auth-DQkqoMYa.mjs";
38
+ import { i as isExpiredTokenError, n as PlatformClient, o as parsePlatformErrorBody, r as isCliOutdatedError, s as platformErrorMessage, t as PlatformApiError } from "./client-RV8NVeB8.mjs";
39
+ import { a as detectPreset, c as resolveCloudflareOperationArgv, i as FRAMEWORK_PRESETS, l as resolveProjectCommand, o as formatProjectCommand, s as hasBackendFiles, t as createCloudflareProcessRunner } from "./cloudflare-process-B-wekeR6.mjs";
40
+ import { a as parseCloudflareAuthSession, i as ensureCloudflareAuthentication, n as cloudflareCredentialOverrides, t as CloudflareAuthenticationError } from "./cloudflare-auth-Qdc7tw8F.mjs";
40
41
  import { n as writeVoidProjectConfig } from "./config-write-B1f88wJA.mjs";
41
- import { r as discoverFrameworkWranglerFiles, t as migrateLegacyVoidConfig } from "./migrate-BPITvDJN.mjs";
42
- import { a as createCloudflareEmptyDomainPublisher, i as createCloudflareEmptyCronPublisher } from "./cloudflare-operations-B9tzgjmf.mjs";
42
+ import { r as discoverFrameworkWranglerFiles, t as migrateLegacyVoidConfig } from "./migrate-DHul7PRV.mjs";
43
+ import { a as createCloudflareEmptyDomainPublisher, i as createCloudflareEmptyCronPublisher } from "./cloudflare-operations-fxHb-byx.mjs";
43
44
  import { t as formatCloudflareUserMessage } from "./cloudflare-user-output-Q-k0QoNQ.mjs";
44
45
  import { c as import_picocolors$1, i as createSpinner, r as cliTitle } from "./output-Dm_A4Tbv.mjs";
45
- import { a as promptProjectSelection, i as reportProjectCreationWarnings, o as promptProjectSetupAction, r as promptAndCreateProject } from "./create-project-l9J7pbtm.mjs";
46
+ import { a as promptProjectSelection, i as reportProjectCreationWarnings, o as promptProjectSetupAction, r as promptAndCreateProject } from "./create-project-bMf6ffLZ.mjs";
46
47
  import { t as getRequestedProjectSlug } from "./resolve-project-BG5ORGID.mjs";
47
48
  import { t as collectGitDeployMetadata } from "./git-metadata-D-gurjzG.mjs";
48
- import { t as promptForLoginToken } from "./login-CAsAsQ-_.mjs";
49
+ import { t as promptForLoginToken } from "./login-UZKFM_u7.mjs";
49
50
  import { createRequire } from "node:module";
50
51
  import { copyFileSync, cpSync, createWriteStream, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
51
52
  import { fileURLToPath } from "node:url";
@@ -60,6 +61,7 @@ import { Minimatch } from "minimatch";
60
61
  import { CloudflareWorkerInspectionError, activateCloudflareVersion, assertCloudflareBindingsPreserved, assertCloudflareDurableObjectLifecycleSupported, assertExistingCloudflareConfig, encodeCloudflareVersionMessage, extractWorkersDevSubdomain, normalizeWorkersDevSubdomain, parseRootWranglerConfig, parseRootWranglerConfig as parseRootWranglerConfig$1, readBindingFieldPresence, readCloudflareTriggerConfig, readConfigEntryString, readExistingCloudflareWorker, readStoredResourceName, readWranglerTopLevelString, renderCloudflareStaticEntry, resolveD1MigrateTarget, stageCloudflareStaticAssets, writeCloudflareFrameworkDeployConfig, writeCloudflareStaticDeployConfig } from "@void/deploy-cloudflare";
61
62
  import { getWorkerAssetsDir, getWorkerBundleDir, getWorkerConfigPath, readBuildOutput } from "@cloudflare/build-output-utils";
62
63
  import ignore from "ignore";
64
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
63
65
  import mime from "mime";
64
66
  import { hash } from "blake3-jit";
65
67
  //#region src/asset-routing.ts
@@ -240,127 +242,84 @@ function deriveAddresses(handlers, domain, mode) {
240
242
  routes
241
243
  };
242
244
  }
243
- function parseWhoamiJson(stdout) {
244
- const start = stdout.indexOf("{");
245
- if (start < 0) return null;
246
- try {
247
- const parsed = JSON.parse(stdout.slice(start));
248
- if (!parsed || typeof parsed !== "object" || typeof parsed.loggedIn !== "boolean") return null;
249
- return parsed;
250
- } catch {
251
- return null;
252
- }
245
+ function parseWhoamiJson(data) {
246
+ if (!isRecord(data) || typeof data.loggedIn !== "boolean") return null;
247
+ if (data.tokenPermissions !== void 0 && (!Array.isArray(data.tokenPermissions) || data.tokenPermissions.some((scope) => typeof scope !== "string"))) return null;
248
+ 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;
249
+ return data;
253
250
  }
254
- function parseRoutingSettings(stdout) {
255
- const name = /^Email Routing for (\S+):\s*$/m.exec(stdout)?.[1];
256
- const enabled = /^\s*Enabled:\s+(true|false)\s*$/m.exec(stdout)?.[1];
257
- const status = /^\s*Status:\s+(\S+)\s*$/m.exec(stdout)?.[1];
258
- const tag = /^\s*Tag:\s+([0-9a-f]{32})\s*$/m.exec(stdout)?.[1];
259
- if (!name || !enabled || !status || !tag) return null;
260
- return {
261
- name,
262
- enabled: enabled === "true",
263
- status,
264
- tag
265
- };
251
+ function isRecord(value) {
252
+ return value !== null && typeof value === "object" && !Array.isArray(value);
266
253
  }
267
- /**
268
- * `wrangler email routing rules list <zone>` stdout (4.131 `src/email-routing/rules/list.ts`):
269
- *
270
- * Rule: <id>
271
- * Name: <name|(none)>
272
- * Enabled: true
273
- * Matchers: to:support@void.cloud
274
- * Actions: worker:acme-support
275
- * Priority: 0
276
- *
277
- * Catch-all rule: disabled, action: drop
278
- * (use `wrangler email routing rules get catch-all` to view details)
279
- *
280
- * With no rule at all: `No routing rules found.` / no custom rule: `No custom routing
281
- * rules found.`. Both forms are pinned by fixture.
282
- */
283
- function parseRoutingRules(stdout) {
284
- const lines = stdout.split(/\r?\n/);
254
+ function readRoutingSettings(data) {
255
+ 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) ? {
256
+ name: data.name,
257
+ enabled: data.enabled,
258
+ status: data.status,
259
+ tag: data.tag
260
+ } : null;
261
+ }
262
+ function readRoutingRules(data) {
263
+ if (!Array.isArray(data)) return null;
285
264
  const rules = [];
286
265
  let catchAll = null;
287
- let sawSentinel = false;
288
- let current = null;
289
- for (const raw of lines) {
290
- const line = raw.trimEnd();
291
- if (/^No (custom )?routing rules found\.$/.test(line)) {
292
- sawSentinel = true;
293
- continue;
294
- }
295
- const catchAllMatch = /^Catch-all rule: (enabled|disabled), action: (.*)$/.exec(line);
296
- if (catchAllMatch) {
266
+ for (const entry of data) {
267
+ 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;
268
+ const actions = [];
269
+ for (const action of entry.actions) {
270
+ if (!isRecord(action) || typeof action.type !== "string") return null;
271
+ if (action.type === "drop") actions.push("drop");
272
+ else if (action.type === "worker" || action.type === "forward") {
273
+ const values = typeof action.value === "string" ? [action.value] : action.value;
274
+ if (!Array.isArray(values) || values.length === 0 || !values.every((value) => typeof value === "string" && value.length > 0)) return null;
275
+ if (action.type === "worker") actions.push("worker:" + values.join(","));
276
+ else actions.push("forward:" + values.join(","));
277
+ } else return null;
278
+ }
279
+ const to = [];
280
+ let all = false;
281
+ for (const matcher of entry.matchers) {
282
+ if (!isRecord(matcher)) return null;
283
+ if (matcher.type === "all") all = true;
284
+ else if (matcher.type === "literal" && matcher.field === "to" || matcher.type === "to") {
285
+ if (typeof matcher.value !== "string" || matcher.value.length === 0) return null;
286
+ to.push(matcher.value.toLowerCase());
287
+ } else return null;
288
+ }
289
+ if (all) {
290
+ if (catchAll || entry.matchers.length !== 1) return null;
297
291
  catchAll = {
298
- enabled: catchAllMatch[1] === "enabled",
299
- actions: splitActionList(catchAllMatch[2] ?? "")
292
+ enabled: entry.enabled,
293
+ actions
300
294
  };
301
- current = null;
302
- continue;
303
- }
304
- const ruleMatch = /^Rule: (\S+)$/.exec(line);
305
- if (ruleMatch) {
306
- current = {
307
- id: ruleMatch[1],
308
- name: "",
309
- enabled: false,
310
- to: [],
311
- actions: []
312
- };
313
- rules.push(current);
314
- continue;
315
- }
316
- if (!current) continue;
317
- const field = /^\s{2}(Name|Enabled|Matchers|Actions|Priority):\s*(.*)$/.exec(line);
318
- if (!field) continue;
319
- const value = (field[2] ?? "").trim();
320
- switch (field[1]) {
321
- case "Name":
322
- current.name = value;
323
- break;
324
- case "Enabled":
325
- current.enabled = value === "true";
326
- break;
327
- case "Matchers":
328
- current.to = value.split(", ").filter((m) => m.startsWith("to:")).map((m) => m.slice(3).toLowerCase());
329
- break;
330
- case "Actions": current.actions = splitActionList(value);
295
+ } else {
296
+ if (typeof entry.id !== "string" || entry.id.length === 0 || entry.name !== void 0 && typeof entry.name !== "string") return null;
297
+ rules.push({
298
+ id: entry.id,
299
+ name: entry.name ?? "",
300
+ enabled: entry.enabled,
301
+ to,
302
+ actions
303
+ });
331
304
  }
332
305
  }
333
- if (!sawSentinel && rules.length === 0 && !catchAll) return null;
334
306
  return {
335
307
  rules,
336
308
  catchAll
337
309
  };
338
310
  }
339
- function splitActionList(value) {
340
- return value.split(", ").map((a) => a.trim()).filter(Boolean);
341
- }
342
- function parseSendingSettings(stdout) {
343
- const name = /^Email Sending for (\S+):\s*$/m.exec(stdout)?.[1];
344
- const enabled = /^\s*Enabled:\s+(true|false)\s*$/m.exec(stdout)?.[1];
345
- const tag = /^\s*Tag:\s+([0-9a-f]{32})\s*$/m.exec(stdout)?.[1];
346
- if (!name || !enabled || !tag) return null;
347
- return {
348
- name,
349
- enabled: enabled === "true",
311
+ function readSendingSettings(data) {
312
+ if (!isRecord(data)) return null;
313
+ const tag = data.tag ?? data.id;
314
+ return typeof data.name === "string" && data.name.length > 0 && typeof data.enabled === "boolean" && typeof tag === "string" && /^[0-9a-f]{32}$/.test(tag) ? {
315
+ name: data.name,
316
+ enabled: data.enabled,
350
317
  tag
351
- };
352
- }
353
- /** wrangler renders an `APIError` note as `<message> [code: <n>]`; extract the code. */
354
- function wranglerApiErrorCode(text) {
355
- const match = /\[code:\s*(\d+)\]/.exec(stripAnsi(text));
356
- return match ? Number(match[1]) : null;
318
+ } : null;
357
319
  }
358
- const ANSI_SGR_RE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
359
- function stripAnsi(text) {
360
- return text.replace(ANSI_SGR_RE, "");
320
+ function providerErrorCode(result) {
321
+ return result.errorCode ?? result.errors?.find((error) => error.code !== void 0)?.code ?? null;
361
322
  }
362
- const ZONE_NOT_FOUND_RE = /Could not find (?:a )?zone for `([^`]+)`/;
363
- const SENDING_NOT_FOUND_RE = /No sending subdomain found for `([^`]+)`/;
364
323
  function isHardFailure(result) {
365
324
  return Boolean(result.signal) || Boolean(result.error);
366
325
  }
@@ -417,8 +376,10 @@ async function preflightEmail(inputs) {
417
376
  const domain = emailFromDomain(inputs.from);
418
377
  if (!domain) throw new Error(`email.from must be an address like you@mail.example.com (got ${JSON.stringify(inputs.from)})`);
419
378
  const apiBaseUrl = inputs.apiBaseUrl ?? "https://api.cloudflare.com/client/v4";
420
- const extra = inputs.wranglerArgs ?? [];
421
- const run = async (args) => inputs.runWrangler([...args, ...extra], { capture: true });
379
+ const run = async (operation) => inputs.runWrangler({
380
+ ...operation,
381
+ ...inputs.configPath ? { configPath: inputs.configPath } : {}
382
+ }, { capture: true });
422
383
  const usageHandlers = [...inputs.handlers.addresses.map((d) => `email/${d.filePath}`), ...inputs.handlers.defaultHandler ? [`email/${inputs.handlers.defaultHandler}`] : []];
423
384
  const pre = {
424
385
  from: inputs.from,
@@ -481,8 +442,8 @@ async function preflightEmail(inputs) {
481
442
  });
482
443
  };
483
444
  for (const conflict of inputs.configConflicts ?? []) block("all", conflict);
484
- const whoami = await run(["whoami", "--json"]);
485
- const who = isHardFailure(whoami) || whoami.status !== 0 ? null : parseWhoamiJson(whoami.stdout);
445
+ const whoami = await run({ type: "auth.whoami" });
446
+ const who = isHardFailure(whoami) || whoami.status !== 0 ? null : parseWhoamiJson(whoami.data);
486
447
  if (!who) {
487
448
  pre.session.state = "unknown";
488
449
  block("all", "could not read the Cloudflare session — run `void cloudflare login`");
@@ -509,22 +470,20 @@ async function preflightEmail(inputs) {
509
470
  let settings = null;
510
471
  let settingsMiss = false;
511
472
  if (pre.session.state === "ok") for (const candidate of zoneCandidates(domain)) {
512
- const result = await run([
513
- "email",
514
- "routing",
515
- "settings",
516
- candidate
517
- ]);
473
+ const result = await run({
474
+ type: "email.routing.settings",
475
+ domain: candidate
476
+ });
518
477
  if (isHardFailure(result)) {
519
478
  settingsMiss = true;
520
479
  break;
521
480
  }
481
+ if (result.status === 0 && result.data === null) continue;
522
482
  if (result.status !== 0) {
523
- if (ZONE_NOT_FOUND_RE.test(stripAnsi(result.stderr))) continue;
524
483
  settingsMiss = true;
525
484
  break;
526
485
  }
527
- settings = parseRoutingSettings(result.stdout);
486
+ settings = readRoutingSettings(result.data);
528
487
  if (!settings) settingsMiss = true;
529
488
  break;
530
489
  }
@@ -621,14 +580,11 @@ async function preflightEmail(inputs) {
621
580
  else if (pre.domainMx.state === "unknown") block("all", `could not read the MX records of ${domain} — unknown — run \`void email setup --platform cloudflare\``);
622
581
  }
623
582
  pre.derived = deriveAddresses(inputs.handlers, domain, mode);
624
- const rulesResult = await run([
625
- "email",
626
- "routing",
627
- "rules",
628
- "list",
629
- apex
630
- ]);
631
- const rules = isHardFailure(rulesResult) || rulesResult.status !== 0 ? null : parseRoutingRules(rulesResult.stdout);
583
+ const rulesResult = await run({
584
+ type: "email.routing.rules.list",
585
+ domain: apex
586
+ });
587
+ const rules = isHardFailure(rulesResult) || rulesResult.status !== 0 ? null : readRoutingRules(rulesResult.data);
632
588
  if (!rules) {
633
589
  pre.rules.state = "unknown";
634
590
  block("all", `could not read Email Routing rules — unknown — run \`void email setup --platform cloudflare\`${tokenHint}`);
@@ -658,18 +614,16 @@ async function preflightEmail(inputs) {
658
614
  }
659
615
  const foreign = new Set(pre.rules.foreign.map((f) => f.address));
660
616
  pre.addresses = pre.derived.addresses.filter((a) => !foreign.has(a));
661
- const sendingResult = await run([
662
- "email",
663
- "sending",
664
- "settings",
617
+ const sendingResult = await run({
618
+ type: "email.sending.settings",
665
619
  domain
666
- ]);
620
+ });
667
621
  if (isHardFailure(sendingResult)) pre.sending.state = "unknown";
622
+ else if (sendingResult.status === 0 && sendingResult.data === null) pre.sending.state = "pending";
668
623
  else if (sendingResult.status === 0) {
669
- const parsed = parseSendingSettings(sendingResult.stdout);
624
+ const parsed = readSendingSettings(sendingResult.data);
670
625
  pre.sending.state = parsed ? parsed.enabled ? "ready" : "pending" : "unknown";
671
- } else if (SENDING_NOT_FOUND_RE.test(stripAnsi(sendingResult.stderr))) pre.sending.state = "pending";
672
- else pre.sending.state = "unknown";
626
+ } else pre.sending.state = "unknown";
673
627
  if (pre.sending.state === "unknown") block("sending", `could not read Email Sending settings — unknown — run \`void email setup --platform cloudflare\`${tokenHint}`);
674
628
  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`);
675
629
  const names = (inputs.existingSendEmail ?? []).map((e) => typeof e?.name === "string" ? e.name : "");
@@ -849,8 +803,10 @@ async function applyEmailSetup(pre, inputs) {
849
803
  }
850
804
  const apex = pre.apex;
851
805
  const apiBaseUrl = inputs.apiBaseUrl ?? "https://api.cloudflare.com/client/v4";
852
- const extra = inputs.wranglerArgs ?? [];
853
- const run = async (args) => inputs.runWrangler([...args, ...extra], { capture: true });
806
+ const run = async (operation) => inputs.runWrangler({
807
+ ...operation,
808
+ ...inputs.configPath ? { configPath: inputs.configPath } : {}
809
+ }, { capture: true });
854
810
  let bearer;
855
811
  let bearerFailed = false;
856
812
  let bearerObtained = false;
@@ -879,17 +835,15 @@ async function applyEmailSetup(pre, inputs) {
879
835
  const apexRoutingOff = pre.mode === "subdomain" && pre.routing.enabled !== true;
880
836
  if (!result.routingReady) {
881
837
  if (pre.mode === "apex") {
882
- const enable = await run([
883
- "email",
884
- "routing",
885
- "enable",
886
- apex
887
- ]);
838
+ const enable = await run({
839
+ type: "email.routing.enable",
840
+ domain: apex
841
+ });
888
842
  if (!isHardFailure(enable) && enable.status === 0) {
889
843
  result.routingReady = true;
890
844
  lines.push(` ✔ routing ${apex} enabled`);
891
845
  } else {
892
- const code = wranglerApiErrorCode(enable.stderr);
846
+ const code = providerErrorCode(enable);
893
847
  lines.push(` ✘ routing could not enable Email Routing on ${apex}${code ? ` [code: ${code}]${CF_EMAIL_ERROR_CODES[code] ? ` — ${CF_EMAIL_ERROR_CODES[code]}` : ""}` : ""}`);
894
848
  }
895
849
  } else if (apexRoutingOff) {
@@ -920,13 +874,11 @@ async function applyEmailSetup(pre, inputs) {
920
874
  const sendingBlocked = pre.blockers.filter((b) => b.scope === "sending");
921
875
  if (!result.sendingReady && sendingBlocked.length > 0) for (const b of sendingBlocked) lines.push(` ! sending ${b.message}`);
922
876
  else if (!result.sendingReady) {
923
- const enable = await run([
924
- "email",
925
- "sending",
926
- "enable",
927
- pre.domain
928
- ]);
929
- const code = isHardFailure(enable) ? null : wranglerApiErrorCode(enable.stderr);
877
+ const enable = await run({
878
+ type: "email.sending.enable",
879
+ domain: pre.domain
880
+ });
881
+ const code = isHardFailure(enable) ? null : providerErrorCode(enable);
930
882
  if (!isHardFailure(enable) && (enable.status === 0 || code === 2040)) {
931
883
  result.sendingReady = true;
932
884
  lines.push(` ✔ sending ${pre.domain} onboarded`);
@@ -976,37 +928,6 @@ async function createNodeEmailDns() {
976
928
  };
977
929
  }
978
930
  //#endregion
979
- //#region src/durable/scan.ts
980
- const MODULE_PATTERN = /\.(?:[cm]?[jt]s)$/;
981
- function words(value) {
982
- return value.split(/[^a-zA-Z0-9]+/).filter(Boolean);
983
- }
984
- function scanDurableObjectModules(root) {
985
- const directory = join(root, "durable-objects");
986
- if (!existsSync(directory)) return [];
987
- const modules = readdirSync(directory, { withFileTypes: true }).filter((entry) => entry.isFile() && MODULE_PATTERN.test(entry.name) && !entry.name.endsWith(".d.ts")).sort((a, b) => a.name.localeCompare(b.name)).map((entry) => {
988
- const parts = words(entry.name.replace(MODULE_PATTERN, ""));
989
- if (parts.length === 0) throw new Error(`durable: cannot derive a name from durable-objects/${entry.name}.`);
990
- const classBase = parts.map((part) => part[0].toUpperCase() + part.slice(1)).join("");
991
- const bindingName = parts.join("_").toUpperCase();
992
- const slug = parts.join("-").toLowerCase();
993
- return {
994
- filePath: join(directory, entry.name),
995
- bindingName,
996
- className: `${classBase}DurableObject`,
997
- migrationTag: `void-durable-${slug}-v1`
998
- };
999
- });
1000
- const bindings = /* @__PURE__ */ new Set();
1001
- const classes = /* @__PURE__ */ new Set();
1002
- for (const module of modules) {
1003
- if (bindings.has(module.bindingName) || classes.has(module.className)) throw new Error(`durable: duplicate generated name for '${module.filePath}'. Rename one of the durable-objects files.`);
1004
- bindings.add(module.bindingName);
1005
- classes.add(module.className);
1006
- }
1007
- return modules;
1008
- }
1009
- //#endregion
1010
931
  //#region ../../node_modules/.pnpm/ci-info@4.4.0/node_modules/ci-info/vendors.json
1011
932
  var require_vendors = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1012
933
  module.exports = [
@@ -1859,7 +1780,38 @@ async function prerenderPagesNode(options) {
1859
1780
  return result;
1860
1781
  }
1861
1782
  //#endregion
1783
+ //#region src/shared/version-metadata.ts
1784
+ /** Build-only marker: an existing-site handoff must not add Worker bindings. */
1785
+ const CLOUDFLARE_PRESERVE_BINDINGS_BUILD_ENV = "VOID_CLOUDFLARE_PRESERVE_BINDINGS";
1786
+ const DEFAULT_VERSION_METADATA_BINDING = "__VOID_VERSION_METADATA";
1787
+ /** Keep the authored singleton name, and reject a cross-type binding collision. */
1788
+ function readVersionMetadataBinding(config) {
1789
+ const name = bindingNamesForField("version_metadata", config.version_metadata)[0];
1790
+ if (!name) return;
1791
+ const owner = findBindingNameOwner(config, "version_metadata", name);
1792
+ if (owner) throw new Error(`Cloudflare version metadata binding ${name} conflicts with ${owner}.`);
1793
+ return name;
1794
+ }
1795
+ /** Self-metadata adds no remote resource or plan entitlement. */
1796
+ function selectNativeVersionMetadataBinding(config, options) {
1797
+ if (!options.nativeBuild) return;
1798
+ const authored = readVersionMetadataBinding(config);
1799
+ if (authored || options.preserveBindings) return authored;
1800
+ const owner = findBindingNameOwner(config, "version_metadata", DEFAULT_VERSION_METADATA_BINDING);
1801
+ if (owner) throw new Error(`Cloudflare version metadata binding ${DEFAULT_VERSION_METADATA_BINDING} conflicts with ${owner}.`);
1802
+ return DEFAULT_VERSION_METADATA_BINDING;
1803
+ }
1804
+ //#endregion
1862
1805
  //#region src/cli/cf-version-api.ts
1806
+ var CloudflareApiError = class extends Error {
1807
+ status;
1808
+ code;
1809
+ constructor(message, status, code) {
1810
+ super(message);
1811
+ this.status = status;
1812
+ this.code = code;
1813
+ }
1814
+ };
1863
1815
  function object(value) {
1864
1816
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
1865
1817
  }
@@ -1959,38 +1911,181 @@ function bindingsFromOutput(env, workerName, activeSecrets, explicitSecrets) {
1959
1911
  ...value.worker !== workerName ? { script_name: requiredString(value.worker, name) } : {}
1960
1912
  });
1961
1913
  break;
1914
+ case "unsafe:durable_object_namespace":
1915
+ if (Object.keys(value).some((key) => ![
1916
+ "type",
1917
+ "class_name",
1918
+ "dev"
1919
+ ].includes(key))) throw new Error(`Cloudflare binding ${name} is not a verified local Durable Object.`);
1920
+ bindings.push({
1921
+ name,
1922
+ type: "durable_object_namespace",
1923
+ class_name: requiredString(value.class_name, name)
1924
+ });
1925
+ break;
1926
+ case "queue":
1927
+ bindings.push({
1928
+ name,
1929
+ type: "queue",
1930
+ queue_name: requiredString(value.name, name),
1931
+ ...value.deliveryDelay === void 0 ? {} : { delivery_delay: value.deliveryDelay }
1932
+ });
1933
+ break;
1934
+ case "ai":
1935
+ case "browser":
1936
+ case "images":
1937
+ bindings.push({
1938
+ name,
1939
+ type: value.type
1940
+ });
1941
+ break;
1942
+ case "hyperdrive":
1943
+ bindings.push({
1944
+ name,
1945
+ type: "hyperdrive",
1946
+ id: requiredString(value.id, name)
1947
+ });
1948
+ break;
1949
+ case "worker":
1950
+ bindings.push({
1951
+ name,
1952
+ type: "service",
1953
+ service: requiredString(value.worker, name),
1954
+ ...value.exportName === void 0 ? {} : { entrypoint: value.exportName },
1955
+ ...value.props === void 0 ? {} : { props: value.props }
1956
+ });
1957
+ break;
1958
+ case "workflow":
1959
+ bindings.push({
1960
+ name,
1961
+ type: "workflow",
1962
+ workflow_name: requiredString(value.name, name),
1963
+ ...value.worker === workerName ? { class_name: requiredString(value.exportName, name) } : { script_name: requiredString(value.worker, name) }
1964
+ });
1965
+ break;
1966
+ case "send-email":
1967
+ bindings.push({
1968
+ name,
1969
+ type: "send_email",
1970
+ ...value.destinationAddress === void 0 ? {} : { destination_address: value.destinationAddress },
1971
+ ...value.allowedDestinationAddresses === void 0 ? {} : { allowed_destination_addresses: value.allowedDestinationAddresses },
1972
+ ...value.allowedSenderAddresses === void 0 ? {} : { allowed_sender_addresses: value.allowedSenderAddresses }
1973
+ });
1974
+ break;
1975
+ case "analytics-engine-dataset":
1976
+ bindings.push({
1977
+ name,
1978
+ type: "analytics_engine",
1979
+ ...value.name === void 0 ? {} : { dataset: value.name }
1980
+ });
1981
+ break;
1982
+ case "vectorize":
1983
+ bindings.push({
1984
+ name,
1985
+ type: "vectorize",
1986
+ index_name: requiredString(value.name, name)
1987
+ });
1988
+ break;
1989
+ case "dispatch-namespace": {
1990
+ const outbound = object(value.outbound);
1991
+ bindings.push({
1992
+ name,
1993
+ type: "dispatch_namespace",
1994
+ namespace: requiredString(value.namespace, name),
1995
+ ...outbound ? { outbound: {
1996
+ worker: { service: requiredString(outbound.worker, name) },
1997
+ ...Array.isArray(outbound.parameters) ? { params: outbound.parameters.map((parameter) => ({ name: parameter })) } : {}
1998
+ } } : {}
1999
+ });
2000
+ break;
2001
+ }
1962
2002
  case "assets":
1963
2003
  bindings.push({
1964
2004
  name,
1965
2005
  type: "assets"
1966
2006
  });
1967
2007
  break;
2008
+ case "version-metadata":
2009
+ bindings.push({
2010
+ name,
2011
+ type: "version_metadata"
2012
+ });
2013
+ break;
1968
2014
  default: throw new Error(`Cloudflare binding ${name} cannot be uploaded with pinned secrets.`);
1969
2015
  }
1970
2016
  }
1971
2017
  return bindings;
1972
2018
  }
1973
- function exportsFromOutput(exports) {
2019
+ function exportsFromOutput(exports, bindings) {
1974
2020
  if (exports === void 0) return;
1975
2021
  const entries = object(exports);
1976
2022
  if (!entries) throw new Error("Cloudflare Durable Object exports were not readable.");
1977
2023
  const result = {};
2024
+ const workflows = /* @__PURE__ */ new Map();
1978
2025
  for (const [name, raw] of Object.entries(entries)) {
1979
2026
  const value = object(raw);
2027
+ if (value?.type === "workflow") {
2028
+ const workflowName = requiredString(value.name, `Workflow export ${name} name`);
2029
+ if (workflows.has(workflowName)) throw new Error(`Cloudflare Workflow ${workflowName} has multiple local exports.`);
2030
+ workflows.set(workflowName, name);
2031
+ result[name] = {
2032
+ type: "workflow",
2033
+ name: workflowName
2034
+ };
2035
+ continue;
2036
+ }
2037
+ if (value?.type === "worker") {
2038
+ result[name] = {
2039
+ type: "worker",
2040
+ ...value.cache === void 0 ? {} : { cache: value.cache }
2041
+ };
2042
+ continue;
2043
+ }
1980
2044
  if (!value || value.type !== "durable-object") throw new Error(`Cloudflare export ${name} cannot be uploaded with pinned secrets.`);
1981
2045
  if (value.state === "deleted") result[name] = {
1982
2046
  type: "durable-object",
1983
2047
  state: "deleted"
1984
2048
  };
1985
- else if (value.storage === "sqlite" || value.storage === "legacy-kv") result[name] = {
1986
- type: "durable-object",
1987
- storage: value.storage,
1988
- state: "created"
1989
- };
1990
- else throw new Error(`Cloudflare Durable Object export ${name} has no storage type.`);
2049
+ else if (value.state === "renamed" || value.state === "transferred") {
2050
+ const renamed = value.state === "renamed";
2051
+ result[name] = {
2052
+ type: "durable-object",
2053
+ state: value.state,
2054
+ [renamed ? "renamed_to" : "transferred_to"]: requiredString(value[renamed ? "renamedTo" : "transferredTo"], name)
2055
+ };
2056
+ } else if (value.storage === "sqlite" || value.storage === "legacy-kv") {
2057
+ if (value.state !== void 0 && !["created", "expecting-transfer"].includes(String(value.state))) throw new Error(`Cloudflare Durable Object export ${name} has an unsupported state.`);
2058
+ result[name] = {
2059
+ type: "durable-object",
2060
+ storage: value.storage,
2061
+ state: value.state ?? "created",
2062
+ ...value.container === void 0 ? {} : { container: requiredString(value.container, name) },
2063
+ ...value.state === "expecting-transfer" ? { transfer_from: requiredString(value.transferFrom, name) } : {}
2064
+ };
2065
+ } else throw new Error(`Cloudflare Durable Object export ${name} has no storage type.`);
2066
+ }
2067
+ for (const binding of bindings) {
2068
+ if (binding.type !== "workflow") continue;
2069
+ const localExport = workflows.get(String(binding.workflow_name));
2070
+ if (localExport !== void 0 && (binding.script_name !== void 0 || binding.class_name !== localExport)) throw new Error(`Cloudflare Workflow binding ${binding.name} conflicts with its local export.`);
1991
2071
  }
1992
2072
  return result;
1993
2073
  }
2074
+ async function preflightWorkflowExports(options) {
2075
+ for (const raw of Object.values(options.exports ?? {})) {
2076
+ const entry = object(raw);
2077
+ if (entry?.type !== "workflow") continue;
2078
+ const name = requiredString(entry.name, "Workflow name");
2079
+ let workflow;
2080
+ try {
2081
+ workflow = await apiResult(`${options.accountUrl}/workflows/${encodeURIComponent(name)}`, options.token, "GET", void 0, options.verifyUploader);
2082
+ } catch (cause) {
2083
+ if (cause instanceof CloudflareApiError && cause.status === 404 && cause.code === 10200) continue;
2084
+ throw cause;
2085
+ }
2086
+ if (workflow.name !== name || workflow.script_name !== options.workerName || typeof workflow.class_name !== "string" || !workflow.class_name) throw new Error(`Cloudflare Workflow ${name} is not owned by this Worker.`);
2087
+ }
2088
+ }
1994
2089
  function assetConfigFromOutput(value) {
1995
2090
  if (value === void 0) return;
1996
2091
  const config = object(value);
@@ -2027,11 +2122,211 @@ async function apiResult(url, token, method, body, verifyUploader, timeout = 12e
2027
2122
  if (!response.ok || parsed?.success !== true || !result) {
2028
2123
  const error = Array.isArray(parsed?.errors) ? object(parsed.errors[0]) : null;
2029
2124
  const code = Number.isSafeInteger(error?.code) ? ` [code: ${error.code}]` : "";
2030
- const message = typeof error?.message === "string" ? ` ${error.message}` : "";
2031
- throw new Error(`Cloudflare version upload failed (${response.status})${code}.${message}`);
2125
+ throw new CloudflareApiError(`Cloudflare version upload failed (${response.status})${code}.`, response.status, typeof error?.code === "number" ? error.code : void 0);
2032
2126
  }
2033
2127
  return result;
2034
2128
  }
2129
+ function assertContainerApplicationIdentity(application, plan, namespace) {
2130
+ if (application.id !== namespace || application.name !== plan.name || application.scheduling_policy !== "durable_object" || object(application.durable_objects)?.namespace_id !== namespace) throw new Error(`Cloudflare Container ${plan.name} application identity does not match the uploaded namespace.`);
2131
+ }
2132
+ function assertContainerApplicationSettings(application, plan) {
2133
+ const desired = object(plan.settings.configuration) ?? {};
2134
+ const current = object(application.configuration) ?? {};
2135
+ const changedConfiguration = Object.entries(desired).some(([key, value]) => {
2136
+ if (key === "wrangler_ssh") {
2137
+ const ssh = object(current.wrangler_ssh) ?? {};
2138
+ const expected = object(value) ?? {};
2139
+ return !isDeepStrictEqual(ssh.enabled ?? false, expected.enabled) || expected.enabled === true && expected.port !== void 0 && !isDeepStrictEqual(ssh.port, expected.port);
2140
+ }
2141
+ if (key === "experimental_flags" && Array.isArray(value)) {
2142
+ const flags = current.experimental_flags ?? [];
2143
+ return !Array.isArray(flags) || !isDeepStrictEqual([...flags].sort(), [...value].sort());
2144
+ }
2145
+ return !isDeepStrictEqual(current[key] ?? [], value);
2146
+ });
2147
+ const logs = object(object(plan.settings.observability)?.logs);
2148
+ const currentLogs = object(object(application.observability)?.logs);
2149
+ if (changedConfiguration || logs !== null && !isDeepStrictEqual(currentLogs?.enabled ?? false, logs.enabled)) throw new Error(`Cloudflare Container ${plan.name} settings differ from the live application. Version-aware deployment cannot safely change these shared settings. Keep the existing Container settings in your configuration, or update this Container explicitly in the Cloudflare dashboard before retrying.`);
2150
+ }
2151
+ async function preflightContainerApplications(plans, bindings, accountUrl, workerName, token, verifyUploader) {
2152
+ for (const plan of plans) {
2153
+ const matches = bindings.map(object).filter((binding) => binding?.type === "durable_object_namespace" && binding.class_name === plan.className && (binding.script_name === void 0 || binding.script_name === workerName) && binding.environment === void 0 && binding.dispatch_namespace === void 0 && binding.namespace_id !== void 0);
2154
+ const namespaces = new Set(matches.map((binding) => binding.namespace_id));
2155
+ if (namespaces.size > 1) throw new Error(`Cloudflare Container ${plan.name} active namespace was ambiguous.`);
2156
+ const namespace = [...namespaces][0];
2157
+ if (namespace === void 0) {
2158
+ if (Object.keys(plan.images).length === 0) throw new Error(`Cloudflare Container ${plan.name} has no images and no verified existing namespace application.`);
2159
+ continue;
2160
+ }
2161
+ if (typeof namespace !== "string" || !/^[a-f0-9]{32}$/.test(namespace)) throw new Error("The active Container namespace ID was invalid.");
2162
+ try {
2163
+ const application = await apiResult(`${accountUrl}/containers/applications/${encodeURIComponent(namespace)}`, token, "GET", void 0, verifyUploader);
2164
+ assertContainerApplicationIdentity(application, plan, namespace);
2165
+ assertContainerApplicationSettings(application, plan);
2166
+ } catch (cause) {
2167
+ if (!(cause instanceof CloudflareApiError) || cause.status !== 404 || Object.keys(plan.images).length === 0) throw cause;
2168
+ }
2169
+ }
2170
+ }
2171
+ function containerPlansFromOutput(containers, exports, bindings) {
2172
+ const plans = [];
2173
+ for (const { config } of containers) {
2174
+ if (config.schedulingPolicy !== "durable-object") throw new Error("Scheduler-managed Cloudflare Containers cannot be uploaded with pinned secrets.");
2175
+ const classes = Object.entries(exports ?? {}).filter(([, value]) => object(value)?.container === config.name);
2176
+ if (classes.length !== 1) throw new Error(`Cloudflare Container ${config.name} needs exactly one local SQLite export.`);
2177
+ const [className, raw] = classes[0];
2178
+ const declaration = object(raw);
2179
+ const localBindings = bindings.filter((binding) => binding.type === "durable_object_namespace" && binding.class_name === className && binding.script_name === void 0);
2180
+ if (declaration.storage !== "sqlite" || localBindings.length === 0) throw new Error(`Cloudflare Container ${config.name} needs a local SQLite Durable Object binding.`);
2181
+ const unsafe = object(config.unsafe);
2182
+ const configuration = object(unsafe?.configuration);
2183
+ if (unsafe && Object.keys(unsafe).some((key) => key !== "configuration") || configuration && Object.keys(configuration).some((key) => key !== "experimental_flags")) throw new Error(`Cloudflare Container ${config.name} has an unsupported setting.`);
2184
+ const settings = {
2185
+ ...configuration || config.ssh || config.authorizedKeys ? { configuration: {
2186
+ ...configuration ?? {},
2187
+ ...config.ssh ? { wrangler_ssh: config.ssh } : {},
2188
+ ...config.authorizedKeys ? { authorized_keys: config.authorizedKeys.map(({ name, publicKey }) => ({
2189
+ name,
2190
+ public_key: publicKey
2191
+ })) } : {}
2192
+ } } : {},
2193
+ ...config.observability ? { observability: { logs: { enabled: config.observability.enabled === true || config.observability.logs?.enabled === true } } } : {}
2194
+ };
2195
+ plans.push({
2196
+ name: config.name,
2197
+ className,
2198
+ images: config.images ?? {},
2199
+ settings
2200
+ });
2201
+ }
2202
+ for (const [name, raw] of Object.entries(exports ?? {})) {
2203
+ const container = object(raw)?.container;
2204
+ if (container !== void 0 && !plans.some((plan) => plan.name === container && plan.className === name)) throw new Error(`Cloudflare export ${name} references a Container outside the verified output.`);
2205
+ }
2206
+ return plans;
2207
+ }
2208
+ async function dockerImageDigest(image, timeout, verifyUploader) {
2209
+ if (!verifyUploader()) throw new Error("The Cloudflare uploader changed during deployment.");
2210
+ const stdout = await new Promise((resolve, reject) => {
2211
+ execFile(process.env.WRANGLER_DOCKER_BIN ?? "docker", [
2212
+ "image",
2213
+ "inspect",
2214
+ "--format",
2215
+ "{{json .RepoDigests}}",
2216
+ image
2217
+ ], {
2218
+ encoding: "utf8",
2219
+ timeout,
2220
+ maxBuffer: 1048576,
2221
+ windowsHide: true
2222
+ }, (error, stdout) => error ? reject(/* @__PURE__ */ new Error("Cloudflare Container image digest could not be verified after push.")) : resolve(stdout));
2223
+ });
2224
+ const digests = JSON.parse(stdout);
2225
+ const repository = image.replace(/:[^/:]+$/, "");
2226
+ const matching = Array.isArray(digests) ? digests.filter((digest) => typeof digest === "string" && digest.startsWith(`${repository}@sha256:`) && /^[a-f0-9]{64}$/.test(digest.slice(digest.lastIndexOf(":") + 1))) : [];
2227
+ if (matching.length !== 1) throw new Error("Cloudflare Container image digest could not be verified after push.");
2228
+ return matching[0];
2229
+ }
2230
+ async function prepareContainerMetadata(options) {
2231
+ if (options.plans.length === 0) return [];
2232
+ const deadline = Date.now() + 9e5;
2233
+ const remaining = () => {
2234
+ const duration = deadline - Date.now();
2235
+ if (duration <= 0) throw new Error("Cloudflare Container image preparation timed out.");
2236
+ return duration;
2237
+ };
2238
+ const prepared = /* @__PURE__ */ new Map();
2239
+ const result = [];
2240
+ for (const plan of options.plans) {
2241
+ const images = {};
2242
+ for (const [name, raw] of Object.entries(plan.images)) {
2243
+ const source = object(raw);
2244
+ const reference = requiredString(source.localReference ?? source.reference, `Container ${plan.name} image`);
2245
+ let image = prepared.get(reference);
2246
+ if (!image) {
2247
+ image = reference;
2248
+ if (source.localReference !== void 0) {
2249
+ if (!options.runCf || !options.verifyUploader()) throw new Error("The verified Cloudflare Container image uploader is unavailable.");
2250
+ const pushed = await options.runCf([
2251
+ "containers",
2252
+ "push",
2253
+ "--tag",
2254
+ reference
2255
+ ], {
2256
+ cwd: options.directory,
2257
+ capture: true,
2258
+ timeoutMs: remaining(),
2259
+ maxOutputBytes: 16777216
2260
+ });
2261
+ if (pushed.status !== 0 || pushed.signal || pushed.error) throw new Error("Cloudflare Container image push failed.");
2262
+ const matches = [...pushed.stdout.matchAll(/Pushed image:\s*(\S+)/g)];
2263
+ const remote = matches.length === 1 ? matches[0][1] : "";
2264
+ if (!remote.startsWith(`registry.cloudflare.com/${options.accountId}/`) || [...remote].some((character) => character.charCodeAt(0) <= 32 || character.charCodeAt(0) === 127)) throw new Error("Cloudflare Container image push did not return the verified account registry.");
2265
+ image = await dockerImageDigest(remote, Math.min(12e4, remaining()), options.verifyUploader);
2266
+ }
2267
+ for (;;) {
2268
+ const ready = await apiResult(`${options.accountUrl}/containers/image-preparations`, options.token, "POST", { image }, options.verifyUploader, Math.min(12e4, remaining()));
2269
+ if (ready.image !== image) throw new Error("Cloudflare Container image preparation returned another image.");
2270
+ if (ready.status === "ready") break;
2271
+ if (ready.status !== "pending") throw new Error("Cloudflare Container image preparation failed or returned an unsupported state.");
2272
+ await setTimeout$1(Math.min(2e3, remaining()));
2273
+ }
2274
+ prepared.set(reference, image);
2275
+ }
2276
+ images[name] = image;
2277
+ }
2278
+ result.push({
2279
+ name: plan.name,
2280
+ class_name: plan.className,
2281
+ ...Object.keys(images).length ? { images } : {}
2282
+ });
2283
+ }
2284
+ return result;
2285
+ }
2286
+ async function ensureContainerApplications(options) {
2287
+ if (options.plans.length === 0) return;
2288
+ const deadline = Date.now() + 6e4;
2289
+ let version;
2290
+ for (;;) try {
2291
+ version = await apiResult(`${options.accountUrl}/workers/scripts/${encodeURIComponent(options.workerName)}/versions/${encodeURIComponent(options.versionId)}`, options.token, "GET", void 0, options.verifyUploader, Math.max(1, Math.min(3e4, deadline - Date.now())));
2292
+ break;
2293
+ } catch (cause) {
2294
+ if (!(cause instanceof CloudflareApiError) || cause.code !== 100146 || Date.now() >= deadline) throw cause;
2295
+ await setTimeout$1(Math.min(2e3, deadline - Date.now()));
2296
+ }
2297
+ const versionBindings = object(version.resources)?.bindings;
2298
+ if (version.id !== options.versionId || !Array.isArray(versionBindings)) throw new Error("The uploaded Container namespace could not be verified.");
2299
+ const metadata = object(object(version.resources)?.script_runtime)?.containers;
2300
+ if (!Array.isArray(metadata) || metadata.length !== options.containers.length || options.containers.some((expected) => {
2301
+ const matching = metadata.map(object).filter((entry) => entry !== null && entry.name === expected.name && entry.class_name === expected.class_name);
2302
+ return matching.length !== 1 || !isDeepStrictEqual(matching[0].images ?? {}, expected.images ?? {});
2303
+ })) throw new Error("The uploaded Worker version did not retain the verified Container image metadata.");
2304
+ for (const plan of options.plans) {
2305
+ const matches = versionBindings.map(object).filter((binding) => binding?.type === "durable_object_namespace" && binding.class_name === plan.className && (binding.script_name === void 0 || binding.script_name === options.workerName) && binding.environment === void 0 && binding.dispatch_namespace === void 0);
2306
+ const namespaces = new Set(matches.map((binding) => binding.namespace_id));
2307
+ if (matches.length === 0 || namespaces.size !== 1) throw new Error(`Cloudflare Container ${plan.name} has no unambiguous uploaded local namespace.`);
2308
+ const namespace = requiredString([...namespaces][0], `Container ${plan.name} namespace`);
2309
+ if (!/^[a-f0-9]{32}$/.test(namespace)) throw new Error("The uploaded Container namespace ID was invalid.");
2310
+ const url = `${options.accountUrl}/containers/applications/${encodeURIComponent(namespace)}`;
2311
+ let application;
2312
+ try {
2313
+ application = await apiResult(url, options.token, "GET", void 0, options.verifyUploader);
2314
+ } catch (cause) {
2315
+ if (!(cause instanceof CloudflareApiError) || cause.status !== 404) throw cause;
2316
+ application = await apiResult(`${options.accountUrl}/containers/applications`, options.token, "POST", {
2317
+ name: plan.name,
2318
+ scheduling_policy: "durable_object",
2319
+ durable_objects: { namespace_id: namespace },
2320
+ ...plan.settings
2321
+ }, options.verifyUploader);
2322
+ }
2323
+ assertContainerApplicationIdentity(application, plan, namespace);
2324
+ assertContainerApplicationSettings(application, plan);
2325
+ const confirmed = await apiResult(url, options.token, "GET", void 0, options.verifyUploader);
2326
+ assertContainerApplicationIdentity(confirmed, plan, namespace);
2327
+ assertContainerApplicationSettings(confirmed, plan);
2328
+ }
2329
+ }
2035
2330
  async function uploadAssets(options) {
2036
2331
  if (!options.output.assetsDirectory) return;
2037
2332
  const directory = getWorkerAssetsDir(options.output.directory);
@@ -2094,7 +2389,8 @@ async function uploadAssets(options) {
2094
2389
  async function uploadPinnedCloudflareVersion(options) {
2095
2390
  const activeSecrets = options.output.activeSecrets;
2096
2391
  if (!activeSecrets) throw new Error("The active Worker secret source was not verified.");
2097
- const config = (await readBuildOutput(options.output.directory)).workers.default.config;
2392
+ const built = await readBuildOutput(options.output.directory);
2393
+ const config = built.workers.default.config;
2098
2394
  if (config.name !== options.workerName || !config.manifest || config.manifest.type !== "complete") throw new Error("The verified Cloudflare Worker output changed before upload.");
2099
2395
  const unsupported = Object.keys(config).filter((key) => !(/* @__PURE__ */ new Set([
2100
2396
  "name",
@@ -2118,6 +2414,8 @@ async function uploadPinnedCloudflareVersion(options) {
2118
2414
  const explicitSecrets = options.secretsFilePath ? object(JSON.parse(readFileSync(options.secretsFilePath, "utf8"))) : {};
2119
2415
  if (!explicitSecrets) throw new Error("The explicit Cloudflare secrets file was not readable.");
2120
2416
  const bindings = bindingsFromOutput(config.env ?? {}, options.workerName, activeSecrets, explicitSecrets);
2417
+ const exports = exportsFromOutput(config.exports, bindings);
2418
+ const containerPlans = containerPlansFromOutput(built.containers, exports, bindings);
2121
2419
  const active = await apiResult(`${options.apiBaseUrl.replace(/\/$/, "")}/accounts/${encodeURIComponent(options.accountId)}/workers/workers/${encodeURIComponent(options.workerName)}/versions/${encodeURIComponent(activeSecrets.versionId)}`, options.token, "GET", void 0, options.verifyUploader);
2122
2420
  if (active.id !== activeSecrets.versionId || !Array.isArray(active.bindings)) throw new Error("The active Cloudflare Worker bindings could not be verified.");
2123
2421
  const activeBindingNames = /* @__PURE__ */ new Set();
@@ -2137,7 +2435,6 @@ async function uploadPinnedCloudflareVersion(options) {
2137
2435
  }
2138
2436
  }
2139
2437
  if (activeSecrets.bindings.some(({ name }) => !activeBindingNames.has(name))) throw new Error("The active Cloudflare Worker secrets changed before upload.");
2140
- const exports = exportsFromOutput(config.exports);
2141
2438
  const assetConfig = assetConfigFromOutput(config.assets);
2142
2439
  if (bindings.some((binding) => binding.type === "assets") && !options.output.assetsDirectory) throw new Error("The Cloudflare Worker has an assets binding without verified assets.");
2143
2440
  const modules = [];
@@ -2151,6 +2448,23 @@ async function uploadPinnedCloudflareVersion(options) {
2151
2448
  });
2152
2449
  }
2153
2450
  const accountUrl = `${options.apiBaseUrl.replace(/\/$/, "")}/accounts/${encodeURIComponent(options.accountId)}`;
2451
+ await preflightWorkflowExports({
2452
+ exports,
2453
+ accountUrl,
2454
+ workerName: options.workerName,
2455
+ token: options.token,
2456
+ verifyUploader: options.verifyUploader
2457
+ });
2458
+ await preflightContainerApplications(containerPlans, active.bindings, accountUrl, options.workerName, options.token, options.verifyUploader);
2459
+ const containers = await prepareContainerMetadata({
2460
+ plans: containerPlans,
2461
+ directory: options.output.directory,
2462
+ accountUrl,
2463
+ accountId: options.accountId,
2464
+ token: options.token,
2465
+ verifyUploader: options.verifyUploader,
2466
+ runCf: options.runCf
2467
+ });
2154
2468
  const jwt = await uploadAssets({
2155
2469
  output: options.output,
2156
2470
  accountUrl,
@@ -2165,14 +2479,30 @@ async function uploadPinnedCloudflareVersion(options) {
2165
2479
  compatibility_flags: config.compatibilityFlags,
2166
2480
  bindings,
2167
2481
  ...exports ? { exports } : {},
2482
+ ...containers.length ? { containers } : {},
2168
2483
  ...jwt ? { assets: {
2169
2484
  jwt,
2170
2485
  ...assetConfig ? { config: assetConfig } : {}
2171
2486
  } } : {},
2172
2487
  annotations: { "workers/message": options.message ?? "" }
2173
2488
  }, options.verifyUploader);
2489
+ const versionId = uploaded.id;
2490
+ if (typeof versionId !== "string" || !/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i.test(versionId)) throw new Error("Cloudflare accepted the Worker upload but returned an invalid version identity. Read back the Worker deployment before retrying.");
2491
+ try {
2492
+ await ensureContainerApplications({
2493
+ plans: containerPlans,
2494
+ containers,
2495
+ accountUrl,
2496
+ workerName: options.workerName,
2497
+ versionId,
2498
+ token: options.token,
2499
+ verifyUploader: options.verifyUploader
2500
+ });
2501
+ } catch {
2502
+ throw new Error(`Cloudflare Worker version ${versionId} was uploaded${options.atomic ? " and may already be published" : ""}, but its Container applications could not be verified. Read back the Worker deployment and Container identities before retrying.`);
2503
+ }
2174
2504
  return {
2175
- id: requiredString(uploaded.id, "Worker Version ID"),
2505
+ id: versionId,
2176
2506
  urls: Array.isArray(uploaded.urls) ? uploaded.urls.filter((url) => typeof url === "string") : []
2177
2507
  };
2178
2508
  }
@@ -2185,10 +2515,6 @@ function failed(message) {
2185
2515
  stderr: message
2186
2516
  };
2187
2517
  }
2188
- function flag(args, name) {
2189
- const index = args.indexOf(name);
2190
- return index < 0 ? void 0 : args[index + 1];
2191
- }
2192
2518
  function parseJson(value) {
2193
2519
  try {
2194
2520
  return JSON.parse(value);
@@ -2257,15 +2583,12 @@ function createCfResourceVerifier(options) {
2257
2583
  const name = identifier("name");
2258
2584
  if (!name) return failed(`Cloudflare Queue binding ${bindingName} has no name.`);
2259
2585
  if (!options.verifyCompatibilityRunner()) return failed("The Cloudflare resource verifier changed during deployment.");
2260
- const found = await Promise.resolve(options.runWrangler([
2261
- "queues",
2262
- "info",
2586
+ const found = await Promise.resolve(options.runWrangler({
2587
+ type: "queues.info",
2263
2588
  name,
2264
- "-c",
2265
- options.configPath,
2266
- "--env="
2267
- ], { capture: true }));
2268
- if (found.status !== 0 || found.signal || found.error || found.stdout.trim() !== `Queue Name: ${name}`) return failed(`Cloudflare Queue ${name} is unavailable; refusing automatic provisioning.`);
2589
+ configPath: options.configPath
2590
+ }, { capture: true }));
2591
+ if (found.status !== 0 || found.signal || found.error || !record(found.data) || found.data.name !== name) return failed(`Cloudflare Queue ${name} is unavailable; refusing automatic provisioning.`);
2269
2592
  continue;
2270
2593
  }
2271
2594
  const resource = value.type === "r2" ? {
@@ -2314,7 +2637,7 @@ function createCfResourceVerifier(options) {
2314
2637
  cwd: directory,
2315
2638
  capture: true
2316
2639
  });
2317
- const body = parseJson(result.stdout);
2640
+ const body = result.data ?? parseJson(result.stdout);
2318
2641
  if (result.status !== 0 || result.signal || result.error || !record(body) || body[resource.response] !== name) return failed(`Cloudflare ${resource.kind} ${name} is unavailable; refusing automatic provisioning.`);
2319
2642
  }
2320
2643
  return {
@@ -2339,6 +2662,8 @@ function createCfCommand(options) {
2339
2662
  if (env.HTTP_PROXY || env.HTTPS_PROXY || env.http_proxy || env.https_proxy) env.NODE_USE_ENV_PROXY = "1";
2340
2663
  if (options.token) env.CLOUDFLARE_API_TOKEN = options.token;
2341
2664
  return await new Promise((resolve) => {
2665
+ const bounded = run.timeoutMs !== void 0;
2666
+ const processGroup = bounded && process.platform !== "win32";
2342
2667
  const child = spawn(process.execPath, [cf, ...args], {
2343
2668
  cwd: run.cwd,
2344
2669
  env,
@@ -2348,17 +2673,49 @@ function createCfCommand(options) {
2348
2673
  "ignore",
2349
2674
  "pipe",
2350
2675
  "pipe"
2351
- ]
2676
+ ],
2677
+ detached: processGroup
2352
2678
  });
2353
2679
  const stdout = [];
2354
2680
  const stderr = [];
2355
2681
  let bytes = 0;
2356
2682
  let error = null;
2683
+ const stop = () => {
2684
+ if (processGroup && child.pid) try {
2685
+ process.kill(-child.pid, "SIGKILL");
2686
+ } catch (cause) {
2687
+ if (cause.code !== "ESRCH") child.kill("SIGKILL");
2688
+ }
2689
+ else if (bounded && process.platform === "win32" && child.pid) execFile("taskkill", [
2690
+ "/PID",
2691
+ String(child.pid),
2692
+ "/T",
2693
+ "/F"
2694
+ ], {
2695
+ windowsHide: true,
2696
+ timeout: 1e4
2697
+ }, (cause) => {
2698
+ if (cause) child.kill("SIGKILL");
2699
+ });
2700
+ else child.kill("SIGKILL");
2701
+ };
2702
+ const interrupted = () => {
2703
+ error = "Cloudflare command interrupted.";
2704
+ stop();
2705
+ };
2706
+ if (bounded) {
2707
+ process.on("SIGINT", interrupted);
2708
+ process.on("SIGTERM", interrupted);
2709
+ }
2710
+ const timer = run.timeoutMs === void 0 ? void 0 : setTimeout(() => {
2711
+ error = "Cloudflare command timed out.";
2712
+ stop();
2713
+ }, run.timeoutMs);
2357
2714
  const collect = (chunks, chunk) => {
2358
2715
  bytes += chunk.length;
2359
- if (bytes > 268435456) {
2360
- error = "Cloudflare command output exceeded 256 MiB.";
2361
- child.kill();
2716
+ if (bytes > (run.maxOutputBytes ?? 268435456)) {
2717
+ error = "Cloudflare command output exceeded its verified limit.";
2718
+ stop();
2362
2719
  } else chunks.push(chunk);
2363
2720
  };
2364
2721
  child.stdout.on("data", (chunk) => collect(stdout, chunk));
@@ -2367,10 +2724,23 @@ function createCfCommand(options) {
2367
2724
  error = cause.message;
2368
2725
  });
2369
2726
  child.on("close", (status, signal) => {
2727
+ clearTimeout(timer);
2728
+ if (bounded) {
2729
+ process.off("SIGINT", interrupted);
2730
+ process.off("SIGTERM", interrupted);
2731
+ }
2732
+ const output = Buffer.concat(stdout).toString("utf8");
2733
+ const diagnostic = Buffer.concat(stderr).toString("utf8");
2734
+ const completed = typeof status === "number" && signal == null && error == null;
2735
+ const code = completed ? (output + "\n" + diagnostic).match(/\[(?:code:\s*)?(\d{4,})\]/)?.[1] : void 0;
2736
+ const missingUploadTarget = completed && status !== 0 && code === void 0 && args[0] === "workers" && args[1] === "versions" && args[2] === "create" && (output + "\n" + diagnostic).replace(/\s+/g, " ").includes("You cannot upload a new version of a Worker that does not yet exist.");
2370
2737
  resolve({
2371
2738
  status: typeof status === "number" ? status : 1,
2372
- stdout: Buffer.concat(stdout).toString("utf8"),
2373
- stderr: Buffer.concat(stderr).toString("utf8").replace(/\[(\d{4,})\]/g, "[code: $1]"),
2739
+ stdout: output,
2740
+ stderr: diagnostic,
2741
+ ...code ? { errorCode: Number(code) } : {},
2742
+ ...missingUploadTarget ? { failure: "not-found" } : {},
2743
+ ...completed && status !== 0 ? { errors: [{ message: output + "\n" + diagnostic }] } : {},
2374
2744
  signal,
2375
2745
  error
2376
2746
  });
@@ -2384,10 +2754,31 @@ function createCfCommand(options) {
2384
2754
  */
2385
2755
  function createCfVersionRunner(options) {
2386
2756
  const { output, workerName, runCf } = options;
2387
- const run = async (args, capture, cwd = output.directory) => await runCf(args, {
2388
- cwd,
2389
- capture
2390
- });
2757
+ const run = async (args, capture, cwd = output.directory) => {
2758
+ const result = await runCf(args, {
2759
+ cwd,
2760
+ capture
2761
+ });
2762
+ return {
2763
+ ...result,
2764
+ data: result.data ?? parseJson(result.stdout)
2765
+ };
2766
+ };
2767
+ const uploadedResult = (result) => {
2768
+ if (result.status !== 0 || result.signal || result.error) return result;
2769
+ if (record(result.data) && typeof result.data.versionId === "string") return result;
2770
+ const versionId = result.stdout.match(/(?:Worker|Current) Version ID:\s*([0-9a-f]{8}-[0-9a-f-]{27})/i)?.[1];
2771
+ const previewUrl = result.stdout.match(/Version Preview(?: Alias)? URL:\s*(https:\/\/\S+)/i)?.[1];
2772
+ const workersDevUrl = result.stdout.match(/(https:\/\/[a-z0-9.-]+\.workers\.dev(?:\/[^\s]*)?)/i)?.[1];
2773
+ return {
2774
+ ...result,
2775
+ data: versionId ? {
2776
+ versionId,
2777
+ ...previewUrl ? { previewUrl } : {},
2778
+ ...workersDevUrl ? { workersDevUrl } : {}
2779
+ } : void 0
2780
+ };
2781
+ };
2391
2782
  const verifyResources = async (directory) => {
2392
2783
  try {
2393
2784
  return await options.verifyResources(directory);
@@ -2395,7 +2786,7 @@ function createCfVersionRunner(options) {
2395
2786
  return failed("Cloudflare resources could not be verified before upload.");
2396
2787
  }
2397
2788
  };
2398
- const uploadPinnedVersion = async (args, atomic) => {
2789
+ const uploadPinnedVersion = async (operation, atomic) => {
2399
2790
  if (!options.pinnedUpload || !options.pinnedUpload.verifyUploader()) return failed("The version-aware Cloudflare uploader is unavailable.");
2400
2791
  const token = await options.pinnedUpload.getToken();
2401
2792
  if (!token) return failed("Cloudflare session has no bearer for version-aware upload.");
@@ -2406,23 +2797,28 @@ function createCfVersionRunner(options) {
2406
2797
  apiBaseUrl: options.pinnedUpload.apiBaseUrl,
2407
2798
  workerName,
2408
2799
  token,
2409
- ...flag(args, "--message") ? { message: flag(args, "--message") } : {},
2410
- ...flag(args, "--secrets-file") ? { secretsFilePath: flag(args, "--secrets-file") } : {},
2800
+ ...operation.message ? { message: operation.message } : {},
2801
+ ...operation.secretsFilePath ? { secretsFilePath: operation.secretsFilePath } : {},
2411
2802
  atomic,
2412
- verifyUploader: options.pinnedUpload.verifyUploader
2803
+ verifyUploader: options.pinnedUpload.verifyUploader,
2804
+ runCf
2413
2805
  });
2414
2806
  const preview = version.urls.find((url) => /^https:\/\//.test(url));
2415
2807
  return {
2416
2808
  status: 0,
2417
- stdout: `Worker Version ID: ${version.id}\n${preview ? `Version Preview URL: ${preview}\n` : ""}`,
2809
+ stdout: "",
2810
+ data: {
2811
+ versionId: version.id,
2812
+ ...preview ? { previewUrl: preview } : {}
2813
+ },
2418
2814
  stderr: ""
2419
2815
  };
2420
2816
  } catch (cause) {
2421
2817
  return failed(cause instanceof Error ? cause.message : "Cloudflare version upload failed.");
2422
2818
  }
2423
2819
  };
2424
- return async (args, command) => {
2425
- if (args[0] === "deployments" && args[1] === "status") {
2820
+ return async (operation, command) => {
2821
+ if (operation.type === "deployments.status") {
2426
2822
  const result = await run([
2427
2823
  "workers",
2428
2824
  "deployments",
@@ -2430,17 +2826,20 @@ function createCfVersionRunner(options) {
2430
2826
  "--worker",
2431
2827
  workerName
2432
2828
  ], true);
2433
- if (result.status !== 0) return result;
2434
- const body = parseJson(result.stdout);
2829
+ if (result.status !== 0 || result.signal || result.error) return result;
2830
+ const body = result.data;
2435
2831
  const deployments = record(body) ? body.deployments : void 0;
2436
- if (Array.isArray(deployments) && deployments.length === 0) return failed("The Cloudflare Worker has no deployments.");
2832
+ if (Array.isArray(deployments) && deployments.length === 0) return {
2833
+ ...failed("The Cloudflare Worker has no deployments."),
2834
+ errorCode: 10007
2835
+ };
2437
2836
  if (!Array.isArray(deployments) || !record(deployments[0])) return failed("The Cloudflare Worker deployment was not readable.");
2438
2837
  return {
2439
2838
  ...result,
2440
- stdout: JSON.stringify(deployments[0])
2839
+ data: deployments[0]
2441
2840
  };
2442
2841
  }
2443
- if (args[0] === "versions" && args[1] === "list") {
2842
+ if (operation.type === "versions.list") {
2444
2843
  const pageSize = 10;
2445
2844
  const readPage = async (page) => {
2446
2845
  const result = await run([
@@ -2454,19 +2853,20 @@ function createCfVersionRunner(options) {
2454
2853
  "--page",
2455
2854
  String(page)
2456
2855
  ], true);
2457
- const body = parseJson(result.stdout);
2856
+ const body = result.data;
2458
2857
  return {
2459
2858
  result,
2460
2859
  versions: Array.isArray(body) && body.length <= pageSize && body.every((item) => record(item) && typeof item.id === "string" && Number.isSafeInteger(item.number)) && new Set(body.map((item) => item.number)).size === body.length ? body : null
2461
2860
  };
2462
2861
  };
2463
2862
  const first = await readPage(1);
2464
- if (first.result.status !== 0) return first.result;
2863
+ if (first.result.status !== 0 || first.result.signal || first.result.error) return first.result;
2465
2864
  if (!first.versions) return failed("The Cloudflare Worker version list was not readable.");
2466
2865
  const versions = [...first.versions];
2467
2866
  if (versions.length === pageSize) {
2468
2867
  const second = await readPage(2);
2469
- if (second.result.status !== 0 || !second.versions) return failed("The Cloudflare Worker version history was not readable.");
2868
+ if (second.result.status !== 0 || second.result.signal || second.result.error) return second.result;
2869
+ if (!second.versions) return failed("The Cloudflare Worker version history was not readable.");
2470
2870
  if (second.versions.length > 0) {
2471
2871
  const firstNumbers = versions.map((item) => item.number);
2472
2872
  const secondNumbers = second.versions.map((item) => item.number);
@@ -2475,7 +2875,8 @@ function createCfVersionRunner(options) {
2475
2875
  let last = Math.max(...secondNumbers);
2476
2876
  for (let page = 3; page <= 1e3 && second.versions.length === pageSize; page++) {
2477
2877
  const next = await readPage(page);
2478
- if (next.result.status !== 0 || !next.versions) return failed("The Cloudflare Worker version history was not readable.");
2878
+ if (next.result.status !== 0 || next.result.signal || next.result.error) return next.result;
2879
+ if (!next.versions) return failed("The Cloudflare Worker version history was not readable.");
2479
2880
  if (next.versions.length === 0) break;
2480
2881
  const numbers = next.versions.map((item) => item.number);
2481
2882
  if (Math.min(...numbers) <= last) return failed("The Cloudflare Worker version history changed during inspection.");
@@ -2489,56 +2890,51 @@ function createCfVersionRunner(options) {
2489
2890
  }
2490
2891
  return {
2491
2892
  ...first.result,
2492
- stdout: JSON.stringify(versions.sort((a, b) => a.number - b.number).slice(-10))
2893
+ data: versions.sort((a, b) => a.number - b.number).slice(-10)
2493
2894
  };
2494
2895
  }
2495
- if (args[0] === "versions" && args[1] === "view" && args[2]) {
2896
+ if (operation.type === "versions.view") {
2496
2897
  const result = await run([
2497
2898
  "workers",
2498
2899
  "versions",
2499
2900
  "get",
2500
- args[2],
2901
+ operation.versionId,
2501
2902
  "--worker-id",
2502
2903
  workerName
2503
2904
  ], true);
2504
- if (result.status !== 0) return result;
2505
- const version = parseJson(result.stdout);
2506
- if (!record(version) || version.id !== args[2] || !Array.isArray(version.bindings)) return failed("The Cloudflare Worker version was not readable.");
2905
+ if (result.status !== 0 || result.signal || result.error) return result;
2906
+ const version = result.data;
2907
+ if (!record(version) || version.id !== operation.versionId || !Array.isArray(version.bindings)) return failed("The Cloudflare Worker version was not readable.");
2507
2908
  return {
2508
2909
  ...result,
2509
- stdout: JSON.stringify({
2910
+ data: {
2510
2911
  ...version,
2511
2912
  resources: { bindings: version.bindings }
2512
- })
2913
+ }
2513
2914
  };
2514
2915
  }
2515
- if (args[0] === "versions" && args[1] === "upload") {
2516
- if (flag(args, "--name") !== workerName || args.includes("--assets") && flag(args, "--assets") !== output.assetsDirectory) return failed("The Cloudflare version upload target changed.");
2916
+ if (operation.type === "versions.upload") {
2917
+ if (operation.workerName !== workerName || operation.assetsDirectory !== void 0 && operation.assetsDirectory !== output.assetsDirectory) return failed("The Cloudflare version upload target changed.");
2517
2918
  const verified = await verifyResources(output.directory);
2518
2919
  if (verified.status !== 0 || verified.signal || verified.error) return verified;
2519
- if (output.activeSecrets) return uploadPinnedVersion(args, false);
2520
- return run([
2920
+ if (output.activeSecrets) return uploadPinnedVersion(operation, false);
2921
+ return uploadedResult(await run([
2521
2922
  "workers",
2522
2923
  "versions",
2523
2924
  "create",
2524
2925
  "--prebuilt",
2525
- ...flag(args, "--message") ? ["--message", flag(args, "--message")] : [],
2526
- ...flag(args, "--secrets-file") ? ["--secrets-file", flag(args, "--secrets-file")] : [],
2527
- ...flag(args, "--preview-alias") ? ["--preview-alias", flag(args, "--preview-alias")] : []
2528
- ], command.capture);
2529
- }
2530
- if (args[0] === "versions" && args[1] === "deploy") {
2531
- if (flag(args, "--name") !== workerName) return failed("The Cloudflare deployment target changed.");
2532
- const flagsStart = args.findIndex((item, index) => index >= 2 && item.startsWith("-"));
2533
- const versions = args.slice(2, flagsStart < 0 ? void 0 : flagsStart);
2534
- if (!versions.every((item) => /^[0-9a-f-]{36}@\d+(?:\.\d+)?$/i.test(item))) return failed("The Cloudflare deployment versions were invalid.");
2535
- const parsed = versions.map((item) => {
2536
- const [version_id, percentage] = item.split("@");
2537
- return {
2538
- version_id,
2539
- percentage: Number(percentage)
2540
- };
2541
- });
2926
+ ...operation.message ? ["--message", operation.message] : [],
2927
+ ...operation.secretsFilePath ? ["--secrets-file", operation.secretsFilePath] : [],
2928
+ ...operation.previewAlias ? ["--preview-alias", operation.previewAlias] : []
2929
+ ], command.capture));
2930
+ }
2931
+ if (operation.type === "versions.deploy") {
2932
+ if (operation.workerName !== workerName) return failed("The Cloudflare deployment target changed.");
2933
+ const parsed = operation.versions.map(({ versionId, percentage }) => ({
2934
+ version_id: versionId,
2935
+ percentage
2936
+ }));
2937
+ if (!parsed.every((version) => /^[0-9a-f-]{36}$/i.test(version.version_id) && Number.isFinite(version.percentage))) return failed("The Cloudflare deployment versions were invalid.");
2542
2938
  if (parsed.length === 0 || new Set(parsed.map((item) => item.version_id)).size !== parsed.length || parsed.some((item) => item.percentage < 0 || item.percentage > 100) || Math.abs(parsed.reduce((sum, item) => sum + item.percentage, 0) - 100) > .001) return failed("The Cloudflare deployment percentages were invalid.");
2543
2939
  return run([
2544
2940
  "workers",
@@ -2550,28 +2946,28 @@ function createCfVersionRunner(options) {
2550
2946
  "percentage",
2551
2947
  "--versions",
2552
2948
  JSON.stringify(parsed),
2553
- ...flag(args, "--message") ? ["--body", JSON.stringify({
2949
+ ...operation.message ? ["--body", JSON.stringify({
2554
2950
  strategy: "percentage",
2555
2951
  versions: parsed,
2556
- annotations: { "workers/message": flag(args, "--message") }
2952
+ annotations: { "workers/message": operation.message }
2557
2953
  })] : []
2558
2954
  ], command.capture);
2559
2955
  }
2560
- if (args[0] === "deploy") {
2561
- if (flag(args, "--name") !== workerName || args.includes("--assets") && flag(args, "--assets") !== output.assetsDirectory) return failed("The Cloudflare bootstrap target changed.");
2956
+ if (operation.type === "worker.deploy") {
2957
+ if (operation.workerName !== workerName || operation.assetsDirectory !== void 0 && operation.assetsDirectory !== output.assetsDirectory) return failed("The Cloudflare bootstrap target changed.");
2562
2958
  const verified = await verifyResources(output.directory);
2563
2959
  if (verified.status !== 0 || verified.signal || verified.error) return verified;
2564
- if (output.activeSecrets) return uploadPinnedVersion(args, true);
2565
- return run([
2960
+ if (output.activeSecrets) return uploadPinnedVersion(operation, true);
2961
+ return uploadedResult(await run([
2566
2962
  "deploy",
2567
2963
  "--prebuilt",
2568
- ...flag(args, "--message") ? ["--message", flag(args, "--message")] : [],
2569
- ...flag(args, "--secrets-file") ? ["--secrets-file", flag(args, "--secrets-file")] : []
2570
- ], command.capture);
2964
+ ...operation.message ? ["--message", operation.message] : [],
2965
+ ...operation.secretsFilePath ? ["--secrets-file", operation.secretsFilePath] : []
2966
+ ], command.capture));
2571
2967
  }
2572
- if (args[0] === "triggers" && args[1] === "deploy") {
2573
- const configPath = flag(args, "-c");
2574
- if (!configPath || flag(args, "--name") !== workerName) return failed("The Cloudflare trigger target changed.");
2968
+ if (operation.type === "triggers.deploy") {
2969
+ const configPath = operation.configPath;
2970
+ if (!configPath || operation.workerName !== workerName) return failed("The Cloudflare trigger target changed.");
2575
2971
  const raw = parseJson(readFileSync(configPath, "utf8"));
2576
2972
  if (!record(raw) || raw.name !== workerName) return failed("The Cloudflare trigger snapshot was not readable.");
2577
2973
  const directory = mkdtempSync(join(tmpdir(), "void-cf-triggers-"));
@@ -2621,12 +3017,12 @@ function createCfVersionRunner(options) {
2621
3017
  });
2622
3018
  }
2623
3019
  }
2624
- if (args[0] === "queues" && args[1] === "consumer" && args[2] === "remove") {
2625
- const queueName = args[3];
2626
- if (!queueName || args[4] !== workerName) return failed("The Cloudflare Queue consumer target changed.");
3020
+ if (operation.type === "queues.consumer.remove") {
3021
+ const queueName = operation.queueName;
3022
+ if (!queueName || operation.workerName !== workerName) return failed("The Cloudflare Queue consumer target changed.");
2627
3023
  const queues = await run(["queues", "list"], true);
2628
- if (queues.status !== 0) return queues;
2629
- const allQueues = parseJson(queues.stdout);
3024
+ if (queues.status !== 0 || queues.signal || queues.error) return queues;
3025
+ const allQueues = queues.data;
2630
3026
  if (!Array.isArray(allQueues)) return failed("The Cloudflare Queue list was not readable.");
2631
3027
  const queue = allQueues.find((item) => record(item) && item.queue_name === queueName);
2632
3028
  if (!record(queue) || typeof queue.queue_id !== "string") return failed(`Cloudflare Queue ${queueName} was not found.`);
@@ -2637,16 +3033,22 @@ function createCfVersionRunner(options) {
2637
3033
  "--queue-id",
2638
3034
  queue.queue_id
2639
3035
  ], true);
2640
- if (consumers.status !== 0) return consumers;
2641
- const allConsumers = parseJson(consumers.stdout);
3036
+ if (consumers.status !== 0 || consumers.signal || consumers.error) return consumers;
3037
+ const allConsumers = consumers.data;
2642
3038
  if (!Array.isArray(allConsumers)) return failed("The Cloudflare Queue consumers were not readable.");
2643
- const matching = allConsumers.filter((item) => record(item) && item.script === workerName);
3039
+ const matching = [];
3040
+ for (const item of allConsumers) {
3041
+ if (!record(item)) return failed("The Cloudflare Queue consumers were not readable.");
3042
+ const name = item.script_name ?? item.script;
3043
+ if (item.script_name !== void 0 && !safePositional(item.script_name) || item.script !== void 0 && !safePositional(item.script) || item.script_name !== void 0 && item.script !== void 0 && item.script_name !== item.script || item.type === "worker" && name === void 0) return failed("The Cloudflare Queue consumer Worker identity was ambiguous.");
3044
+ if (name === workerName) matching.push(item);
3045
+ }
2644
3046
  if (matching.length === 0) return {
2645
3047
  status: 0,
2646
3048
  stdout: "",
2647
3049
  stderr: ""
2648
3050
  };
2649
- if (matching.length !== 1 || !record(matching[0]) || typeof matching[0].consumer_id !== "string") return failed("The Cloudflare Queue consumer identity was ambiguous.");
3051
+ if (matching.length !== 1 || !safePositional(matching[0].consumer_id) || matching[0].type !== void 0 && matching[0].type !== "worker") return failed("The Cloudflare Queue consumer identity was ambiguous.");
2650
3052
  return run([
2651
3053
  "queues",
2652
3054
  "consumers",
@@ -2657,7 +3059,7 @@ function createCfVersionRunner(options) {
2657
3059
  "--force"
2658
3060
  ], command.capture);
2659
3061
  }
2660
- return failed(`Unsupported Cloudflare activation operation: ${args[0] ?? ""} ${args[1] ?? ""}`);
3062
+ return failed(`Unsupported Cloudflare activation operation: ${operation.type}`);
2661
3063
  };
2662
3064
  }
2663
3065
  //#endregion
@@ -2708,13 +3110,13 @@ function clearCloudflareCandidate(root) {
2708
3110
  * Narrow native Cloudflare deploys to app shapes whose emitted Worker artifact
2709
3111
  * the current verifier understands. This runs before any account operation.
2710
3112
  */
2711
- function classifyCloudflareDeployApp(root, voidConfig, options) {
3113
+ function classifyCloudflareDeployApp(root, voidConfig, options, application) {
2712
3114
  if (isNodeTarget(voidConfig.target)) return unsupported([
2713
3115
  `deploy: --platform cloudflare is not supported for the "${voidConfig.target}" target.`,
2714
3116
  "This backend deploys Cloudflare Workers only. Deploy a node/bun/deno build with its",
2715
3117
  "own host/runtime, or use the default Cloudflare target for --platform cloudflare."
2716
3118
  ].join("\n"));
2717
- const framework = detectFramework(root);
3119
+ const framework = application?.framework ?? detectFramework(root);
2718
3120
  if (framework) {
2719
3121
  if (!FRAMEWORK_PRESETS[framework.name]) return unsupported(`deploy: --platform cloudflare has no deployment preset for framework ${framework.name}.`);
2720
3122
  }
@@ -2733,13 +3135,16 @@ function classifyCloudflareDeployApp(root, voidConfig, options) {
2733
3135
  "A full Void app has at least one of: routes/, pages/, middleware, crons/, queues/,",
2734
3136
  "email/, or an SSR entry. Add your server code, or use `void deploy` for a static site."
2735
3137
  ].join("\n"));
2736
- const paths = resolveProjectPaths(root, voidConfig);
2737
3138
  try {
3139
+ const analysis = application ?? analyzeApplicationSync(root, {
3140
+ config: voidConfig,
3141
+ environment: "prod"
3142
+ });
3143
+ const paths = analysis.paths;
2738
3144
  assertNoRootSourceConventionConflicts(paths);
2739
3145
  validateSsrEntry(root, paths);
2740
- scanJobsSync(root, paths.cronsDir);
2741
- syncWranglerDurableObjects(root, scanDurableObjectModules(root));
2742
- syncWranglerWebSockets(root, scanWebSocketRoutesSync(root, paths, "prod"));
3146
+ syncWranglerDurableObjects(root, analysis.durableObjects);
3147
+ syncWranglerWebSockets(root, analysis.websockets);
2743
3148
  } catch (error) {
2744
3149
  return unsupported(`deploy: ${getErrorMessage$1(error)}`);
2745
3150
  }
@@ -2886,7 +3291,7 @@ async function prepareExistingCloudflareDeploy(options) {
2886
3291
  }
2887
3292
  /** Offer the existing destination before the legacy hosted-platform fallback. */
2888
3293
  async function maybeLinkCloudflareDeploy(root, options = {}, deps = {}) {
2889
- if (options.platform || options.backend || options.projectSlug || process.env.VOID_PROJECT || readDeploymentPlatform(root) !== null) return null;
3294
+ if (options.platform || options.projectSlug || process.env.VOID_PROJECT || readDeploymentPlatform(root) !== null) return null;
2890
3295
  const files = [.../* @__PURE__ */ new Set([...[
2891
3296
  "wrangler.jsonc",
2892
3297
  "wrangler.json",
@@ -4029,8 +4434,7 @@ function buildVoidManifest(opts) {
4029
4434
  };
4030
4435
  if (opts.hasEmailHandlers) manifest.emailProtocolVersion = 1;
4031
4436
  if (bindings.needsAuth) manifest.auth = true;
4032
- const hasMigrations = migrations && migrations.length > 0;
4033
- if (bindings.needsD1 || bindings.needsAuth || hasMigrations) {
4437
+ if (needsApplicationDatabase(bindings, migrations?.length ?? 0)) {
4034
4438
  if (resolvedDialect !== "sqlite") manifest.bindings.hyperdrive = ["HYPERDRIVE"];
4035
4439
  else manifest.bindings.d1 = [options?.bindingNames?.d1 ?? "DB"];
4036
4440
  }
@@ -4145,8 +4549,7 @@ function buildFrameworkManifest(opts) {
4145
4549
  assetManifestVersion: 2,
4146
4550
  bindings: {}
4147
4551
  };
4148
- const hasMigrations = opts.migrations && opts.migrations.length > 0;
4149
- if (opts.bindings.needsD1 || opts.bindings.needsAuth || hasMigrations) {
4552
+ if (needsApplicationDatabase(opts.bindings, opts.migrations?.length ?? 0)) {
4150
4553
  if (dialect !== "sqlite") manifest.bindings.hyperdrive = ["HYPERDRIVE"];
4151
4554
  else manifest.bindings.d1 = [po?.bindingNames?.d1 ?? "DB"];
4152
4555
  }
@@ -4395,7 +4798,7 @@ async function generateWrapper(workerDir, workerMain, root, features) {
4395
4798
  let migrationPlanBundle;
4396
4799
  if (features.hasMigrations) {
4397
4800
  const { collectMigrations } = await import("./collect-DLyFDiKW.mjs");
4398
- const { buildMigrationPlanBundle } = await import("./plan-DpuOr14e.mjs");
4801
+ const { buildMigrationPlanBundle } = await import("./plan-D1Q5rf-r.mjs");
4399
4802
  const migrationsDir = readProjectPaths(root).migrationsDir;
4400
4803
  migrations = collectMigrations(migrationsDir);
4401
4804
  migrationPlanBundle = features.migrationDialect === "sqlite" ? buildMigrationPlanBundle(migrations) : void 0;
@@ -4636,7 +5039,7 @@ function generateWrapperSource(renamedEntry, handlersModule, features, migration
4636
5039
  }
4637
5040
  if (features.readiness || features.crons.length > 0 || features.queues.length > 0 || features.hasMigrations) {
4638
5041
  lines.push(" if (request.method === \"GET\" && url.pathname === \"/__void/ready\") {");
4639
- lines.push(" return Response.json({ ready: true });");
5042
+ lines.push(` return Response.json({ ready: true${features.versionMetadataBinding ? `, versionId: env[${JSON.stringify(features.versionMetadataBinding)}]?.id` : ""} }${features.versionMetadataBinding ? ", { headers: { \"cache-control\": \"no-store\" } }" : ""});`);
4640
5043
  lines.push(" }");
4641
5044
  }
4642
5045
  if (features.queues.length > 0) {
@@ -4806,7 +5209,7 @@ let loadedProvisionModule;
4806
5209
  * so a managed deploy never imports it — and thus never loads `proper-lockfile`.
4807
5210
  */
4808
5211
  async function loadProvisionModule() {
4809
- loadedProvisionModule ??= await import("./provision-Fr9pRqce.mjs");
5212
+ loadedProvisionModule ??= await import("./provision-C4ORE7G7.mjs");
4810
5213
  return loadedProvisionModule;
4811
5214
  }
4812
5215
  /**
@@ -4829,7 +5232,7 @@ const log = {
4829
5232
  };
4830
5233
  var DeployEventError = class extends Error {};
4831
5234
  function resolveDeploymentPlatform(root, options) {
4832
- return options?.platform ?? options?.backend ?? readDeploymentPlatform(root) ?? "void";
5235
+ return options?.platform ?? readDeploymentPlatform(root) ?? "void";
4833
5236
  }
4834
5237
  async function generateDeployWrapperIfNeeded(root, workerDir, workerMain, features) {
4835
5238
  if (!needsWrapper(features)) return false;
@@ -4965,7 +5368,7 @@ async function runDeploy(root, options) {
4965
5368
  * `VOID_DEPLOY_PROJECT_ID` is, so it cannot leak into an unrelated child process or a
4966
5369
  * later `void dev` in the same shell.
4967
5370
  *
4968
- * Deliberately NOT set by `runCloudflareDeploy` (`--backend cloudflare`): that flow
5371
+ * Deliberately NOT set by `runCloudflareDeploy` (`--platform cloudflare`): that flow
4969
5372
  * hands the built output to `wrangler deploy` against the user's own account and IS the
4970
5373
  * self-host path the warning is about. (It rejects framework SSR outright today, so no
4971
5374
  * Class A build reaches it either way.)
@@ -4975,6 +5378,7 @@ const MANAGED_DEPLOY_BUILD_ENV = { VOID_DEPLOY_BUILD: "1" };
4975
5378
  function scrubManagedDeployBuildMarkers(env) {
4976
5379
  const { deleteEnvVarCaseAware } = requireProvisionModule();
4977
5380
  deleteEnvVarCaseAware(env, "VOID_DEPLOY_PROJECT_ID");
5381
+ deleteEnvVarCaseAware(env, CLOUDFLARE_PRESERVE_BINDINGS_BUILD_ENV);
4978
5382
  for (const name of Object.keys(MANAGED_DEPLOY_BUILD_ENV)) deleteEnvVarCaseAware(env, name);
4979
5383
  return env;
4980
5384
  }
@@ -5391,14 +5795,14 @@ function reconstructExpectedDurableObjectState(config, root, rootConfig, options
5391
5795
  name: sandbox.binding,
5392
5796
  class_name: sandbox.className
5393
5797
  });
5394
- if (!migrations.some((migration) => migration.tag === "void-sandbox-v2" || Array.isArray(migration.new_sqlite_classes) && migration.new_sqlite_classes.includes(sandbox.className))) migrations.push({
5798
+ if (!hasSandboxSqliteMigration(migrations, sandbox.className)) migrations.push({
5395
5799
  tag: SANDBOX_MIGRATION_TAG,
5396
5800
  new_sqlite_classes: [sandbox.className]
5397
5801
  });
5398
5802
  }
5399
- const paths = resolveProjectPaths(root, config);
5400
- const websocketRoutes = scanWebSocketRoutesSync(root, paths);
5401
- const durableObjects = scanDurableObjectModules(root);
5803
+ const analysis = options.analysis ?? analyzeApplicationSync(root, { config });
5804
+ const websocketRoutes = analysis.allWebsockets;
5805
+ const durableObjects = analysis.durableObjects;
5402
5806
  const live = options.needsLive ? resolveLiveBindingConfig(root, {
5403
5807
  reservedBindings: [...websocketRoutes.map((route) => route.bindingName), ...durableObjects.map((module) => module.bindingName)],
5404
5808
  reservedClasses: [
@@ -5428,11 +5832,11 @@ function reconstructExpectedDurableObjectState(config, root, rootConfig, options
5428
5832
  * chunks from its own `__dirname` (the proxy workers, the experimental-config module), so the
5429
5833
  * whole directory is the trust surface, not just the entry file. Content hash of the bytes, NOT
5430
5834
  * mtime. Resolves the SAME entry {@link createDeployWranglerRunner} spawns (via
5431
- * {@link resolveWranglerArgv}, whose launcher's LAST argv element is the JS entry). Returns
5835
+ * {@link resolveCloudflareOperationArgv}, whose launcher's LAST argv element is the JS entry). Returns
5432
5836
  * `undefined` when the entry cannot be resolved or a file cannot be read, so the caller FAILS
5433
5837
  * CLOSED — it never deploys a binary it could not verify. The resolver is injectable for tests.
5434
5838
  */
5435
- function hashWranglerUploader(root, resolveArgv = resolveWranglerArgv) {
5839
+ function hashWranglerUploader(root, resolveArgv = resolveCloudflareOperationArgv) {
5436
5840
  let entry;
5437
5841
  try {
5438
5842
  const argv = resolveArgv(root);
@@ -6261,42 +6665,30 @@ function assertDistinctKvNamespaces(kvNamespaces, config, configPath, isRealKvId
6261
6665
  /**
6262
6666
  * Compute the DESIRED id/name-bearing Cloudflare resources from SOURCE
6263
6667
  * (pre-build, fail-fast) — the single set shared by the provision and drift
6264
- * paths. Reuses the deploy-time inference primitives directly
6265
- * (`inferProjectBindings` + the scanners + the pg-dialect / `hasRevalidate`
6266
- * signals) rather than `assembleVoidManifest`, which runs only inside the
6267
- * managed `runFullDeploy` branches that need a `PlatformClient`.
6668
+ * paths. Consumes the same application analysis as the plugin and managed
6669
+ * packaging; provider identities and stored resource names are resolved here.
6268
6670
  *
6269
6671
  * Resource NAMES prefer the value already stored in the root wrangler config
6270
6672
  * (so a re-provision reuses the same database/bucket and the provisioner's
6271
6673
  * stored-name identity check passes); otherwise they default off the worker
6272
6674
  * name. Returns a per-binding REASON map used to explain drift.
6273
6675
  */
6274
- async function computeDesiredCloudflareResources(root, voidConfig, configPath) {
6676
+ async function computeDesiredCloudflareResources(root, voidConfig, configPath, application) {
6275
6677
  const { isRealD1Id, isRealKvId } = await loadProvisionModule();
6276
- const paths = resolveProjectPaths(root, voidConfig);
6678
+ const analysis = application ? await completeApplicationAnalysis(application) : await analyzeApplication(root, {
6679
+ config: voidConfig,
6680
+ environment: "prod"
6681
+ });
6682
+ const { paths, bindings, authEnabled, dialect, bindingNames, migrations, needsDatabase } = analysis;
6277
6683
  const config = parseRootWranglerConfig(configPath);
6278
6684
  const workerName = readWranglerTopLevelString(config, "name") ?? deriveWorkerName(root);
6279
- const isFrameworkMode = detectFramework(root) != null;
6280
- const scanDirs = voidConfig.inference?.scanDirs ?? (isFrameworkMode ? FRAMEWORK_SCAN_DIRS : void 0);
6281
- const inferenceOptions = voidConfig.inference?.bindings ? { bindings: voidConfig.inference.bindings } : void 0;
6282
- const bindings = inferProjectBindings(root, {
6283
- ...inferenceOptions,
6284
- paths,
6285
- environment: "prod"
6286
- }, scanDirs);
6287
- const authEnabled = bindings.needsAuth || findVoidAuthConfig(root, paths) !== null || voidConfig.auth !== void 0;
6288
- const dialect = getDatabaseDialect(voidConfig);
6289
- const bindingNames = resolveBindingNames(voidConfig.inference?.bindings);
6290
- const migrations = collectMigrations(paths.migrationsDir);
6291
- const hasMigrations = migrations.length > 0;
6292
6685
  const migrationsDirRelative = paths.sourceDir ? `./${paths.sourceDir}/db/migrations` : "./db/migrations";
6293
6686
  const desired = {
6294
6687
  kvNamespaces: [],
6295
6688
  queues: []
6296
6689
  };
6297
6690
  const reasons = /* @__PURE__ */ new Map();
6298
- const needsDatabase = bindings.needsD1 || authEnabled || hasMigrations;
6299
- const databaseReason = bindings.needsD1 ? "app uses env.DB (D1)" : authEnabled ? "auth requires a D1 database" : "checked-in migrations need a D1 database";
6691
+ const databaseReason = analysis.reasons.database[0];
6300
6692
  if (needsDatabase && dialect === "sqlite") {
6301
6693
  const binding = bindingNames?.d1 ?? "DB";
6302
6694
  const storedName = readStoredResourceName(config, "d1_databases", binding, "database_name");
@@ -6315,7 +6707,7 @@ async function computeDesiredCloudflareResources(root, voidConfig, configPath) {
6315
6707
  binding,
6316
6708
  title: suffixedResourceName(workerName, "kv")
6317
6709
  });
6318
- reasons.set(binding, "app uses env.KV");
6710
+ reasons.set(binding, analysis.reasons.kv[0]);
6319
6711
  }
6320
6712
  if (bindings.needsR2) {
6321
6713
  const binding = bindingNames?.r2 ?? "STORAGE";
@@ -6325,9 +6717,9 @@ async function computeDesiredCloudflareResources(root, voidConfig, configPath) {
6325
6717
  binding,
6326
6718
  bucketName: storedName && !generatedDefault ? storedName : suffixedResourceName(workerName, "storage")
6327
6719
  };
6328
- reasons.set(binding, "app uses env.STORAGE (R2)");
6720
+ reasons.set(binding, analysis.reasons.r2[0]);
6329
6721
  }
6330
- desired.queues = scanQueuesSync(root, paths.queuesDir).map((q) => q.name);
6722
+ desired.queues = analysis.queues.map((q) => q.name);
6331
6723
  if (needsDatabase && dialect !== "sqlite") {
6332
6724
  const binding = "HYPERDRIVE";
6333
6725
  desired.hyperdrive = {
@@ -6337,10 +6729,8 @@ async function computeDesiredCloudflareResources(root, voidConfig, configPath) {
6337
6729
  };
6338
6730
  reasons.set(binding, `app uses ${dialect === "mysql" ? "MySQL" : "PostgreSQL"} (needs a Hyperdrive config)`);
6339
6731
  }
6340
- if (hasRevalidate(collectRevalidate(voidConfig, existsSync(paths.pagesDir) ? await scanPages(root, {
6341
- output: voidConfig.output,
6342
- paths
6343
- }) : null))) {
6732
+ const pageScan = analysis.scan.pages;
6733
+ if (hasRevalidate(collectRevalidate(voidConfig, pageScan))) {
6344
6734
  const binding = "ISR_CACHE";
6345
6735
  desired.kvNamespaces.push({
6346
6736
  binding,
@@ -6349,9 +6739,10 @@ async function computeDesiredCloudflareResources(root, voidConfig, configPath) {
6349
6739
  reasons.set(binding, "routes use `revalidate` (ISR) — needs an ISR_CACHE KV namespace");
6350
6740
  }
6351
6741
  assertDistinctKvNamespaces(desired.kvNamespaces, config, configPath, isRealKvId);
6352
- const expectedCrons = [...new Set(scanJobsSync(root, paths.cronsDir).flatMap((job) => job.crons))];
6353
- const emailHandlers = scanEmailHandlersSync(paths.sourceRoot);
6742
+ const expectedCrons = [...new Set(analysis.jobs.flatMap((job) => job.crons))];
6743
+ const emailHandlers = analysis.email;
6354
6744
  return {
6745
+ analysis,
6355
6746
  desired,
6356
6747
  reasons,
6357
6748
  authEnabled,
@@ -6363,7 +6754,7 @@ async function computeDesiredCloudflareResources(root, voidConfig, configPath) {
6363
6754
  expectedCrons,
6364
6755
  needsEmail: bindings.needsEmail === true || hasEmailHandlers(emailHandlers),
6365
6756
  emailHandlers,
6366
- needsSandbox: isSandboxEnabled(voidConfig) || bindings.needsSandbox,
6757
+ needsSandbox: bindings.needsSandbox,
6367
6758
  needsLive: bindings.needsLive === true
6368
6759
  };
6369
6760
  }
@@ -6717,49 +7108,15 @@ async function checkWranglerMigrationApplySet(configPath, config, binding, migra
6717
7108
  };
6718
7109
  return { ok: true };
6719
7110
  }
6720
- /**
6721
- * Default {@link DeployWranglerRun}: spawns the project-local wrangler REAL CLI
6722
- * (via {@link resolveWranglerArgv}, shell-free) with ONE pinned transport in the
6723
- * child env (via {@link deployChildEnv} — reused provisioner scrub/pin: CF Access +
6724
- * every transport selector incl. `CLOUDFLARE_ENV` + the deploy config-override vars
6725
- * scrubbed, one account + API base pinned), so `whoami`/`migrate`/`deploy` can never
6726
- * consult a different ambient account or endpoint. Noninteractive output is captured so Void
6727
- * controls the terminal; browser login still inherits stdio. `opts.env` layers one more pin
6728
- * over the child env for a single spawn (`auth token` gets
6729
- * `WRANGLER_WRITE_LOGS=false`, see {@link runWranglerAuthToken}).
6730
- */
7111
+ /** Run typed provider operations in the pinned account and API context. */
6731
7112
  function createDeployWranglerRunner(root, accountId, apiBaseUrl) {
6732
- const [command, ...prefix] = resolveWranglerArgv(root);
6733
- return (args, opts) => {
6734
- const childEnv = deployChildEnv(accountId, apiBaseUrl);
6735
- const interactiveLogin = cloudflareAuthNeedsTerminal(args);
6736
- if (!opts.capture || interactiveLogin) childEnv.WRANGLER_LOG = "none";
6737
- if (opts.env) Object.assign(childEnv, opts.env);
6738
- const result = spawnSync(command, [...prefix, ...args], {
6739
- cwd: root,
6740
- stdio: interactiveLogin ? "inherit" : opts.input === void 0 ? [
6741
- "ignore",
6742
- "pipe",
6743
- "pipe"
6744
- ] : [
6745
- "pipe",
6746
- "pipe",
6747
- "pipe"
6748
- ],
6749
- input: opts.input,
6750
- encoding: "utf-8",
6751
- env: childEnv,
6752
- shell: false,
6753
- windowsHide: true
6754
- });
6755
- return {
6756
- status: typeof result.status === "number" ? result.status : 1,
6757
- stdout: result.stdout ?? "",
6758
- stderr: result.stderr ?? "",
6759
- signal: result.signal ?? null,
6760
- error: result.error ? result.error.message : null
6761
- };
6762
- };
7113
+ const runner = createCloudflareProcessRunner({
7114
+ root,
7115
+ accountId,
7116
+ apiBaseUrl,
7117
+ env: () => deployChildEnv(accountId, apiBaseUrl)
7118
+ });
7119
+ return (operation, opts) => runner(operation, opts);
6763
7120
  }
6764
7121
  /** Retry an existing-Worker read only when a fresh identity check proves the session is gone. */
6765
7122
  async function prepareExistingCloudflareDeployWithAuth(options, beforeRun) {
@@ -6768,18 +7125,17 @@ async function prepareExistingCloudflareDeployWithAuth(options, beforeRun) {
6768
7125
  } catch (error) {
6769
7126
  if (!(error instanceof CloudflareWorkerInspectionError)) throw error;
6770
7127
  if (!beforeRun()) throw new CloudflareAuthenticationError("The Cloudflare uploader changed during Worker verification.", "uploader-changed");
6771
- const whoamiArgs = [...existingWranglerConfigArgs(options.configPath), "--env="];
6772
- const identity = await options.runWrangler([
6773
- "whoami",
6774
- "--json",
6775
- ...whoamiArgs
6776
- ], { capture: true });
6777
- const session = isCleanWranglerExit(identity) ? parseCloudflareAuthSession(identity.stdout) : null;
7128
+ const configPath = existsSync(options.configPath) ? options.configPath : void 0;
7129
+ const identity = await options.runWrangler({
7130
+ type: "auth.whoami",
7131
+ configPath
7132
+ }, { capture: true });
7133
+ const session = isCleanWranglerExit(identity) ? parseCloudflareAuthSession(identity.data) : null;
6778
7134
  const wrongAccount = session !== null && session.accounts.length > 0 && !session.accounts.some((account) => account.id === options.accountId);
6779
7135
  if (session && !wrongAccount) throw error;
6780
7136
  let loggedOut = false;
6781
7137
  try {
6782
- loggedOut = JSON.parse(identity.stdout).loggedIn === false;
7138
+ loggedOut = identity.data?.loggedIn === false;
6783
7139
  } catch {}
6784
7140
  if (!(loggedOut || wrongAccount || /authentication is required|not authenticated|no api token|invalid.*token|expired.*token|unauthorized/i.test(identity.stderr)) || identity.signal || identity.error) throw new Error("Could not verify the Cloudflare session after the Worker check failed. Run `void cloudflare status` and retry.");
6785
7141
  const overrides = cloudflareCredentialOverrides();
@@ -6790,8 +7146,8 @@ async function prepareExistingCloudflareDeployWithAuth(options, beforeRun) {
6790
7146
  forceLogin: true,
6791
7147
  requireAccounts: true,
6792
7148
  beforeRun,
6793
- runWrangler: (args, capture) => options.runWrangler([...args], { capture }),
6794
- whoamiArgs
7149
+ runWrangler: (operation, capture) => options.runWrangler(operation, { capture }),
7150
+ configPath
6795
7151
  })).accounts.some((account) => account.id === options.accountId)) throw new Error(`The signed-in Cloudflare user cannot access account ${options.accountId}. Sign in with a user who can access it, then retry.`);
6796
7152
  if (!beforeRun()) throw new CloudflareAuthenticationError("The Cloudflare uploader changed during sign-in.", "uploader-changed");
6797
7153
  return await prepareExistingCloudflareDeploy(options);
@@ -6801,8 +7157,8 @@ async function chooseCloudflareDeployAccount(options) {
6801
7157
  const accounts = (await ensureCloudflareAuthentication({
6802
7158
  beforeRun: options.beforeWranglerRun,
6803
7159
  requireAccounts: true,
6804
- runWrangler: (args, capture) => options.runWrangler([...args], { capture }),
6805
- whoamiArgs: [...existingWranglerConfigArgs(options.configPath), "--env="]
7160
+ runWrangler: (operation, capture) => options.runWrangler(operation, { capture }),
7161
+ configPath: existsSync(options.configPath) ? options.configPath : void 0
6806
7162
  })).accounts;
6807
7163
  let account = accounts[0];
6808
7164
  if (accounts.length > 1) {
@@ -6848,14 +7204,10 @@ function isWranglerNonInteractiveOrCI() {
6848
7204
  async function runWranglerAuthToken(runWrangler, configPath) {
6849
7205
  let result;
6850
7206
  try {
6851
- result = await Promise.resolve(runWrangler([
6852
- "auth",
6853
- "token",
6854
- "--json",
6855
- "-c",
6856
- configPath,
6857
- "--env="
6858
- ], {
7207
+ result = await Promise.resolve(runWrangler({
7208
+ type: "auth.token",
7209
+ configPath
7210
+ }, {
6859
7211
  capture: true,
6860
7212
  env: { WRANGLER_WRITE_LOGS: "false" }
6861
7213
  }));
@@ -6863,15 +7215,7 @@ async function runWranglerAuthToken(runWrangler, configPath) {
6863
7215
  return null;
6864
7216
  }
6865
7217
  if (!isCleanWranglerExit(result)) return null;
6866
- const start = result.stdout.indexOf("{");
6867
- const end = result.stdout.lastIndexOf("}");
6868
- if (start === -1 || end <= start) return null;
6869
- let parsed;
6870
- try {
6871
- parsed = JSON.parse(result.stdout.slice(start, end + 1));
6872
- } catch {
6873
- return null;
6874
- }
7218
+ const parsed = result.data;
6875
7219
  const type = parsed?.type;
6876
7220
  const token = parsed?.token;
6877
7221
  return (type === "oauth" || type === "api_token") && typeof token === "string" && token.length > 0 ? token : null;
@@ -6921,11 +7265,7 @@ async function runCloudflareEmailStep(input) {
6921
7265
  existingAddresses: rootConfig.addresses,
6922
7266
  existingVars: rootConfig.vars && typeof rootConfig.vars === "object" && !Array.isArray(rootConfig.vars) ? rootConfig.vars : void 0,
6923
7267
  configConflicts: findEmailConfigConflicts(rootConfig),
6924
- wranglerArgs: [
6925
- "-c",
6926
- input.configPath,
6927
- "--env="
6928
- ],
7268
+ configPath: input.configPath,
6929
7269
  apiBaseUrl: input.apiBaseUrl,
6930
7270
  runWrangler: input.runWrangler,
6931
7271
  getSessionBearer: () => runWranglerAuthToken(input.runWrangler, input.configPath),
@@ -7086,14 +7426,12 @@ async function ensureCloudflareAuthSecret(options) {
7086
7426
  beforeRun();
7087
7427
  let put;
7088
7428
  try {
7089
- put = await Promise.resolve(options.runWrangler([
7090
- "secret",
7091
- "put",
7092
- BETTER_AUTH_SECRET_NAME,
7093
- "-c",
7094
- options.configPath,
7095
- "--env="
7096
- ], {
7429
+ put = await Promise.resolve(options.runWrangler({
7430
+ type: "secret.put",
7431
+ name: BETTER_AUTH_SECRET_NAME,
7432
+ configPath: options.configPath,
7433
+ workerName: options.workerName
7434
+ }, {
7097
7435
  capture: true,
7098
7436
  input: `${secret}\n`
7099
7437
  }));
@@ -7120,13 +7458,23 @@ async function prepareCloudflareDeploySource(root, options, voidConfig) {
7120
7458
  ].join("\n"));
7121
7459
  return null;
7122
7460
  }
7123
- const classification = classifyCloudflareDeployApp(root, voidConfig, options);
7461
+ let application;
7462
+ try {
7463
+ application = analyzeApplicationSync(root, {
7464
+ config: voidConfig,
7465
+ environment: "prod"
7466
+ });
7467
+ } catch (error) {
7468
+ log.error(`deploy: ${getErrorMessage(error)}`);
7469
+ return null;
7470
+ }
7471
+ const classification = classifyCloudflareDeployApp(root, voidConfig, options, application);
7124
7472
  if (!classification.supported) {
7125
7473
  log.error(classification.reason);
7126
7474
  return null;
7127
7475
  }
7128
7476
  const configPath = discoverRootWranglerConfig(root);
7129
- const facts = await computeDesiredCloudflareResources(root, voidConfig, configPath);
7477
+ const facts = await computeDesiredCloudflareResources(root, voidConfig, configPath, application);
7130
7478
  const { desired, dialect, migrations: preBuildMigrations, migrationsDir } = facts;
7131
7479
  if (dialect !== "sqlite" && preBuildMigrations.length > 0 && !process.env.DATABASE_URL) {
7132
7480
  log.error([
@@ -7154,51 +7502,35 @@ async function applyAndVerifyRemoteD1Migrations(d1MigrateToken, configPath, runW
7154
7502
  if (d1MigrateToken) {
7155
7503
  log.step("Applying D1 migrations...");
7156
7504
  if (!assertUploaderStillPinned()) return false;
7157
- const migrate = await Promise.resolve(runWrangler([
7158
- "d1",
7159
- "migrations",
7160
- "apply",
7161
- d1MigrateToken,
7162
- "--remote",
7163
- "-c",
7164
- configPath,
7165
- "--env="
7166
- ], { capture: false }));
7505
+ const migrate = await Promise.resolve(runWrangler({
7506
+ type: "d1.migrations.apply",
7507
+ database: d1MigrateToken,
7508
+ configPath
7509
+ }, { capture: false }));
7167
7510
  if (!isCleanWranglerExit(migrate)) {
7168
7511
  log.error(`deploy: remote D1 migrations failed for "${d1MigrateToken}" (exit ${String(migrate.status)}). Fix the migration and rerun. Note: D1 always enforces foreign_keys=ON — a Drizzle table-rebuild using PRAGMA foreign_keys=OFF must use PRAGMA defer_foreign_keys instead.`);
7169
7512
  return false;
7170
7513
  }
7171
7514
  if (!assertUploaderStillPinned()) return false;
7172
- const migrateList = await Promise.resolve(runWrangler([
7173
- "d1",
7174
- "migrations",
7175
- "list",
7176
- d1MigrateToken,
7177
- "--remote",
7178
- "-c",
7179
- configPath,
7180
- "--env="
7181
- ], { capture: true }));
7515
+ const migrateList = await Promise.resolve(runWrangler({
7516
+ type: "d1.migrations.list",
7517
+ database: d1MigrateToken,
7518
+ configPath
7519
+ }, { capture: true }));
7182
7520
  if (!isCleanWranglerExit(migrateList)) {
7183
7521
  log.error(`deploy: could not verify the remote migration state for "${d1MigrateToken}" (\`d1 migrations list\` exit ${String(migrateList.status)}). Refusing to deploy against an unverified schema — rerun once wrangler can reach your account.`);
7184
7522
  return false;
7185
7523
  }
7186
- if (migrateList.stdout.includes("Migrations to be applied:")) {
7187
- log.error([
7188
- `deploy: remote D1 still has UNAPPLIED migrations for "${d1MigrateToken}" after apply.`,
7189
- "`wrangler d1 migrations apply` exits 0 even when its confirmation is declined, so",
7190
- "the schema may be un-migrated while the code that needs it is about to deploy.",
7191
- "Re-run and confirm the migration (or apply it non-interactively), then rerun."
7192
- ].join("\n"));
7524
+ const migrationState = migrateList.data;
7525
+ if (Array.isArray(migrationState?.pending) && migrationState.pending.length > 0) {
7526
+ log.error([`deploy: remote D1 still has UNAPPLIED migrations for "${d1MigrateToken}" after apply.`, "The schema is not current for this deployment. Fix or apply the pending migrations, then retry."].join("\n"));
7193
7527
  return false;
7194
7528
  }
7195
- if (!migrateList.stdout.includes("No migrations to apply!")) {
7529
+ if (!Array.isArray(migrationState?.pending) || !migrationState.pending.every((name) => typeof name === "string")) {
7196
7530
  log.error([
7197
7531
  `deploy: could not confirm the remote migration state for "${d1MigrateToken}".`,
7198
- "`wrangler d1 migrations list` printed neither the \"no migrations to apply\" nor the",
7199
- "\"migrations to be applied\" marker (WRANGLER_LOG is pinned so they are never",
7200
- "suppressed) — refusing to deploy against an unverified schema. Rerun once wrangler",
7201
- "can reach your account."
7532
+ "Cloudflare returned an invalid pending migration set. Refusing to deploy against an unverified schema.",
7533
+ "Retry once the database migration state can be read."
7202
7534
  ].join("\n"));
7203
7535
  return false;
7204
7536
  }
@@ -7334,6 +7666,12 @@ async function runCloudflareDeploy(root, options, cliLog, deps = {}) {
7334
7666
  if (!source) return exit(1);
7335
7667
  const { configPath, desired, reasons, authEnabled, dialect, migrationsDir, migrations: preBuildMigrations, needsAI, aiBindingName, needsSandbox, needsLive, expectedCrons, needsEmail, emailHandlers } = source;
7336
7668
  let rootConfig = parseRootWranglerConfig(configPath);
7669
+ if (needsSandbox) try {
7670
+ assertSandboxNamespaceConfig(rootConfig, resolveSandboxConfig(voidConfig, root));
7671
+ } catch (error) {
7672
+ log.error(`deploy: ${getErrorMessage(error)}`);
7673
+ return exit(1);
7674
+ }
7337
7675
  let apiBaseUrl;
7338
7676
  try {
7339
7677
  apiBaseUrl = resolveDeployTransport(root, rootConfig);
@@ -7449,8 +7787,8 @@ async function runCloudflareDeploy(root, options, cliLog, deps = {}) {
7449
7787
  await ensureCloudflareAuthentication({
7450
7788
  acceptApiToken: true,
7451
7789
  beforeRun: assertUploaderStillPinned,
7452
- runWrangler: (args, capture) => runWrangler([...args], { capture }),
7453
- whoamiArgs: [...existingWranglerConfigArgs(configPath), "--env="]
7790
+ runWrangler: (operation, capture) => runWrangler(operation, { capture }),
7791
+ configPath: existsSync(configPath) ? configPath : void 0
7454
7792
  });
7455
7793
  } catch (error) {
7456
7794
  if (error instanceof CloudflareAuthenticationError && error.reason === "uploader-changed") return exit(1);
@@ -7509,13 +7847,10 @@ async function runCloudflareDeploy(root, options, cliLog, deps = {}) {
7509
7847
  }
7510
7848
  if (needsSandbox) {
7511
7849
  if (!assertUploaderStillPinned()) return exit(1);
7512
- if (!isCleanWranglerExit(await Promise.resolve(runWrangler([
7513
- "containers",
7514
- "list",
7515
- "--json",
7516
- ...existingWranglerConfigArgs(configPath),
7517
- "--env="
7518
- ], { capture: true })))) {
7850
+ if (!isCleanWranglerExit(await Promise.resolve(runWrangler({
7851
+ type: "containers.list",
7852
+ configPath: existsSync(configPath) ? configPath : void 0
7853
+ }, { capture: true })))) {
7519
7854
  log.error(formatCloudflareSandboxUnavailable({ preflight: true }));
7520
7855
  return exit(1);
7521
7856
  }
@@ -7594,19 +7929,16 @@ async function runCloudflareDeploy(root, options, cliLog, deps = {}) {
7594
7929
  }
7595
7930
  const absentQueues = [];
7596
7931
  for (const name of desired.queues) {
7597
- const info = await Promise.resolve(runWrangler([
7598
- "queues",
7599
- "info",
7932
+ const info = await Promise.resolve(runWrangler({
7933
+ type: "queues.info",
7600
7934
  name,
7601
- "-c",
7602
- configPath,
7603
- "--env="
7604
- ], { capture: true }));
7935
+ configPath
7936
+ }, { capture: true }));
7605
7937
  if (provisionModule.isQueueConfirmedAbsent(info, name)) absentQueues.push(name);
7606
7938
  }
7607
7939
  if (absentQueues.length > 0) await provisionAndVerify();
7608
7940
  }
7609
- const frameworkQueues = detectedFramework ? scanQueuesSync(root, resolveProjectPaths(root, voidConfig).queuesDir) : [];
7941
+ const frameworkQueues = detectedFramework ? source.analysis.queues : [];
7610
7942
  writeProvisionedWranglerConfig(root, {
7611
7943
  name: workerName,
7612
7944
  accountId,
@@ -7632,12 +7964,15 @@ async function runCloudflareDeploy(root, options, cliLog, deps = {}) {
7632
7964
  const expectedContainers = reconstructExpectedContainers(voidConfig, root, preBuildRootConfig, needsSandbox);
7633
7965
  const expectedDurableObjectState = reconstructExpectedDurableObjectState(voidConfig, root, preBuildRootConfig, {
7634
7966
  needsSandbox,
7635
- needsLive
7967
+ needsLive,
7968
+ analysis: source.analysis
7636
7969
  });
7637
7970
  removeDeployPointer(root);
7638
7971
  const buildChildEnv = scrubManagedDeployBuildMarkers(scrubWranglerAuthCredentials(deployChildEnv(accountId, apiBaseUrl)));
7639
7972
  requireProvisionModule().deleteEnvVarCaseAware(buildChildEnv, CLOUDFLARE_DEPLOY_BUILD_ENV);
7640
7973
  buildChildEnv[CLOUDFLARE_DEPLOY_BUILD_ENV] = "1";
7974
+ requireProvisionModule().deleteEnvVarCaseAware(buildChildEnv, CLOUDFLARE_PRESERVE_BINDINGS_BUILD_ENV);
7975
+ if (existingWorker) buildChildEnv[CLOUDFLARE_PRESERVE_BINDINGS_BUILD_ENV] = "1";
7641
7976
  requireProvisionModule().deleteEnvVarCaseAware(buildChildEnv, "NODE_ENV");
7642
7977
  buildChildEnv.NODE_ENV = "production";
7643
7978
  const runBuild = deps.runBuild ?? ((env) => {
@@ -7656,7 +7991,7 @@ async function runCloudflareDeploy(root, options, cliLog, deps = {}) {
7656
7991
  }
7657
7992
  log.step("Preparing deployment...");
7658
7993
  if (detectedFramework && frameworkPreset) try {
7659
- await (deps.prepareFrameworkArtifact ?? prepareCloudflareFrameworkArtifact)(root, configPath, accountId, workerName, voidConfig, detectedFramework, frameworkPreset, schemaFacts.requiredKeys, needsAI ? { binding: aiBindingName } : void 0);
7994
+ await (deps.prepareFrameworkArtifact ?? prepareCloudflareFrameworkArtifact)(root, configPath, accountId, workerName, voidConfig, detectedFramework, frameworkPreset, schemaFacts.requiredKeys, needsAI ? { binding: aiBindingName } : void 0, existingWorker !== null);
7660
7995
  } catch (error) {
7661
7996
  log.error(`deploy: could not prepare ${detectedFramework.name} for Cloudflare — ${getErrorMessage(error)}`);
7662
7997
  return exit(1);
@@ -7907,6 +8242,8 @@ async function runCloudflareDeploy(root, options, cliLog, deps = {}) {
7907
8242
  workerName,
7908
8243
  configPath: generatedConfigPath,
7909
8244
  preserveTriggers: existingWorker !== null,
8245
+ requireVersionMetadata: existingWorker === null || readVersionMetadataBinding(parseRootWranglerConfig(generatedConfigPath)) !== void 0,
8246
+ requirePreviewForReadiness: existingWorker !== null && readVersionMetadataBinding(parseRootWranglerConfig(generatedConfigPath)) === void 0,
7910
8247
  expectedPreviousVersionId: existingWorker?.versionId,
7911
8248
  trustedInheritanceVersionIds: existingWorker ? candidateReceipt?.parentVersionId === existingWorker.versionId ? candidateReceipt.versionIds : void 0 : initialCloudflareCandidate?.versionIds,
7912
8249
  validateUploadedVersion: existingWorker ? async (versionId) => {
@@ -7982,10 +8319,6 @@ function discoverRootWranglerConfig(root) {
7982
8319
  if (existsSync(join(root, "wrangler.toml"))) throw new Error("Found wrangler.toml. Direct Cloudflare deployment needs one root wrangler.jsonc or wrangler.json with the same settings. Convert the config before deploying.");
7983
8320
  return materializeCloudflareConfig(root);
7984
8321
  }
7985
- /** Avoid making Wrangler open a config path that Void has not created yet. */
7986
- function existingWranglerConfigArgs(configPath) {
7987
- return existsSync(configPath) ? ["-c", configPath] : [];
7988
- }
7989
8322
  async function reportCloudflareActivationFailure(cliLog, message) {
7990
8323
  cliLog.error("cloudflare_deploy_failed", new Error(message));
7991
8324
  await cliLog.close().catch(() => {});
@@ -8018,7 +8351,9 @@ function formatCloudflareActivationError(result) {
8018
8351
  if (result.rollback === "failed") return "deploy: the verified Wrangler uploader changed after version activation, and automatic traffic restoration failed. Inspect `void project status` immediately.";
8019
8352
  if (result.rollback === "unsafe") return "deploy: the verified Wrangler uploader changed after version activation. Void did not invoke the changed binary to restore traffic; reinstall Wrangler from a trusted lockfile and inspect `void project status` immediately.";
8020
8353
  return "deploy: the verified Wrangler uploader changed before version activation.";
8021
- case "status-failed": return `deploy: could not snapshot the current Cloudflare deployment${status}; no version was uploaded.`;
8354
+ case "status-failed":
8355
+ if (result.versionId) return `deploy: Worker version ${result.versionId} was published, but current deployment status could not be verified${status}. Inspect \`void project status\` before retrying.`;
8356
+ return `deploy: could not snapshot the current Cloudflare deployment${status}; no version was uploaded.`;
8022
8357
  case "deployment-changed": return result.triggerRepair === "succeeded" ? `deploy: another Cloudflare operation superseded this deployment. Void restored triggers for the active version ${result.activeVersionId ?? "(unknown)"}; retry from fresh deployment status.` : `deploy: another Cloudflare operation superseded this deployment, but its trigger state could not be verified. Inspect the active Worker version ${result.activeVersionId ?? "(unknown)"} immediately.`;
8023
8358
  case "inheritance-source-unavailable": return "deploy: could not verify which Worker Version would supply inherited secrets; production traffic is unchanged.";
8024
8359
  case "inheritance-source-changed": return [
@@ -8034,16 +8369,18 @@ function formatCloudflareActivationError(result) {
8034
8369
  ].join("\n");
8035
8370
  case "upload-failed": return `deploy: Cloudflare Worker Version upload failed${status}; production traffic is unchanged.${cloudflareCode}${diagnostic}`;
8036
8371
  case "upload-output-invalid": return "deploy: Wrangler uploaded a version but did not report its version id; production traffic is unchanged.";
8037
- case "preview-unavailable": return [
8038
- "deploy: the uploaded Worker Version has no preview URL, so Void cannot verify readiness.",
8039
- "Set CLOUDFLARE_WORKERS_SUBDOMAIN to your account subdomain (for example \"my-team\")",
8040
- "and retry. Void caches it after a successful workers.dev deployment; production traffic is unchanged."
8041
- ].join("\n");
8372
+ case "preview-unavailable":
8373
+ if (result.diagnostic) return `deploy: candidate readiness cannot be verified.\n\n${result.diagnostic}`;
8374
+ return [
8375
+ "deploy: the uploaded Worker Version has no preview URL, so Void cannot verify readiness.",
8376
+ "Set CLOUDFLARE_WORKERS_SUBDOMAIN to your account subdomain (for example \"my-team\")",
8377
+ "and retry. Void caches it after a successful workers.dev deployment; production traffic is unchanged."
8378
+ ].join("\n");
8042
8379
  case "readiness-staging-failed": return `deploy: Cloudflare could not stage the uploaded Worker Version at zero-percent traffic${status}; production traffic is unchanged.${cloudflareCode}${diagnostic}${result.cloudflareErrorCode === 10210 && /Durable Object migration/i.test(result.diagnostic ?? "") ? "\n\nReview the Durable Object migration, then run `void deploy --platform cloudflare --atomic`. For future deploys, set deploy.cloudflare.mode to \"atomic\" in void.config.ts." : ""}`;
8043
8380
  case "atomic-failed": return `deploy: Cloudflare could not publish the Worker atomically${status}; inspect production status before retrying.${cloudflareCode}${diagnostic}`;
8044
8381
  case "atomic-output-invalid": return "deploy: Cloudflare published atomically but did not report a version id or URL. Inspect `void project status` immediately.";
8045
8382
  case "atomic-readiness-failed":
8046
- case "atomic-readiness-access-denied": return "deploy: the atomic Worker deployment is live, but /__void/ready could not be verified. Inspect `void project status` and Worker logs immediately.";
8383
+ case "atomic-readiness-access-denied": return `deploy: the atomic Worker deployment is live, but /__void/ready could not be verified. Inspect \`void project status\` and Worker logs immediately.${diagnostic}`;
8047
8384
  case "readiness-failed": return ["deploy: the uploaded Worker Version did not pass /__void/ready.", formatCloudflareReadinessRestoration(result)].join("\n");
8048
8385
  case "readiness-access-denied": return [
8049
8386
  "deploy: Cloudflare Access blocked the uploaded Worker Version readiness probe.",
@@ -8058,7 +8395,7 @@ function formatCloudflareActivationError(result) {
8058
8395
  "deploy: the first Worker deployment did not pass /__void/ready.",
8059
8396
  "Cloudflare requires this bootstrap to be non-versioned, so the new Worker is already active;",
8060
8397
  "inspect its logs and `void project status` before retrying."
8061
- ].join("\n");
8398
+ ].join("\n") + diagnostic;
8062
8399
  case "bootstrap-readiness-access-denied": return [
8063
8400
  "deploy: Cloudflare Access blocked the first Worker deployment readiness probe.",
8064
8401
  "For CI, set CF_ACCESS_CLIENT_ID and CF_ACCESS_CLIENT_SECRET to an admitted service token;",
@@ -8068,7 +8405,7 @@ function formatCloudflareActivationError(result) {
8068
8405
  "Worker is already active behind Access."
8069
8406
  ].join("\n");
8070
8407
  case "activation-failed": return `deploy: the ready Worker Version could not be activated${status}; production traffic is unchanged.`;
8071
- case "triggers-failed": return `deploy: the Worker activated, but Cloudflare trigger synchronization failed${status}. ${result.rollback === "succeeded" ? "Traffic was restored to the previous version." : result.rollback === "unavailable" ? result.previousVersionId === null ? "This was the first deployment, so no previous version was available to restore." : "Traffic was restored to the previous version, but its trigger configuration was unavailable. Inspect its routes, schedules, queues, and workflows in Cloudflare before retrying." : "Automatic traffic restoration failed; inspect `void project status` immediately."}`;
8408
+ case "triggers-failed": return `deploy: the Worker activated, but Cloudflare trigger synchronization failed${status}. ${result.rollback === "succeeded" ? "Traffic was restored to the previous version." : result.rollback === "unavailable" ? result.previousVersionId === null ? "This was the first deployment, so no previous version was available to restore." : "Traffic or trigger restoration could not be completed. Inspect its routes, schedules, queues, and workflows in Cloudflare before retrying." : "Automatic traffic restoration failed; inspect `void project status` immediately."}`;
8072
8409
  }
8073
8410
  }
8074
8411
  function formatCloudflareReadinessRestoration(result) {
@@ -8089,29 +8426,36 @@ function cloudflareWorkerReadinessHeaders() {
8089
8426
  });
8090
8427
  }
8091
8428
  /** Build the trusted Wrangler pointer for a framework that emits Worker files directly. */
8092
- async function prepareCloudflareFrameworkArtifact(root, configPath, accountId, workerName, voidConfig, framework, preset, requiredSecretNames = [], ai) {
8429
+ async function prepareCloudflareFrameworkArtifact(root, configPath, accountId, workerName, voidConfig, framework, preset, requiredSecretNames = [], ai, preserveBindings = false) {
8093
8430
  const workerMain = join(root, preset.workerDir, preset.workerMain);
8094
8431
  const assetsDirectory = join(root, preset.assetsDir);
8095
8432
  if (!existsSync(workerMain)) throw new Error(`framework ${framework.name} did not emit its Worker entry at ${preset.workerDir}/${preset.workerMain}`);
8096
8433
  if (!existsSync(assetsDirectory)) throw new Error(`framework ${framework.name} did not emit its asset directory at ${preset.assetsDir}`);
8097
- const paths = resolveProjectPaths(root, voidConfig);
8098
- const crons = scanJobsSync(root, paths.cronsDir);
8099
- const queues = scanQueuesSync(root, paths.queuesDir);
8434
+ const analysis = analyzeApplicationSync(root, {
8435
+ config: voidConfig,
8436
+ environment: "prod"
8437
+ });
8438
+ const crons = analysis.jobs;
8439
+ const queues = analysis.queues;
8100
8440
  const revalidate = collectRevalidate(voidConfig, null);
8441
+ const rootConfig = parseRootWranglerConfig(configPath);
8442
+ const versionMetadataBinding = selectNativeVersionMetadataBinding(rootConfig, {
8443
+ nativeBuild: true,
8444
+ preserveBindings
8445
+ });
8101
8446
  await generateDeployWrapperIfNeeded(root, join(root, preset.workerDir), preset.workerMain, {
8102
- auth: null,
8103
8447
  crons,
8104
8448
  queues,
8105
8449
  hasMigrations: false,
8106
- migrationDialect: getDatabaseDialect(voidConfig),
8450
+ migrationDialect: analysis.dialect,
8107
8451
  readiness: true,
8452
+ versionMetadataBinding,
8108
8453
  ...hasRevalidate(revalidate) && { isr: {
8109
8454
  revalidate,
8110
8455
  queryAllowlist: voidConfig.routing?.revalidateQueryAllowlist
8111
8456
  } }
8112
8457
  });
8113
8458
  if (preset.cloudflareArtifact === "vite-plugin") return;
8114
- const rootConfig = parseRootWranglerConfig(configPath);
8115
8459
  writeCloudflareFrameworkDeployConfig({
8116
8460
  configPath,
8117
8461
  deployPointerPath: deployPointerPath(root),
@@ -8123,6 +8467,7 @@ async function prepareCloudflareFrameworkArtifact(root, configPath, accountId, w
8123
8467
  compatibilityFlags: preset.cloudflareCompatibilityFlags,
8124
8468
  crons: [...new Set(crons.flatMap((job) => job.crons))],
8125
8469
  requiredSecrets: requiredSecretNames,
8470
+ versionMetadataBinding,
8126
8471
  ...ai ? { ai: {
8127
8472
  ...ai,
8128
8473
  gateway: voidConfig.ai?.gateway
@@ -8162,23 +8507,14 @@ function resolveCloudflareStaticPreset(root, voidConfig, options) {
8162
8507
  */
8163
8508
  async function resolveCloudflareVoidStaticPreset(root, voidConfig) {
8164
8509
  if (voidConfig.output !== "static") return null;
8165
- const paths = resolveProjectPaths(root, voidConfig);
8166
- if (!existsSync(paths.pagesDir) || validateSsrEntry(root, paths) !== null) return null;
8167
- const routeScan = await scanRoutes(root, {
8168
- output: voidConfig.output,
8169
- paths,
8510
+ const analysis = await analyzeApplication(root, {
8511
+ config: voidConfig,
8170
8512
  environment: "prod"
8171
8513
  });
8172
- const pageScan = routeScan.pages.pages.length > 0 ? routeScan.pages : await scanPages(root, {
8173
- output: voidConfig.output,
8174
- paths
8175
- });
8514
+ const { paths, authEnabled, bindings, scan: routeScan } = analysis;
8515
+ if (!existsSync(paths.pagesDir) || validateSsrEntry(root, paths) !== null) return null;
8516
+ const pageScan = routeScan.pages;
8176
8517
  if (pageScan.pages.length === 0 || !pageScan.pages.every((page) => page.shouldPrerender && (page.params.length === 0 && !page.catchAll || page.hasGetPrerenderPaths))) return null;
8177
- const bindings = inferProjectBindings(root, {
8178
- paths,
8179
- environment: "prod"
8180
- });
8181
- const authEnabled = bindings.needsAuth || findVoidAuthConfig(root, paths) !== null || voidConfig.auth !== void 0;
8182
8518
  if (hasNonPageBackend({
8183
8519
  hasRoutes: routeScan.routes.length > 0,
8184
8520
  hasMiddleware: routeScan.middleware.length > 0,
@@ -8186,7 +8522,7 @@ async function resolveCloudflareVoidStaticPreset(root, voidConfig) {
8186
8522
  hasEmail: routeScan.email !== void 0 && hasEmailHandlers(routeScan.email),
8187
8523
  authEnabled,
8188
8524
  hasLive: bindings.needsLive === true
8189
- }) || scanJobsSync(root, paths.cronsDir).length > 0 || scanQueuesSync(root, paths.queuesDir).length > 0) return null;
8525
+ }) || analysis.jobs.length > 0 || analysis.queues.length > 0) return null;
8190
8526
  return {
8191
8527
  buildCommand: resolveFullDeployBuildCommand(root, voidConfig.inference?.build).command,
8192
8528
  outputDir: join(root, "dist", "client"),
@@ -8247,8 +8583,8 @@ async function runCloudflareStaticDeploy(root, options, voidConfig, preset, cliL
8247
8583
  await ensureCloudflareAuthentication({
8248
8584
  acceptApiToken: true,
8249
8585
  beforeRun: uploaderIsPinned,
8250
- runWrangler: (args, capture) => runWrangler([...args], { capture }),
8251
- whoamiArgs: [...existingWranglerConfigArgs(configPath), "--env="]
8586
+ runWrangler: (operation, capture) => runWrangler(operation, { capture }),
8587
+ configPath: existsSync(configPath) ? configPath : void 0
8252
8588
  });
8253
8589
  } catch (error) {
8254
8590
  if (error instanceof CloudflareAuthenticationError && error.reason === "uploader-changed") return exit(1);
@@ -8316,12 +8652,6 @@ async function runCloudflareStaticDeploy(root, options, voidConfig, preset, cliL
8316
8652
  });
8317
8653
  const entryPath = join(root, ".void", "deploy", "cloudflare", "static-edge.mjs");
8318
8654
  mkdirSync(dirname(entryPath), { recursive: true });
8319
- writeFileSync(entryPath, renderCloudflareStaticEntry({
8320
- runtimeImport: "void/edge",
8321
- headerRules: manifest.headerRules,
8322
- redirectRules: manifest.redirectRules,
8323
- fallbackRules: manifest.fallbackRules
8324
- }));
8325
8655
  const workerName = readWranglerTopLevelString(rootConfig, "name") ?? deriveWorkerName(root);
8326
8656
  const staticConfigPath = join(dirname(entryPath), "wrangler-static.json");
8327
8657
  writeFileSync(staticConfigPath, `${JSON.stringify(rootConfig, null, 2)}\n`, { mode: 384 });
@@ -8335,6 +8665,22 @@ async function runCloudflareStaticDeploy(root, options, voidConfig, preset, cliL
8335
8665
  assetConfig: manifest.assetConfig ?? { not_found_handling: "none" }
8336
8666
  });
8337
8667
  deduplicateGeneratedCompatibilityFlags(staticConfigPath, configPath);
8668
+ const staticConfig = parseRootWranglerConfig(staticConfigPath);
8669
+ const versionMetadataBinding = selectNativeVersionMetadataBinding(staticConfig, {
8670
+ nativeBuild: true,
8671
+ preserveBindings: existingWorker !== null
8672
+ });
8673
+ if (versionMetadataBinding && staticConfig.version_metadata === void 0) writeFileSync(staticConfigPath, `${JSON.stringify({
8674
+ ...staticConfig,
8675
+ version_metadata: { binding: versionMetadataBinding }
8676
+ }, null, 2)}\n`, { mode: 384 });
8677
+ writeFileSync(entryPath, renderCloudflareStaticEntry({
8678
+ runtimeImport: "void/edge",
8679
+ headerRules: manifest.headerRules,
8680
+ redirectRules: manifest.redirectRules,
8681
+ fallbackRules: manifest.fallbackRules,
8682
+ versionMetadataBinding
8683
+ }));
8338
8684
  if (existingWorker) assertExistingCloudflareConfig(existingWorker, parseRootWranglerConfig(staticConfigPath), true);
8339
8685
  const stagedAssetsDirectory = join(root, ".void", "deploy", "cloudflare", "assets");
8340
8686
  stageCloudflareStaticAssets({
@@ -8385,6 +8731,8 @@ async function runCloudflareStaticDeploy(root, options, voidConfig, preset, cliL
8385
8731
  verifyUploader: cfOutput ? cfUploaderIsPinned : uploaderIsPinned,
8386
8732
  configPath: staticConfigPath,
8387
8733
  preserveTriggers: existingWorker !== null,
8734
+ requireVersionMetadata: existingWorker === null || versionMetadataBinding !== void 0,
8735
+ requirePreviewForReadiness: existingWorker !== null && versionMetadataBinding === void 0,
8388
8736
  expectedPreviousVersionId: existingWorker?.versionId,
8389
8737
  trustedInheritanceVersionIds: existingWorker ? candidateReceipt?.parentVersionId === existingWorker.versionId ? candidateReceipt.versionIds : void 0 : initialCloudflareCandidate?.versionIds,
8390
8738
  validateUploadedVersion: existingWorker ? async (versionId) => {
@@ -9142,7 +9490,6 @@ async function runFrameworkDeploy(root, config, client, detected, fwPreset, void
9142
9490
  mode: "framework",
9143
9491
  framework: detected.name
9144
9492
  });
9145
- const dialect = getDatabaseDialect(voidConfig);
9146
9493
  const buildCmd = voidConfig.inference?.build ?? formatProjectCommand(root, fwPreset.buildCommand);
9147
9494
  if (!skipBuild) {
9148
9495
  log.step(`Building (${buildCmd})...`);
@@ -9172,15 +9519,12 @@ async function runFrameworkDeploy(root, config, client, detected, fwPreset, void
9172
9519
  log.error(`deploy: Worker output '${fwPreset.workerDir}' not found. Run '${buildCmd}' first.`);
9173
9520
  process.exit(1);
9174
9521
  }
9175
- const inferenceOptions = voidConfig.inference?.bindings ? { bindings: voidConfig.inference.bindings } : void 0;
9176
- const paths = resolveProjectPaths(root, voidConfig);
9177
- const scanDirs = voidConfig.inference?.scanDirs ?? FRAMEWORK_SCAN_DIRS;
9178
- const bindings = inferProjectBindings(root, {
9179
- ...inferenceOptions,
9180
- paths,
9522
+ const analysis = analyzeApplicationSync(root, {
9523
+ config: voidConfig,
9181
9524
  environment: "prod"
9182
- }, scanDirs);
9183
- if (bindings.needsAuth || findVoidAuthConfig(root, paths) !== null || voidConfig.auth !== void 0) {
9525
+ });
9526
+ const { paths, bindings, authEnabled, dialect } = analysis;
9527
+ if (authEnabled) {
9184
9528
  log.error("deploy: Void-managed Better Auth is only supported in Void apps. Use Better Auth's official integration for this framework instead.");
9185
9529
  process.exit(1);
9186
9530
  }
@@ -9190,7 +9534,7 @@ async function runFrameworkDeploy(root, config, client, detected, fwPreset, void
9190
9534
  }
9191
9535
  const migrationsDir = paths.migrationsDir;
9192
9536
  assertDeploySchemaDriftFree(root, dialect);
9193
- const rawMigrations = collectMigrations(migrationsDir);
9537
+ const rawMigrations = analysis.migrations;
9194
9538
  let validatedMigrations;
9195
9539
  try {
9196
9540
  assertJournalCoherence(migrationsDir, rawMigrations);
@@ -9205,7 +9549,7 @@ async function runFrameworkDeploy(root, config, client, detected, fwPreset, void
9205
9549
  log.error(getErrorMessage(err));
9206
9550
  process.exit(1);
9207
9551
  }
9208
- if (dialect !== "sqlite" && (bindings.needsD1 || rawMigrations.length > 0)) {
9552
+ if (dialect !== "sqlite" && analysis.needsDatabase) {
9209
9553
  let resources = null;
9210
9554
  try {
9211
9555
  resources = await client.getResources(config.projectId);
@@ -9229,11 +9573,10 @@ async function runFrameworkDeploy(root, config, client, detected, fwPreset, void
9229
9573
  }
9230
9574
  }
9231
9575
  }
9232
- const rawJobs = scanJobsSync(root, paths.cronsDir);
9233
- const rawQueues = scanQueuesSync(root, paths.queuesDir);
9576
+ const rawJobs = analysis.jobs;
9577
+ const rawQueues = analysis.queues;
9234
9578
  const originalEntry = join(workerDir, fwPreset.workerMain);
9235
9579
  const wrapperFeatures = {
9236
- auth: null,
9237
9580
  crons: rawJobs,
9238
9581
  queues: rawQueues,
9239
9582
  hasMigrations: validatedMigrations != null && validatedMigrations.length > 0,
@@ -9289,7 +9632,7 @@ async function runFrameworkDeploy(root, config, client, detected, fwPreset, void
9289
9632
  const packageStartedAt = Date.now();
9290
9633
  const neededSet = skipped > 0 ? new Set(needed) : null;
9291
9634
  const packageOptions = {
9292
- bindingNames: resolveBindingNames(voidConfig.inference?.bindings),
9635
+ bindingNames: analysis.bindingNames,
9293
9636
  workerMain: actualWorkerMain,
9294
9637
  compatibilityDate: wranglerCompat?.compatibilityDate,
9295
9638
  compatibilityFlags: wranglerCompat?.compatibilityFlags,
@@ -9396,20 +9739,12 @@ async function runFullDeploy(root, config, client, voidConfig, fwPreset, skipBui
9396
9739
  if (result.paths.length === 0) log.info("No pages to prerender");
9397
9740
  else if (result.failed.length > 0) voidWarn(`${result.failed.length} page(s) failed to prerender`);
9398
9741
  }
9399
- const paths = resolveProjectPaths(root, deployConfig);
9400
- const dialect = getDatabaseDialect(deployConfig);
9401
- const deployScanDirs = deployConfig.inference?.scanDirs ?? (isFrameworkMode ? FRAMEWORK_SCAN_DIRS : void 0);
9402
- const bindings = inferProjectBindings(root, {
9403
- paths,
9742
+ const analysis = await analyzeApplication(root, {
9743
+ config: deployConfig,
9404
9744
  environment: "prod"
9405
- }, deployScanDirs);
9406
- if (isSandboxEnabled(deployConfig)) bindings.needsSandbox = true;
9407
- const authEnabled = bindings.needsAuth || findVoidAuthConfig(root, paths) !== null || deployConfig.auth !== void 0;
9408
- const effectiveBindings = authEnabled ? {
9409
- ...bindings,
9410
- needsAuth: true,
9411
- needsD1: true
9412
- } : bindings;
9745
+ });
9746
+ const { paths, dialect, bindings, authEnabled, scan: routeScan } = analysis;
9747
+ const effectiveBindings = bindings;
9413
9748
  if (isFrameworkMode && authEnabled) {
9414
9749
  log.error("Void-managed Better Auth is only supported in Void apps. Use Better Auth's official integration for this framework instead.");
9415
9750
  process.exit(1);
@@ -9439,7 +9774,7 @@ async function runFullDeploy(root, config, client, voidConfig, fwPreset, skipBui
9439
9774
  lintDestinationSplats(fallbackRules, "fallbacks (post-asset)");
9440
9775
  const migrationsDir = paths.migrationsDir;
9441
9776
  assertDeploySchemaDriftFree(root, dialect);
9442
- const rawMigrations = collectMigrations(migrationsDir);
9777
+ const rawMigrations = analysis.migrations;
9443
9778
  let validatedMigrations;
9444
9779
  try {
9445
9780
  assertJournalCoherence(migrationsDir, rawMigrations);
@@ -9454,7 +9789,7 @@ async function runFullDeploy(root, config, client, voidConfig, fwPreset, skipBui
9454
9789
  log.error(getErrorMessage(err));
9455
9790
  process.exit(1);
9456
9791
  }
9457
- if (dialect !== "sqlite" && (effectiveBindings.needsD1 || rawMigrations.length > 0)) {
9792
+ if (dialect !== "sqlite" && analysis.needsDatabase) {
9458
9793
  let resources = null;
9459
9794
  try {
9460
9795
  resources = await client.getResources(config.projectId);
@@ -9478,18 +9813,13 @@ async function runFullDeploy(root, config, client, voidConfig, fwPreset, skipBui
9478
9813
  }
9479
9814
  }
9480
9815
  }
9481
- const rawJobs = scanJobsSync(root, paths.cronsDir);
9482
- const rawQueues = scanQueuesSync(root, paths.queuesDir);
9816
+ const rawJobs = analysis.jobs;
9817
+ const rawQueues = analysis.queues;
9483
9818
  const schedules = rawJobs.flatMap((job) => job.crons.map((cron) => ({
9484
9819
  name: job.name,
9485
9820
  cron
9486
9821
  })));
9487
- const routeScan = await scanRoutes(root, {
9488
- output: deployConfig.output,
9489
- paths,
9490
- environment: "prod"
9491
- });
9492
- const webSockets = routeScan.websockets ?? scanWebSocketRoutesSync(root, paths, "prod");
9822
+ const webSockets = analysis.websockets;
9493
9823
  const liveConfig = await resolveDeployLiveBindingConfig(root, client, config.projectId, {
9494
9824
  isFrameworkMode,
9495
9825
  needsLive: effectiveBindings.needsLive,
@@ -9511,10 +9841,7 @@ async function runFullDeploy(root, config, client, voidConfig, fwPreset, skipBui
9511
9841
  const hasWebSockets = webSockets.length > 0;
9512
9842
  const ssrEntry = isFrameworkMode ? null : validateSsrEntry(root, paths);
9513
9843
  const isSsr = isFrameworkMode ? true : Boolean(ssrEntry) || hasPages && deployConfig.output !== "static";
9514
- const pageScan = existsSync(paths.pagesDir) ? routeScan.pages.pages.length > 0 ? routeScan.pages : await scanPages(root, {
9515
- output: deployConfig.output,
9516
- paths
9517
- }) : null;
9844
+ const pageScan = hasPages ? routeScan.pages : null;
9518
9845
  const revalidate = collectRevalidate(deployConfig, pageScan);
9519
9846
  if (deployConfig.output === "static" && pageScan && !isSsr) {
9520
9847
  const allPrerenderable = pageScan.pages.every((p) => p.shouldPrerender && (p.params.length === 0 && !p.catchAll || p.hasGetPrerenderPaths));
@@ -9638,7 +9965,6 @@ async function runFullDeploy(root, config, client, voidConfig, fwPreset, skipBui
9638
9965
  const workerDir = join(distDir, workerDirName);
9639
9966
  if (isFrameworkMode) {
9640
9967
  const wrapperFeatures = {
9641
- auth: null,
9642
9968
  crons: rawJobs,
9643
9969
  queues: rawQueues,
9644
9970
  hasMigrations: validatedMigrations != null && validatedMigrations.length > 0,
@@ -9909,4 +10235,4 @@ function resolveWorkerDirName(distDir, fwPreset, buildCommand) {
9909
10235
  throw new Error("deploy: Worker output not found. Expected dist/ssr/ or dist/rsc/.");
9910
10236
  }
9911
10237
  //#endregion
9912
- export { snapshotPassthroughRawFields as $, parseRootWranglerConfig$1 as A, resolveDeploymentPlatform as B, generateDeployWrapperIfNeeded as C, resolveAssetConfig as Ct, hashWranglerUploader as D, hasPrerenderSources as E, resolveCloudflareVoidStaticPreset as F, resolveWorkerDirName as G, resolveFullDeployBuildCommand as H, resolveDeployLiveBindingConfig as I, runDeploy as J, resolveWranglerMigrationPattern as K, resolveDeployTransport as L, promptDeployProjectConfig as M, reconstructExpectedContainers as N, isWranglerNonInteractiveOrCI as O, reconstructExpectedDurableObjectState as P, snapshotPassthroughIdentities as Q, resolveDeployWorkerMain as R, formatCloudflareSandboxUnavailable as S, hasDocumentRoutes as St, hasNonPageBackend as T, resolveStaticBuildCommand as U, resolveDistDir as V, resolveStaticDeployPreset as W, runWranglerAuthToken as X, runStaticDeploy as Y, snapshotBindingIdentities as Z, deriveWorkerName as _, findEmailConfigConflicts as _t, applyDeployEvent as a, warnUnpublishedRouting as at, ensurePreviouslyDeployedLiveBindingConfig as b, renderEmailChecklist as bt, assertMigrationsProduceAuthSchema as c, prerenderPagesNode as ct, assertWranglerUploaderUnchanged as d, collectRevalidate as dt, streamDeployEvents as et, checkWranglerMigrationApplySet as f, hasRevalidate as ft, deployChildEnv as g, createNodeEmailDns as gt, createDeployWranglerRunner as h, applyEmailSetup as ht, DEPLOY_WHOLE_OBJECT_PINNED_FIELDS as i, warnRedundantForceOn3xx as it, prepareCloudflareFrameworkArtifact as j, loadProvisionModule as k, assertNoConfigOverrideInProjectDotenv as l, buildEnv as lt, computeDesiredCloudflareResources as m, SEND_EMAIL_BINDING as mt, DEPLOY_PASSTHROUGH_ASSERT_FIELDS as n, verifyGeneratedDeployConfig as nt, assembleVoidManifest as o, generateQueueResolverSource as ot, compareWranglerMigrationPaths as p, scanDurableObjectModules as pt, runCloudflareDeploy as q, DEPLOY_PASSTHROUGH_EXCLUDE_FIELDS as r, warnIgnoredNotFoundForFramework as rt, assertManagedDurableStateSupported as s, prerenderPages as st, CloudflareAuthSecretError as t, validateAuthMigrationSchema as tt, assertRemoteMigrationsDeployable as u, collectPages as ut, discoverRootWranglerConfig as v, preflightEmail as vt, hasDeploySchemaDrift as w, resolveAssetRoutingPolicy as wt, formatCloudflareActivationError as x, hasApiRoutes as xt, ensureCloudflareAuthSecret as y, renderAddressMap as yt, resolveDeployWranglerCompat as z };
10238
+ export { snapshotPassthroughRawFields as $, parseRootWranglerConfig$1 as A, resolveDeploymentPlatform as B, generateDeployWrapperIfNeeded as C, hasDocumentRoutes as Ct, hashWranglerUploader as D, hasPrerenderSources as E, resolveCloudflareVoidStaticPreset as F, resolveWorkerDirName as G, resolveFullDeployBuildCommand as H, resolveDeployLiveBindingConfig as I, runDeploy as J, resolveWranglerMigrationPattern as K, resolveDeployTransport as L, promptDeployProjectConfig as M, reconstructExpectedContainers as N, isWranglerNonInteractiveOrCI as O, reconstructExpectedDurableObjectState as P, snapshotPassthroughIdentities as Q, resolveDeployWorkerMain as R, formatCloudflareSandboxUnavailable as S, hasApiRoutes as St, hasNonPageBackend as T, resolveAssetRoutingPolicy as Tt, resolveStaticBuildCommand as U, resolveDistDir as V, resolveStaticDeployPreset as W, runWranglerAuthToken as X, runStaticDeploy as Y, snapshotBindingIdentities as Z, deriveWorkerName as _, createNodeEmailDns as _t, applyDeployEvent as a, warnUnpublishedRouting as at, ensurePreviouslyDeployedLiveBindingConfig as b, renderAddressMap as bt, assertMigrationsProduceAuthSchema as c, selectNativeVersionMetadataBinding as ct, assertWranglerUploaderUnchanged as d, buildEnv as dt, streamDeployEvents as et, checkWranglerMigrationApplySet as f, collectPages as ft, deployChildEnv as g, applyEmailSetup as gt, createDeployWranglerRunner as h, SEND_EMAIL_BINDING as ht, DEPLOY_WHOLE_OBJECT_PINNED_FIELDS as i, warnRedundantForceOn3xx as it, prepareCloudflareFrameworkArtifact as j, loadProvisionModule as k, assertNoConfigOverrideInProjectDotenv as l, prerenderPages as lt, computeDesiredCloudflareResources as m, hasRevalidate as mt, DEPLOY_PASSTHROUGH_ASSERT_FIELDS as n, verifyGeneratedDeployConfig as nt, assembleVoidManifest as o, generateQueueResolverSource as ot, compareWranglerMigrationPaths as p, collectRevalidate as pt, runCloudflareDeploy as q, DEPLOY_PASSTHROUGH_EXCLUDE_FIELDS as r, warnIgnoredNotFoundForFramework as rt, assertManagedDurableStateSupported as s, CLOUDFLARE_PRESERVE_BINDINGS_BUILD_ENV as st, CloudflareAuthSecretError as t, validateAuthMigrationSchema as tt, assertRemoteMigrationsDeployable as u, prerenderPagesNode as ut, discoverRootWranglerConfig as v, findEmailConfigConflicts as vt, hasDeploySchemaDrift as w, resolveAssetConfig as wt, formatCloudflareActivationError as x, renderEmailChecklist as xt, ensureCloudflareAuthSecret as y, preflightEmail as yt, resolveDeployWranglerCompat as z };