void 0.22.0 → 0.24.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 (301) hide show
  1. package/AGENTS_TEMPLATE.md +1 -1
  2. package/README.md +5 -1
  3. package/dist/{account-cmd-CjyxcGkM.mjs → account-cmd-vJhh5lrb.mjs} +5 -5
  4. package/dist/{scan-ClYmX3sa.mjs → application-analysis-BVsVqc11.mjs} +135 -18
  5. package/dist/application-routing-B7QEjqSR.mjs +27 -0
  6. package/dist/{auth-DLNN0D3Z.mjs → auth-CQkSDS50.mjs} +26 -96
  7. package/dist/auth-DX3JURcX.mjs +2 -0
  8. package/dist/{auth-link-BLO4ptzo.mjs → auth-link-D75t9wO2.mjs} +5 -5
  9. package/dist/{auth-router-ZUg9MF_U.mjs → auth-router-ClfhL1J7.mjs} +7 -7
  10. package/dist/{better-auth-shared-rsBGBvWJ.mjs → better-auth-shared-hy6RPh9W.mjs} +13 -2
  11. package/dist/{build-cmd-BxR5FROK.mjs → build-cmd-zMi1iFnS.mjs} +21 -9
  12. package/dist/{cache-D0sWhgKI.mjs → cache-ZtQmyxVV.mjs} +5 -5
  13. package/dist/{cancel-deploy-D4NUqFbi.mjs → cancel-deploy-Box0kkEV.mjs} +5 -5
  14. package/dist/{cf-access-P0iZnu0q.mjs → cf-access-BGe5sWXF.mjs} +4 -48
  15. package/dist/{cf-access-DRsQRe6k.mjs → cf-access-TWNzXltp.mjs} +3 -11
  16. package/dist/{cf-build-output-BJ6yGEIS.mjs → cf-build-output-BPqOT964.mjs} +2 -1
  17. package/dist/cf-build-output-_0HNWysu.mjs +2 -0
  18. package/dist/cli/cli.mjs +89 -461
  19. package/dist/cli/{cf-compat.mjs → cloudflare-operation-process.mjs} +454 -424
  20. package/dist/cli/env-schema-probe.d.mts +2 -1
  21. package/dist/cli/env-schema-probe.mjs +4 -4
  22. package/dist/client-B9UBI03V.mjs +2 -0
  23. package/dist/{client-BTZ3XkrB.mjs → client-DKAsFA4X.mjs} +161 -24
  24. package/dist/{cloudflare-auth-DQkqoMYa.mjs → cloudflare-auth-Qdc7tw8F.mjs} +41 -38
  25. package/dist/{cloudflare-cmd-BKlfGHAy.mjs → cloudflare-cmd-CmY8dGkd.mjs} +6 -6
  26. package/dist/cloudflare-config-Bktvwtpf.mjs +182 -0
  27. package/dist/{cloudflare-connect-Ctmrw579.mjs → cloudflare-connect-IiR3ZieC.mjs} +4 -4
  28. package/dist/{cloudflare-operations-BT6OWFBk.mjs → cloudflare-operations-BCdXSsmo.mjs} +1 -1
  29. package/dist/{cloudflare-operations-B9tzgjmf.mjs → cloudflare-operations-DzH-A966.mjs} +116 -172
  30. package/dist/{preset-UHj9ARyP.mjs → cloudflare-process-B-wekeR6.mjs} +93 -6
  31. package/dist/{config-VavjpDnp.d.mts → config-BMHb8RCj.d.mts} +1 -0
  32. package/dist/config-C_XRIPx2.mjs +89 -0
  33. package/dist/config-entry.d.mts +1 -1
  34. package/dist/{connect-Dg-WkW-C.mjs → connect-B-usTnlF.mjs} +9 -9
  35. package/dist/{create-project-l9J7pbtm.mjs → create-project-CYOOAYcn.mjs} +12 -12
  36. package/dist/{create-project-DdoqwFLF.mjs → create-project-FWbf5Olo.mjs} +1 -1
  37. package/dist/{db-gp2sCXyN.mjs → db-Ck2eZ2iB.mjs} +26 -26
  38. package/dist/{delete-TTedbH6B.mjs → delete-sK56qtzZ.mjs} +5 -5
  39. package/dist/{deploy-CH-o2CaZ.mjs → deploy-gCVaiJbS.mjs} +984 -616
  40. package/dist/{deploy-B19L2Bra.mjs → deploy-irP5q0-x.mjs} +1 -1
  41. package/dist/{domain-JRF59P_r.mjs → domain-DFfQE8jg.mjs} +6 -6
  42. package/dist/{email-Bo6G9LOZ.mjs → email-CdnhUHvJ.mjs} +15 -30
  43. package/dist/{env-BYOrWQnv.mjs → env-DmGdHi5i.mjs} +8 -8
  44. package/dist/{env-public-BxU_0yTL.d.mts → env-public-BX_r8HR6.d.mts} +1 -1
  45. package/dist/{env-types-C2_bIk_b.mjs → env-types-D5kds_OZ.mjs} +1 -1
  46. package/dist/{env-validation-BB4GkLxn.mjs → env-validation-C_8tPdhs.mjs} +20 -73
  47. package/dist/{env-validation-BXge7uyK.mjs → env-validation-zZOCcQVZ.mjs} +1 -1
  48. package/dist/{fetch-CXDChK7B.mjs → fetch-BIZJh7vR.mjs} +2 -2
  49. package/dist/{fetch-stream-AOByI7Ki.mjs → fetch-stream-IvCYKyQL.mjs} +24 -4
  50. package/dist/{gen-sCtlCOdA.mjs → gen-CshAkukR.mjs} +4 -4
  51. package/dist/gen-DvQTWzDU.mjs +2 -0
  52. package/dist/{github-cmd-0-7PexDq.mjs → github-cmd-DftuEvIt.mjs} +13 -13
  53. package/dist/{handler-HEcZsaij.d.mts → handler-CZ4nAylQ.d.mts} +1 -1
  54. package/dist/{headers-DWi2IXWx.mjs → headers-2rKda9Y6.mjs} +1 -1
  55. package/dist/{help-GKtwl07I.mjs → help-Dotbf6iz.mjs} +236 -750
  56. package/dist/help-mabuIV98.mjs +2 -0
  57. package/dist/{inbound-aVHEUhKo.mjs → inbound-fRkqcXaC.mjs} +1 -1
  58. package/dist/index.d.mts +1 -1
  59. package/dist/index.mjs +299 -1517
  60. package/dist/info-B97bTX9N.mjs +113 -0
  61. package/dist/{init-FZx3Elvz.mjs → init-BTQEZZGJ.mjs} +61 -27
  62. package/dist/limits-Bq5LG8Id.d.mts +27 -0
  63. package/dist/limits-Cjuk2VPm.mjs +68 -0
  64. package/dist/{link-BrQfb_CU.mjs → link-B46sYH0K.mjs} +6 -6
  65. package/dist/{list-D44jmIAM.mjs → list-BnPrIsU_.mjs} +6 -6
  66. package/dist/{live-CKJlvlNp.d.mts → live-Chw1eIMv.d.mts} +1 -1
  67. package/dist/{local-d1-Bg9OzEEO.mjs → local-d1-2CMnpuW_.mjs} +2 -2
  68. package/dist/log-vqY3pqwI.mjs +29 -0
  69. package/dist/{login-CAsAsQ-_.mjs → login-BuiMyUrx.mjs} +8 -8
  70. package/dist/login-DIy9Sb-U.mjs +2 -0
  71. package/dist/{logs-BjKvFnVM.mjs → logs-CZG9d-ic.mjs} +10 -11
  72. package/dist/migrate-BCiJ8w0f.mjs +2 -0
  73. package/dist/{migrate-BPITvDJN.mjs → migrate-q4_njtaW.mjs} +4 -3
  74. package/dist/{node-Dt7z256D.mjs → node-BkyRWRx8.mjs} +1 -1
  75. package/dist/operator-args-B3BTmSoi.mjs +697 -0
  76. package/dist/{operator-auth-8KnOVXVd.mjs → operator-auth-7erYc6Lt.mjs} +2 -2
  77. package/dist/{operator-client-DUwumHpQ.mjs → operator-client-CpHjTd0t.mjs} +3 -3
  78. package/dist/{operator-cmd-BK5CCiT9.mjs → operator-cmd-DGqPJUD3.mjs} +70 -24
  79. package/dist/{output-Dm_A4Tbv.mjs → output-CbM85T4k.mjs} +18 -10
  80. package/dist/pages/client.d.mts +34 -2
  81. package/dist/pages/client.mjs +59 -3
  82. package/dist/pages/index.d.mts +1 -1
  83. package/dist/pages/index.mjs +2 -2
  84. package/dist/pages/islands-plugin.mjs +1 -1
  85. package/dist/pages/protocol.d.mts +2 -2
  86. package/dist/pages/protocol.mjs +2 -308
  87. package/dist/{parse-filename-DioPHiR9.mjs → parse-filename-CUbj-1MP.mjs} +35 -1
  88. package/dist/plan-D1Q5rf-r.mjs +2 -0
  89. package/dist/plan-NPpwZ_kc.mjs +58 -0
  90. package/dist/platform-args-3k8X9-H_.mjs +514 -0
  91. package/dist/platform-args-DdnM6WIf.mjs +2 -0
  92. package/dist/{platform-auth-config-BlN8xTdD.mjs → platform-auth-config-DDrxUy4m.mjs} +6 -6
  93. package/dist/{platform-auth-protection-7d5aV2Jg.mjs → platform-auth-protection-BLadnnct.mjs} +8 -8
  94. package/dist/{platform-auth-recovery-Dxij8ZbR.mjs → platform-auth-recovery-CmCzX2Fl.mjs} +6 -6
  95. package/dist/{platform-cmd-B3hwKrFK.mjs → platform-cmd-DMDEc9uS.mjs} +29 -8
  96. package/dist/{platform-cmd-E0FuL212.mjs → platform-cmd-pMTRhvYs.mjs} +1 -1
  97. package/dist/{platform-domain-D6Xcy9ZX.mjs → platform-domain-Di6EYrQl.mjs} +5 -5
  98. package/dist/{platform-lifecycle-k0E0xoxx.mjs → platform-lifecycle-BBoNzojY.mjs} +1541 -572
  99. package/dist/{platform-lifecycle-DMh_qrry.mjs → platform-lifecycle-BFdSi9Qt.mjs} +1 -1
  100. package/dist/{platform-management-Brz_BDiT.mjs → platform-management-CWOQOWXm.mjs} +10 -7
  101. package/dist/{platform-management-DOtss0BN.mjs → platform-management-DG47jys8.mjs} +1 -1
  102. package/dist/platform-plans-config-Vl9BP3H4.mjs +359 -0
  103. package/dist/{platform-recovery-BvtcXON5.mjs → platform-recovery-BJnQJEtQ.mjs} +4 -4
  104. package/dist/{platform-registry-QBa4DK32.mjs → platform-registry-DkPPXfp_.mjs} +2 -2
  105. package/dist/{plugin-inference-BMfKRSqE.mjs → plugin-inference-CXWnn79A.mjs} +174 -64
  106. package/dist/{prepare-C-6YZyyg.mjs → prepare-CBLT3CHH.mjs} +3 -3
  107. package/dist/prepare-CnjcTV2s.mjs +2 -0
  108. package/dist/{prepare-CRhJbVrG.mjs → prepare-DnaKBi--.mjs} +5 -21
  109. package/dist/{project-C0Fqu05O.mjs → project-DKG5Ra8p.mjs} +15 -82
  110. package/dist/project-DsblYifa.mjs +2 -0
  111. package/dist/{project-cmd-BZLCqOqw.mjs → project-cmd-DxnXstAq.mjs} +19 -19
  112. package/dist/{project-team-BvgM0WoX.mjs → project-team-BmHcaALJ.mjs} +5 -5
  113. package/dist/{project-token-l5DqK6Az.mjs → project-token-GionZlBI.mjs} +5 -5
  114. package/dist/{project-zero-trust-v6Mvpp8y.mjs → project-zero-trust-JLCjVQA7.mjs} +5 -5
  115. package/dist/prompts-2TGE6oXx.mjs +2 -0
  116. package/dist/prompts-BNUoTiaf.mjs +2044 -0
  117. package/dist/{protocol-BBa6cstI.d.mts → protocol-CjF_iI9X.d.mts} +2 -2
  118. package/dist/protocol-U7bfjHmA.mjs +331 -0
  119. package/dist/{provision-Fr9pRqce.mjs → provision-CVku1JFU.mjs} +1 -1
  120. package/dist/{provision-C4IGqkBf.mjs → provision-CaoxO_U7.mjs} +90 -199
  121. package/dist/{requests-DN-BbNiM.mjs → requests-BOjIVAeF.mjs} +5 -5
  122. package/dist/resource-name-C7LVpcRm.mjs +11 -0
  123. package/dist/{rollback-D7rzSaZM.mjs → rollback-CwR3pLj3.mjs} +6 -6
  124. package/dist/route-url-CG7U-cRN.mjs +15 -0
  125. package/dist/{runner-B8wXwWlo.mjs → runner-CXA9Fh8h.mjs} +1 -1
  126. package/dist/{runner-p-dMs2UN.mjs → runner-Ol0TNjk6.mjs} +2 -2
  127. package/dist/runtime/ai.d.mts +12 -8
  128. package/dist/runtime/ai.mjs +87 -22
  129. package/dist/runtime/better-auth-mysql.mjs +1 -1
  130. package/dist/runtime/better-auth-pg.mjs +1 -1
  131. package/dist/runtime/better-auth.mjs +1 -1
  132. package/dist/runtime/client-react.mjs +2 -2
  133. package/dist/runtime/client-solid.mjs +2 -2
  134. package/dist/runtime/client-svelte.mjs +2 -2
  135. package/dist/runtime/client-vue.mjs +2 -2
  136. package/dist/runtime/client.mjs +2 -2
  137. package/dist/runtime/durable.d.mts +3 -1
  138. package/dist/runtime/durable.mjs +4 -1
  139. package/dist/runtime/email/testing.mjs +2 -2
  140. package/dist/runtime/email.mjs +1 -1
  141. package/dist/runtime/env-public.d.mts +1 -1
  142. package/dist/runtime/fetch-stream.mjs +1 -1
  143. package/dist/runtime/fetch.mjs +1 -1
  144. package/dist/runtime/handler.d.mts +1 -1
  145. package/dist/runtime/kv.mjs +0 -1
  146. package/dist/runtime/limits.d.mts +2 -0
  147. package/dist/runtime/limits.mjs +2 -0
  148. package/dist/runtime/live-client.d.mts +1 -1
  149. package/dist/runtime/live-server.mjs +25 -16
  150. package/dist/runtime/live.d.mts +1 -1
  151. package/dist/runtime/migration-handler.mjs +62 -42
  152. package/dist/runtime/remote/index.mjs +2 -2
  153. package/dist/runtime/route-url.d.mts +4 -0
  154. package/dist/runtime/route-url.mjs +2 -0
  155. package/dist/runtime/routing.d.mts +180 -0
  156. package/dist/runtime/routing.mjs +1082 -0
  157. package/dist/runtime/sandbox-container.d.mts +1 -1
  158. package/dist/runtime/sandbox-container.mjs +1 -1
  159. package/dist/runtime/sandbox.d.mts +3 -3
  160. package/dist/runtime/sandbox.mjs +75 -45
  161. package/dist/runtime/sse.mjs +1 -1
  162. package/dist/runtime/validator.d.mts +1 -1
  163. package/dist/runtime/ws-server.d.mts +4 -2
  164. package/dist/runtime/ws-server.mjs +27 -2
  165. package/dist/runtime/ws.d.mts +2 -2
  166. package/dist/runtime/ws.mjs +8 -6
  167. package/dist/{sandbox-qpNBT8a3.d.mts → sandbox-XZAqzFlG.d.mts} +11 -8
  168. package/dist/{sandbox-container-DdNEBfCc.d.mts → sandbox-container-Bo0eiFKz.d.mts} +2 -1
  169. package/dist/{sandbox-container-4fdqLnyb.mjs → sandbox-container-C6ItmVuN.mjs} +4 -2
  170. package/dist/{scan-4tfN-PSn.mjs → scan-C7okrLyM.mjs} +4 -35
  171. package/dist/{secret-BOtOl_cb.mjs → secret-ByWJWBUG.mjs} +7 -7
  172. package/dist/{skills-D1II1Juz.mjs → skills-C1PlclPt.mjs} +1 -1
  173. package/dist/{sse-BaC1jXko.mjs → sse-CQNaDFFV.mjs} +6 -3
  174. package/dist/{subcommand-prompt-CY1C4fvl.mjs → subcommand-prompt-klFCfcor.mjs} +40 -32
  175. package/dist/validate-Dq_L3s0S.mjs +2 -0
  176. package/dist/{validate-CIUwFpjB.mjs → validate-ctOrgiS3.mjs} +2 -1
  177. package/dist/{wrangler-DQF1vKyf.mjs → wrangler-7K-bW_DL.mjs} +8 -239
  178. package/dist/{ws-BoY7vQML.d.mts → ws-CL1w7GXU.d.mts} +13 -2
  179. package/package.json +16 -10
  180. package/skills/migrate-vite-cloudflare-to-void/SKILL.md +34 -157
  181. package/skills/void/SKILL.md +55 -135
  182. package/skills/void/docs/guide/ai.md +94 -84
  183. package/skills/void/docs/guide/app-types.md +3 -32
  184. package/skills/void/docs/guide/auth.md +12 -116
  185. package/skills/void/docs/guide/database/d1.md +9 -54
  186. package/skills/void/docs/guide/database/mysql.md +1 -1
  187. package/skills/void/docs/guide/database/postgresql.md +5 -26
  188. package/skills/void/docs/guide/database.md +23 -75
  189. package/skills/void/docs/guide/deployment.md +48 -112
  190. package/skills/void/docs/guide/durable-state.md +43 -18
  191. package/skills/void/docs/guide/edge/headers.md +4 -48
  192. package/skills/void/docs/guide/edge/prerendering.md +5 -20
  193. package/skills/void/docs/guide/edge/redirects.md +11 -64
  194. package/skills/void/docs/guide/edge/revalidation.md +5 -18
  195. package/skills/void/docs/guide/edge/rewrites.md +56 -284
  196. package/skills/void/docs/guide/edge/static-assets.md +22 -72
  197. package/skills/void/docs/guide/email/domains.md +116 -0
  198. package/skills/void/docs/guide/email/receiving.md +139 -0
  199. package/skills/void/docs/guide/email/sending.md +231 -0
  200. package/skills/void/docs/guide/email.md +13 -619
  201. package/skills/void/docs/guide/env-migration.md +11 -11
  202. package/skills/void/docs/guide/env-vars.md +9 -29
  203. package/skills/void/docs/guide/index.md +0 -15
  204. package/skills/void/docs/guide/jobs.md +3 -18
  205. package/skills/void/docs/guide/kv.md +5 -11
  206. package/skills/void/docs/guide/live.md +5 -56
  207. package/skills/void/docs/guide/pages-routing/actions-and-forms.md +78 -125
  208. package/skills/void/docs/guide/pages-routing/head.md +10 -10
  209. package/skills/void/docs/guide/pages-routing/islands.md +6 -36
  210. package/skills/void/docs/guide/pages-routing/layouts.md +6 -128
  211. package/skills/void/docs/guide/pages-routing/loaders.md +3 -19
  212. package/skills/void/docs/guide/pages-routing/markdown.md +13 -171
  213. package/skills/void/docs/guide/pages-routing/overview.md +7 -17
  214. package/skills/void/docs/guide/pages-routing/view-transitions.md +1 -1
  215. package/skills/void/docs/guide/platform/administration/access.md +1 -4
  216. package/skills/void/docs/guide/platform/administration/email.md +35 -8
  217. package/skills/void/docs/guide/platform/administration/operations.md +26 -4
  218. package/skills/void/docs/guide/platform/administration/plans.md +126 -0
  219. package/skills/void/docs/guide/platform/administration/projects.md +5 -2
  220. package/skills/void/docs/guide/platform/administration/zero-trust.md +49 -160
  221. package/skills/void/docs/guide/platform/development/runtime.md +3 -13
  222. package/skills/void/docs/guide/platform/development/schema-ci.md +0 -58
  223. package/skills/void/docs/guide/platform/installation/ci.md +1 -1
  224. package/skills/void/docs/guide/platform/installation/credentials.md +15 -6
  225. package/skills/void/docs/guide/platform/installation/domains.md +35 -2
  226. package/skills/void/docs/guide/platform/installation/first-deployment.md +4 -2
  227. package/skills/void/docs/guide/platform/installation/maintenance.md +15 -1
  228. package/skills/void/docs/guide/platform/installation/prerequisites.md +24 -18
  229. package/skills/void/docs/guide/platform/installation/setup.md +88 -14
  230. package/skills/void/docs/guide/platform-administration.md +1 -0
  231. package/skills/void/docs/guide/queues.md +7 -9
  232. package/skills/void/docs/guide/quickstart.md +38 -37
  233. package/skills/void/docs/guide/remote-dev.md +5 -10
  234. package/skills/void/docs/guide/sandboxes.md +29 -21
  235. package/skills/void/docs/guide/server-routing.md +9 -72
  236. package/skills/void/docs/guide/sse.md +4 -18
  237. package/skills/void/docs/guide/ssg.md +3 -15
  238. package/skills/void/docs/guide/ssr.md +14 -62
  239. package/skills/void/docs/guide/storage.md +9 -4
  240. package/skills/void/docs/guide/type-safety.md +3 -14
  241. package/skills/void/docs/guide/typed-fetch.md +3 -7
  242. package/skills/void/docs/guide/websockets.md +68 -40
  243. package/skills/void/docs/integrations/agents.md +3 -3
  244. package/skills/void/docs/integrations/cloudflare.md +85 -316
  245. package/skills/void/docs/integrations/frameworks/analog.md +5 -64
  246. package/skills/void/docs/integrations/frameworks/astro.md +4 -73
  247. package/skills/void/docs/integrations/frameworks/nuxt.md +5 -62
  248. package/skills/void/docs/integrations/frameworks/overview.md +11 -54
  249. package/skills/void/docs/integrations/frameworks/react-router.md +5 -60
  250. package/skills/void/docs/integrations/frameworks/sveltekit.md +6 -65
  251. package/skills/void/docs/integrations/frameworks/tanstack-start.md +4 -62
  252. package/skills/void/docs/integrations/nodejs-bun-deno.md +5 -69
  253. package/skills/void/docs/reference/api/auth.md +156 -0
  254. package/skills/void/docs/reference/api/client.md +87 -0
  255. package/skills/void/docs/reference/api/database.md +95 -0
  256. package/skills/void/docs/reference/api/durable.md +46 -0
  257. package/skills/void/docs/reference/api/env.md +50 -0
  258. package/skills/void/docs/reference/api/handlers.md +254 -0
  259. package/skills/void/docs/reference/api/pages.md +241 -0
  260. package/skills/void/docs/reference/api/plugin.md +39 -0
  261. package/skills/void/docs/reference/api/resources.md +109 -0
  262. package/skills/void/docs/reference/api/rewrites.md +76 -0
  263. package/skills/void/docs/reference/api/types.md +92 -0
  264. package/skills/void/docs/reference/api.md +56 -1218
  265. package/skills/void/docs/reference/cli/auth.md +89 -0
  266. package/skills/void/docs/reference/cli/database.md +128 -0
  267. package/skills/void/docs/reference/cli/deploy.md +105 -0
  268. package/skills/void/docs/reference/cli/domains.md +41 -0
  269. package/skills/void/docs/reference/cli/email.md +129 -0
  270. package/skills/void/docs/reference/cli/generate.md +116 -0
  271. package/skills/void/docs/reference/cli/github.md +189 -0
  272. package/skills/void/docs/reference/cli/platform-config.md +92 -0
  273. package/skills/void/docs/reference/cli/platform-email.md +81 -0
  274. package/skills/void/docs/reference/cli/platform-installation.md +142 -0
  275. package/skills/void/docs/reference/cli/platform-operations.md +90 -0
  276. package/skills/void/docs/reference/cli/platform-users.md +89 -0
  277. package/skills/void/docs/reference/cli/platform-zero-trust.md +59 -0
  278. package/skills/void/docs/reference/cli/platform.md +70 -0
  279. package/skills/void/docs/reference/cli/project.md +214 -0
  280. package/skills/void/docs/reference/cli/secrets.md +76 -0
  281. package/skills/void/docs/reference/cli/setup.md +70 -0
  282. package/skills/void/docs/reference/cli.md +32 -1682
  283. package/skills/void/docs/reference/config.md +12 -18
  284. package/skills/void/docs/reference/resource-inference.md +3 -58
  285. package/skills/void/docs/reference/structure.md +14 -41
  286. package/dist/auth-CZuiVsFh.mjs +0 -2
  287. package/dist/canonical-json-DuDiiUsQ.mjs +0 -13
  288. package/dist/client-Czz8o5jP.mjs +0 -2
  289. package/dist/dist-BuDuKZJv.mjs +0 -2
  290. package/dist/dist-CTBk70IR.mjs +0 -1310
  291. package/dist/gen-CNJ62MM7.mjs +0 -2
  292. package/dist/help-CmZzxUba.mjs +0 -2
  293. package/dist/log-ChfPKsVd.mjs +0 -26
  294. package/dist/login-CGcRKEoi.mjs +0 -2
  295. package/dist/migrate-CYfbKkXh.mjs +0 -2
  296. package/dist/plan-BEZ8VJW0.mjs +0 -256
  297. package/dist/plan-DpuOr14e.mjs +0 -2
  298. package/dist/prepare-Bm3iq-u4.mjs +0 -2
  299. package/dist/project-BuUwfj6f.mjs +0 -2
  300. package/dist/validate-EKmJWxmy.mjs +0 -2
  301. /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";
6
- 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";
8
- import { r as ensurePlatformProxyAccess, t as ensureCloudflaredToken } from "./cf-access-P0iZnu0q.mjs";
9
- 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";
12
- import { i as voidWarn, r as voidLog } from "./log-ChfPKsVd.mjs";
13
- import { n as lintDuplicateSources, r as mergeRoutingRules, t as lintDestinationSplats } from "./headers-DWi2IXWx.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";
14
7
  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";
8
+ import { o as scanDurableObjectModules, t as analyzeApplicationSync } from "./application-analysis-BVsVqc11.mjs";
18
9
  import { t as collectMigrations } from "./collect-DiTn9yr6.mjs";
19
- import { r as buildMigrationPlanBundle } from "./plan-BEZ8VJW0.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";
13
+ import { n as scopedAccessCredentials, r as trustedCloudflareWorkerAccessHeaders, t as cfAccessHeaders } from "./cf-access-TWNzXltp.mjs";
14
+ import { r as ensurePlatformProxyAccess, t as ensureCloudflaredToken } from "./cf-access-BGe5sWXF.mjs";
15
+ import { l as writeProjectConfig, n as readDeploymentPlatform, o as shouldLinkDeployProject, r as readProjectConfig, s as writeDeploymentPlatform, t as attachSelectedPlatform } from "./project-DKG5Ra8p.mjs";
16
+ import { a as getApiUrl, c as getToken, l as getTokenSource, o as getProxyUrl } from "./auth-CQkSDS50.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";
19
+ import { i as voidWarn, r as voidLog } from "./log-vqY3pqwI.mjs";
20
+ import { n as lintDuplicateSources, r as mergeRoutingRules, t as lintDestinationSplats } from "./headers-2rKda9Y6.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
- 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";
25
+ import { t as findEnvFile } from "./env-types-D5kds_OZ.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-C_8tPdhs.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
- 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";
28
+ import { A as confirm, B as note, H as outro, K as select, L as log$1, N as intro, tt as isCancel } from "./prompts-BNUoTiaf.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-DKAsFA4X.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-q4_njtaW.mjs";
43
+ import { a as createCloudflareEmptyDomainPublisher, i as createCloudflareEmptyCronPublisher } from "./cloudflare-operations-DzH-A966.mjs";
43
44
  import { t as formatCloudflareUserMessage } from "./cloudflare-user-output-Q-k0QoNQ.mjs";
44
- 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";
45
+ import { c as import_picocolors$1, i as createSpinner, r as cliTitle } from "./output-CbM85T4k.mjs";
46
+ import { a as promptProjectSelection, i as reportProjectCreationWarnings, o as promptProjectSetupAction, r as promptAndCreateProject } from "./create-project-CYOOAYcn.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-BuiMyUrx.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
@@ -110,6 +112,7 @@ function resolveAssetRoutingPolicy(inputs) {
110
112
  }
111
113
  //#endregion
112
114
  //#region src/cli/email-cloudflare.ts
115
+ var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
113
116
  /** The `send_email` binding name Void provisions and the runtime reads. */
114
117
  const SEND_EMAIL_BINDING = "SEND_EMAIL";
115
118
  /** The root-config `vars` key carrying `void.config.ts` `email.from` into the worker. */
@@ -240,127 +243,84 @@ function deriveAddresses(handlers, domain, mode) {
240
243
  routes
241
244
  };
242
245
  }
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
- }
246
+ function parseWhoamiJson(data) {
247
+ if (!isRecord(data) || typeof data.loggedIn !== "boolean") return null;
248
+ if (data.tokenPermissions !== void 0 && (!Array.isArray(data.tokenPermissions) || data.tokenPermissions.some((scope) => typeof scope !== "string"))) return null;
249
+ 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;
250
+ return data;
253
251
  }
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
- };
252
+ function isRecord(value) {
253
+ return value !== null && typeof value === "object" && !Array.isArray(value);
266
254
  }
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/);
255
+ function readRoutingSettings(data) {
256
+ 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) ? {
257
+ name: data.name,
258
+ enabled: data.enabled,
259
+ status: data.status,
260
+ tag: data.tag
261
+ } : null;
262
+ }
263
+ function readRoutingRules(data) {
264
+ if (!Array.isArray(data)) return null;
285
265
  const rules = [];
286
266
  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) {
267
+ for (const entry of data) {
268
+ 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;
269
+ const actions = [];
270
+ for (const action of entry.actions) {
271
+ if (!isRecord(action) || typeof action.type !== "string") return null;
272
+ if (action.type === "drop") actions.push("drop");
273
+ else if (action.type === "worker" || action.type === "forward") {
274
+ const values = typeof action.value === "string" ? [action.value] : action.value;
275
+ if (!Array.isArray(values) || values.length === 0 || !values.every((value) => typeof value === "string" && value.length > 0)) return null;
276
+ if (action.type === "worker") actions.push("worker:" + values.join(","));
277
+ else actions.push("forward:" + values.join(","));
278
+ } else return null;
279
+ }
280
+ const to = [];
281
+ let all = false;
282
+ for (const matcher of entry.matchers) {
283
+ if (!isRecord(matcher)) return null;
284
+ if (matcher.type === "all") all = true;
285
+ else if (matcher.type === "literal" && matcher.field === "to" || matcher.type === "to") {
286
+ if (typeof matcher.value !== "string" || matcher.value.length === 0) return null;
287
+ to.push(matcher.value.toLowerCase());
288
+ } else return null;
289
+ }
290
+ if (all) {
291
+ if (catchAll || entry.matchers.length !== 1) return null;
297
292
  catchAll = {
298
- enabled: catchAllMatch[1] === "enabled",
299
- actions: splitActionList(catchAllMatch[2] ?? "")
293
+ enabled: entry.enabled,
294
+ actions
300
295
  };
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);
296
+ } else {
297
+ if (typeof entry.id !== "string" || entry.id.length === 0 || entry.name !== void 0 && typeof entry.name !== "string") return null;
298
+ rules.push({
299
+ id: entry.id,
300
+ name: entry.name ?? "",
301
+ enabled: entry.enabled,
302
+ to,
303
+ actions
304
+ });
331
305
  }
332
306
  }
333
- if (!sawSentinel && rules.length === 0 && !catchAll) return null;
334
307
  return {
335
308
  rules,
336
309
  catchAll
337
310
  };
338
311
  }
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",
312
+ function readSendingSettings(data) {
313
+ if (!isRecord(data)) return null;
314
+ const tag = data.tag ?? data.id;
315
+ return typeof data.name === "string" && data.name.length > 0 && typeof data.enabled === "boolean" && typeof tag === "string" && /^[0-9a-f]{32}$/.test(tag) ? {
316
+ name: data.name,
317
+ enabled: data.enabled,
350
318
  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;
319
+ } : null;
357
320
  }
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, "");
321
+ function providerErrorCode(result) {
322
+ return result.errorCode ?? result.errors?.find((error) => error.code !== void 0)?.code ?? null;
361
323
  }
362
- const ZONE_NOT_FOUND_RE = /Could not find (?:a )?zone for `([^`]+)`/;
363
- const SENDING_NOT_FOUND_RE = /No sending subdomain found for `([^`]+)`/;
364
324
  function isHardFailure(result) {
365
325
  return Boolean(result.signal) || Boolean(result.error);
366
326
  }
@@ -417,8 +377,10 @@ async function preflightEmail(inputs) {
417
377
  const domain = emailFromDomain(inputs.from);
418
378
  if (!domain) throw new Error(`email.from must be an address like you@mail.example.com (got ${JSON.stringify(inputs.from)})`);
419
379
  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 });
380
+ const run = async (operation) => inputs.runWrangler({
381
+ ...operation,
382
+ ...inputs.configPath ? { configPath: inputs.configPath } : {}
383
+ }, { capture: true });
422
384
  const usageHandlers = [...inputs.handlers.addresses.map((d) => `email/${d.filePath}`), ...inputs.handlers.defaultHandler ? [`email/${inputs.handlers.defaultHandler}`] : []];
423
385
  const pre = {
424
386
  from: inputs.from,
@@ -481,8 +443,8 @@ async function preflightEmail(inputs) {
481
443
  });
482
444
  };
483
445
  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);
446
+ const whoami = await run({ type: "auth.whoami" });
447
+ const who = isHardFailure(whoami) || whoami.status !== 0 ? null : parseWhoamiJson(whoami.data);
486
448
  if (!who) {
487
449
  pre.session.state = "unknown";
488
450
  block("all", "could not read the Cloudflare session — run `void cloudflare login`");
@@ -509,22 +471,20 @@ async function preflightEmail(inputs) {
509
471
  let settings = null;
510
472
  let settingsMiss = false;
511
473
  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
- ]);
474
+ const result = await run({
475
+ type: "email.routing.settings",
476
+ domain: candidate
477
+ });
518
478
  if (isHardFailure(result)) {
519
479
  settingsMiss = true;
520
480
  break;
521
481
  }
482
+ if (result.status === 0 && result.data === null) continue;
522
483
  if (result.status !== 0) {
523
- if (ZONE_NOT_FOUND_RE.test(stripAnsi(result.stderr))) continue;
524
484
  settingsMiss = true;
525
485
  break;
526
486
  }
527
- settings = parseRoutingSettings(result.stdout);
487
+ settings = readRoutingSettings(result.data);
528
488
  if (!settings) settingsMiss = true;
529
489
  break;
530
490
  }
@@ -621,14 +581,11 @@ async function preflightEmail(inputs) {
621
581
  else if (pre.domainMx.state === "unknown") block("all", `could not read the MX records of ${domain} — unknown — run \`void email setup --platform cloudflare\``);
622
582
  }
623
583
  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);
584
+ const rulesResult = await run({
585
+ type: "email.routing.rules.list",
586
+ domain: apex
587
+ });
588
+ const rules = isHardFailure(rulesResult) || rulesResult.status !== 0 ? null : readRoutingRules(rulesResult.data);
632
589
  if (!rules) {
633
590
  pre.rules.state = "unknown";
634
591
  block("all", `could not read Email Routing rules — unknown — run \`void email setup --platform cloudflare\`${tokenHint}`);
@@ -658,18 +615,16 @@ async function preflightEmail(inputs) {
658
615
  }
659
616
  const foreign = new Set(pre.rules.foreign.map((f) => f.address));
660
617
  pre.addresses = pre.derived.addresses.filter((a) => !foreign.has(a));
661
- const sendingResult = await run([
662
- "email",
663
- "sending",
664
- "settings",
618
+ const sendingResult = await run({
619
+ type: "email.sending.settings",
665
620
  domain
666
- ]);
621
+ });
667
622
  if (isHardFailure(sendingResult)) pre.sending.state = "unknown";
623
+ else if (sendingResult.status === 0 && sendingResult.data === null) pre.sending.state = "pending";
668
624
  else if (sendingResult.status === 0) {
669
- const parsed = parseSendingSettings(sendingResult.stdout);
625
+ const parsed = readSendingSettings(sendingResult.data);
670
626
  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";
627
+ } else pre.sending.state = "unknown";
673
628
  if (pre.sending.state === "unknown") block("sending", `could not read Email Sending settings — unknown — run \`void email setup --platform cloudflare\`${tokenHint}`);
674
629
  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
630
  const names = (inputs.existingSendEmail ?? []).map((e) => typeof e?.name === "string" ? e.name : "");
@@ -849,8 +804,10 @@ async function applyEmailSetup(pre, inputs) {
849
804
  }
850
805
  const apex = pre.apex;
851
806
  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 });
807
+ const run = async (operation) => inputs.runWrangler({
808
+ ...operation,
809
+ ...inputs.configPath ? { configPath: inputs.configPath } : {}
810
+ }, { capture: true });
854
811
  let bearer;
855
812
  let bearerFailed = false;
856
813
  let bearerObtained = false;
@@ -879,17 +836,15 @@ async function applyEmailSetup(pre, inputs) {
879
836
  const apexRoutingOff = pre.mode === "subdomain" && pre.routing.enabled !== true;
880
837
  if (!result.routingReady) {
881
838
  if (pre.mode === "apex") {
882
- const enable = await run([
883
- "email",
884
- "routing",
885
- "enable",
886
- apex
887
- ]);
839
+ const enable = await run({
840
+ type: "email.routing.enable",
841
+ domain: apex
842
+ });
888
843
  if (!isHardFailure(enable) && enable.status === 0) {
889
844
  result.routingReady = true;
890
845
  lines.push(` ✔ routing ${apex} enabled`);
891
846
  } else {
892
- const code = wranglerApiErrorCode(enable.stderr);
847
+ const code = providerErrorCode(enable);
893
848
  lines.push(` ✘ routing could not enable Email Routing on ${apex}${code ? ` [code: ${code}]${CF_EMAIL_ERROR_CODES[code] ? ` — ${CF_EMAIL_ERROR_CODES[code]}` : ""}` : ""}`);
894
849
  }
895
850
  } else if (apexRoutingOff) {
@@ -920,13 +875,11 @@ async function applyEmailSetup(pre, inputs) {
920
875
  const sendingBlocked = pre.blockers.filter((b) => b.scope === "sending");
921
876
  if (!result.sendingReady && sendingBlocked.length > 0) for (const b of sendingBlocked) lines.push(` ! sending ${b.message}`);
922
877
  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);
878
+ const enable = await run({
879
+ type: "email.sending.enable",
880
+ domain: pre.domain
881
+ });
882
+ const code = isHardFailure(enable) ? null : providerErrorCode(enable);
930
883
  if (!isHardFailure(enable) && (enable.status === 0 || code === 2040)) {
931
884
  result.sendingReady = true;
932
885
  lines.push(` ✔ sending ${pre.domain} onboarded`);
@@ -976,37 +929,6 @@ async function createNodeEmailDns() {
976
929
  };
977
930
  }
978
931
  //#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
932
  //#region ../../node_modules/.pnpm/ci-info@4.4.0/node_modules/ci-info/vendors.json
1011
933
  var require_vendors = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1012
934
  module.exports = [
@@ -1617,8 +1539,8 @@ function buildPrerenderBindings(input) {
1617
1539
  if (input.voidToken && input.voidProjectId) {
1618
1540
  voidBindings.__VOID_TOKEN = input.voidToken;
1619
1541
  voidBindings.__VOID_PROJECT_ID = input.voidProjectId;
1620
- if (input.voidStaging) voidBindings.__VOID_STAGING = "1";
1621
- voidBindings.__VOID_PROXY_URL = input.voidProxyUrl ?? (input.voidStaging ? "https://proxy.staging.void.cloud" : "https://proxy.void.cloud");
1542
+ if (!input.voidProxyUrl) throw new Error("prerender: No connected platform proxy is available. Run `void connect <url>` first.");
1543
+ voidBindings.__VOID_PROXY_URL = input.voidProxyUrl;
1622
1544
  const access = scopedAccessCredentials(input.accessEnv ?? {}, voidBindings.__VOID_PROXY_URL);
1623
1545
  if (access) voidBindings.__VOID_ACCESS_CREDENTIALS = access;
1624
1546
  if (input.remoteMode) voidBindings.__VOID_REMOTE = "1";
@@ -1646,7 +1568,6 @@ async function createCloudflarePrerenderRunner(options) {
1646
1568
  } catch {}
1647
1569
  }
1648
1570
  const voidToken = voidProjectId ? options.voidToken ?? getToken(root) : null;
1649
- const voidStaging = isStagingMode(root);
1650
1571
  const voidProxyUrl = voidToken && voidProjectId ? getProxyUrl(root) : void 0;
1651
1572
  if (voidProxyUrl) ensurePlatformProxyAccess(getApiUrl(root), voidProxyUrl);
1652
1573
  const bindings = buildPrerenderBindings({
@@ -1656,7 +1577,6 @@ async function createCloudflarePrerenderRunner(options) {
1656
1577
  voidToken,
1657
1578
  voidProjectId,
1658
1579
  voidProxyUrl,
1659
- voidStaging,
1660
1580
  remoteMode
1661
1581
  });
1662
1582
  const { Miniflare, convertV4MiniflareOptions } = await import("miniflare");
@@ -1748,7 +1668,7 @@ async function prerenderPages(options) {
1748
1668
  paths,
1749
1669
  ...await renderPrerenderPaths(paths, options.clientDir, (path) => runner.fetchPath(path), (path) => runner.fetchPageData(path), (path, outputFile, error) => {
1750
1670
  if (error !== void 0) voidWarn(`prerender ${path} failed: ${error}`);
1751
- else voidLog(`prerender \x1b[36m${outputFile}\x1b[39m`);
1671
+ else voidLog(`prerender ${import_picocolors.default.blue(outputFile)}`);
1752
1672
  })
1753
1673
  };
1754
1674
  } finally {
@@ -1854,12 +1774,43 @@ async function prerenderPagesNode(options) {
1854
1774
  ssrDir: options.ssrDir,
1855
1775
  clientDir: options.clientDir
1856
1776
  });
1857
- for (const path of result.rendered) voidLog(`prerender \x1b[36m${outputFileForPath(path)}\x1b[39m`);
1777
+ for (const path of result.rendered) voidLog(`prerender ${import_picocolors.default.blue(outputFileForPath(path))}`);
1858
1778
  for (const { path, error } of result.failed) voidWarn(`prerender ${path} failed: ${error}`);
1859
1779
  return result;
1860
1780
  }
1861
1781
  //#endregion
1782
+ //#region src/shared/version-metadata.ts
1783
+ /** Build-only marker: an existing-site handoff must not add Worker bindings. */
1784
+ const CLOUDFLARE_PRESERVE_BINDINGS_BUILD_ENV = "VOID_CLOUDFLARE_PRESERVE_BINDINGS";
1785
+ const DEFAULT_VERSION_METADATA_BINDING = "__VOID_VERSION_METADATA";
1786
+ /** Keep the authored singleton name, and reject a cross-type binding collision. */
1787
+ function readVersionMetadataBinding(config) {
1788
+ const name = bindingNamesForField("version_metadata", config.version_metadata)[0];
1789
+ if (!name) return;
1790
+ const owner = findBindingNameOwner(config, "version_metadata", name);
1791
+ if (owner) throw new Error(`Cloudflare version metadata binding ${name} conflicts with ${owner}.`);
1792
+ return name;
1793
+ }
1794
+ /** Self-metadata adds no remote resource or plan entitlement. */
1795
+ function selectNativeVersionMetadataBinding(config, options) {
1796
+ if (!options.nativeBuild) return;
1797
+ const authored = readVersionMetadataBinding(config);
1798
+ if (authored || options.preserveBindings) return authored;
1799
+ const owner = findBindingNameOwner(config, "version_metadata", DEFAULT_VERSION_METADATA_BINDING);
1800
+ if (owner) throw new Error(`Cloudflare version metadata binding ${DEFAULT_VERSION_METADATA_BINDING} conflicts with ${owner}.`);
1801
+ return DEFAULT_VERSION_METADATA_BINDING;
1802
+ }
1803
+ //#endregion
1862
1804
  //#region src/cli/cf-version-api.ts
1805
+ var CloudflareApiError = class extends Error {
1806
+ status;
1807
+ code;
1808
+ constructor(message, status, code) {
1809
+ super(message);
1810
+ this.status = status;
1811
+ this.code = code;
1812
+ }
1813
+ };
1863
1814
  function object(value) {
1864
1815
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
1865
1816
  }
@@ -1959,38 +1910,181 @@ function bindingsFromOutput(env, workerName, activeSecrets, explicitSecrets) {
1959
1910
  ...value.worker !== workerName ? { script_name: requiredString(value.worker, name) } : {}
1960
1911
  });
1961
1912
  break;
1913
+ case "unsafe:durable_object_namespace":
1914
+ if (Object.keys(value).some((key) => ![
1915
+ "type",
1916
+ "class_name",
1917
+ "dev"
1918
+ ].includes(key))) throw new Error(`Cloudflare binding ${name} is not a verified local Durable Object.`);
1919
+ bindings.push({
1920
+ name,
1921
+ type: "durable_object_namespace",
1922
+ class_name: requiredString(value.class_name, name)
1923
+ });
1924
+ break;
1925
+ case "queue":
1926
+ bindings.push({
1927
+ name,
1928
+ type: "queue",
1929
+ queue_name: requiredString(value.name, name),
1930
+ ...value.deliveryDelay === void 0 ? {} : { delivery_delay: value.deliveryDelay }
1931
+ });
1932
+ break;
1933
+ case "ai":
1934
+ case "browser":
1935
+ case "images":
1936
+ bindings.push({
1937
+ name,
1938
+ type: value.type
1939
+ });
1940
+ break;
1941
+ case "hyperdrive":
1942
+ bindings.push({
1943
+ name,
1944
+ type: "hyperdrive",
1945
+ id: requiredString(value.id, name)
1946
+ });
1947
+ break;
1948
+ case "worker":
1949
+ bindings.push({
1950
+ name,
1951
+ type: "service",
1952
+ service: requiredString(value.worker, name),
1953
+ ...value.exportName === void 0 ? {} : { entrypoint: value.exportName },
1954
+ ...value.props === void 0 ? {} : { props: value.props }
1955
+ });
1956
+ break;
1957
+ case "workflow":
1958
+ bindings.push({
1959
+ name,
1960
+ type: "workflow",
1961
+ workflow_name: requiredString(value.name, name),
1962
+ ...value.worker === workerName ? { class_name: requiredString(value.exportName, name) } : { script_name: requiredString(value.worker, name) }
1963
+ });
1964
+ break;
1965
+ case "send-email":
1966
+ bindings.push({
1967
+ name,
1968
+ type: "send_email",
1969
+ ...value.destinationAddress === void 0 ? {} : { destination_address: value.destinationAddress },
1970
+ ...value.allowedDestinationAddresses === void 0 ? {} : { allowed_destination_addresses: value.allowedDestinationAddresses },
1971
+ ...value.allowedSenderAddresses === void 0 ? {} : { allowed_sender_addresses: value.allowedSenderAddresses }
1972
+ });
1973
+ break;
1974
+ case "analytics-engine-dataset":
1975
+ bindings.push({
1976
+ name,
1977
+ type: "analytics_engine",
1978
+ ...value.name === void 0 ? {} : { dataset: value.name }
1979
+ });
1980
+ break;
1981
+ case "vectorize":
1982
+ bindings.push({
1983
+ name,
1984
+ type: "vectorize",
1985
+ index_name: requiredString(value.name, name)
1986
+ });
1987
+ break;
1988
+ case "dispatch-namespace": {
1989
+ const outbound = object(value.outbound);
1990
+ bindings.push({
1991
+ name,
1992
+ type: "dispatch_namespace",
1993
+ namespace: requiredString(value.namespace, name),
1994
+ ...outbound ? { outbound: {
1995
+ worker: { service: requiredString(outbound.worker, name) },
1996
+ ...Array.isArray(outbound.parameters) ? { params: outbound.parameters.map((parameter) => ({ name: parameter })) } : {}
1997
+ } } : {}
1998
+ });
1999
+ break;
2000
+ }
1962
2001
  case "assets":
1963
2002
  bindings.push({
1964
2003
  name,
1965
2004
  type: "assets"
1966
2005
  });
1967
2006
  break;
2007
+ case "version-metadata":
2008
+ bindings.push({
2009
+ name,
2010
+ type: "version_metadata"
2011
+ });
2012
+ break;
1968
2013
  default: throw new Error(`Cloudflare binding ${name} cannot be uploaded with pinned secrets.`);
1969
2014
  }
1970
2015
  }
1971
2016
  return bindings;
1972
2017
  }
1973
- function exportsFromOutput(exports) {
2018
+ function exportsFromOutput(exports, bindings) {
1974
2019
  if (exports === void 0) return;
1975
2020
  const entries = object(exports);
1976
2021
  if (!entries) throw new Error("Cloudflare Durable Object exports were not readable.");
1977
2022
  const result = {};
2023
+ const workflows = /* @__PURE__ */ new Map();
1978
2024
  for (const [name, raw] of Object.entries(entries)) {
1979
2025
  const value = object(raw);
2026
+ if (value?.type === "workflow") {
2027
+ const workflowName = requiredString(value.name, `Workflow export ${name} name`);
2028
+ if (workflows.has(workflowName)) throw new Error(`Cloudflare Workflow ${workflowName} has multiple local exports.`);
2029
+ workflows.set(workflowName, name);
2030
+ result[name] = {
2031
+ type: "workflow",
2032
+ name: workflowName
2033
+ };
2034
+ continue;
2035
+ }
2036
+ if (value?.type === "worker") {
2037
+ result[name] = {
2038
+ type: "worker",
2039
+ ...value.cache === void 0 ? {} : { cache: value.cache }
2040
+ };
2041
+ continue;
2042
+ }
1980
2043
  if (!value || value.type !== "durable-object") throw new Error(`Cloudflare export ${name} cannot be uploaded with pinned secrets.`);
1981
2044
  if (value.state === "deleted") result[name] = {
1982
2045
  type: "durable-object",
1983
2046
  state: "deleted"
1984
2047
  };
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.`);
2048
+ else if (value.state === "renamed" || value.state === "transferred") {
2049
+ const renamed = value.state === "renamed";
2050
+ result[name] = {
2051
+ type: "durable-object",
2052
+ state: value.state,
2053
+ [renamed ? "renamed_to" : "transferred_to"]: requiredString(value[renamed ? "renamedTo" : "transferredTo"], name)
2054
+ };
2055
+ } else if (value.storage === "sqlite" || value.storage === "legacy-kv") {
2056
+ if (value.state !== void 0 && !["created", "expecting-transfer"].includes(String(value.state))) throw new Error(`Cloudflare Durable Object export ${name} has an unsupported state.`);
2057
+ result[name] = {
2058
+ type: "durable-object",
2059
+ storage: value.storage,
2060
+ state: value.state ?? "created",
2061
+ ...value.container === void 0 ? {} : { container: requiredString(value.container, name) },
2062
+ ...value.state === "expecting-transfer" ? { transfer_from: requiredString(value.transferFrom, name) } : {}
2063
+ };
2064
+ } else throw new Error(`Cloudflare Durable Object export ${name} has no storage type.`);
2065
+ }
2066
+ for (const binding of bindings) {
2067
+ if (binding.type !== "workflow") continue;
2068
+ const localExport = workflows.get(String(binding.workflow_name));
2069
+ 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
2070
  }
1992
2071
  return result;
1993
2072
  }
2073
+ async function preflightWorkflowExports(options) {
2074
+ for (const raw of Object.values(options.exports ?? {})) {
2075
+ const entry = object(raw);
2076
+ if (entry?.type !== "workflow") continue;
2077
+ const name = requiredString(entry.name, "Workflow name");
2078
+ let workflow;
2079
+ try {
2080
+ workflow = await apiResult(`${options.accountUrl}/workflows/${encodeURIComponent(name)}`, options.token, "GET", void 0, options.verifyUploader);
2081
+ } catch (cause) {
2082
+ if (cause instanceof CloudflareApiError && cause.status === 404 && cause.code === 10200) continue;
2083
+ throw cause;
2084
+ }
2085
+ 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.`);
2086
+ }
2087
+ }
1994
2088
  function assetConfigFromOutput(value) {
1995
2089
  if (value === void 0) return;
1996
2090
  const config = object(value);
@@ -2027,11 +2121,211 @@ async function apiResult(url, token, method, body, verifyUploader, timeout = 12e
2027
2121
  if (!response.ok || parsed?.success !== true || !result) {
2028
2122
  const error = Array.isArray(parsed?.errors) ? object(parsed.errors[0]) : null;
2029
2123
  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}`);
2124
+ throw new CloudflareApiError(`Cloudflare version upload failed (${response.status})${code}.`, response.status, typeof error?.code === "number" ? error.code : void 0);
2032
2125
  }
2033
2126
  return result;
2034
2127
  }
2128
+ function assertContainerApplicationIdentity(application, plan, namespace) {
2129
+ 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.`);
2130
+ }
2131
+ function assertContainerApplicationSettings(application, plan) {
2132
+ const desired = object(plan.settings.configuration) ?? {};
2133
+ const current = object(application.configuration) ?? {};
2134
+ const changedConfiguration = Object.entries(desired).some(([key, value]) => {
2135
+ if (key === "wrangler_ssh") {
2136
+ const ssh = object(current.wrangler_ssh) ?? {};
2137
+ const expected = object(value) ?? {};
2138
+ return !isDeepStrictEqual(ssh.enabled ?? false, expected.enabled) || expected.enabled === true && expected.port !== void 0 && !isDeepStrictEqual(ssh.port, expected.port);
2139
+ }
2140
+ if (key === "experimental_flags" && Array.isArray(value)) {
2141
+ const flags = current.experimental_flags ?? [];
2142
+ return !Array.isArray(flags) || !isDeepStrictEqual([...flags].sort(), [...value].sort());
2143
+ }
2144
+ return !isDeepStrictEqual(current[key] ?? [], value);
2145
+ });
2146
+ const logs = object(object(plan.settings.observability)?.logs);
2147
+ const currentLogs = object(object(application.observability)?.logs);
2148
+ 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.`);
2149
+ }
2150
+ async function preflightContainerApplications(plans, bindings, accountUrl, workerName, token, verifyUploader) {
2151
+ for (const plan of plans) {
2152
+ 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);
2153
+ const namespaces = new Set(matches.map((binding) => binding.namespace_id));
2154
+ if (namespaces.size > 1) throw new Error(`Cloudflare Container ${plan.name} active namespace was ambiguous.`);
2155
+ const namespace = [...namespaces][0];
2156
+ if (namespace === void 0) {
2157
+ if (Object.keys(plan.images).length === 0) throw new Error(`Cloudflare Container ${plan.name} has no images and no verified existing namespace application.`);
2158
+ continue;
2159
+ }
2160
+ if (typeof namespace !== "string" || !/^[a-f0-9]{32}$/.test(namespace)) throw new Error("The active Container namespace ID was invalid.");
2161
+ try {
2162
+ const application = await apiResult(`${accountUrl}/containers/applications/${encodeURIComponent(namespace)}`, token, "GET", void 0, verifyUploader);
2163
+ assertContainerApplicationIdentity(application, plan, namespace);
2164
+ assertContainerApplicationSettings(application, plan);
2165
+ } catch (cause) {
2166
+ if (!(cause instanceof CloudflareApiError) || cause.status !== 404 || Object.keys(plan.images).length === 0) throw cause;
2167
+ }
2168
+ }
2169
+ }
2170
+ function containerPlansFromOutput(containers, exports, bindings) {
2171
+ const plans = [];
2172
+ for (const { config } of containers) {
2173
+ if (config.schedulingPolicy !== "durable-object") throw new Error("Scheduler-managed Cloudflare Containers cannot be uploaded with pinned secrets.");
2174
+ const classes = Object.entries(exports ?? {}).filter(([, value]) => object(value)?.container === config.name);
2175
+ if (classes.length !== 1) throw new Error(`Cloudflare Container ${config.name} needs exactly one local SQLite export.`);
2176
+ const [className, raw] = classes[0];
2177
+ const declaration = object(raw);
2178
+ const localBindings = bindings.filter((binding) => binding.type === "durable_object_namespace" && binding.class_name === className && binding.script_name === void 0);
2179
+ if (declaration.storage !== "sqlite" || localBindings.length === 0) throw new Error(`Cloudflare Container ${config.name} needs a local SQLite Durable Object binding.`);
2180
+ const unsafe = object(config.unsafe);
2181
+ const configuration = object(unsafe?.configuration);
2182
+ 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.`);
2183
+ const settings = {
2184
+ ...configuration || config.ssh || config.authorizedKeys ? { configuration: {
2185
+ ...configuration ?? {},
2186
+ ...config.ssh ? { wrangler_ssh: config.ssh } : {},
2187
+ ...config.authorizedKeys ? { authorized_keys: config.authorizedKeys.map(({ name, publicKey }) => ({
2188
+ name,
2189
+ public_key: publicKey
2190
+ })) } : {}
2191
+ } } : {},
2192
+ ...config.observability ? { observability: { logs: { enabled: config.observability.enabled === true || config.observability.logs?.enabled === true } } } : {}
2193
+ };
2194
+ plans.push({
2195
+ name: config.name,
2196
+ className,
2197
+ images: config.images ?? {},
2198
+ settings
2199
+ });
2200
+ }
2201
+ for (const [name, raw] of Object.entries(exports ?? {})) {
2202
+ const container = object(raw)?.container;
2203
+ 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.`);
2204
+ }
2205
+ return plans;
2206
+ }
2207
+ async function dockerImageDigest(image, timeout, verifyUploader) {
2208
+ if (!verifyUploader()) throw new Error("The Cloudflare uploader changed during deployment.");
2209
+ const stdout = await new Promise((resolve, reject) => {
2210
+ execFile(process.env.WRANGLER_DOCKER_BIN ?? "docker", [
2211
+ "image",
2212
+ "inspect",
2213
+ "--format",
2214
+ "{{json .RepoDigests}}",
2215
+ image
2216
+ ], {
2217
+ encoding: "utf8",
2218
+ timeout,
2219
+ maxBuffer: 1048576,
2220
+ windowsHide: true
2221
+ }, (error, stdout) => error ? reject(/* @__PURE__ */ new Error("Cloudflare Container image digest could not be verified after push.")) : resolve(stdout));
2222
+ });
2223
+ const digests = JSON.parse(stdout);
2224
+ const repository = image.replace(/:[^/:]+$/, "");
2225
+ 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))) : [];
2226
+ if (matching.length !== 1) throw new Error("Cloudflare Container image digest could not be verified after push.");
2227
+ return matching[0];
2228
+ }
2229
+ async function prepareContainerMetadata(options) {
2230
+ if (options.plans.length === 0) return [];
2231
+ const deadline = Date.now() + 9e5;
2232
+ const remaining = () => {
2233
+ const duration = deadline - Date.now();
2234
+ if (duration <= 0) throw new Error("Cloudflare Container image preparation timed out.");
2235
+ return duration;
2236
+ };
2237
+ const prepared = /* @__PURE__ */ new Map();
2238
+ const result = [];
2239
+ for (const plan of options.plans) {
2240
+ const images = {};
2241
+ for (const [name, raw] of Object.entries(plan.images)) {
2242
+ const source = object(raw);
2243
+ const reference = requiredString(source.localReference ?? source.reference, `Container ${plan.name} image`);
2244
+ let image = prepared.get(reference);
2245
+ if (!image) {
2246
+ image = reference;
2247
+ if (source.localReference !== void 0) {
2248
+ if (!options.runCf || !options.verifyUploader()) throw new Error("The verified Cloudflare Container image uploader is unavailable.");
2249
+ const pushed = await options.runCf([
2250
+ "containers",
2251
+ "push",
2252
+ "--tag",
2253
+ reference
2254
+ ], {
2255
+ cwd: options.directory,
2256
+ capture: true,
2257
+ timeoutMs: remaining(),
2258
+ maxOutputBytes: 16777216
2259
+ });
2260
+ if (pushed.status !== 0 || pushed.signal || pushed.error) throw new Error("Cloudflare Container image push failed.");
2261
+ const matches = [...pushed.stdout.matchAll(/Pushed image:\s*(\S+)/g)];
2262
+ const remote = matches.length === 1 ? matches[0][1] : "";
2263
+ 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.");
2264
+ image = await dockerImageDigest(remote, Math.min(12e4, remaining()), options.verifyUploader);
2265
+ }
2266
+ for (;;) {
2267
+ const ready = await apiResult(`${options.accountUrl}/containers/image-preparations`, options.token, "POST", { image }, options.verifyUploader, Math.min(12e4, remaining()));
2268
+ if (ready.image !== image) throw new Error("Cloudflare Container image preparation returned another image.");
2269
+ if (ready.status === "ready") break;
2270
+ if (ready.status !== "pending") throw new Error("Cloudflare Container image preparation failed or returned an unsupported state.");
2271
+ await setTimeout$1(Math.min(2e3, remaining()));
2272
+ }
2273
+ prepared.set(reference, image);
2274
+ }
2275
+ images[name] = image;
2276
+ }
2277
+ result.push({
2278
+ name: plan.name,
2279
+ class_name: plan.className,
2280
+ ...Object.keys(images).length ? { images } : {}
2281
+ });
2282
+ }
2283
+ return result;
2284
+ }
2285
+ async function ensureContainerApplications(options) {
2286
+ if (options.plans.length === 0) return;
2287
+ const deadline = Date.now() + 6e4;
2288
+ let version;
2289
+ for (;;) try {
2290
+ 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())));
2291
+ break;
2292
+ } catch (cause) {
2293
+ if (!(cause instanceof CloudflareApiError) || cause.code !== 100146 || Date.now() >= deadline) throw cause;
2294
+ await setTimeout$1(Math.min(2e3, deadline - Date.now()));
2295
+ }
2296
+ const versionBindings = object(version.resources)?.bindings;
2297
+ if (version.id !== options.versionId || !Array.isArray(versionBindings)) throw new Error("The uploaded Container namespace could not be verified.");
2298
+ const metadata = object(object(version.resources)?.script_runtime)?.containers;
2299
+ if (!Array.isArray(metadata) || metadata.length !== options.containers.length || options.containers.some((expected) => {
2300
+ const matching = metadata.map(object).filter((entry) => entry !== null && entry.name === expected.name && entry.class_name === expected.class_name);
2301
+ return matching.length !== 1 || !isDeepStrictEqual(matching[0].images ?? {}, expected.images ?? {});
2302
+ })) throw new Error("The uploaded Worker version did not retain the verified Container image metadata.");
2303
+ for (const plan of options.plans) {
2304
+ 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);
2305
+ const namespaces = new Set(matches.map((binding) => binding.namespace_id));
2306
+ if (matches.length === 0 || namespaces.size !== 1) throw new Error(`Cloudflare Container ${plan.name} has no unambiguous uploaded local namespace.`);
2307
+ const namespace = requiredString([...namespaces][0], `Container ${plan.name} namespace`);
2308
+ if (!/^[a-f0-9]{32}$/.test(namespace)) throw new Error("The uploaded Container namespace ID was invalid.");
2309
+ const url = `${options.accountUrl}/containers/applications/${encodeURIComponent(namespace)}`;
2310
+ let application;
2311
+ try {
2312
+ application = await apiResult(url, options.token, "GET", void 0, options.verifyUploader);
2313
+ } catch (cause) {
2314
+ if (!(cause instanceof CloudflareApiError) || cause.status !== 404) throw cause;
2315
+ application = await apiResult(`${options.accountUrl}/containers/applications`, options.token, "POST", {
2316
+ name: plan.name,
2317
+ scheduling_policy: "durable_object",
2318
+ durable_objects: { namespace_id: namespace },
2319
+ ...plan.settings
2320
+ }, options.verifyUploader);
2321
+ }
2322
+ assertContainerApplicationIdentity(application, plan, namespace);
2323
+ assertContainerApplicationSettings(application, plan);
2324
+ const confirmed = await apiResult(url, options.token, "GET", void 0, options.verifyUploader);
2325
+ assertContainerApplicationIdentity(confirmed, plan, namespace);
2326
+ assertContainerApplicationSettings(confirmed, plan);
2327
+ }
2328
+ }
2035
2329
  async function uploadAssets(options) {
2036
2330
  if (!options.output.assetsDirectory) return;
2037
2331
  const directory = getWorkerAssetsDir(options.output.directory);
@@ -2094,7 +2388,8 @@ async function uploadAssets(options) {
2094
2388
  async function uploadPinnedCloudflareVersion(options) {
2095
2389
  const activeSecrets = options.output.activeSecrets;
2096
2390
  if (!activeSecrets) throw new Error("The active Worker secret source was not verified.");
2097
- const config = (await readBuildOutput(options.output.directory)).workers.default.config;
2391
+ const built = await readBuildOutput(options.output.directory);
2392
+ const config = built.workers.default.config;
2098
2393
  if (config.name !== options.workerName || !config.manifest || config.manifest.type !== "complete") throw new Error("The verified Cloudflare Worker output changed before upload.");
2099
2394
  const unsupported = Object.keys(config).filter((key) => !(/* @__PURE__ */ new Set([
2100
2395
  "name",
@@ -2118,6 +2413,8 @@ async function uploadPinnedCloudflareVersion(options) {
2118
2413
  const explicitSecrets = options.secretsFilePath ? object(JSON.parse(readFileSync(options.secretsFilePath, "utf8"))) : {};
2119
2414
  if (!explicitSecrets) throw new Error("The explicit Cloudflare secrets file was not readable.");
2120
2415
  const bindings = bindingsFromOutput(config.env ?? {}, options.workerName, activeSecrets, explicitSecrets);
2416
+ const exports = exportsFromOutput(config.exports, bindings);
2417
+ const containerPlans = containerPlansFromOutput(built.containers, exports, bindings);
2121
2418
  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
2419
  if (active.id !== activeSecrets.versionId || !Array.isArray(active.bindings)) throw new Error("The active Cloudflare Worker bindings could not be verified.");
2123
2420
  const activeBindingNames = /* @__PURE__ */ new Set();
@@ -2137,7 +2434,6 @@ async function uploadPinnedCloudflareVersion(options) {
2137
2434
  }
2138
2435
  }
2139
2436
  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
2437
  const assetConfig = assetConfigFromOutput(config.assets);
2142
2438
  if (bindings.some((binding) => binding.type === "assets") && !options.output.assetsDirectory) throw new Error("The Cloudflare Worker has an assets binding without verified assets.");
2143
2439
  const modules = [];
@@ -2151,6 +2447,23 @@ async function uploadPinnedCloudflareVersion(options) {
2151
2447
  });
2152
2448
  }
2153
2449
  const accountUrl = `${options.apiBaseUrl.replace(/\/$/, "")}/accounts/${encodeURIComponent(options.accountId)}`;
2450
+ await preflightWorkflowExports({
2451
+ exports,
2452
+ accountUrl,
2453
+ workerName: options.workerName,
2454
+ token: options.token,
2455
+ verifyUploader: options.verifyUploader
2456
+ });
2457
+ await preflightContainerApplications(containerPlans, active.bindings, accountUrl, options.workerName, options.token, options.verifyUploader);
2458
+ const containers = await prepareContainerMetadata({
2459
+ plans: containerPlans,
2460
+ directory: options.output.directory,
2461
+ accountUrl,
2462
+ accountId: options.accountId,
2463
+ token: options.token,
2464
+ verifyUploader: options.verifyUploader,
2465
+ runCf: options.runCf
2466
+ });
2154
2467
  const jwt = await uploadAssets({
2155
2468
  output: options.output,
2156
2469
  accountUrl,
@@ -2165,14 +2478,30 @@ async function uploadPinnedCloudflareVersion(options) {
2165
2478
  compatibility_flags: config.compatibilityFlags,
2166
2479
  bindings,
2167
2480
  ...exports ? { exports } : {},
2481
+ ...containers.length ? { containers } : {},
2168
2482
  ...jwt ? { assets: {
2169
2483
  jwt,
2170
2484
  ...assetConfig ? { config: assetConfig } : {}
2171
2485
  } } : {},
2172
2486
  annotations: { "workers/message": options.message ?? "" }
2173
2487
  }, options.verifyUploader);
2488
+ const versionId = uploaded.id;
2489
+ 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.");
2490
+ try {
2491
+ await ensureContainerApplications({
2492
+ plans: containerPlans,
2493
+ containers,
2494
+ accountUrl,
2495
+ workerName: options.workerName,
2496
+ versionId,
2497
+ token: options.token,
2498
+ verifyUploader: options.verifyUploader
2499
+ });
2500
+ } catch {
2501
+ 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.`);
2502
+ }
2174
2503
  return {
2175
- id: requiredString(uploaded.id, "Worker Version ID"),
2504
+ id: versionId,
2176
2505
  urls: Array.isArray(uploaded.urls) ? uploaded.urls.filter((url) => typeof url === "string") : []
2177
2506
  };
2178
2507
  }
@@ -2185,10 +2514,6 @@ function failed(message) {
2185
2514
  stderr: message
2186
2515
  };
2187
2516
  }
2188
- function flag(args, name) {
2189
- const index = args.indexOf(name);
2190
- return index < 0 ? void 0 : args[index + 1];
2191
- }
2192
2517
  function parseJson(value) {
2193
2518
  try {
2194
2519
  return JSON.parse(value);
@@ -2257,15 +2582,12 @@ function createCfResourceVerifier(options) {
2257
2582
  const name = identifier("name");
2258
2583
  if (!name) return failed(`Cloudflare Queue binding ${bindingName} has no name.`);
2259
2584
  if (!options.verifyCompatibilityRunner()) return failed("The Cloudflare resource verifier changed during deployment.");
2260
- const found = await Promise.resolve(options.runWrangler([
2261
- "queues",
2262
- "info",
2585
+ const found = await Promise.resolve(options.runWrangler({
2586
+ type: "queues.info",
2263
2587
  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.`);
2588
+ configPath: options.configPath
2589
+ }, { capture: true }));
2590
+ 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
2591
  continue;
2270
2592
  }
2271
2593
  const resource = value.type === "r2" ? {
@@ -2314,7 +2636,7 @@ function createCfResourceVerifier(options) {
2314
2636
  cwd: directory,
2315
2637
  capture: true
2316
2638
  });
2317
- const body = parseJson(result.stdout);
2639
+ const body = result.data ?? parseJson(result.stdout);
2318
2640
  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
2641
  }
2320
2642
  return {
@@ -2339,6 +2661,8 @@ function createCfCommand(options) {
2339
2661
  if (env.HTTP_PROXY || env.HTTPS_PROXY || env.http_proxy || env.https_proxy) env.NODE_USE_ENV_PROXY = "1";
2340
2662
  if (options.token) env.CLOUDFLARE_API_TOKEN = options.token;
2341
2663
  return await new Promise((resolve) => {
2664
+ const bounded = run.timeoutMs !== void 0;
2665
+ const processGroup = bounded && process.platform !== "win32";
2342
2666
  const child = spawn(process.execPath, [cf, ...args], {
2343
2667
  cwd: run.cwd,
2344
2668
  env,
@@ -2348,17 +2672,49 @@ function createCfCommand(options) {
2348
2672
  "ignore",
2349
2673
  "pipe",
2350
2674
  "pipe"
2351
- ]
2675
+ ],
2676
+ detached: processGroup
2352
2677
  });
2353
2678
  const stdout = [];
2354
2679
  const stderr = [];
2355
2680
  let bytes = 0;
2356
2681
  let error = null;
2682
+ const stop = () => {
2683
+ if (processGroup && child.pid) try {
2684
+ process.kill(-child.pid, "SIGKILL");
2685
+ } catch (cause) {
2686
+ if (cause.code !== "ESRCH") child.kill("SIGKILL");
2687
+ }
2688
+ else if (bounded && process.platform === "win32" && child.pid) execFile("taskkill", [
2689
+ "/PID",
2690
+ String(child.pid),
2691
+ "/T",
2692
+ "/F"
2693
+ ], {
2694
+ windowsHide: true,
2695
+ timeout: 1e4
2696
+ }, (cause) => {
2697
+ if (cause) child.kill("SIGKILL");
2698
+ });
2699
+ else child.kill("SIGKILL");
2700
+ };
2701
+ const interrupted = () => {
2702
+ error = "Cloudflare command interrupted.";
2703
+ stop();
2704
+ };
2705
+ if (bounded) {
2706
+ process.on("SIGINT", interrupted);
2707
+ process.on("SIGTERM", interrupted);
2708
+ }
2709
+ const timer = run.timeoutMs === void 0 ? void 0 : setTimeout(() => {
2710
+ error = "Cloudflare command timed out.";
2711
+ stop();
2712
+ }, run.timeoutMs);
2357
2713
  const collect = (chunks, chunk) => {
2358
2714
  bytes += chunk.length;
2359
- if (bytes > 268435456) {
2360
- error = "Cloudflare command output exceeded 256 MiB.";
2361
- child.kill();
2715
+ if (bytes > (run.maxOutputBytes ?? 268435456)) {
2716
+ error = "Cloudflare command output exceeded its verified limit.";
2717
+ stop();
2362
2718
  } else chunks.push(chunk);
2363
2719
  };
2364
2720
  child.stdout.on("data", (chunk) => collect(stdout, chunk));
@@ -2367,10 +2723,23 @@ function createCfCommand(options) {
2367
2723
  error = cause.message;
2368
2724
  });
2369
2725
  child.on("close", (status, signal) => {
2726
+ clearTimeout(timer);
2727
+ if (bounded) {
2728
+ process.off("SIGINT", interrupted);
2729
+ process.off("SIGTERM", interrupted);
2730
+ }
2731
+ const output = Buffer.concat(stdout).toString("utf8");
2732
+ const diagnostic = Buffer.concat(stderr).toString("utf8");
2733
+ const completed = typeof status === "number" && signal == null && error == null;
2734
+ const code = completed ? (output + "\n" + diagnostic).match(/\[(?:code:\s*)?(\d{4,})\]/)?.[1] : void 0;
2735
+ 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
2736
  resolve({
2371
2737
  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]"),
2738
+ stdout: output,
2739
+ stderr: diagnostic,
2740
+ ...code ? { errorCode: Number(code) } : {},
2741
+ ...missingUploadTarget ? { failure: "not-found" } : {},
2742
+ ...completed && status !== 0 ? { errors: [{ message: output + "\n" + diagnostic }] } : {},
2374
2743
  signal,
2375
2744
  error
2376
2745
  });
@@ -2384,10 +2753,31 @@ function createCfCommand(options) {
2384
2753
  */
2385
2754
  function createCfVersionRunner(options) {
2386
2755
  const { output, workerName, runCf } = options;
2387
- const run = async (args, capture, cwd = output.directory) => await runCf(args, {
2388
- cwd,
2389
- capture
2390
- });
2756
+ const run = async (args, capture, cwd = output.directory) => {
2757
+ const result = await runCf(args, {
2758
+ cwd,
2759
+ capture
2760
+ });
2761
+ return {
2762
+ ...result,
2763
+ data: result.data ?? parseJson(result.stdout)
2764
+ };
2765
+ };
2766
+ const uploadedResult = (result) => {
2767
+ if (result.status !== 0 || result.signal || result.error) return result;
2768
+ if (record(result.data) && typeof result.data.versionId === "string") return result;
2769
+ const versionId = result.stdout.match(/(?:Worker|Current) Version ID:\s*([0-9a-f]{8}-[0-9a-f-]{27})/i)?.[1];
2770
+ const previewUrl = result.stdout.match(/Version Preview(?: Alias)? URL:\s*(https:\/\/\S+)/i)?.[1];
2771
+ const workersDevUrl = result.stdout.match(/(https:\/\/[a-z0-9.-]+\.workers\.dev(?:\/[^\s]*)?)/i)?.[1];
2772
+ return {
2773
+ ...result,
2774
+ data: versionId ? {
2775
+ versionId,
2776
+ ...previewUrl ? { previewUrl } : {},
2777
+ ...workersDevUrl ? { workersDevUrl } : {}
2778
+ } : void 0
2779
+ };
2780
+ };
2391
2781
  const verifyResources = async (directory) => {
2392
2782
  try {
2393
2783
  return await options.verifyResources(directory);
@@ -2395,7 +2785,7 @@ function createCfVersionRunner(options) {
2395
2785
  return failed("Cloudflare resources could not be verified before upload.");
2396
2786
  }
2397
2787
  };
2398
- const uploadPinnedVersion = async (args, atomic) => {
2788
+ const uploadPinnedVersion = async (operation, atomic) => {
2399
2789
  if (!options.pinnedUpload || !options.pinnedUpload.verifyUploader()) return failed("The version-aware Cloudflare uploader is unavailable.");
2400
2790
  const token = await options.pinnedUpload.getToken();
2401
2791
  if (!token) return failed("Cloudflare session has no bearer for version-aware upload.");
@@ -2406,23 +2796,28 @@ function createCfVersionRunner(options) {
2406
2796
  apiBaseUrl: options.pinnedUpload.apiBaseUrl,
2407
2797
  workerName,
2408
2798
  token,
2409
- ...flag(args, "--message") ? { message: flag(args, "--message") } : {},
2410
- ...flag(args, "--secrets-file") ? { secretsFilePath: flag(args, "--secrets-file") } : {},
2799
+ ...operation.message ? { message: operation.message } : {},
2800
+ ...operation.secretsFilePath ? { secretsFilePath: operation.secretsFilePath } : {},
2411
2801
  atomic,
2412
- verifyUploader: options.pinnedUpload.verifyUploader
2802
+ verifyUploader: options.pinnedUpload.verifyUploader,
2803
+ runCf
2413
2804
  });
2414
2805
  const preview = version.urls.find((url) => /^https:\/\//.test(url));
2415
2806
  return {
2416
2807
  status: 0,
2417
- stdout: `Worker Version ID: ${version.id}\n${preview ? `Version Preview URL: ${preview}\n` : ""}`,
2808
+ stdout: "",
2809
+ data: {
2810
+ versionId: version.id,
2811
+ ...preview ? { previewUrl: preview } : {}
2812
+ },
2418
2813
  stderr: ""
2419
2814
  };
2420
2815
  } catch (cause) {
2421
2816
  return failed(cause instanceof Error ? cause.message : "Cloudflare version upload failed.");
2422
2817
  }
2423
2818
  };
2424
- return async (args, command) => {
2425
- if (args[0] === "deployments" && args[1] === "status") {
2819
+ return async (operation, command) => {
2820
+ if (operation.type === "deployments.status") {
2426
2821
  const result = await run([
2427
2822
  "workers",
2428
2823
  "deployments",
@@ -2430,17 +2825,20 @@ function createCfVersionRunner(options) {
2430
2825
  "--worker",
2431
2826
  workerName
2432
2827
  ], true);
2433
- if (result.status !== 0) return result;
2434
- const body = parseJson(result.stdout);
2828
+ if (result.status !== 0 || result.signal || result.error) return result;
2829
+ const body = result.data;
2435
2830
  const deployments = record(body) ? body.deployments : void 0;
2436
- if (Array.isArray(deployments) && deployments.length === 0) return failed("The Cloudflare Worker has no deployments.");
2831
+ if (Array.isArray(deployments) && deployments.length === 0) return {
2832
+ ...failed("The Cloudflare Worker has no deployments."),
2833
+ errorCode: 10007
2834
+ };
2437
2835
  if (!Array.isArray(deployments) || !record(deployments[0])) return failed("The Cloudflare Worker deployment was not readable.");
2438
2836
  return {
2439
2837
  ...result,
2440
- stdout: JSON.stringify(deployments[0])
2838
+ data: deployments[0]
2441
2839
  };
2442
2840
  }
2443
- if (args[0] === "versions" && args[1] === "list") {
2841
+ if (operation.type === "versions.list") {
2444
2842
  const pageSize = 10;
2445
2843
  const readPage = async (page) => {
2446
2844
  const result = await run([
@@ -2454,19 +2852,20 @@ function createCfVersionRunner(options) {
2454
2852
  "--page",
2455
2853
  String(page)
2456
2854
  ], true);
2457
- const body = parseJson(result.stdout);
2855
+ const body = result.data;
2458
2856
  return {
2459
2857
  result,
2460
2858
  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
2859
  };
2462
2860
  };
2463
2861
  const first = await readPage(1);
2464
- if (first.result.status !== 0) return first.result;
2862
+ if (first.result.status !== 0 || first.result.signal || first.result.error) return first.result;
2465
2863
  if (!first.versions) return failed("The Cloudflare Worker version list was not readable.");
2466
2864
  const versions = [...first.versions];
2467
2865
  if (versions.length === pageSize) {
2468
2866
  const second = await readPage(2);
2469
- if (second.result.status !== 0 || !second.versions) return failed("The Cloudflare Worker version history was not readable.");
2867
+ if (second.result.status !== 0 || second.result.signal || second.result.error) return second.result;
2868
+ if (!second.versions) return failed("The Cloudflare Worker version history was not readable.");
2470
2869
  if (second.versions.length > 0) {
2471
2870
  const firstNumbers = versions.map((item) => item.number);
2472
2871
  const secondNumbers = second.versions.map((item) => item.number);
@@ -2475,7 +2874,8 @@ function createCfVersionRunner(options) {
2475
2874
  let last = Math.max(...secondNumbers);
2476
2875
  for (let page = 3; page <= 1e3 && second.versions.length === pageSize; page++) {
2477
2876
  const next = await readPage(page);
2478
- if (next.result.status !== 0 || !next.versions) return failed("The Cloudflare Worker version history was not readable.");
2877
+ if (next.result.status !== 0 || next.result.signal || next.result.error) return next.result;
2878
+ if (!next.versions) return failed("The Cloudflare Worker version history was not readable.");
2479
2879
  if (next.versions.length === 0) break;
2480
2880
  const numbers = next.versions.map((item) => item.number);
2481
2881
  if (Math.min(...numbers) <= last) return failed("The Cloudflare Worker version history changed during inspection.");
@@ -2489,56 +2889,62 @@ function createCfVersionRunner(options) {
2489
2889
  }
2490
2890
  return {
2491
2891
  ...first.result,
2492
- stdout: JSON.stringify(versions.sort((a, b) => a.number - b.number).slice(-10))
2892
+ data: versions.sort((a, b) => a.number - b.number).slice(-10)
2493
2893
  };
2494
2894
  }
2495
- if (args[0] === "versions" && args[1] === "view" && args[2]) {
2496
- const result = await run([
2895
+ if (operation.type === "versions.view") {
2896
+ const args = [
2497
2897
  "workers",
2498
2898
  "versions",
2499
2899
  "get",
2500
- args[2],
2900
+ operation.versionId,
2501
2901
  "--worker-id",
2502
- workerName
2503
- ], 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.");
2902
+ workerName,
2903
+ ...operation.includeSource ? ["--include", "modules"] : []
2904
+ ];
2905
+ const called = operation.includeSource ? await runCf(args, {
2906
+ cwd: output.directory,
2907
+ capture: true,
2908
+ timeoutMs: 3e4,
2909
+ maxOutputBytes: 1048576
2910
+ }) : await run(args, true);
2911
+ const result = {
2912
+ ...called,
2913
+ data: called.data ?? parseJson(called.stdout)
2914
+ };
2915
+ if (result.status !== 0 || result.signal || result.error) return result;
2916
+ const version = result.data;
2917
+ if (!record(version) || version.id !== operation.versionId || !Array.isArray(version.bindings)) return failed("The Cloudflare Worker version was not readable.");
2507
2918
  return {
2508
2919
  ...result,
2509
- stdout: JSON.stringify({
2920
+ data: {
2510
2921
  ...version,
2511
2922
  resources: { bindings: version.bindings }
2512
- })
2923
+ }
2513
2924
  };
2514
2925
  }
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.");
2926
+ if (operation.type === "versions.upload") {
2927
+ if (operation.workerName !== workerName || operation.assetsDirectory !== void 0 && operation.assetsDirectory !== output.assetsDirectory) return failed("The Cloudflare version upload target changed.");
2517
2928
  const verified = await verifyResources(output.directory);
2518
2929
  if (verified.status !== 0 || verified.signal || verified.error) return verified;
2519
- if (output.activeSecrets) return uploadPinnedVersion(args, false);
2520
- return run([
2930
+ if (output.activeSecrets) return uploadPinnedVersion(operation, false);
2931
+ return uploadedResult(await run([
2521
2932
  "workers",
2522
2933
  "versions",
2523
2934
  "create",
2524
2935
  "--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
- });
2936
+ ...operation.message ? ["--message", operation.message] : [],
2937
+ ...operation.secretsFilePath ? ["--secrets-file", operation.secretsFilePath] : [],
2938
+ ...operation.previewAlias ? ["--preview-alias", operation.previewAlias] : []
2939
+ ], command.capture));
2940
+ }
2941
+ if (operation.type === "versions.deploy") {
2942
+ if (operation.workerName !== workerName) return failed("The Cloudflare deployment target changed.");
2943
+ const parsed = operation.versions.map(({ versionId, percentage }) => ({
2944
+ version_id: versionId,
2945
+ percentage
2946
+ }));
2947
+ 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
2948
  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
2949
  return run([
2544
2950
  "workers",
@@ -2550,28 +2956,28 @@ function createCfVersionRunner(options) {
2550
2956
  "percentage",
2551
2957
  "--versions",
2552
2958
  JSON.stringify(parsed),
2553
- ...flag(args, "--message") ? ["--body", JSON.stringify({
2959
+ ...operation.message ? ["--body", JSON.stringify({
2554
2960
  strategy: "percentage",
2555
2961
  versions: parsed,
2556
- annotations: { "workers/message": flag(args, "--message") }
2962
+ annotations: { "workers/message": operation.message }
2557
2963
  })] : []
2558
2964
  ], command.capture);
2559
2965
  }
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.");
2966
+ if (operation.type === "worker.deploy") {
2967
+ if (operation.workerName !== workerName || operation.assetsDirectory !== void 0 && operation.assetsDirectory !== output.assetsDirectory) return failed("The Cloudflare bootstrap target changed.");
2562
2968
  const verified = await verifyResources(output.directory);
2563
2969
  if (verified.status !== 0 || verified.signal || verified.error) return verified;
2564
- if (output.activeSecrets) return uploadPinnedVersion(args, true);
2565
- return run([
2970
+ if (output.activeSecrets) return uploadPinnedVersion(operation, true);
2971
+ return uploadedResult(await run([
2566
2972
  "deploy",
2567
2973
  "--prebuilt",
2568
- ...flag(args, "--message") ? ["--message", flag(args, "--message")] : [],
2569
- ...flag(args, "--secrets-file") ? ["--secrets-file", flag(args, "--secrets-file")] : []
2570
- ], command.capture);
2974
+ ...operation.message ? ["--message", operation.message] : [],
2975
+ ...operation.secretsFilePath ? ["--secrets-file", operation.secretsFilePath] : []
2976
+ ], command.capture));
2571
2977
  }
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.");
2978
+ if (operation.type === "triggers.deploy") {
2979
+ const configPath = operation.configPath;
2980
+ if (!configPath || operation.workerName !== workerName) return failed("The Cloudflare trigger target changed.");
2575
2981
  const raw = parseJson(readFileSync(configPath, "utf8"));
2576
2982
  if (!record(raw) || raw.name !== workerName) return failed("The Cloudflare trigger snapshot was not readable.");
2577
2983
  const directory = mkdtempSync(join(tmpdir(), "void-cf-triggers-"));
@@ -2621,12 +3027,12 @@ function createCfVersionRunner(options) {
2621
3027
  });
2622
3028
  }
2623
3029
  }
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.");
3030
+ if (operation.type === "queues.consumer.remove") {
3031
+ const queueName = operation.queueName;
3032
+ if (!queueName || operation.workerName !== workerName) return failed("The Cloudflare Queue consumer target changed.");
2627
3033
  const queues = await run(["queues", "list"], true);
2628
- if (queues.status !== 0) return queues;
2629
- const allQueues = parseJson(queues.stdout);
3034
+ if (queues.status !== 0 || queues.signal || queues.error) return queues;
3035
+ const allQueues = queues.data;
2630
3036
  if (!Array.isArray(allQueues)) return failed("The Cloudflare Queue list was not readable.");
2631
3037
  const queue = allQueues.find((item) => record(item) && item.queue_name === queueName);
2632
3038
  if (!record(queue) || typeof queue.queue_id !== "string") return failed(`Cloudflare Queue ${queueName} was not found.`);
@@ -2637,16 +3043,22 @@ function createCfVersionRunner(options) {
2637
3043
  "--queue-id",
2638
3044
  queue.queue_id
2639
3045
  ], true);
2640
- if (consumers.status !== 0) return consumers;
2641
- const allConsumers = parseJson(consumers.stdout);
3046
+ if (consumers.status !== 0 || consumers.signal || consumers.error) return consumers;
3047
+ const allConsumers = consumers.data;
2642
3048
  if (!Array.isArray(allConsumers)) return failed("The Cloudflare Queue consumers were not readable.");
2643
- const matching = allConsumers.filter((item) => record(item) && item.script === workerName);
3049
+ const matching = [];
3050
+ for (const item of allConsumers) {
3051
+ if (!record(item)) return failed("The Cloudflare Queue consumers were not readable.");
3052
+ const name = item.script_name ?? item.script;
3053
+ 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.");
3054
+ if (name === workerName) matching.push(item);
3055
+ }
2644
3056
  if (matching.length === 0) return {
2645
3057
  status: 0,
2646
3058
  stdout: "",
2647
3059
  stderr: ""
2648
3060
  };
2649
- if (matching.length !== 1 || !record(matching[0]) || typeof matching[0].consumer_id !== "string") return failed("The Cloudflare Queue consumer identity was ambiguous.");
3061
+ 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
3062
  return run([
2651
3063
  "queues",
2652
3064
  "consumers",
@@ -2657,7 +3069,7 @@ function createCfVersionRunner(options) {
2657
3069
  "--force"
2658
3070
  ], command.capture);
2659
3071
  }
2660
- return failed(`Unsupported Cloudflare activation operation: ${args[0] ?? ""} ${args[1] ?? ""}`);
3072
+ return failed(`Unsupported Cloudflare activation operation: ${operation.type}`);
2661
3073
  };
2662
3074
  }
2663
3075
  //#endregion
@@ -2708,13 +3120,13 @@ function clearCloudflareCandidate(root) {
2708
3120
  * Narrow native Cloudflare deploys to app shapes whose emitted Worker artifact
2709
3121
  * the current verifier understands. This runs before any account operation.
2710
3122
  */
2711
- function classifyCloudflareDeployApp(root, voidConfig, options) {
3123
+ function classifyCloudflareDeployApp(root, voidConfig, options, application) {
2712
3124
  if (isNodeTarget(voidConfig.target)) return unsupported([
2713
3125
  `deploy: --platform cloudflare is not supported for the "${voidConfig.target}" target.`,
2714
3126
  "This backend deploys Cloudflare Workers only. Deploy a node/bun/deno build with its",
2715
3127
  "own host/runtime, or use the default Cloudflare target for --platform cloudflare."
2716
3128
  ].join("\n"));
2717
- const framework = detectFramework(root);
3129
+ const framework = application?.framework ?? detectFramework(root);
2718
3130
  if (framework) {
2719
3131
  if (!FRAMEWORK_PRESETS[framework.name]) return unsupported(`deploy: --platform cloudflare has no deployment preset for framework ${framework.name}.`);
2720
3132
  }
@@ -2733,13 +3145,16 @@ function classifyCloudflareDeployApp(root, voidConfig, options) {
2733
3145
  "A full Void app has at least one of: routes/, pages/, middleware, crons/, queues/,",
2734
3146
  "email/, or an SSR entry. Add your server code, or use `void deploy` for a static site."
2735
3147
  ].join("\n"));
2736
- const paths = resolveProjectPaths(root, voidConfig);
2737
3148
  try {
3149
+ const analysis = application ?? analyzeApplicationSync(root, {
3150
+ config: voidConfig,
3151
+ environment: "prod"
3152
+ });
3153
+ const paths = analysis.paths;
2738
3154
  assertNoRootSourceConventionConflicts(paths);
2739
3155
  validateSsrEntry(root, paths);
2740
- scanJobsSync(root, paths.cronsDir);
2741
- syncWranglerDurableObjects(root, scanDurableObjectModules(root));
2742
- syncWranglerWebSockets(root, scanWebSocketRoutesSync(root, paths, "prod"));
3156
+ syncWranglerDurableObjects(root, analysis.durableObjects);
3157
+ syncWranglerWebSockets(root, analysis.websockets);
2743
3158
  } catch (error) {
2744
3159
  return unsupported(`deploy: ${getErrorMessage$1(error)}`);
2745
3160
  }
@@ -2886,7 +3301,7 @@ async function prepareExistingCloudflareDeploy(options) {
2886
3301
  }
2887
3302
  /** Offer the existing destination before the legacy hosted-platform fallback. */
2888
3303
  async function maybeLinkCloudflareDeploy(root, options = {}, deps = {}) {
2889
- if (options.platform || options.backend || options.projectSlug || process.env.VOID_PROJECT || readDeploymentPlatform(root) !== null) return null;
3304
+ if (options.platform || options.projectSlug || process.env.VOID_PROJECT || readDeploymentPlatform(root) !== null) return null;
2890
3305
  const files = [.../* @__PURE__ */ new Set([...[
2891
3306
  "wrangler.jsonc",
2892
3307
  "wrangler.json",
@@ -3511,9 +3926,6 @@ function openDeployLog(opts) {
3511
3926
  }
3512
3927
  };
3513
3928
  }
3514
- //#endregion
3515
- //#region src/cli/log-summary.ts
3516
- var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
3517
3929
  const FIELD_VALUE_MAX = 80;
3518
3930
  const LINE_MAX = 160;
3519
3931
  const MSG_COLUMN_MAX = 28;
@@ -4029,8 +4441,7 @@ function buildVoidManifest(opts) {
4029
4441
  };
4030
4442
  if (opts.hasEmailHandlers) manifest.emailProtocolVersion = 1;
4031
4443
  if (bindings.needsAuth) manifest.auth = true;
4032
- const hasMigrations = migrations && migrations.length > 0;
4033
- if (bindings.needsD1 || bindings.needsAuth || hasMigrations) {
4444
+ if (needsApplicationDatabase(bindings, migrations?.length ?? 0)) {
4034
4445
  if (resolvedDialect !== "sqlite") manifest.bindings.hyperdrive = ["HYPERDRIVE"];
4035
4446
  else manifest.bindings.d1 = [options?.bindingNames?.d1 ?? "DB"];
4036
4447
  }
@@ -4145,8 +4556,7 @@ function buildFrameworkManifest(opts) {
4145
4556
  assetManifestVersion: 2,
4146
4557
  bindings: {}
4147
4558
  };
4148
- const hasMigrations = opts.migrations && opts.migrations.length > 0;
4149
- if (opts.bindings.needsD1 || opts.bindings.needsAuth || hasMigrations) {
4559
+ if (needsApplicationDatabase(opts.bindings, opts.migrations?.length ?? 0)) {
4150
4560
  if (dialect !== "sqlite") manifest.bindings.hyperdrive = ["HYPERDRIVE"];
4151
4561
  else manifest.bindings.d1 = [po?.bindingNames?.d1 ?? "DB"];
4152
4562
  }
@@ -4395,7 +4805,7 @@ async function generateWrapper(workerDir, workerMain, root, features) {
4395
4805
  let migrationPlanBundle;
4396
4806
  if (features.hasMigrations) {
4397
4807
  const { collectMigrations } = await import("./collect-DLyFDiKW.mjs");
4398
- const { buildMigrationPlanBundle } = await import("./plan-DpuOr14e.mjs");
4808
+ const { buildMigrationPlanBundle } = await import("./plan-D1Q5rf-r.mjs");
4399
4809
  const migrationsDir = readProjectPaths(root).migrationsDir;
4400
4810
  migrations = collectMigrations(migrationsDir);
4401
4811
  migrationPlanBundle = features.migrationDialect === "sqlite" ? buildMigrationPlanBundle(migrations) : void 0;
@@ -4636,7 +5046,7 @@ function generateWrapperSource(renamedEntry, handlersModule, features, migration
4636
5046
  }
4637
5047
  if (features.readiness || features.crons.length > 0 || features.queues.length > 0 || features.hasMigrations) {
4638
5048
  lines.push(" if (request.method === \"GET\" && url.pathname === \"/__void/ready\") {");
4639
- lines.push(" return Response.json({ ready: true });");
5049
+ lines.push(` return Response.json({ ready: true${features.versionMetadataBinding ? `, versionId: env[${JSON.stringify(features.versionMetadataBinding)}]?.id` : ""} }${features.versionMetadataBinding ? ", { headers: { \"cache-control\": \"no-store\" } }" : ""});`);
4640
5050
  lines.push(" }");
4641
5051
  }
4642
5052
  if (features.queues.length > 0) {
@@ -4806,7 +5216,7 @@ let loadedProvisionModule;
4806
5216
  * so a managed deploy never imports it — and thus never loads `proper-lockfile`.
4807
5217
  */
4808
5218
  async function loadProvisionModule() {
4809
- loadedProvisionModule ??= await import("./provision-Fr9pRqce.mjs");
5219
+ loadedProvisionModule ??= await import("./provision-CVku1JFU.mjs");
4810
5220
  return loadedProvisionModule;
4811
5221
  }
4812
5222
  /**
@@ -4829,7 +5239,7 @@ const log = {
4829
5239
  };
4830
5240
  var DeployEventError = class extends Error {};
4831
5241
  function resolveDeploymentPlatform(root, options) {
4832
- return options?.platform ?? options?.backend ?? readDeploymentPlatform(root) ?? "void";
5242
+ return options?.platform ?? readDeploymentPlatform(root) ?? "void";
4833
5243
  }
4834
5244
  async function generateDeployWrapperIfNeeded(root, workerDir, workerMain, features) {
4835
5245
  if (!needsWrapper(features)) return false;
@@ -4965,7 +5375,7 @@ async function runDeploy(root, options) {
4965
5375
  * `VOID_DEPLOY_PROJECT_ID` is, so it cannot leak into an unrelated child process or a
4966
5376
  * later `void dev` in the same shell.
4967
5377
  *
4968
- * Deliberately NOT set by `runCloudflareDeploy` (`--backend cloudflare`): that flow
5378
+ * Deliberately NOT set by `runCloudflareDeploy` (`--platform cloudflare`): that flow
4969
5379
  * hands the built output to `wrangler deploy` against the user's own account and IS the
4970
5380
  * self-host path the warning is about. (It rejects framework SSR outright today, so no
4971
5381
  * Class A build reaches it either way.)
@@ -4975,6 +5385,7 @@ const MANAGED_DEPLOY_BUILD_ENV = { VOID_DEPLOY_BUILD: "1" };
4975
5385
  function scrubManagedDeployBuildMarkers(env) {
4976
5386
  const { deleteEnvVarCaseAware } = requireProvisionModule();
4977
5387
  deleteEnvVarCaseAware(env, "VOID_DEPLOY_PROJECT_ID");
5388
+ deleteEnvVarCaseAware(env, CLOUDFLARE_PRESERVE_BINDINGS_BUILD_ENV);
4978
5389
  for (const name of Object.keys(MANAGED_DEPLOY_BUILD_ENV)) deleteEnvVarCaseAware(env, name);
4979
5390
  return env;
4980
5391
  }
@@ -5391,14 +5802,14 @@ function reconstructExpectedDurableObjectState(config, root, rootConfig, options
5391
5802
  name: sandbox.binding,
5392
5803
  class_name: sandbox.className
5393
5804
  });
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({
5805
+ if (!hasSandboxSqliteMigration(migrations, sandbox.className)) migrations.push({
5395
5806
  tag: SANDBOX_MIGRATION_TAG,
5396
5807
  new_sqlite_classes: [sandbox.className]
5397
5808
  });
5398
5809
  }
5399
- const paths = resolveProjectPaths(root, config);
5400
- const websocketRoutes = scanWebSocketRoutesSync(root, paths);
5401
- const durableObjects = scanDurableObjectModules(root);
5810
+ const analysis = options.analysis ?? analyzeApplicationSync(root, { config });
5811
+ const websocketRoutes = analysis.allWebsockets;
5812
+ const durableObjects = analysis.durableObjects;
5402
5813
  const live = options.needsLive ? resolveLiveBindingConfig(root, {
5403
5814
  reservedBindings: [...websocketRoutes.map((route) => route.bindingName), ...durableObjects.map((module) => module.bindingName)],
5404
5815
  reservedClasses: [
@@ -5428,11 +5839,11 @@ function reconstructExpectedDurableObjectState(config, root, rootConfig, options
5428
5839
  * chunks from its own `__dirname` (the proxy workers, the experimental-config module), so the
5429
5840
  * whole directory is the trust surface, not just the entry file. Content hash of the bytes, NOT
5430
5841
  * mtime. Resolves the SAME entry {@link createDeployWranglerRunner} spawns (via
5431
- * {@link resolveWranglerArgv}, whose launcher's LAST argv element is the JS entry). Returns
5842
+ * {@link resolveCloudflareOperationArgv}, whose launcher's LAST argv element is the JS entry). Returns
5432
5843
  * `undefined` when the entry cannot be resolved or a file cannot be read, so the caller FAILS
5433
5844
  * CLOSED — it never deploys a binary it could not verify. The resolver is injectable for tests.
5434
5845
  */
5435
- function hashWranglerUploader(root, resolveArgv = resolveWranglerArgv) {
5846
+ function hashWranglerUploader(root, resolveArgv = resolveCloudflareOperationArgv) {
5436
5847
  let entry;
5437
5848
  try {
5438
5849
  const argv = resolveArgv(root);
@@ -6261,42 +6672,30 @@ function assertDistinctKvNamespaces(kvNamespaces, config, configPath, isRealKvId
6261
6672
  /**
6262
6673
  * Compute the DESIRED id/name-bearing Cloudflare resources from SOURCE
6263
6674
  * (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`.
6675
+ * paths. Consumes the same application analysis as the plugin and managed
6676
+ * packaging; provider identities and stored resource names are resolved here.
6268
6677
  *
6269
6678
  * Resource NAMES prefer the value already stored in the root wrangler config
6270
6679
  * (so a re-provision reuses the same database/bucket and the provisioner's
6271
6680
  * stored-name identity check passes); otherwise they default off the worker
6272
6681
  * name. Returns a per-binding REASON map used to explain drift.
6273
6682
  */
6274
- async function computeDesiredCloudflareResources(root, voidConfig, configPath) {
6683
+ async function computeDesiredCloudflareResources(root, voidConfig, configPath, application) {
6275
6684
  const { isRealD1Id, isRealKvId } = await loadProvisionModule();
6276
- const paths = resolveProjectPaths(root, voidConfig);
6685
+ const analysis = application ? await completeApplicationAnalysis(application) : await analyzeApplication(root, {
6686
+ config: voidConfig,
6687
+ environment: "prod"
6688
+ });
6689
+ const { paths, bindings, authEnabled, dialect, bindingNames, migrations, needsDatabase } = analysis;
6277
6690
  const config = parseRootWranglerConfig(configPath);
6278
6691
  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
6692
  const migrationsDirRelative = paths.sourceDir ? `./${paths.sourceDir}/db/migrations` : "./db/migrations";
6293
6693
  const desired = {
6294
6694
  kvNamespaces: [],
6295
6695
  queues: []
6296
6696
  };
6297
6697
  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";
6698
+ const databaseReason = analysis.reasons.database[0];
6300
6699
  if (needsDatabase && dialect === "sqlite") {
6301
6700
  const binding = bindingNames?.d1 ?? "DB";
6302
6701
  const storedName = readStoredResourceName(config, "d1_databases", binding, "database_name");
@@ -6315,7 +6714,7 @@ async function computeDesiredCloudflareResources(root, voidConfig, configPath) {
6315
6714
  binding,
6316
6715
  title: suffixedResourceName(workerName, "kv")
6317
6716
  });
6318
- reasons.set(binding, "app uses env.KV");
6717
+ reasons.set(binding, analysis.reasons.kv[0]);
6319
6718
  }
6320
6719
  if (bindings.needsR2) {
6321
6720
  const binding = bindingNames?.r2 ?? "STORAGE";
@@ -6325,9 +6724,9 @@ async function computeDesiredCloudflareResources(root, voidConfig, configPath) {
6325
6724
  binding,
6326
6725
  bucketName: storedName && !generatedDefault ? storedName : suffixedResourceName(workerName, "storage")
6327
6726
  };
6328
- reasons.set(binding, "app uses env.STORAGE (R2)");
6727
+ reasons.set(binding, analysis.reasons.r2[0]);
6329
6728
  }
6330
- desired.queues = scanQueuesSync(root, paths.queuesDir).map((q) => q.name);
6729
+ desired.queues = analysis.queues.map((q) => q.name);
6331
6730
  if (needsDatabase && dialect !== "sqlite") {
6332
6731
  const binding = "HYPERDRIVE";
6333
6732
  desired.hyperdrive = {
@@ -6337,10 +6736,8 @@ async function computeDesiredCloudflareResources(root, voidConfig, configPath) {
6337
6736
  };
6338
6737
  reasons.set(binding, `app uses ${dialect === "mysql" ? "MySQL" : "PostgreSQL"} (needs a Hyperdrive config)`);
6339
6738
  }
6340
- if (hasRevalidate(collectRevalidate(voidConfig, existsSync(paths.pagesDir) ? await scanPages(root, {
6341
- output: voidConfig.output,
6342
- paths
6343
- }) : null))) {
6739
+ const pageScan = analysis.scan.pages;
6740
+ if (hasRevalidate(collectRevalidate(voidConfig, pageScan))) {
6344
6741
  const binding = "ISR_CACHE";
6345
6742
  desired.kvNamespaces.push({
6346
6743
  binding,
@@ -6349,9 +6746,10 @@ async function computeDesiredCloudflareResources(root, voidConfig, configPath) {
6349
6746
  reasons.set(binding, "routes use `revalidate` (ISR) — needs an ISR_CACHE KV namespace");
6350
6747
  }
6351
6748
  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);
6749
+ const expectedCrons = [...new Set(analysis.jobs.flatMap((job) => job.crons))];
6750
+ const emailHandlers = analysis.email;
6354
6751
  return {
6752
+ analysis,
6355
6753
  desired,
6356
6754
  reasons,
6357
6755
  authEnabled,
@@ -6363,7 +6761,7 @@ async function computeDesiredCloudflareResources(root, voidConfig, configPath) {
6363
6761
  expectedCrons,
6364
6762
  needsEmail: bindings.needsEmail === true || hasEmailHandlers(emailHandlers),
6365
6763
  emailHandlers,
6366
- needsSandbox: isSandboxEnabled(voidConfig) || bindings.needsSandbox,
6764
+ needsSandbox: bindings.needsSandbox,
6367
6765
  needsLive: bindings.needsLive === true
6368
6766
  };
6369
6767
  }
@@ -6717,49 +7115,15 @@ async function checkWranglerMigrationApplySet(configPath, config, binding, migra
6717
7115
  };
6718
7116
  return { ok: true };
6719
7117
  }
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
- */
7118
+ /** Run typed provider operations in the pinned account and API context. */
6731
7119
  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
- };
7120
+ const runner = createCloudflareProcessRunner({
7121
+ root,
7122
+ accountId,
7123
+ apiBaseUrl,
7124
+ env: () => deployChildEnv(accountId, apiBaseUrl)
7125
+ });
7126
+ return (operation, opts) => runner(operation, opts);
6763
7127
  }
6764
7128
  /** Retry an existing-Worker read only when a fresh identity check proves the session is gone. */
6765
7129
  async function prepareExistingCloudflareDeployWithAuth(options, beforeRun) {
@@ -6768,18 +7132,17 @@ async function prepareExistingCloudflareDeployWithAuth(options, beforeRun) {
6768
7132
  } catch (error) {
6769
7133
  if (!(error instanceof CloudflareWorkerInspectionError)) throw error;
6770
7134
  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;
7135
+ const configPath = existsSync(options.configPath) ? options.configPath : void 0;
7136
+ const identity = await options.runWrangler({
7137
+ type: "auth.whoami",
7138
+ configPath
7139
+ }, { capture: true });
7140
+ const session = isCleanWranglerExit(identity) ? parseCloudflareAuthSession(identity.data) : null;
6778
7141
  const wrongAccount = session !== null && session.accounts.length > 0 && !session.accounts.some((account) => account.id === options.accountId);
6779
7142
  if (session && !wrongAccount) throw error;
6780
7143
  let loggedOut = false;
6781
7144
  try {
6782
- loggedOut = JSON.parse(identity.stdout).loggedIn === false;
7145
+ loggedOut = identity.data?.loggedIn === false;
6783
7146
  } catch {}
6784
7147
  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
7148
  const overrides = cloudflareCredentialOverrides();
@@ -6790,8 +7153,8 @@ async function prepareExistingCloudflareDeployWithAuth(options, beforeRun) {
6790
7153
  forceLogin: true,
6791
7154
  requireAccounts: true,
6792
7155
  beforeRun,
6793
- runWrangler: (args, capture) => options.runWrangler([...args], { capture }),
6794
- whoamiArgs
7156
+ runWrangler: (operation, capture) => options.runWrangler(operation, { capture }),
7157
+ configPath
6795
7158
  })).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
7159
  if (!beforeRun()) throw new CloudflareAuthenticationError("The Cloudflare uploader changed during sign-in.", "uploader-changed");
6797
7160
  return await prepareExistingCloudflareDeploy(options);
@@ -6801,8 +7164,8 @@ async function chooseCloudflareDeployAccount(options) {
6801
7164
  const accounts = (await ensureCloudflareAuthentication({
6802
7165
  beforeRun: options.beforeWranglerRun,
6803
7166
  requireAccounts: true,
6804
- runWrangler: (args, capture) => options.runWrangler([...args], { capture }),
6805
- whoamiArgs: [...existingWranglerConfigArgs(options.configPath), "--env="]
7167
+ runWrangler: (operation, capture) => options.runWrangler(operation, { capture }),
7168
+ configPath: existsSync(options.configPath) ? options.configPath : void 0
6806
7169
  })).accounts;
6807
7170
  let account = accounts[0];
6808
7171
  if (accounts.length > 1) {
@@ -6848,14 +7211,10 @@ function isWranglerNonInteractiveOrCI() {
6848
7211
  async function runWranglerAuthToken(runWrangler, configPath) {
6849
7212
  let result;
6850
7213
  try {
6851
- result = await Promise.resolve(runWrangler([
6852
- "auth",
6853
- "token",
6854
- "--json",
6855
- "-c",
6856
- configPath,
6857
- "--env="
6858
- ], {
7214
+ result = await Promise.resolve(runWrangler({
7215
+ type: "auth.token",
7216
+ configPath
7217
+ }, {
6859
7218
  capture: true,
6860
7219
  env: { WRANGLER_WRITE_LOGS: "false" }
6861
7220
  }));
@@ -6863,15 +7222,7 @@ async function runWranglerAuthToken(runWrangler, configPath) {
6863
7222
  return null;
6864
7223
  }
6865
7224
  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
- }
7225
+ const parsed = result.data;
6875
7226
  const type = parsed?.type;
6876
7227
  const token = parsed?.token;
6877
7228
  return (type === "oauth" || type === "api_token") && typeof token === "string" && token.length > 0 ? token : null;
@@ -6907,7 +7258,8 @@ async function runCloudflareEmailStep(input) {
6907
7258
  };
6908
7259
  const from = input.voidConfig.email?.from;
6909
7260
  if (!from) {
6910
- log.warn(["deploy: this app uses email, but void.config.ts names no sender.", "Set email.from to \"you@mail.acme.com\" in void.config.ts — deploying without email."].join("\n"));
7261
+ if (Array.isArray(input.rootConfig.send_email) && input.rootConfig.send_email.some((binding) => binding?.name === "SEND_EMAIL") || Array.isArray(input.rootConfig.addresses) && input.rootConfig.addresses.length > 0) log.info("deploy: email.from is unset; skipping automatic email setup. Existing Cloudflare email configuration is preserved.");
7262
+ else log.warn(["deploy: this app uses email, but void.config.ts names no sender.", "Set email.from to \"you@mail.acme.com\" in void.config.ts — automatic email setup skipped."].join("\n"));
6911
7263
  return without;
6912
7264
  }
6913
7265
  const { rootConfig } = input;
@@ -6921,11 +7273,7 @@ async function runCloudflareEmailStep(input) {
6921
7273
  existingAddresses: rootConfig.addresses,
6922
7274
  existingVars: rootConfig.vars && typeof rootConfig.vars === "object" && !Array.isArray(rootConfig.vars) ? rootConfig.vars : void 0,
6923
7275
  configConflicts: findEmailConfigConflicts(rootConfig),
6924
- wranglerArgs: [
6925
- "-c",
6926
- input.configPath,
6927
- "--env="
6928
- ],
7276
+ configPath: input.configPath,
6929
7277
  apiBaseUrl: input.apiBaseUrl,
6930
7278
  runWrangler: input.runWrangler,
6931
7279
  getSessionBearer: () => runWranglerAuthToken(input.runWrangler, input.configPath),
@@ -7086,14 +7434,12 @@ async function ensureCloudflareAuthSecret(options) {
7086
7434
  beforeRun();
7087
7435
  let put;
7088
7436
  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
- ], {
7437
+ put = await Promise.resolve(options.runWrangler({
7438
+ type: "secret.put",
7439
+ name: BETTER_AUTH_SECRET_NAME,
7440
+ configPath: options.configPath,
7441
+ workerName: options.workerName
7442
+ }, {
7097
7443
  capture: true,
7098
7444
  input: `${secret}\n`
7099
7445
  }));
@@ -7120,13 +7466,23 @@ async function prepareCloudflareDeploySource(root, options, voidConfig) {
7120
7466
  ].join("\n"));
7121
7467
  return null;
7122
7468
  }
7123
- const classification = classifyCloudflareDeployApp(root, voidConfig, options);
7469
+ let application;
7470
+ try {
7471
+ application = analyzeApplicationSync(root, {
7472
+ config: voidConfig,
7473
+ environment: "prod"
7474
+ });
7475
+ } catch (error) {
7476
+ log.error(`deploy: ${getErrorMessage(error)}`);
7477
+ return null;
7478
+ }
7479
+ const classification = classifyCloudflareDeployApp(root, voidConfig, options, application);
7124
7480
  if (!classification.supported) {
7125
7481
  log.error(classification.reason);
7126
7482
  return null;
7127
7483
  }
7128
7484
  const configPath = discoverRootWranglerConfig(root);
7129
- const facts = await computeDesiredCloudflareResources(root, voidConfig, configPath);
7485
+ const facts = await computeDesiredCloudflareResources(root, voidConfig, configPath, application);
7130
7486
  const { desired, dialect, migrations: preBuildMigrations, migrationsDir } = facts;
7131
7487
  if (dialect !== "sqlite" && preBuildMigrations.length > 0 && !process.env.DATABASE_URL) {
7132
7488
  log.error([
@@ -7154,51 +7510,35 @@ async function applyAndVerifyRemoteD1Migrations(d1MigrateToken, configPath, runW
7154
7510
  if (d1MigrateToken) {
7155
7511
  log.step("Applying D1 migrations...");
7156
7512
  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 }));
7513
+ const migrate = await Promise.resolve(runWrangler({
7514
+ type: "d1.migrations.apply",
7515
+ database: d1MigrateToken,
7516
+ configPath
7517
+ }, { capture: false }));
7167
7518
  if (!isCleanWranglerExit(migrate)) {
7168
7519
  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
7520
  return false;
7170
7521
  }
7171
7522
  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 }));
7523
+ const migrateList = await Promise.resolve(runWrangler({
7524
+ type: "d1.migrations.list",
7525
+ database: d1MigrateToken,
7526
+ configPath
7527
+ }, { capture: true }));
7182
7528
  if (!isCleanWranglerExit(migrateList)) {
7183
7529
  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
7530
  return false;
7185
7531
  }
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"));
7532
+ const migrationState = migrateList.data;
7533
+ if (Array.isArray(migrationState?.pending) && migrationState.pending.length > 0) {
7534
+ 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
7535
  return false;
7194
7536
  }
7195
- if (!migrateList.stdout.includes("No migrations to apply!")) {
7537
+ if (!Array.isArray(migrationState?.pending) || !migrationState.pending.every((name) => typeof name === "string")) {
7196
7538
  log.error([
7197
7539
  `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."
7540
+ "Cloudflare returned an invalid pending migration set. Refusing to deploy against an unverified schema.",
7541
+ "Retry once the database migration state can be read."
7202
7542
  ].join("\n"));
7203
7543
  return false;
7204
7544
  }
@@ -7334,6 +7674,12 @@ async function runCloudflareDeploy(root, options, cliLog, deps = {}) {
7334
7674
  if (!source) return exit(1);
7335
7675
  const { configPath, desired, reasons, authEnabled, dialect, migrationsDir, migrations: preBuildMigrations, needsAI, aiBindingName, needsSandbox, needsLive, expectedCrons, needsEmail, emailHandlers } = source;
7336
7676
  let rootConfig = parseRootWranglerConfig(configPath);
7677
+ if (needsSandbox) try {
7678
+ assertSandboxNamespaceConfig(rootConfig, resolveSandboxConfig(voidConfig, root));
7679
+ } catch (error) {
7680
+ log.error(`deploy: ${getErrorMessage(error)}`);
7681
+ return exit(1);
7682
+ }
7337
7683
  let apiBaseUrl;
7338
7684
  try {
7339
7685
  apiBaseUrl = resolveDeployTransport(root, rootConfig);
@@ -7449,8 +7795,8 @@ async function runCloudflareDeploy(root, options, cliLog, deps = {}) {
7449
7795
  await ensureCloudflareAuthentication({
7450
7796
  acceptApiToken: true,
7451
7797
  beforeRun: assertUploaderStillPinned,
7452
- runWrangler: (args, capture) => runWrangler([...args], { capture }),
7453
- whoamiArgs: [...existingWranglerConfigArgs(configPath), "--env="]
7798
+ runWrangler: (operation, capture) => runWrangler(operation, { capture }),
7799
+ configPath: existsSync(configPath) ? configPath : void 0
7454
7800
  });
7455
7801
  } catch (error) {
7456
7802
  if (error instanceof CloudflareAuthenticationError && error.reason === "uploader-changed") return exit(1);
@@ -7509,13 +7855,10 @@ async function runCloudflareDeploy(root, options, cliLog, deps = {}) {
7509
7855
  }
7510
7856
  if (needsSandbox) {
7511
7857
  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 })))) {
7858
+ if (!isCleanWranglerExit(await Promise.resolve(runWrangler({
7859
+ type: "containers.list",
7860
+ configPath: existsSync(configPath) ? configPath : void 0
7861
+ }, { capture: true })))) {
7519
7862
  log.error(formatCloudflareSandboxUnavailable({ preflight: true }));
7520
7863
  return exit(1);
7521
7864
  }
@@ -7594,19 +7937,16 @@ async function runCloudflareDeploy(root, options, cliLog, deps = {}) {
7594
7937
  }
7595
7938
  const absentQueues = [];
7596
7939
  for (const name of desired.queues) {
7597
- const info = await Promise.resolve(runWrangler([
7598
- "queues",
7599
- "info",
7940
+ const info = await Promise.resolve(runWrangler({
7941
+ type: "queues.info",
7600
7942
  name,
7601
- "-c",
7602
- configPath,
7603
- "--env="
7604
- ], { capture: true }));
7943
+ configPath
7944
+ }, { capture: true }));
7605
7945
  if (provisionModule.isQueueConfirmedAbsent(info, name)) absentQueues.push(name);
7606
7946
  }
7607
7947
  if (absentQueues.length > 0) await provisionAndVerify();
7608
7948
  }
7609
- const frameworkQueues = detectedFramework ? scanQueuesSync(root, resolveProjectPaths(root, voidConfig).queuesDir) : [];
7949
+ const frameworkQueues = detectedFramework ? source.analysis.queues : [];
7610
7950
  writeProvisionedWranglerConfig(root, {
7611
7951
  name: workerName,
7612
7952
  accountId,
@@ -7632,12 +7972,15 @@ async function runCloudflareDeploy(root, options, cliLog, deps = {}) {
7632
7972
  const expectedContainers = reconstructExpectedContainers(voidConfig, root, preBuildRootConfig, needsSandbox);
7633
7973
  const expectedDurableObjectState = reconstructExpectedDurableObjectState(voidConfig, root, preBuildRootConfig, {
7634
7974
  needsSandbox,
7635
- needsLive
7975
+ needsLive,
7976
+ analysis: source.analysis
7636
7977
  });
7637
7978
  removeDeployPointer(root);
7638
7979
  const buildChildEnv = scrubManagedDeployBuildMarkers(scrubWranglerAuthCredentials(deployChildEnv(accountId, apiBaseUrl)));
7639
7980
  requireProvisionModule().deleteEnvVarCaseAware(buildChildEnv, CLOUDFLARE_DEPLOY_BUILD_ENV);
7640
7981
  buildChildEnv[CLOUDFLARE_DEPLOY_BUILD_ENV] = "1";
7982
+ requireProvisionModule().deleteEnvVarCaseAware(buildChildEnv, CLOUDFLARE_PRESERVE_BINDINGS_BUILD_ENV);
7983
+ if (existingWorker) buildChildEnv[CLOUDFLARE_PRESERVE_BINDINGS_BUILD_ENV] = "1";
7641
7984
  requireProvisionModule().deleteEnvVarCaseAware(buildChildEnv, "NODE_ENV");
7642
7985
  buildChildEnv.NODE_ENV = "production";
7643
7986
  const runBuild = deps.runBuild ?? ((env) => {
@@ -7656,7 +7999,7 @@ async function runCloudflareDeploy(root, options, cliLog, deps = {}) {
7656
7999
  }
7657
8000
  log.step("Preparing deployment...");
7658
8001
  if (detectedFramework && frameworkPreset) try {
7659
- await (deps.prepareFrameworkArtifact ?? prepareCloudflareFrameworkArtifact)(root, configPath, accountId, workerName, voidConfig, detectedFramework, frameworkPreset, schemaFacts.requiredKeys, needsAI ? { binding: aiBindingName } : void 0);
8002
+ await (deps.prepareFrameworkArtifact ?? prepareCloudflareFrameworkArtifact)(root, configPath, accountId, workerName, voidConfig, detectedFramework, frameworkPreset, schemaFacts.requiredKeys, needsAI ? { binding: aiBindingName } : void 0, existingWorker !== null);
7660
8003
  } catch (error) {
7661
8004
  log.error(`deploy: could not prepare ${detectedFramework.name} for Cloudflare — ${getErrorMessage(error)}`);
7662
8005
  return exit(1);
@@ -7907,6 +8250,8 @@ async function runCloudflareDeploy(root, options, cliLog, deps = {}) {
7907
8250
  workerName,
7908
8251
  configPath: generatedConfigPath,
7909
8252
  preserveTriggers: existingWorker !== null,
8253
+ requireVersionMetadata: existingWorker === null || readVersionMetadataBinding(parseRootWranglerConfig(generatedConfigPath)) !== void 0,
8254
+ requirePreviewForReadiness: existingWorker !== null && readVersionMetadataBinding(parseRootWranglerConfig(generatedConfigPath)) === void 0,
7910
8255
  expectedPreviousVersionId: existingWorker?.versionId,
7911
8256
  trustedInheritanceVersionIds: existingWorker ? candidateReceipt?.parentVersionId === existingWorker.versionId ? candidateReceipt.versionIds : void 0 : initialCloudflareCandidate?.versionIds,
7912
8257
  validateUploadedVersion: existingWorker ? async (versionId) => {
@@ -7982,10 +8327,6 @@ function discoverRootWranglerConfig(root) {
7982
8327
  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
8328
  return materializeCloudflareConfig(root);
7984
8329
  }
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
8330
  async function reportCloudflareActivationFailure(cliLog, message) {
7990
8331
  cliLog.error("cloudflare_deploy_failed", new Error(message));
7991
8332
  await cliLog.close().catch(() => {});
@@ -8018,7 +8359,9 @@ function formatCloudflareActivationError(result) {
8018
8359
  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
8360
  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
8361
  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.`;
8362
+ case "status-failed":
8363
+ 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.`;
8364
+ return `deploy: could not snapshot the current Cloudflare deployment${status}; no version was uploaded.`;
8022
8365
  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
8366
  case "inheritance-source-unavailable": return "deploy: could not verify which Worker Version would supply inherited secrets; production traffic is unchanged.";
8024
8367
  case "inheritance-source-changed": return [
@@ -8034,16 +8377,18 @@ function formatCloudflareActivationError(result) {
8034
8377
  ].join("\n");
8035
8378
  case "upload-failed": return `deploy: Cloudflare Worker Version upload failed${status}; production traffic is unchanged.${cloudflareCode}${diagnostic}`;
8036
8379
  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");
8380
+ case "preview-unavailable":
8381
+ if (result.diagnostic) return `deploy: candidate readiness cannot be verified.\n\n${result.diagnostic}`;
8382
+ return [
8383
+ "deploy: the uploaded Worker Version has no preview URL, so Void cannot verify readiness.",
8384
+ "Set CLOUDFLARE_WORKERS_SUBDOMAIN to your account subdomain (for example \"my-team\")",
8385
+ "and retry. Void caches it after a successful workers.dev deployment; production traffic is unchanged."
8386
+ ].join("\n");
8042
8387
  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
8388
  case "atomic-failed": return `deploy: Cloudflare could not publish the Worker atomically${status}; inspect production status before retrying.${cloudflareCode}${diagnostic}`;
8044
8389
  case "atomic-output-invalid": return "deploy: Cloudflare published atomically but did not report a version id or URL. Inspect `void project status` immediately.";
8045
8390
  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.";
8391
+ 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
8392
  case "readiness-failed": return ["deploy: the uploaded Worker Version did not pass /__void/ready.", formatCloudflareReadinessRestoration(result)].join("\n");
8048
8393
  case "readiness-access-denied": return [
8049
8394
  "deploy: Cloudflare Access blocked the uploaded Worker Version readiness probe.",
@@ -8058,7 +8403,7 @@ function formatCloudflareActivationError(result) {
8058
8403
  "deploy: the first Worker deployment did not pass /__void/ready.",
8059
8404
  "Cloudflare requires this bootstrap to be non-versioned, so the new Worker is already active;",
8060
8405
  "inspect its logs and `void project status` before retrying."
8061
- ].join("\n");
8406
+ ].join("\n") + diagnostic;
8062
8407
  case "bootstrap-readiness-access-denied": return [
8063
8408
  "deploy: Cloudflare Access blocked the first Worker deployment readiness probe.",
8064
8409
  "For CI, set CF_ACCESS_CLIENT_ID and CF_ACCESS_CLIENT_SECRET to an admitted service token;",
@@ -8068,7 +8413,7 @@ function formatCloudflareActivationError(result) {
8068
8413
  "Worker is already active behind Access."
8069
8414
  ].join("\n");
8070
8415
  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."}`;
8416
+ 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
8417
  }
8073
8418
  }
8074
8419
  function formatCloudflareReadinessRestoration(result) {
@@ -8089,29 +8434,36 @@ function cloudflareWorkerReadinessHeaders() {
8089
8434
  });
8090
8435
  }
8091
8436
  /** 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) {
8437
+ async function prepareCloudflareFrameworkArtifact(root, configPath, accountId, workerName, voidConfig, framework, preset, requiredSecretNames = [], ai, preserveBindings = false) {
8093
8438
  const workerMain = join(root, preset.workerDir, preset.workerMain);
8094
8439
  const assetsDirectory = join(root, preset.assetsDir);
8095
8440
  if (!existsSync(workerMain)) throw new Error(`framework ${framework.name} did not emit its Worker entry at ${preset.workerDir}/${preset.workerMain}`);
8096
8441
  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);
8442
+ const analysis = analyzeApplicationSync(root, {
8443
+ config: voidConfig,
8444
+ environment: "prod"
8445
+ });
8446
+ const crons = analysis.jobs;
8447
+ const queues = analysis.queues;
8100
8448
  const revalidate = collectRevalidate(voidConfig, null);
8449
+ const rootConfig = parseRootWranglerConfig(configPath);
8450
+ const versionMetadataBinding = selectNativeVersionMetadataBinding(rootConfig, {
8451
+ nativeBuild: true,
8452
+ preserveBindings
8453
+ });
8101
8454
  await generateDeployWrapperIfNeeded(root, join(root, preset.workerDir), preset.workerMain, {
8102
- auth: null,
8103
8455
  crons,
8104
8456
  queues,
8105
8457
  hasMigrations: false,
8106
- migrationDialect: getDatabaseDialect(voidConfig),
8458
+ migrationDialect: analysis.dialect,
8107
8459
  readiness: true,
8460
+ versionMetadataBinding,
8108
8461
  ...hasRevalidate(revalidate) && { isr: {
8109
8462
  revalidate,
8110
8463
  queryAllowlist: voidConfig.routing?.revalidateQueryAllowlist
8111
8464
  } }
8112
8465
  });
8113
8466
  if (preset.cloudflareArtifact === "vite-plugin") return;
8114
- const rootConfig = parseRootWranglerConfig(configPath);
8115
8467
  writeCloudflareFrameworkDeployConfig({
8116
8468
  configPath,
8117
8469
  deployPointerPath: deployPointerPath(root),
@@ -8123,6 +8475,7 @@ async function prepareCloudflareFrameworkArtifact(root, configPath, accountId, w
8123
8475
  compatibilityFlags: preset.cloudflareCompatibilityFlags,
8124
8476
  crons: [...new Set(crons.flatMap((job) => job.crons))],
8125
8477
  requiredSecrets: requiredSecretNames,
8478
+ versionMetadataBinding,
8126
8479
  ...ai ? { ai: {
8127
8480
  ...ai,
8128
8481
  gateway: voidConfig.ai?.gateway
@@ -8162,23 +8515,14 @@ function resolveCloudflareStaticPreset(root, voidConfig, options) {
8162
8515
  */
8163
8516
  async function resolveCloudflareVoidStaticPreset(root, voidConfig) {
8164
8517
  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,
8518
+ const analysis = await analyzeApplication(root, {
8519
+ config: voidConfig,
8170
8520
  environment: "prod"
8171
8521
  });
8172
- const pageScan = routeScan.pages.pages.length > 0 ? routeScan.pages : await scanPages(root, {
8173
- output: voidConfig.output,
8174
- paths
8175
- });
8522
+ const { paths, authEnabled, bindings, scan: routeScan } = analysis;
8523
+ if (!existsSync(paths.pagesDir) || validateSsrEntry(root, paths) !== null) return null;
8524
+ const pageScan = routeScan.pages;
8176
8525
  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
8526
  if (hasNonPageBackend({
8183
8527
  hasRoutes: routeScan.routes.length > 0,
8184
8528
  hasMiddleware: routeScan.middleware.length > 0,
@@ -8186,7 +8530,7 @@ async function resolveCloudflareVoidStaticPreset(root, voidConfig) {
8186
8530
  hasEmail: routeScan.email !== void 0 && hasEmailHandlers(routeScan.email),
8187
8531
  authEnabled,
8188
8532
  hasLive: bindings.needsLive === true
8189
- }) || scanJobsSync(root, paths.cronsDir).length > 0 || scanQueuesSync(root, paths.queuesDir).length > 0) return null;
8533
+ }) || analysis.jobs.length > 0 || analysis.queues.length > 0) return null;
8190
8534
  return {
8191
8535
  buildCommand: resolveFullDeployBuildCommand(root, voidConfig.inference?.build).command,
8192
8536
  outputDir: join(root, "dist", "client"),
@@ -8247,8 +8591,8 @@ async function runCloudflareStaticDeploy(root, options, voidConfig, preset, cliL
8247
8591
  await ensureCloudflareAuthentication({
8248
8592
  acceptApiToken: true,
8249
8593
  beforeRun: uploaderIsPinned,
8250
- runWrangler: (args, capture) => runWrangler([...args], { capture }),
8251
- whoamiArgs: [...existingWranglerConfigArgs(configPath), "--env="]
8594
+ runWrangler: (operation, capture) => runWrangler(operation, { capture }),
8595
+ configPath: existsSync(configPath) ? configPath : void 0
8252
8596
  });
8253
8597
  } catch (error) {
8254
8598
  if (error instanceof CloudflareAuthenticationError && error.reason === "uploader-changed") return exit(1);
@@ -8316,12 +8660,6 @@ async function runCloudflareStaticDeploy(root, options, voidConfig, preset, cliL
8316
8660
  });
8317
8661
  const entryPath = join(root, ".void", "deploy", "cloudflare", "static-edge.mjs");
8318
8662
  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
8663
  const workerName = readWranglerTopLevelString(rootConfig, "name") ?? deriveWorkerName(root);
8326
8664
  const staticConfigPath = join(dirname(entryPath), "wrangler-static.json");
8327
8665
  writeFileSync(staticConfigPath, `${JSON.stringify(rootConfig, null, 2)}\n`, { mode: 384 });
@@ -8335,6 +8673,22 @@ async function runCloudflareStaticDeploy(root, options, voidConfig, preset, cliL
8335
8673
  assetConfig: manifest.assetConfig ?? { not_found_handling: "none" }
8336
8674
  });
8337
8675
  deduplicateGeneratedCompatibilityFlags(staticConfigPath, configPath);
8676
+ const staticConfig = parseRootWranglerConfig(staticConfigPath);
8677
+ const versionMetadataBinding = selectNativeVersionMetadataBinding(staticConfig, {
8678
+ nativeBuild: true,
8679
+ preserveBindings: existingWorker !== null
8680
+ });
8681
+ if (versionMetadataBinding && staticConfig.version_metadata === void 0) writeFileSync(staticConfigPath, `${JSON.stringify({
8682
+ ...staticConfig,
8683
+ version_metadata: { binding: versionMetadataBinding }
8684
+ }, null, 2)}\n`, { mode: 384 });
8685
+ writeFileSync(entryPath, renderCloudflareStaticEntry({
8686
+ runtimeImport: "void/edge",
8687
+ headerRules: manifest.headerRules,
8688
+ redirectRules: manifest.redirectRules,
8689
+ fallbackRules: manifest.fallbackRules,
8690
+ versionMetadataBinding
8691
+ }));
8338
8692
  if (existingWorker) assertExistingCloudflareConfig(existingWorker, parseRootWranglerConfig(staticConfigPath), true);
8339
8693
  const stagedAssetsDirectory = join(root, ".void", "deploy", "cloudflare", "assets");
8340
8694
  stageCloudflareStaticAssets({
@@ -8385,6 +8739,8 @@ async function runCloudflareStaticDeploy(root, options, voidConfig, preset, cliL
8385
8739
  verifyUploader: cfOutput ? cfUploaderIsPinned : uploaderIsPinned,
8386
8740
  configPath: staticConfigPath,
8387
8741
  preserveTriggers: existingWorker !== null,
8742
+ requireVersionMetadata: existingWorker === null || versionMetadataBinding !== void 0,
8743
+ requirePreviewForReadiness: existingWorker !== null && versionMetadataBinding === void 0,
8388
8744
  expectedPreviousVersionId: existingWorker?.versionId,
8389
8745
  trustedInheritanceVersionIds: existingWorker ? candidateReceipt?.parentVersionId === existingWorker.versionId ? candidateReceipt.versionIds : void 0 : initialCloudflareCandidate?.versionIds,
8390
8746
  validateUploadedVersion: existingWorker ? async (versionId) => {
@@ -8537,11 +8893,16 @@ async function runDeployInner(root, options, cliLog) {
8537
8893
  }
8538
8894
  }
8539
8895
  async function runManagedDeploy(root, options, cliLog) {
8896
+ const apiUrl = getApiUrl(root);
8897
+ const requestedProjectSlug = getRequestedProjectSlug(options);
8540
8898
  let reauthenticated = false;
8541
8899
  while (true) try {
8900
+ const savedProject = readProjectConfig(root);
8901
+ const linkedProject = savedProject?.platformUrl === apiUrl ? savedProject : null;
8902
+ if (savedProject && !linkedProject && !requestedProjectSlug) throw new Error(`deploy: This project is linked to ${savedProject.platformUrl}. Pass --project <slug> to deploy to ${apiUrl} without changing the saved link.`);
8542
8903
  let token = process.env.VOID_TOKEN ? getToken(root) : null;
8543
8904
  if (!token && isGithubActionsOidcAvailable()) {
8544
- const slug = getRequestedProjectSlug(options) ?? readProjectConfig(root)?.slug;
8905
+ const slug = requestedProjectSlug ?? linkedProject?.slug;
8545
8906
  if (!slug) throw new Error("deploy: running in GitHub Actions but the project is unknown — pass --project <slug> or link the project.");
8546
8907
  cliLog.info("github_oidc_exchange_start", { slug });
8547
8908
  const tokenOrigin = getApiUrl(root);
@@ -8560,8 +8921,6 @@ async function runManagedDeploy(root, options, cliLog) {
8560
8921
  root,
8561
8922
  cliLog
8562
8923
  });
8563
- const requestedProjectSlug = getRequestedProjectSlug(options);
8564
- const linkedProject = readProjectConfig(root);
8565
8924
  let config = null;
8566
8925
  if (requestedProjectSlug) {
8567
8926
  const linked = linkedProject?.slug === requestedProjectSlug ? await client.getProject(linkedProject.projectId) : null;
@@ -8659,24 +9018,31 @@ function assertProjectDeployAccess(project) {
8659
9018
  }
8660
9019
  async function promptDeployProjectConfig(root, client, cliLog) {
8661
9020
  const projects = await client.listProjectChoices();
9021
+ const link = shouldLinkDeployProject(root, client.apiUrl);
8662
9022
  const deployableProjects = projects.filter((project) => project.role !== "reader");
8663
9023
  if (deployableProjects.length !== projects.length) log.warn("Projects with reader access are hidden from deploy selection. Ask a project admin or owner to grant collaborator access to deploy to them.");
8664
- if (await promptProjectSetupAction("Set up a Void project for deploy:", { includeLink: deployableProjects.length > 0 }) === "create") return promptAndCreateProject(root, client);
9024
+ if (await promptProjectSetupAction("Set up a Void project for deploy:", {
9025
+ includeLink: deployableProjects.length > 0,
9026
+ ...link ? {} : { link: false }
9027
+ }) === "create") return promptAndCreateProject(root, client, { link });
8665
9028
  const result = await promptProjectSelection(deployableProjects, "Select a project to deploy to:", root, {
8666
9029
  confirmExisting: true,
8667
- includeCreate: false
9030
+ includeCreate: false,
9031
+ ...link ? {} : { link: false }
8668
9032
  });
8669
9033
  if (result.action === "create") throw new Error("Unexpected project selection result.");
8670
9034
  const config = {
8671
9035
  projectId: result.project.id,
8672
9036
  slug: result.project.slug
8673
9037
  };
8674
- writeProjectConfig(root, attachSelectedPlatform(root, config, client.apiUrl));
8675
- log.step(`Linked to ${import_picocolors$1.default.blue(config.slug)}`);
8676
- cliLog?.info("project_linked", {
8677
- projectId: config.projectId,
8678
- slug: config.slug
8679
- });
9038
+ if (shouldLinkDeployProject(root, client.apiUrl)) {
9039
+ writeProjectConfig(root, attachSelectedPlatform(root, config, client.apiUrl));
9040
+ log.step(`Linked to ${import_picocolors$1.default.blue(config.slug)}`);
9041
+ cliLog?.info("project_linked", {
9042
+ projectId: config.projectId,
9043
+ slug: config.slug
9044
+ });
9045
+ } else log.step(`Using ${import_picocolors$1.default.blue(config.slug)} for this deployment. Saved destination unchanged.`);
8680
9046
  return config;
8681
9047
  }
8682
9048
  function resolveStaticBuildCommand(preset, buildOverride, hasExplicitDir = false) {
@@ -8785,7 +9151,9 @@ function applyDeployEvent(event, s) {
8785
9151
  kind: "start",
8786
9152
  deploymentId: event.deploymentId
8787
9153
  };
8788
- case "heartbeat": return { kind: "continue" };
9154
+ case "heartbeat":
9155
+ if (event.status === "recovering") s.message("Recovering interrupted deployment...");
9156
+ return { kind: "continue" };
8789
9157
  case "asset_upload":
8790
9158
  if (event.new > 0) s.message(`Uploading assets (${event.new} new, ${event.cached} cached)`);
8791
9159
  else s.message(`Uploading assets (${event.total} cached)`);
@@ -8838,6 +9206,8 @@ function applyDeployEvent(event, s) {
8838
9206
  async function streamDeployEvents(projectId, events, s, cliLog) {
8839
9207
  const startedAt = Date.now();
8840
9208
  let deploymentId = null;
9209
+ let lastProgressAt = startedAt;
9210
+ let waitNotice;
8841
9211
  let interrupted = false;
8842
9212
  cliLog?.info("stream_start", { projectId });
8843
9213
  const onSigint = () => {
@@ -8852,6 +9222,26 @@ async function streamDeployEvents(projectId, events, s, cliLog) {
8852
9222
  process.once("SIGINT", onSigint);
8853
9223
  try {
8854
9224
  for await (const event of events) {
9225
+ if (event.event === "heartbeat") {
9226
+ const fields = {
9227
+ deploymentId,
9228
+ step: event.step,
9229
+ status: event.status,
9230
+ recoveryAt: event.recoveryAt
9231
+ };
9232
+ if (event.status === "recovering" && waitNotice !== "recovering") {
9233
+ log.info(`Deployment ${deploymentId ?? event.deploymentId ?? ""} is recovering; waiting for its result.`);
9234
+ cliLog?.info("deploy_recovering", fields);
9235
+ waitNotice = "recovering";
9236
+ } else if (Date.now() - lastProgressAt >= 9e4 && waitNotice === void 0) {
9237
+ log.warn(`Deployment ${deploymentId ?? event.deploymentId ?? ""} has not reported new progress for 90 seconds (last reported progress: ${event.step.replaceAll("_", " ")}).` + (event.recoveryAt ? ` Automatic recovery can start after ${event.recoveryAt} if execution was interrupted.` : "") + " Inspect with `void project status`.");
9238
+ cliLog?.warn("deploy_progress_stalled", fields);
9239
+ waitNotice = "stalled";
9240
+ }
9241
+ } else {
9242
+ lastProgressAt = Date.now();
9243
+ waitNotice = void 0;
9244
+ }
8855
9245
  const result = applyDeployEvent(event, s);
8856
9246
  if (result.kind === "start") {
8857
9247
  deploymentId = result.deploymentId;
@@ -9142,7 +9532,6 @@ async function runFrameworkDeploy(root, config, client, detected, fwPreset, void
9142
9532
  mode: "framework",
9143
9533
  framework: detected.name
9144
9534
  });
9145
- const dialect = getDatabaseDialect(voidConfig);
9146
9535
  const buildCmd = voidConfig.inference?.build ?? formatProjectCommand(root, fwPreset.buildCommand);
9147
9536
  if (!skipBuild) {
9148
9537
  log.step(`Building (${buildCmd})...`);
@@ -9172,15 +9561,12 @@ async function runFrameworkDeploy(root, config, client, detected, fwPreset, void
9172
9561
  log.error(`deploy: Worker output '${fwPreset.workerDir}' not found. Run '${buildCmd}' first.`);
9173
9562
  process.exit(1);
9174
9563
  }
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,
9564
+ const analysis = analyzeApplicationSync(root, {
9565
+ config: voidConfig,
9181
9566
  environment: "prod"
9182
- }, scanDirs);
9183
- if (bindings.needsAuth || findVoidAuthConfig(root, paths) !== null || voidConfig.auth !== void 0) {
9567
+ });
9568
+ const { paths, bindings, authEnabled, dialect } = analysis;
9569
+ if (authEnabled) {
9184
9570
  log.error("deploy: Void-managed Better Auth is only supported in Void apps. Use Better Auth's official integration for this framework instead.");
9185
9571
  process.exit(1);
9186
9572
  }
@@ -9190,7 +9576,7 @@ async function runFrameworkDeploy(root, config, client, detected, fwPreset, void
9190
9576
  }
9191
9577
  const migrationsDir = paths.migrationsDir;
9192
9578
  assertDeploySchemaDriftFree(root, dialect);
9193
- const rawMigrations = collectMigrations(migrationsDir);
9579
+ const rawMigrations = analysis.migrations;
9194
9580
  let validatedMigrations;
9195
9581
  try {
9196
9582
  assertJournalCoherence(migrationsDir, rawMigrations);
@@ -9205,14 +9591,14 @@ async function runFrameworkDeploy(root, config, client, detected, fwPreset, void
9205
9591
  log.error(getErrorMessage(err));
9206
9592
  process.exit(1);
9207
9593
  }
9208
- if (dialect !== "sqlite" && (bindings.needsD1 || rawMigrations.length > 0)) {
9594
+ if (dialect !== "sqlite" && analysis.needsDatabase) {
9209
9595
  let resources = null;
9210
9596
  try {
9211
9597
  resources = await client.getResources(config.projectId);
9212
9598
  } catch {}
9213
9599
  if (resources) {
9214
9600
  if (!resources.some((resource) => resource.type === "hyperdrive")) {
9215
- const { text: promptText, isCancel: isCancelPrompt } = await import("./dist-BuDuKZJv.mjs");
9601
+ const { text: promptText, isCancel: isCancelPrompt } = await import("./prompts-2TGE6oXx.mjs");
9216
9602
  const connectionString = await promptText({
9217
9603
  message: `Your project uses ${dialect === "mysql" ? "MySQL" : "PostgreSQL"}. Enter your connection string:`,
9218
9604
  placeholder: dialect === "mysql" ? "mysql://user:pass@host:3306/dbname" : "postgresql://user:pass@host:5432/dbname",
@@ -9229,11 +9615,10 @@ async function runFrameworkDeploy(root, config, client, detected, fwPreset, void
9229
9615
  }
9230
9616
  }
9231
9617
  }
9232
- const rawJobs = scanJobsSync(root, paths.cronsDir);
9233
- const rawQueues = scanQueuesSync(root, paths.queuesDir);
9618
+ const rawJobs = analysis.jobs;
9619
+ const rawQueues = analysis.queues;
9234
9620
  const originalEntry = join(workerDir, fwPreset.workerMain);
9235
9621
  const wrapperFeatures = {
9236
- auth: null,
9237
9622
  crons: rawJobs,
9238
9623
  queues: rawQueues,
9239
9624
  hasMigrations: validatedMigrations != null && validatedMigrations.length > 0,
@@ -9289,7 +9674,7 @@ async function runFrameworkDeploy(root, config, client, detected, fwPreset, void
9289
9674
  const packageStartedAt = Date.now();
9290
9675
  const neededSet = skipped > 0 ? new Set(needed) : null;
9291
9676
  const packageOptions = {
9292
- bindingNames: resolveBindingNames(voidConfig.inference?.bindings),
9677
+ bindingNames: analysis.bindingNames,
9293
9678
  workerMain: actualWorkerMain,
9294
9679
  compatibilityDate: wranglerCompat?.compatibilityDate,
9295
9680
  compatibilityFlags: wranglerCompat?.compatibilityFlags,
@@ -9396,20 +9781,12 @@ async function runFullDeploy(root, config, client, voidConfig, fwPreset, skipBui
9396
9781
  if (result.paths.length === 0) log.info("No pages to prerender");
9397
9782
  else if (result.failed.length > 0) voidWarn(`${result.failed.length} page(s) failed to prerender`);
9398
9783
  }
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,
9784
+ const analysis = await analyzeApplication(root, {
9785
+ config: deployConfig,
9404
9786
  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;
9787
+ });
9788
+ const { paths, dialect, bindings, authEnabled, scan: routeScan } = analysis;
9789
+ const effectiveBindings = bindings;
9413
9790
  if (isFrameworkMode && authEnabled) {
9414
9791
  log.error("Void-managed Better Auth is only supported in Void apps. Use Better Auth's official integration for this framework instead.");
9415
9792
  process.exit(1);
@@ -9439,7 +9816,7 @@ async function runFullDeploy(root, config, client, voidConfig, fwPreset, skipBui
9439
9816
  lintDestinationSplats(fallbackRules, "fallbacks (post-asset)");
9440
9817
  const migrationsDir = paths.migrationsDir;
9441
9818
  assertDeploySchemaDriftFree(root, dialect);
9442
- const rawMigrations = collectMigrations(migrationsDir);
9819
+ const rawMigrations = analysis.migrations;
9443
9820
  let validatedMigrations;
9444
9821
  try {
9445
9822
  assertJournalCoherence(migrationsDir, rawMigrations);
@@ -9454,14 +9831,14 @@ async function runFullDeploy(root, config, client, voidConfig, fwPreset, skipBui
9454
9831
  log.error(getErrorMessage(err));
9455
9832
  process.exit(1);
9456
9833
  }
9457
- if (dialect !== "sqlite" && (effectiveBindings.needsD1 || rawMigrations.length > 0)) {
9834
+ if (dialect !== "sqlite" && analysis.needsDatabase) {
9458
9835
  let resources = null;
9459
9836
  try {
9460
9837
  resources = await client.getResources(config.projectId);
9461
9838
  } catch {}
9462
9839
  if (resources) {
9463
9840
  if (!resources.some((resource) => resource.type === "hyperdrive")) {
9464
- const { text: promptText, isCancel: isCancelPrompt } = await import("./dist-BuDuKZJv.mjs");
9841
+ const { text: promptText, isCancel: isCancelPrompt } = await import("./prompts-2TGE6oXx.mjs");
9465
9842
  const connectionString = await promptText({
9466
9843
  message: `Your project uses ${dialect === "mysql" ? "MySQL" : "PostgreSQL"}. Enter your connection string:`,
9467
9844
  placeholder: dialect === "mysql" ? "mysql://user:pass@host:3306/dbname" : "postgresql://user:pass@host:5432/dbname",
@@ -9478,18 +9855,13 @@ async function runFullDeploy(root, config, client, voidConfig, fwPreset, skipBui
9478
9855
  }
9479
9856
  }
9480
9857
  }
9481
- const rawJobs = scanJobsSync(root, paths.cronsDir);
9482
- const rawQueues = scanQueuesSync(root, paths.queuesDir);
9858
+ const rawJobs = analysis.jobs;
9859
+ const rawQueues = analysis.queues;
9483
9860
  const schedules = rawJobs.flatMap((job) => job.crons.map((cron) => ({
9484
9861
  name: job.name,
9485
9862
  cron
9486
9863
  })));
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");
9864
+ const webSockets = analysis.websockets;
9493
9865
  const liveConfig = await resolveDeployLiveBindingConfig(root, client, config.projectId, {
9494
9866
  isFrameworkMode,
9495
9867
  needsLive: effectiveBindings.needsLive,
@@ -9511,10 +9883,7 @@ async function runFullDeploy(root, config, client, voidConfig, fwPreset, skipBui
9511
9883
  const hasWebSockets = webSockets.length > 0;
9512
9884
  const ssrEntry = isFrameworkMode ? null : validateSsrEntry(root, paths);
9513
9885
  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;
9886
+ const pageScan = hasPages ? routeScan.pages : null;
9518
9887
  const revalidate = collectRevalidate(deployConfig, pageScan);
9519
9888
  if (deployConfig.output === "static" && pageScan && !isSsr) {
9520
9889
  const allPrerenderable = pageScan.pages.every((p) => p.shouldPrerender && (p.params.length === 0 && !p.catchAll || p.hasGetPrerenderPaths));
@@ -9638,7 +10007,6 @@ async function runFullDeploy(root, config, client, voidConfig, fwPreset, skipBui
9638
10007
  const workerDir = join(distDir, workerDirName);
9639
10008
  if (isFrameworkMode) {
9640
10009
  const wrapperFeatures = {
9641
- auth: null,
9642
10010
  crons: rawJobs,
9643
10011
  queues: rawQueues,
9644
10012
  hasMigrations: validatedMigrations != null && validatedMigrations.length > 0,
@@ -9680,7 +10048,7 @@ async function createProjectFromSlug(root, client, slug, cliLog) {
9680
10048
  projectId: project.id,
9681
10049
  slug: project.slug
9682
10050
  };
9683
- writeProjectConfig(root, attachSelectedPlatform(root, config, client.apiUrl));
10051
+ if (shouldLinkDeployProject(root, client.apiUrl)) writeProjectConfig(root, attachSelectedPlatform(root, config, client.apiUrl));
9684
10052
  log.success(`Project created: ${import_picocolors$1.default.blue(config.slug)}`);
9685
10053
  reportProjectCreationWarnings(project);
9686
10054
  cliLog?.info("project_create_success", {
@@ -9909,4 +10277,4 @@ function resolveWorkerDirName(distDir, fwPreset, buildCommand) {
9909
10277
  throw new Error("deploy: Worker output not found. Expected dist/ssr/ or dist/rsc/.");
9910
10278
  }
9911
10279
  //#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 };
10280
+ 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 };