void 0.21.9 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. package/README.md +5 -1
  2. package/dist/{account-cmd-DZZGK80W.mjs → account-cmd-C84Ee8cO.mjs} +5 -5
  3. package/dist/{scan-ClYmX3sa.mjs → application-analysis-BVsVqc11.mjs} +135 -18
  4. package/dist/application-routing-B7QEjqSR.mjs +27 -0
  5. package/dist/{auth-CZuiVsFh.mjs → auth-CCjt0hcq.mjs} +1 -1
  6. package/dist/{auth-DLNN0D3Z.mjs → auth-CSkdO2Bb.mjs} +4 -47
  7. package/dist/{auth-link-B_CeugBw.mjs → auth-link-CioEg6uY.mjs} +5 -5
  8. package/dist/{auth-router-BgEFRuvZ.mjs → auth-router-BsR981d4.mjs} +5 -5
  9. package/dist/{better-auth-shared-rsBGBvWJ.mjs → better-auth-shared-hy6RPh9W.mjs} +13 -2
  10. package/dist/{build-cmd-Dpt0jd-x.mjs → build-cmd-LzvNJORH.mjs} +19 -7
  11. package/dist/{cache-7_UeZdTk.mjs → cache-C4MvnrMH.mjs} +4 -4
  12. package/dist/{cancel-deploy-D23R1RXT.mjs → cancel-deploy-abUxpP2n.mjs} +4 -4
  13. package/dist/{cf-build-output-CGT03qCD.mjs → cf-build-output-BPqOT964.mjs} +141 -63
  14. package/dist/cf-build-output-_0HNWysu.mjs +2 -0
  15. package/dist/cli/cli.mjs +114 -460
  16. package/dist/cli/cloudflare-operation-process.mjs +1103 -0
  17. package/dist/cli/env-schema-probe.d.mts +2 -1
  18. package/dist/cli/env-schema-probe.mjs +3 -3
  19. package/dist/client-Cu7jWiF1.mjs +2 -0
  20. package/dist/{client-QTl6ko_D.mjs → client-RV8NVeB8.mjs} +165 -21
  21. package/dist/{cloudflare-auth-C4_GPZr0.mjs → cloudflare-auth-Qdc7tw8F.mjs} +41 -43
  22. package/dist/{cloudflare-cmd-bSmZ1d5L.mjs → cloudflare-cmd-BcrVyTmJ.mjs} +9 -10
  23. package/dist/cloudflare-config-Bktvwtpf.mjs +182 -0
  24. package/dist/{cloudflare-connect-jAwacxxP.mjs → cloudflare-connect-B8uPZ4nx.mjs} +4 -4
  25. package/dist/{cloudflare-operations-BipGMJ5O.mjs → cloudflare-operations-AiWashgg.mjs} +1 -1
  26. package/dist/{cloudflare-operations-sMZXpk_S.mjs → cloudflare-operations-fxHb-byx.mjs} +118 -203
  27. package/dist/{preset-UHj9ARyP.mjs → cloudflare-process-B-wekeR6.mjs} +93 -6
  28. package/dist/{config-CF69HgXc.d.mts → config-BMHb8RCj.d.mts} +2 -2
  29. package/dist/{config-CafTW6Cz.mjs → config-Br_JZD6u.mjs} +2 -7
  30. package/dist/config-C_XRIPx2.mjs +89 -0
  31. package/dist/{config-s7Xj7tPb.mjs → config-CyQ-wVd7.mjs} +1 -1
  32. package/dist/config-entry.d.mts +1 -1
  33. package/dist/{config-write-BSduPMY8.mjs → config-write-B1f88wJA.mjs} +1 -1
  34. package/dist/{connect-BsUSRzln.mjs → connect-Bd4kJd9U.mjs} +6 -6
  35. package/dist/{create-project-BniEV0OW.mjs → create-project-Boczwj5r.mjs} +1 -1
  36. package/dist/{create-project-DBfFSZKY.mjs → create-project-bMf6ffLZ.mjs} +9 -5
  37. package/dist/{db-hRrvZaq_.mjs → db-8uG64XLl.mjs} +26 -26
  38. package/dist/{delete-D6dZ9B6B.mjs → delete-NvbiyeJf.mjs} +4 -4
  39. package/dist/{deploy-WaAQez1O.mjs → deploy-DzAIwqNU.mjs} +944 -607
  40. package/dist/{deploy-DDz7c8LK.mjs → deploy-bjXdFCtn.mjs} +1 -1
  41. package/dist/{dist-BR1quN_w.mjs → dist-AoCzRTJE.mjs} +226 -60
  42. package/dist/{dist-C5fND3R0.mjs → dist-BuDuKZJv.mjs} +1 -1
  43. package/dist/{dist-Dn6nn2IU.mjs → dist-CTBk70IR.mjs} +42 -42
  44. package/dist/{domain-Dmhvb2oU.mjs → domain-y5Tvydvo.mjs} +5 -5
  45. package/dist/{email-DFi-s2t4.mjs → email-B3umsW75.mjs} +15 -30
  46. package/dist/{env-Csi-tMbT.mjs → env-BS6qYHDb.mjs} +6 -6
  47. package/dist/{env-public-D_6u46fX.d.mts → env-public-BX_r8HR6.d.mts} +2 -1
  48. package/dist/{env-validation-BB4GkLxn.mjs → env-validation-BPn7vk-V.mjs} +18 -71
  49. package/dist/{env-validation-BXge7uyK.mjs → env-validation-DbTg7-ar.mjs} +1 -1
  50. package/dist/{fetch-CXDChK7B.mjs → fetch-BIZJh7vR.mjs} +2 -2
  51. package/dist/{fetch-stream-AOByI7Ki.mjs → fetch-stream-IvCYKyQL.mjs} +24 -4
  52. package/dist/{gen-Dz3X1Qab.mjs → gen-B580--vC.mjs} +5 -5
  53. package/dist/gen-BVaUUumi.mjs +2 -0
  54. package/dist/{github-cmd-BED3_9JD.mjs → github-cmd-v45BdfKX.mjs} +6 -8
  55. package/dist/{handler-BXJTXd02.d.mts → handler-CZ4nAylQ.d.mts} +2 -1
  56. package/dist/{headers-BOg_velo.mjs → headers-DWi2IXWx.mjs} +1 -1
  57. package/dist/help-DofyZuY7.mjs +2 -0
  58. package/dist/{help-DC7gdz7L.mjs → help-daGKjXGk.mjs} +268 -666
  59. package/dist/index.d.mts +1 -1
  60. package/dist/index.mjs +305 -1490
  61. package/dist/info-B97bTX9N.mjs +113 -0
  62. package/dist/{init-WO0JlPx8.mjs → init-Bvy7zrBo.mjs} +51 -20
  63. package/dist/limits-Bq5LG8Id.d.mts +27 -0
  64. package/dist/limits-Cjuk2VPm.mjs +68 -0
  65. package/dist/{link-CsHOinF7.mjs → link-CDqqCjFl.mjs} +5 -5
  66. package/dist/{list-CDb-4bZ1.mjs → list-CZj0dzKY.mjs} +5 -5
  67. package/dist/{live-CKiJilLr.d.mts → live-Chw1eIMv.d.mts} +1 -1
  68. package/dist/{local-d1-CC8sKFGu.mjs → local-d1-2CMnpuW_.mjs} +2 -2
  69. package/dist/login-DYt_An22.mjs +2 -0
  70. package/dist/{login-DvqXsfGs.mjs → login-UZKFM_u7.mjs} +5 -5
  71. package/dist/{logs-BdfiOezj.mjs → logs-CUZ6t9t3.mjs} +5 -5
  72. package/dist/migrate-8_2u55MD.mjs +2 -0
  73. package/dist/{migrate-B8KuoYsO.mjs → migrate-DHul7PRV.mjs} +5 -4
  74. package/dist/{node-BM43oz4G.mjs → node-BkyRWRx8.mjs} +2 -2
  75. package/dist/operator-args-CLgKGlwU.mjs +690 -0
  76. package/dist/{operator-cmd-03819ATr.mjs → operator-cmd-DBA6dl0m.mjs} +69 -23
  77. package/dist/output-Dm_A4Tbv.mjs +81 -0
  78. package/dist/pages/client.d.mts +34 -2
  79. package/dist/pages/client.mjs +59 -3
  80. package/dist/pages/index.d.mts +1 -1
  81. package/dist/pages/index.mjs +3 -3
  82. package/dist/pages/islands-plugin.d.mts +16 -6
  83. package/dist/pages/islands-plugin.mjs +2 -2
  84. package/dist/pages/protocol.d.mts +2 -2
  85. package/dist/pages/protocol.mjs +2 -308
  86. package/dist/{parse-filename-DioPHiR9.mjs → parse-filename-CUbj-1MP.mjs} +35 -1
  87. package/dist/{output-CCH48AMM.mjs → picocolors-BTps1_gs.mjs} +2 -76
  88. package/dist/plan-D1Q5rf-r.mjs +2 -0
  89. package/dist/plan-NPpwZ_kc.mjs +58 -0
  90. package/dist/platform-args-BJdRtlLq.mjs +506 -0
  91. package/dist/platform-args-D3RXyR6h.mjs +2 -0
  92. package/dist/{platform-auth-config-z92h63q5.mjs → platform-auth-config-B56E9YP1.mjs} +4 -4
  93. package/dist/{platform-auth-protection-DeE3yr2R.mjs → platform-auth-protection-BDWO_aER.mjs} +3 -3
  94. package/dist/{platform-auth-recovery-o_340Ixx.mjs → platform-auth-recovery-1gg4CSRe.mjs} +4 -4
  95. package/dist/{platform-cmd-C4ZV3Vpy.mjs → platform-cmd-Bt-1w6pY.mjs} +1 -1
  96. package/dist/{platform-cmd-DNl8WosH.mjs → platform-cmd-DMcQStSc.mjs} +26 -5
  97. package/dist/{platform-domain-CU1JtJkW.mjs → platform-domain-BNkcz0OB.mjs} +36 -9
  98. package/dist/{platform-lifecycle-Bc047IeT.mjs → platform-lifecycle-9OyBALhH.mjs} +850 -276
  99. package/dist/{platform-lifecycle-Bs2qx1E3.mjs → platform-lifecycle-BE6C_jxh.mjs} +1 -1
  100. package/dist/{platform-management-CVpGI9Y5.mjs → platform-management-CjwLVQwN.mjs} +59 -11
  101. package/dist/{platform-management-C8tt6D8h.mjs → platform-management-CnyTdcWX.mjs} +1 -1
  102. package/dist/platform-plans-config-BNGKGr4P.mjs +359 -0
  103. package/dist/{platform-recovery-dvUaptLr.mjs → platform-recovery-D6KSpuFm.mjs} +2 -2
  104. package/dist/{plugin-inference-BMfKRSqE.mjs → plugin-inference-CXWnn79A.mjs} +174 -64
  105. package/dist/{prepare-DyZ-Yok5.mjs → prepare-B5Mkic5u.mjs} +3 -3
  106. package/dist/{prepare-C3kt3Rst.mjs → prepare-DOsL0CC9.mjs} +4 -20
  107. package/dist/prepare-cgvDMtSb.mjs +2 -0
  108. package/dist/{project-cmd-B44I8J_W.mjs → project-cmd-CNzrqvBv.mjs} +30 -16
  109. package/dist/{project-team-BkbYIsXN.mjs → project-team-DQOWPfQT.mjs} +4 -4
  110. package/dist/{project-token-C8xEEQnB.mjs → project-token-CTDYU0Cc.mjs} +4 -4
  111. package/dist/project-zero-trust-BNAkW-_0.mjs +63 -0
  112. package/dist/{protocol-ZH3jP4a7.d.mts → protocol-CjF_iI9X.d.mts} +2 -2
  113. package/dist/protocol-U7bfjHmA.mjs +331 -0
  114. package/dist/{provision-DNtrtaVD.mjs → provision-C4ORE7G7.mjs} +1 -1
  115. package/dist/{provision-BhreDAOS.mjs → provision-CJFgTZY8.mjs} +111 -215
  116. package/dist/{requests-DFyhBMaf.mjs → requests-MhxYnau8.mjs} +4 -4
  117. package/dist/resource-name-C7LVpcRm.mjs +11 -0
  118. package/dist/{rollback-DHHxXZiS.mjs → rollback-CJ6iSDoU.mjs} +5 -5
  119. package/dist/route-url-CG7U-cRN.mjs +15 -0
  120. package/dist/{runner-B8wXwWlo.mjs → runner-CXA9Fh8h.mjs} +1 -1
  121. package/dist/{runner-p-dMs2UN.mjs → runner-Ol0TNjk6.mjs} +2 -2
  122. package/dist/runtime/ai.d.mts +12 -8
  123. package/dist/runtime/ai.mjs +84 -17
  124. package/dist/runtime/better-auth-mysql.mjs +1 -1
  125. package/dist/runtime/better-auth-pg.mjs +1 -1
  126. package/dist/runtime/better-auth.mjs +1 -1
  127. package/dist/runtime/client-react.mjs +2 -2
  128. package/dist/runtime/client-solid.mjs +2 -2
  129. package/dist/runtime/client-svelte.mjs +2 -2
  130. package/dist/runtime/client-vue.mjs +2 -2
  131. package/dist/runtime/client.mjs +2 -2
  132. package/dist/runtime/durable.d.mts +3 -1
  133. package/dist/runtime/durable.mjs +4 -1
  134. package/dist/runtime/email/testing.mjs +1 -1
  135. package/dist/runtime/env-public.d.mts +1 -1
  136. package/dist/runtime/fetch-stream.mjs +1 -1
  137. package/dist/runtime/fetch.mjs +1 -1
  138. package/dist/runtime/handler.d.mts +1 -1
  139. package/dist/runtime/kv.mjs +0 -1
  140. package/dist/runtime/limits.d.mts +2 -0
  141. package/dist/runtime/limits.mjs +2 -0
  142. package/dist/runtime/live-client.d.mts +1 -1
  143. package/dist/runtime/live-server.mjs +25 -16
  144. package/dist/runtime/live.d.mts +1 -1
  145. package/dist/runtime/migration-handler.mjs +62 -42
  146. package/dist/runtime/route-url.d.mts +4 -0
  147. package/dist/runtime/route-url.mjs +2 -0
  148. package/dist/runtime/routing.d.mts +180 -0
  149. package/dist/runtime/routing.mjs +1082 -0
  150. package/dist/runtime/sandbox-container.d.mts +3 -0
  151. package/dist/runtime/sandbox-container.mjs +2 -0
  152. package/dist/runtime/sandbox.d.mts +4 -32
  153. package/dist/runtime/sandbox.mjs +151 -75
  154. package/dist/runtime/sse.mjs +1 -1
  155. package/dist/runtime/validator.d.mts +1 -1
  156. package/dist/runtime/ws-server.d.mts +4 -2
  157. package/dist/runtime/ws-server.mjs +27 -2
  158. package/dist/runtime/ws.d.mts +2 -2
  159. package/dist/runtime/ws.mjs +8 -6
  160. package/dist/sandbox-XZAqzFlG.d.mts +52 -0
  161. package/dist/sandbox-container-Bo0eiFKz.d.mts +73 -0
  162. package/dist/sandbox-container-C6ItmVuN.mjs +281 -0
  163. package/dist/{scan-4tfN-PSn.mjs → scan-C7okrLyM.mjs} +4 -35
  164. package/dist/{secret-DN9sSNiV.mjs → secret-Dli5fP0B.mjs} +6 -6
  165. package/dist/{skills-O6FUaizK.mjs → skills-D1II1Juz.mjs} +1 -1
  166. package/dist/{sse-BaC1jXko.mjs → sse-CQNaDFFV.mjs} +6 -3
  167. package/dist/{subcommand-prompt-BuGYkAkC.mjs → subcommand-prompt-CY1C4fvl.mjs} +2 -2
  168. package/dist/validate-Dq_L3s0S.mjs +2 -0
  169. package/dist/{validate-CIUwFpjB.mjs → validate-ctOrgiS3.mjs} +2 -1
  170. package/dist/{wrangler-BymcxrRa.mjs → wrangler-7K-bW_DL.mjs} +14 -235
  171. package/dist/{ws-BwcqizuH.d.mts → ws-CL1w7GXU.d.mts} +13 -2
  172. package/package.json +48 -33
  173. package/sandbox.Dockerfile +4 -0
  174. package/schema.json +10 -22
  175. package/skills/migrate-vite-cloudflare-to-void/SKILL.md +34 -157
  176. package/skills/void/SKILL.md +50 -133
  177. package/skills/void/docs/guide/ai.md +94 -84
  178. package/skills/void/docs/guide/app-types.md +3 -32
  179. package/skills/void/docs/guide/auth.md +12 -116
  180. package/skills/void/docs/guide/database/d1.md +9 -54
  181. package/skills/void/docs/guide/database/mysql.md +1 -1
  182. package/skills/void/docs/guide/database/postgresql.md +5 -26
  183. package/skills/void/docs/guide/database.md +23 -75
  184. package/skills/void/docs/guide/deployment.md +27 -113
  185. package/skills/void/docs/guide/durable-state.md +43 -18
  186. package/skills/void/docs/guide/edge/headers.md +3 -47
  187. package/skills/void/docs/guide/edge/prerendering.md +5 -20
  188. package/skills/void/docs/guide/edge/redirects.md +11 -64
  189. package/skills/void/docs/guide/edge/revalidation.md +6 -19
  190. package/skills/void/docs/guide/edge/rewrites.md +56 -284
  191. package/skills/void/docs/guide/edge/static-assets.md +23 -72
  192. package/skills/void/docs/guide/email/domains.md +112 -0
  193. package/skills/void/docs/guide/email/receiving.md +139 -0
  194. package/skills/void/docs/guide/email/sending.md +231 -0
  195. package/skills/void/docs/guide/email.md +13 -619
  196. package/skills/void/docs/guide/env-migration.md +11 -11
  197. package/skills/void/docs/guide/env-vars.md +9 -29
  198. package/skills/void/docs/guide/index.md +0 -15
  199. package/skills/void/docs/guide/jobs.md +3 -18
  200. package/skills/void/docs/guide/kv.md +5 -11
  201. package/skills/void/docs/guide/live.md +5 -56
  202. package/skills/void/docs/guide/pages-routing/actions-and-forms.md +78 -125
  203. package/skills/void/docs/guide/pages-routing/head.md +10 -10
  204. package/skills/void/docs/guide/pages-routing/islands.md +6 -36
  205. package/skills/void/docs/guide/pages-routing/layouts.md +6 -128
  206. package/skills/void/docs/guide/pages-routing/loaders.md +3 -19
  207. package/skills/void/docs/guide/pages-routing/markdown.md +13 -171
  208. package/skills/void/docs/guide/pages-routing/overview.md +7 -17
  209. package/skills/void/docs/guide/pages-routing/view-transitions.md +1 -1
  210. package/skills/void/docs/guide/platform/administration/access.md +1 -4
  211. package/skills/void/docs/guide/platform/administration/email.md +35 -8
  212. package/skills/void/docs/guide/platform/administration/operations.md +26 -4
  213. package/skills/void/docs/guide/platform/administration/plans.md +126 -0
  214. package/skills/void/docs/guide/platform/administration/projects.md +5 -2
  215. package/skills/void/docs/guide/platform/administration/zero-trust.md +189 -0
  216. package/skills/void/docs/guide/platform/development/local.md +2 -2
  217. package/skills/void/docs/guide/platform/development/runtime.md +3 -13
  218. package/skills/void/docs/guide/platform/development/schema-ci.md +0 -58
  219. package/skills/void/docs/guide/platform/installation/credentials.md +6 -4
  220. package/skills/void/docs/guide/platform/installation/domains.md +31 -3
  221. package/skills/void/docs/guide/platform/installation/first-deployment.md +6 -0
  222. package/skills/void/docs/guide/platform/installation/maintenance.md +3 -1
  223. package/skills/void/docs/guide/platform/installation/prerequisites.md +21 -16
  224. package/skills/void/docs/guide/platform/installation/setup.md +9 -5
  225. package/skills/void/docs/guide/platform/installation/uninstall.md +17 -2
  226. package/skills/void/docs/guide/platform-administration.md +2 -0
  227. package/skills/void/docs/guide/queues.md +7 -9
  228. package/skills/void/docs/guide/quickstart.md +38 -37
  229. package/skills/void/docs/guide/remote-dev.md +4 -9
  230. package/skills/void/docs/guide/sandboxes.md +78 -41
  231. package/skills/void/docs/guide/server-routing.md +9 -72
  232. package/skills/void/docs/guide/sse.md +4 -18
  233. package/skills/void/docs/guide/ssg.md +3 -15
  234. package/skills/void/docs/guide/ssr.md +14 -62
  235. package/skills/void/docs/guide/storage.md +9 -4
  236. package/skills/void/docs/guide/type-safety.md +3 -14
  237. package/skills/void/docs/guide/typed-fetch.md +3 -7
  238. package/skills/void/docs/guide/websockets.md +68 -40
  239. package/skills/void/docs/integrations/agents.md +3 -3
  240. package/skills/void/docs/integrations/cloudflare.md +85 -316
  241. package/skills/void/docs/integrations/frameworks/analog.md +5 -64
  242. package/skills/void/docs/integrations/frameworks/astro.md +4 -73
  243. package/skills/void/docs/integrations/frameworks/nuxt.md +5 -62
  244. package/skills/void/docs/integrations/frameworks/overview.md +11 -54
  245. package/skills/void/docs/integrations/frameworks/react-router.md +5 -60
  246. package/skills/void/docs/integrations/frameworks/sveltekit.md +6 -65
  247. package/skills/void/docs/integrations/frameworks/tanstack-start.md +4 -62
  248. package/skills/void/docs/integrations/nodejs-bun-deno.md +5 -69
  249. package/skills/void/docs/reference/api/auth.md +156 -0
  250. package/skills/void/docs/reference/api/client.md +87 -0
  251. package/skills/void/docs/reference/api/database.md +95 -0
  252. package/skills/void/docs/reference/api/durable.md +46 -0
  253. package/skills/void/docs/reference/api/env.md +50 -0
  254. package/skills/void/docs/reference/api/handlers.md +254 -0
  255. package/skills/void/docs/reference/api/pages.md +241 -0
  256. package/skills/void/docs/reference/api/plugin.md +39 -0
  257. package/skills/void/docs/reference/api/resources.md +109 -0
  258. package/skills/void/docs/reference/api/rewrites.md +76 -0
  259. package/skills/void/docs/reference/api/types.md +92 -0
  260. package/skills/void/docs/reference/api.md +56 -1218
  261. package/skills/void/docs/reference/cli/auth.md +88 -0
  262. package/skills/void/docs/reference/cli/database.md +128 -0
  263. package/skills/void/docs/reference/cli/deploy.md +85 -0
  264. package/skills/void/docs/reference/cli/domains.md +41 -0
  265. package/skills/void/docs/reference/cli/email.md +129 -0
  266. package/skills/void/docs/reference/cli/generate.md +116 -0
  267. package/skills/void/docs/reference/cli/github.md +189 -0
  268. package/skills/void/docs/reference/cli/platform-config.md +92 -0
  269. package/skills/void/docs/reference/cli/platform-email.md +81 -0
  270. package/skills/void/docs/reference/cli/platform-installation.md +127 -0
  271. package/skills/void/docs/reference/cli/platform-operations.md +90 -0
  272. package/skills/void/docs/reference/cli/platform-users.md +89 -0
  273. package/skills/void/docs/reference/cli/platform-zero-trust.md +45 -0
  274. package/skills/void/docs/reference/cli/platform.md +70 -0
  275. package/skills/void/docs/reference/cli/project.md +214 -0
  276. package/skills/void/docs/reference/cli/secrets.md +76 -0
  277. package/skills/void/docs/reference/cli/setup.md +70 -0
  278. package/skills/void/docs/reference/cli.md +33 -1621
  279. package/skills/void/docs/reference/config.md +26 -32
  280. package/skills/void/docs/reference/resource-inference.md +3 -58
  281. package/skills/void/docs/reference/structure.md +14 -41
  282. package/dist/canonical-json-DuDiiUsQ.mjs +0 -13
  283. package/dist/cli/cf-compat.mjs +0 -968
  284. package/dist/client-4cDVv7BO.mjs +0 -2
  285. package/dist/gen-Vnv2f65C.mjs +0 -2
  286. package/dist/help-DQMfeKMz.mjs +0 -2
  287. package/dist/login-DFQk7rbW.mjs +0 -2
  288. package/dist/migrate-TsHGBnDA.mjs +0 -2
  289. package/dist/plan-BEZ8VJW0.mjs +0 -256
  290. package/dist/plan-DpuOr14e.mjs +0 -2
  291. package/dist/prepare-BZXkjdNe.mjs +0 -2
  292. package/dist/validate-EKmJWxmy.mjs +0 -2
  293. /package/dist/cli/{cf-compat.d.mts → cloudflare-operation-process.d.mts} +0 -0
@@ -1,20 +1,21 @@
1
1
  import { n as __toESM } from "./rolldown-runtime-DXIUcv95.mjs";
2
2
  import { a as join, n as dirname, u as resolve } from "./dist-BVqXeR_h.mjs";
3
- import { c as import_picocolors$1, d as require_picocolors, i as createSpinner, n as cliCommandName, r as cliTitle } from "./output-CCH48AMM.mjs";
3
+ import { t as require_picocolors } from "./picocolors-BTps1_gs.mjs";
4
4
  import { t as cfAccessHeaders } from "./cf-access-DRsQRe6k.mjs";
5
- import { d as openBrowser } from "./auth-DLNN0D3Z.mjs";
5
+ import { d as openBrowser } from "./auth-CSkdO2Bb.mjs";
6
6
  import { a as listInstallationManifests, d as readInstallationRecoverySecrets, f as readPendingInstallationSecrets, g as savePlatformConnection, h as resolveInstallationManifest, i as latestPlatformWorkerCutoverGeneration, l as platformWorkerSecretUpdateIntentState, n as assertPlatformInstallationSlugAvailable, s as normalizePlatformApiUrl, t as acquirePlatformLifecycleLock, u as promoteInstallationSecrets, v as writeInstallationManifest, y as writePendingInstallationSecrets } from "./platform-registry-QBa4DK32.mjs";
7
7
  import { n as ensurePlatformAccessToken } from "./cf-access-P0iZnu0q.mjs";
8
- import { B as text$1, E as confirm, F as password, G as settings, I as select, K as u$1, M as multiselect, N as note, O as intro, P as outro, R as symbol, U as CANCEL_SYMBOL, W as isCancel, g as S_PASSWORD_MASK, i as S_BAR_END, j as log, r as S_BAR } from "./dist-Dn6nn2IU.mjs";
9
- import { o as resolveWranglerArgv } from "./preset-UHj9ARyP.mjs";
10
- import { c as runCloudflareAuthCommand, n as announceCloudflareLogin } from "./cloudflare-auth-C4_GPZr0.mjs";
11
- import { _ as resolvePinnedApiBaseUrl, o as createDefaultRunWrangler } from "./provision-BhreDAOS.mjs";
8
+ import { B as text$1, E as confirm, F as password, G as settings, I as select, K as u$1, M as multiselect, N as note, O as intro, P as outro, R as symbol, U as CANCEL_SYMBOL, W as isCancel, g as S_PASSWORD_MASK, i as S_BAR_END, j as log, r as S_BAR } from "./dist-CTBk70IR.mjs";
9
+ import { c as resolveCloudflareOperationArgv } from "./cloudflare-process-B-wekeR6.mjs";
10
+ import { o as runCloudflareAuthCommand } from "./cloudflare-auth-Qdc7tw8F.mjs";
11
+ import { _ as resolvePinnedApiBaseUrl, o as createDefaultRunWrangler } from "./provision-CJFgTZY8.mjs";
12
+ import { c as import_picocolors$1, i as createSpinner, n as cliCommandName, r as cliTitle } from "./output-Dm_A4Tbv.mjs";
12
13
  import { copyFileSync, cpSync, existsSync, lstatSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
13
14
  import { fileURLToPath } from "node:url";
14
15
  import { createHash, createHmac, randomBytes, randomUUID } from "node:crypto";
15
16
  import { tmpdir } from "node:os";
16
17
  import { execFile } from "node:child_process";
17
- import { PLATFORM_AUTH_SETUP_PROOF_PREFIX, PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE, PLATFORM_LOGIN_KINDS, PLATFORM_R2_OWNERSHIP_MARKER_KEY, PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY, PLATFORM_SANDBOX_DRAIN_KEY, createApplicationRoutes, createPlatformDeploymentManifest, createPlatformInstallationManifest, createPlatformResourceNames, createPlatformResourceOwnershipMarker, createPlatformWorkerConfigs, normalizeApplicationDomain, normalizeInstallationSlug, parsePlatformAccessProtection, parsePlatformAdmissionPolicy, parsePlatformAuthenticationSetup, parsePlatformInstallationManifest, parsePlatformLoginConfiguration, parsePlatformResourceOwnershipMarker, recordInstallationResource, recordInstallationStep, setInstallationDesiredState, setInstallationEndpoints, transitionInstallation } from "@void/platform";
18
+ import { PLATFORM_AUTH_SETUP_PROOF_PREFIX, PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE, PLATFORM_HEALTH_HOSTNAME_LABEL, PLATFORM_LOGIN_KINDS, PLATFORM_R2_OWNERSHIP_MARKER_KEY, PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY, createApplicationRoutes, createPlatformDeploymentManifest, createPlatformInstallationManifest, createPlatformResourceNames, createPlatformResourceOwnershipMarker, createPlatformWorkerConfigs, normalizeApplicationDomain, normalizeInstallationSlug, normalizePlatformDashboardUrl, parsePlatformAccessProtection, parsePlatformAdmissionPolicy, parsePlatformAuthenticationSetup, parsePlatformInstallationManifest, parsePlatformLoginConfiguration, parsePlatformResourceOwnershipMarker, platformDashboardUrl, recordInstallationResource, recordInstallationStep, setInstallationDesiredState, setInstallationEndpoints, transitionInstallation } from "@void/platform";
18
19
  import { stripVTControlCharacters } from "node:util";
19
20
  import { encodeCloudflareVersionMessage, parseCloudflareVersionMessage, readCloudflareTriggerConfig, readCloudflareVersionMigrationTag, stabilizeCloudflareVersionTriggers } from "@void/deploy-cloudflare";
20
21
  import { readFile } from "node:fs/promises";
@@ -652,7 +653,7 @@ function parsePostgresConnectionString(value) {
652
653
  let mtls;
653
654
  if (sslmode) {
654
655
  if (sslmode !== "require" && sslmode !== "verify-ca" && sslmode !== "verify-full") throw new Error("platform: Unsupported sslmode. Cloudflare Hyperdrive supports \"require\", \"verify-ca\", or \"verify-full\".");
655
- mtls = { sslmode };
656
+ mtls = { sslmode: "require" };
656
657
  }
657
658
  return {
658
659
  database,
@@ -716,6 +717,14 @@ function text(value, name) {
716
717
  if (typeof value !== "string" || !value || value.length > 8192) throw new Error(`platform: Cloudflare Access did not return ${name}.`);
717
718
  return value;
718
719
  }
720
+ function platformAccessIdentityProvider(provider) {
721
+ const id = text(provider.id, "identity provider ID");
722
+ const name = provider.name;
723
+ return {
724
+ id,
725
+ name: name == null || typeof name === "string" && !name.trim() ? id : text(name, "identity provider name")
726
+ };
727
+ }
719
728
  function ids(value) {
720
729
  if (!Array.isArray(value)) return [];
721
730
  return value.map((entry) => typeof entry === "string" ? entry : text(object(entry, "policy").id, "policy ID"));
@@ -756,6 +765,11 @@ var PlatformAccessClient = class {
756
765
  throw new Error("platform: Access inventory exceeded the supported pagination limit.");
757
766
  }
758
767
  };
768
+ /**
769
+ * Project Zero Trust keeps its own, broader copy of this list in
770
+ * platform/packages/api/src/zero-trust/cloudflare.ts; review both when adding
771
+ * a rule type.
772
+ */
759
773
  function identityRule(rule) {
760
774
  if (rule.email_domain) {
761
775
  const domain = object(rule.email_domain, "email domain").domain;
@@ -840,10 +854,7 @@ async function inspectPlatformAccess(client, input) {
840
854
  audience: "validation"
841
855
  }]
842
856
  });
843
- const identityProviders = (await client.list("identity_providers")).map((provider) => ({
844
- id: text(provider.id, "identity provider ID"),
845
- name: text(provider.name ?? provider.id, "identity provider name")
846
- }));
857
+ const identityProviders = (await client.list("identity_providers")).map(platformAccessIdentityProvider);
847
858
  if (!input.identityProviderIds.length || !input.policyIds.length || input.identityProviderIds.length > 16 || input.policyIds.length > 16 || new Set(input.identityProviderIds).size !== input.identityProviderIds.length || new Set(input.policyIds).size !== input.policyIds.length) throw new Error("platform: Select explicit identity providers and company policies for Access.");
848
859
  if (input.identityProviderIds.some((id) => !identityProviders.some((provider) => provider.id === id))) throw new Error("platform: A selected Access identity provider is unavailable in this account.");
849
860
  const policies = await Promise.all(input.policyIds.map(async (id) => object(await client.get(`policies/${encodeURIComponent(id)}`), "Access policy")));
@@ -1271,9 +1282,9 @@ async function preparePlatformAccessSetup(input) {
1271
1282
  const identityProviderIds = spec.identityProviderIds ?? (process.stdin.isTTY ? answer$1(await multiselect({
1272
1283
  message: "Company identity providers:",
1273
1284
  required: true,
1274
- options: providers.filter((provider) => typeof provider.id === "string").map((provider) => ({
1275
- value: String(provider.id),
1276
- label: stripVTControlCharacters(String(provider.name ?? provider.id))
1285
+ options: providers.filter((provider) => typeof provider.id === "string").map(platformAccessIdentityProvider).map((provider) => ({
1286
+ value: provider.id,
1287
+ label: stripVTControlCharacters(provider.name)
1277
1288
  }))
1278
1289
  })) : []);
1279
1290
  const policyIds = spec.policyIds ?? (process.stdin.isTTY ? answer$1(await multiselect({
@@ -1411,10 +1422,9 @@ async function resolvePlatformCloudflareCredentialWithInput(readInput, dependenc
1411
1422
  input,
1412
1423
  credential: result.credential
1413
1424
  };
1414
- announceCloudflareLogin();
1415
- const login = await runWrangler(["login"], false);
1425
+ const login = await runWrangler({ type: "auth.login" }, false);
1416
1426
  assertNotCancelled();
1417
- if (login.status !== 0) throw new Error("platform: Cloudflare browser login did not complete.");
1427
+ if (login.status !== 0 || login.signal || login.error) throw new Error("platform: Cloudflare browser login did not complete.");
1418
1428
  const credential = await readWranglerOAuthCredential(runWrangler);
1419
1429
  assertNotCancelled();
1420
1430
  if (!credential) throw new Error("platform: Cloudflare sign-in completed without a usable OAuth credential.");
@@ -1437,15 +1447,11 @@ async function readExistingCredential(dependencies, runWrangler) {
1437
1447
  return await readWranglerOAuthCredential(runWrangler);
1438
1448
  }
1439
1449
  async function readWranglerOAuthCredential(runWrangler = (args) => runCloudflareAuthCommand(process.cwd(), args)) {
1440
- const result = await runWrangler([
1441
- "auth",
1442
- "token",
1443
- "--json"
1444
- ], true);
1445
- if (result.status !== 0) return null;
1450
+ const result = await runWrangler({ type: "auth.token" }, true);
1451
+ if (result.status !== 0 || result.signal || result.error) return null;
1446
1452
  try {
1447
- const parsed = JSON.parse(result.stdout);
1448
- if (parsed.type !== "oauth" || typeof parsed.token !== "string" || !parsed.token.trim()) return null;
1453
+ const parsed = result.data;
1454
+ if (!parsed || parsed.type !== "oauth" || typeof parsed.token !== "string" || !parsed.token.trim()) return null;
1449
1455
  return {
1450
1456
  source: "oauth",
1451
1457
  token: parsed.token.trim()
@@ -1485,6 +1491,7 @@ async function verifyInstalledPlatformAccessProtection(input) {
1485
1491
  if ([
1486
1492
  input.plan.apiUrl,
1487
1493
  input.plan.proxyUrl,
1494
+ ...input.dashboardUrl ? [input.dashboardUrl] : [],
1488
1495
  ...input.manifest.endpoints.dashboardUrl ? [input.manifest.endpoints.dashboardUrl] : []
1489
1496
  ].map((value) => new URL(value).origin).some((origin) => !origins.includes(origin)) || protection.applications.some((entry) => entry.audience !== app.aud || !platformAccessPublicDestinations(app).includes(new URL(entry.origin).hostname))) throw new Error("platform: Access audiences and complete origin coverage must match the active platform protection. Restore the current Access checkpoint or repair its applications.");
1490
1497
  for (const other of await access.list("apps")) {
@@ -1500,7 +1507,7 @@ async function verifyInstalledPlatformAccessProtection(input) {
1500
1507
  }
1501
1508
  const existingWorkers = input.plan.actions.filter((action) => action.kind === "worker" && action.action === "update").map((action) => action.name);
1502
1509
  if (existingWorkers.length) {
1503
- const { verifyProtectionWorkerOrigins } = await import("./platform-auth-protection-DeE3yr2R.mjs");
1510
+ const { verifyProtectionWorkerOrigins } = await import("./platform-auth-protection-BDWO_aER.mjs");
1504
1511
  await verifyProtectionWorkerOrigins({
1505
1512
  request: input.client.request.bind(input.client),
1506
1513
  listWorkerDomains: input.client.listWorkerDomains.bind(input.client),
@@ -1593,6 +1600,7 @@ async function verifyPlatformAuthenticationDatabase(client, accountId, resource,
1593
1600
  //#endregion
1594
1601
  //#region src/cli/platform-plan.ts
1595
1602
  async function createPlatformInstallPlan(client, input, existing, options) {
1603
+ const dashboardUrl = input.dashboardUrl !== void 0 ? normalizePlatformDashboardUrl(input.dashboardUrl) : existing ? existing.deployment.controlPlane.dashboardUrl ?? (existing.endpoints.dashboardUrl ? normalizePlatformDashboardUrl(existing.endpoints.dashboardUrl) : null) ?? platformDashboardUrl(existing.deployment) : null;
1596
1604
  const account = resolveAccount(await client.listAccounts(), input.accountId);
1597
1605
  if (existing && existing.account.id !== account.id) throw new Error(`platform: Refusing to resume installation ${existing.id} in a different Cloudflare account.`);
1598
1606
  const providedEmailSharedZoneId = input.emailSharedZoneId?.trim();
@@ -1641,7 +1649,8 @@ async function createPlatformInstallPlan(client, input, existing, options) {
1641
1649
  },
1642
1650
  controlPlane: {
1643
1651
  apiHostname: input.controlPlaneDomain || null,
1644
- dashboardHostname: null,
1652
+ dashboardHostname: dashboardUrl ? new URL(dashboardUrl).hostname : null,
1653
+ ...dashboardUrl ? { dashboardUrl } : {},
1645
1654
  proxyHostname: null
1646
1655
  },
1647
1656
  displayName: input.displayName,
@@ -1659,13 +1668,21 @@ async function createPlatformInstallPlan(client, input, existing, options) {
1659
1668
  sandbox: true
1660
1669
  }
1661
1670
  } : existingWithoutEmailZone;
1662
- const comparableExistingWithoutEmailZone = existing && deployment.capabilities.email && !existing.deployment.capabilities.email ? {
1671
+ const existingWithDashboard = {
1663
1672
  ...existingWithSandbox,
1673
+ controlPlane: {
1674
+ ...existingWithSandbox.controlPlane,
1675
+ dashboardHostname: deployment.controlPlane.dashboardHostname,
1676
+ ...deployment.controlPlane.dashboardUrl ? { dashboardUrl: deployment.controlPlane.dashboardUrl } : {}
1677
+ }
1678
+ };
1679
+ const comparableExistingWithoutEmailZone = existing && deployment.capabilities.email && !existing.deployment.capabilities.email ? {
1680
+ ...existingWithDashboard,
1664
1681
  capabilities: {
1665
- ...existingWithSandbox.capabilities,
1682
+ ...existingWithDashboard.capabilities,
1666
1683
  email: true
1667
1684
  }
1668
- } : existingWithSandbox;
1685
+ } : existingWithDashboard;
1669
1686
  if (existing && (JSON.stringify(comparableExistingWithoutEmailZone) !== JSON.stringify(deploymentWithoutEmailZone) || recordedEmailZone !== void 0 && recordedEmailZone !== configuredEmailZone || recordedSenderDomain !== void 0 && recordedSenderDomain !== configuredSenderDomain)) throw new Error(`platform: --resume inputs do not match installation ${existing.id}. Use its recorded configuration.`);
1670
1687
  const names = deployment.resources;
1671
1688
  const wildcardHostname = `*.${deployment.application.baseDomain}`;
@@ -1689,7 +1706,7 @@ async function createPlatformInstallPlan(client, input, existing, options) {
1689
1706
  }
1690
1707
  throw error;
1691
1708
  });
1692
- const [workersSubdomain, aiGateways, routingKv, isrKv, legacyKv, r2Buckets, usageQueues, cronQueues, dispatchNamespaces, hyperdrives, workers] = await Promise.all([
1709
+ const [workersSubdomain, aiGateways, routingKv, isrKv, legacyKv, r2Buckets, usageQueues, cronQueues, deploymentQueues, dispatchNamespaces, hyperdrives, workers] = await Promise.all([
1693
1710
  Promise.resolve(accountSubdomain),
1694
1711
  gatewayInventory,
1695
1712
  client.listKvNamespaces(account.id, names.routingKv),
@@ -1698,6 +1715,7 @@ async function createPlatformInstallPlan(client, input, existing, options) {
1698
1715
  client.listR2Buckets(account.id),
1699
1716
  client.listQueues(account.id, names.usageQueue),
1700
1717
  client.listQueues(account.id, names.cronQueue),
1718
+ options?.deploymentExecution ?? existing?.resources.some((resource) => resource.key === "deployment-queue") ? client.listQueues(account.id, names.deploymentQueue) : Promise.resolve([]),
1701
1719
  client.listDispatchNamespaces(account.id),
1702
1720
  client.listHyperdriveConfigs(account.id),
1703
1721
  client.listWorkerNames(account.id)
@@ -1732,11 +1750,13 @@ async function createPlatformInstallPlan(client, input, existing, options) {
1732
1750
  const expectedPatterns = new Set(createApplicationRoutes(deployment.application).map((route) => route.pattern));
1733
1751
  const expectedWorker = deployment.resources.dispatchWorker;
1734
1752
  const ownedWorker = intentProofs.has("dispatch-worker") || existing?.resources.some((resource) => resource.key === "dispatch-worker" && resource.kind === "worker" && resource.name === expectedWorker && resource.state === "ready");
1735
- const conflicts = (await client.listWorkerRoutes(zone.id)).filter((route) => expectedPatterns.has(route.pattern) && (route.script !== expectedWorker || !ownedWorker));
1753
+ const [workerRoutes, workerDomains] = await Promise.all([client.listWorkerRoutes(zone.id), client.listWorkerDomains(account.id)]);
1754
+ const conflicts = workerRoutes.filter((route) => expectedPatterns.has(route.pattern) && (route.script !== expectedWorker || !ownedWorker));
1736
1755
  if (conflicts.length > 0) {
1737
1756
  const conflict = conflicts[0];
1738
1757
  throw new Error(`platform: Application route ${JSON.stringify(conflict.pattern)} is already assigned to ${JSON.stringify(conflict.script ?? "no Worker")}. Choose another application domain.`);
1739
1758
  }
1759
+ assertApplicationRoutesPreserveWorkerDomains([...expectedPatterns], expectedWorker, ownedWorker === true, zone.name, workerRoutes, workerDomains);
1740
1760
  }
1741
1761
  if (deployment.controlPlane.apiHostname) {
1742
1762
  const apiHostname = deployment.controlPlane.apiHostname;
@@ -1779,6 +1799,7 @@ async function createPlatformInstallPlan(client, input, existing, options) {
1779
1799
  addNamedPlan(actions, existing, "assets-bucket", "r2-bucket", names.assetsBucket, r2Buckets.filter((resource) => resource.name === names.assetsBucket), "reuse", options, remoteOwnership, intentProofs, ambiguousResources);
1780
1800
  addNamedPlan(actions, existing, "usage-queue", "queue", names.usageQueue, usageQueues, "reuse", options, remoteOwnership);
1781
1801
  addNamedPlan(actions, existing, "cron-queue", "queue", names.cronQueue, cronQueues, "reuse", options, remoteOwnership);
1802
+ if (options?.deploymentExecution ?? existing?.resources.some((resource) => resource.key === "deployment-queue")) addNamedPlan(actions, existing, "deployment-queue", "queue", names.deploymentQueue, deploymentQueues, "reuse", options, remoteOwnership);
1782
1803
  addNamedPlan(actions, existing, "dispatch-namespace", "dispatch-namespace", names.dispatchNamespace, dispatchNamespaces.filter((resource) => resource.name === names.dispatchNamespace), "reuse", options, remoteOwnership);
1783
1804
  const recordedHyperdrive = existing?.resources.find((resource) => resource.key === "hyperdrive");
1784
1805
  const externalHyperdriveId = input.externalHyperdrive?.id || (recordedHyperdrive?.ownership === "adopted" ? recordedHyperdrive.id : "");
@@ -1831,6 +1852,18 @@ async function createPlatformInstallPlan(client, input, existing, options) {
1831
1852
  };
1832
1853
  }
1833
1854
  async function revalidatePlatformInstallAction(client, original, manifest, action, options) {
1855
+ if (options?.stableWorkerUpdate && !options.repairMissing && action.kind === "worker" && action.action === "update" && original.actions.some((entry) => entry.key === action.key && entry.kind === action.kind && entry.action === action.action && entry.id === action.id && entry.name === action.name) && original.actions.every((entry) => {
1856
+ const resource = manifest.resources.find((candidate) => candidate.key === entry.key);
1857
+ return (entry.kind === "worker" ? entry.action === "update" : entry.action === "reuse") && resource?.kind === entry.kind && resource.state === "ready" && resource.id === entry.id && resource.name === entry.name;
1858
+ })) {
1859
+ const recorded = manifest.resources.find((resource) => resource.key === action.key);
1860
+ if (!recorded || recorded.kind !== "worker" || recorded.state !== "ready" || recorded.id !== action.name || recorded.name !== action.name) throw new Error(`platform: Worker ${JSON.stringify(action.name)} no longer matches the approved installation plan. Review a new plan before retrying.`);
1861
+ if (!matchesResourceMarker((await client.getWorkerPlainTextBindings(original.account.id, action.name)).PLATFORM_OWNERSHIP_MARKER, manifest, action.key, "worker", {
1862
+ id: action.name,
1863
+ name: action.name
1864
+ })) throw new Error(`platform: Cloudflare Worker ${JSON.stringify(action.name)} no longer has this installation's ownership marker. Review a new plan before retrying.`);
1865
+ return;
1866
+ }
1834
1867
  const deployment = original.deployment;
1835
1868
  const current = (await createPlatformInstallPlan(client, {
1836
1869
  accountId: original.account.id,
@@ -1844,7 +1877,8 @@ async function revalidatePlatformInstallAction(client, original, manifest, actio
1844
1877
  }, manifest, {
1845
1878
  ...options,
1846
1879
  includeEmailGateway: original.actions.some((entry) => entry.key === "email-gateway-worker"),
1847
- enableSandbox: original.deployment.capabilities.sandbox
1880
+ enableSandbox: original.deployment.capabilities.sandbox,
1881
+ deploymentExecution: original.actions.some((entry) => entry.key === "deployment-queue")
1848
1882
  })).actions.find((entry) => entry.key === action.key);
1849
1883
  if (!current || current.kind !== action.kind || current.name !== action.name || current.action !== action.action) throw new Error(`platform: Cloudflare changed after the installation plan was approved for ${action.kind} ${JSON.stringify(action.name)}. Review a new plan before retrying.`);
1850
1884
  if (action.kind === "worker" && action.action === "update") {
@@ -2038,6 +2072,7 @@ const RESOURCE_NAME_KEY_BY_ACTION = {
2038
2072
  "api-worker": "apiWorker",
2039
2073
  "assets-bucket": "assetsBucket",
2040
2074
  "cron-queue": "cronQueue",
2075
+ "deployment-queue": "deploymentQueue",
2041
2076
  "dispatch-namespace": "dispatchNamespace",
2042
2077
  "dispatch-worker": "dispatchWorker",
2043
2078
  hyperdrive: "hyperdrive",
@@ -2049,6 +2084,33 @@ const RESOURCE_NAME_KEY_BY_ACTION = {
2049
2084
  "tail-worker": "tailWorker",
2050
2085
  "usage-queue": "usageQueue"
2051
2086
  };
2087
+ function assertApplicationRoutesPreserveWorkerDomains(patterns, dispatchWorker, ownedDispatchWorker, zoneName, workerRoutes, workerDomains) {
2088
+ const plannedRoutes = patterns.map(parseWorkerRoutePattern);
2089
+ const existingRoutes = workerRoutes.map((route) => ({
2090
+ ...parseWorkerRoutePattern(route.pattern),
2091
+ script: route.script
2092
+ }));
2093
+ for (const domain of workerDomains) {
2094
+ const hostname = domain.hostname.toLowerCase();
2095
+ if (!(domain.zoneName ? domain.zoneName.toLowerCase() === zoneName.toLowerCase() : hostname === zoneName.toLowerCase() || hostname.endsWith(`.${zoneName.toLowerCase()}`)) || domain.service === dispatchWorker && ownedDispatchWorker) continue;
2096
+ for (const planned of plannedRoutes) {
2097
+ if (!workerRouteHostMatches(planned.host, hostname)) continue;
2098
+ if (!["http", "https"].every((scheme) => existingRoutes.some((route) => route.script !== dispatchWorker && route.path === "/*" && (!route.scheme || route.scheme === scheme) && workerRouteHostMatches(route.host, hostname) && route.host.replace(/^\*/, "").length > planned.host.replace(/^\*/, "").length))) throw new Error(`platform: Application route ${JSON.stringify(planned.pattern)} would intercept Worker custom domain ${JSON.stringify(domain.hostname)}, currently served by ${JSON.stringify(domain.service)}. Choose another application domain, or use \`void platform install --workers-dev\` for a new installation and add a suitable domain later.`);
2099
+ }
2100
+ }
2101
+ }
2102
+ function parseWorkerRoutePattern(pattern) {
2103
+ const match = /^(?:(https?):\/\/)?([^/]+)(\/.*)?$/.exec(pattern);
2104
+ return {
2105
+ host: match?.[2]?.toLowerCase() ?? "",
2106
+ path: match?.[3] ?? "/",
2107
+ pattern,
2108
+ scheme: match?.[1] ?? null
2109
+ };
2110
+ }
2111
+ function workerRouteHostMatches(host, hostname) {
2112
+ return host.startsWith("*") ? hostname.endsWith(host.slice(1)) : host === hostname;
2113
+ }
2052
2114
  function platformProvisionIntentHash(manifest, key, kind, name, previousResourceId = null) {
2053
2115
  return createHash("sha256").update(JSON.stringify({
2054
2116
  key,
@@ -2084,10 +2146,12 @@ async function readRemoteOwnership(client, accountId, routingNamespaces, existin
2084
2146
  }
2085
2147
  function formatPlatformInstallPlan(plan) {
2086
2148
  const lines = [
2087
- `${import_picocolors$1.default.bold(`${plan.deployment.displayName} in ${plan.account.name}`)} (${plan.account.id})`,
2088
- plan.deployment.application.zoneName ? `${import_picocolors$1.default.bold("Applications:")} *.${plan.deployment.application.baseDomain}` : `${import_picocolors$1.default.bold("Applications:")} workers.dev (testing); add a domain with void platform domain set <domain>`,
2149
+ `${import_picocolors$1.default.bold(`${plan.deployment.displayName} in ${plan.account.name}`)}`,
2150
+ `${import_picocolors$1.default.bold("Account:")} ${plan.account.id}`,
2151
+ plan.deployment.application.zoneName ? `${import_picocolors$1.default.bold("Applications:")} *.${plan.deployment.application.baseDomain}` : `${import_picocolors$1.default.bold("Applications:")} workers.dev for testing. Add a domain with void platform domain set <domain>`,
2089
2152
  `${import_picocolors$1.default.bold("Control plane:")} ${plan.apiUrl}`,
2090
2153
  `${import_picocolors$1.default.bold("Resource proxy:")} ${plan.proxyUrl}`,
2154
+ ...platformDashboardUrl(plan.deployment) ? [`${import_picocolors$1.default.bold("Dashboard:")} ${platformDashboardUrl(plan.deployment)} (deploy separately)`] : [],
2091
2155
  `${import_picocolors$1.default.bold("Login methods:")} ${plan.deployment.authentication.providers.join(", ")}`,
2092
2156
  `${import_picocolors$1.default.bold("Access protection:")} ${plan.deployment.authentication.accessProtection ? "Enabled" : "Not enabled"}`,
2093
2157
  `${import_picocolors$1.default.bold("Login callback:")} ${plan.apiUrl}/auth/callback`,
@@ -2216,6 +2280,284 @@ function recordPlatformWorkerTraffic(manifest, workerName, inventory, updatedAt)
2216
2280
  return next;
2217
2281
  }
2218
2282
  //#endregion
2283
+ //#region src/cli/platform-trigger-proof.ts
2284
+ function record(value) {
2285
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
2286
+ }
2287
+ function sameStrings(actual, expected) {
2288
+ const sortedExpected = [...expected].sort();
2289
+ return actual.length === expected.length && [...actual].sort().every((value, index) => value === sortedExpected[index]);
2290
+ }
2291
+ function desiredQueue(value) {
2292
+ const queue = record(value);
2293
+ if (!queue || typeof queue.queue !== "string" || Object.keys(queue).some((key) => ![
2294
+ "queue",
2295
+ "max_batch_size",
2296
+ "max_batch_timeout",
2297
+ "max_retries",
2298
+ "max_concurrency",
2299
+ "retry_delay",
2300
+ "dead_letter_queue"
2301
+ ].includes(key)) || [
2302
+ "max_batch_size",
2303
+ "max_batch_timeout",
2304
+ "max_retries",
2305
+ "max_concurrency",
2306
+ "retry_delay"
2307
+ ].some((key) => queue[key] !== void 0 && typeof queue[key] !== "number") || queue.dead_letter_queue !== void 0 && typeof queue.dead_letter_queue !== "string") return null;
2308
+ return queue;
2309
+ }
2310
+ function matchesQueueConsumer(liveValue, desired, workerName) {
2311
+ const live = record(liveValue);
2312
+ const settings = record(live?.settings);
2313
+ if (!live || live.type !== "worker" || live.script_name !== void 0 && live.script_name !== workerName || live.queue_name !== desired.queue || !settings) return false;
2314
+ const expected = {
2315
+ batch_size: desired.max_batch_size ?? 10,
2316
+ max_wait_time_ms: Number(desired.max_batch_timeout ?? 5) * 1e3,
2317
+ max_retries: desired.max_retries ?? 3,
2318
+ max_concurrency: desired.max_concurrency ?? null,
2319
+ retry_delay: desired.retry_delay ?? 0
2320
+ };
2321
+ return Object.entries(expected).every(([key, value]) => key === "max_concurrency" ? (settings[key] ?? null) === value : settings[key] === value) && (live.dead_letter_queue ?? "") === (desired.dead_letter_queue ?? "");
2322
+ }
2323
+ /**
2324
+ * Read every surface that `triggers deploy` can mutate for a platform Worker. An incomplete
2325
+ * response, unsupported trigger kind, or read failure requires normal trigger publication.
2326
+ */
2327
+ async function platformWorkerLiveTriggersMatch(options) {
2328
+ const { accountId, workerName, desired, readCloudflare } = options;
2329
+ if (desired.preserve || desired.addresses !== void 0 || desired.workflows.length > 0 || !desired.crons.every((cron) => typeof cron === "string")) return false;
2330
+ const queues = desired.queueConsumers.map(desiredQueue);
2331
+ if (queues.some((queue) => queue === null)) return false;
2332
+ const base = `/accounts/${encodeURIComponent(accountId)}`;
2333
+ const script = `${base}/workers/scripts/${encodeURIComponent(workerName)}`;
2334
+ try {
2335
+ const [workerValue, schedulesValue, workflowsValue, nextWorkflowsValue] = await Promise.all([
2336
+ readCloudflare(`${base}/workers/workers/${encodeURIComponent(workerName)}`),
2337
+ readCloudflare(`${script}/schedules`),
2338
+ readCloudflare(`${base}/workflows?page=1&per_page=100`),
2339
+ readCloudflare(`${base}/workflows?page=2&per_page=100`)
2340
+ ]);
2341
+ const worker = record(workerValue);
2342
+ const subdomain = record(worker?.subdomain);
2343
+ const schedules = record(schedulesValue)?.schedules;
2344
+ if (worker?.name !== workerName || !subdomain || !desired.preserveWorkersDev && subdomain.enabled !== desired.workers_dev || !desired.preservePreviewUrls && subdomain.previews_enabled !== desired.preview_urls || !Array.isArray(schedules) || !Array.isArray(worker.references && record(worker.references)?.queues) || !Array.isArray(workflowsValue) || workflowsValue.length >= 100 || !Array.isArray(nextWorkflowsValue) || nextWorkflowsValue.length !== 0) return false;
2345
+ const liveCrons = schedules.map((entry) => record(entry)?.cron);
2346
+ if (!liveCrons.every((cron) => typeof cron === "string") || !sameStrings(liveCrons, desired.crons) || workflowsValue.some((entry) => {
2347
+ const workflow = record(entry);
2348
+ return !workflow || typeof workflow.script_name !== "string" || workflow.script_name === workerName;
2349
+ })) return false;
2350
+ const references = record(worker.references).queues;
2351
+ if (references.length !== queues.length) return false;
2352
+ const matched = /* @__PURE__ */ new Set();
2353
+ const consumers = await Promise.all(references.map(async (value) => {
2354
+ const reference = record(value);
2355
+ if (typeof reference?.queue_id !== "string" || typeof reference.queue_consumer_id !== "string" || typeof reference.queue_name !== "string") return null;
2356
+ return {
2357
+ queueName: reference.queue_name,
2358
+ consumer: await readCloudflare(`${base}/queues/${encodeURIComponent(reference.queue_id)}/consumers/${encodeURIComponent(reference.queue_consumer_id)}`)
2359
+ };
2360
+ }));
2361
+ for (const entry of consumers) {
2362
+ if (!entry || matched.has(entry.queueName)) return false;
2363
+ const desiredQueueConfig = queues.find((queue) => queue?.queue === entry.queueName);
2364
+ if (!desiredQueueConfig || !matchesQueueConsumer(entry.consumer, desiredQueueConfig, workerName)) return false;
2365
+ matched.add(entry.queueName);
2366
+ }
2367
+ return matched.size === queues.length;
2368
+ } catch {
2369
+ return false;
2370
+ }
2371
+ }
2372
+ //#endregion
2373
+ //#region src/cli/platform-zero-trust-uninstall.ts
2374
+ /**
2375
+ * Uninstall turns off the platform API, and only that API can remove the
2376
+ * Cloudflare Access applications project Zero Trust created. The shared
2377
+ * project application covers every hostname under the application domain, so
2378
+ * a left-over one keeps asking for sign-in on hosts Void no longer serves.
2379
+ * Uninstall therefore reads the saved Zero Trust state straight from
2380
+ * PostgreSQL, which works whether or not the API still answers, and refuses
2381
+ * while anything remains to clean up or the state cannot be read.
2382
+ */
2383
+ const ZERO_TRUST_SETTING = "zeroTrust.configuration";
2384
+ const ZERO_TRUST_LOCK = "hashtext('zero-trust:configuration')";
2385
+ const UNINSTALL_SETTING = "platform.uninstall";
2386
+ const LISTED_APPLICATIONS = 20;
2387
+ function isRecord(value) {
2388
+ return !!value && typeof value === "object" && !Array.isArray(value);
2389
+ }
2390
+ function optionalText(value, label) {
2391
+ if (value === null || value === void 0) return null;
2392
+ if (typeof value !== "string") throw new Error(`the saved ${label} is malformed`);
2393
+ return value;
2394
+ }
2395
+ function savedApplication(value, label) {
2396
+ if (value === null || value === void 0) return null;
2397
+ if (!isRecord(value) || typeof value.id !== "string" || typeof value.name !== "string") throw new Error(`the saved ${label} is malformed`);
2398
+ const destinations = Array.isArray(value.destinations) ? value.destinations.filter((entry) => typeof entry === "string") : [];
2399
+ return {
2400
+ id: value.id,
2401
+ name: value.name,
2402
+ destinations
2403
+ };
2404
+ }
2405
+ /**
2406
+ * Interpret the saved configuration and project rows. Anything that does not
2407
+ * have the expected shape throws, so the caller refuses instead of guessing.
2408
+ */
2409
+ function projectZeroTrustUninstallState(setting, projectRows) {
2410
+ const applications = [];
2411
+ let active = false;
2412
+ let destinations = [];
2413
+ if (setting !== null && setting !== void 0) {
2414
+ if (!isRecord(setting) || typeof setting.enabled !== "boolean") throw new Error("the saved Zero Trust configuration is malformed");
2415
+ const operation = setting.operation ?? null;
2416
+ if (operation !== null && !isRecord(operation)) throw new Error("the saved Zero Trust operation is malformed");
2417
+ active = setting.enabled || operation !== null;
2418
+ const wildcard = savedApplication(setting.wildcardApplication, "shared project application");
2419
+ const health = savedApplication(setting.healthApplication, "health check exception");
2420
+ const wildcardName = optionalText(setting.wildcardApplicationName, "application name");
2421
+ const healthName = optionalText(setting.healthApplicationName, "application name");
2422
+ if (wildcard || active && wildcardName) applications.push({
2423
+ role: "shared project application",
2424
+ id: wildcard?.id ?? null,
2425
+ name: wildcard?.name ?? wildcardName
2426
+ });
2427
+ if (health || active && healthName) applications.push({
2428
+ role: "health check exception",
2429
+ id: health?.id ?? null,
2430
+ name: health?.name ?? healthName
2431
+ });
2432
+ destinations = wildcard?.destinations ?? [];
2433
+ }
2434
+ const unfinishedDeletions = [];
2435
+ for (const row of projectRows) {
2436
+ const deleting = row.project_status === "deleting";
2437
+ const owned = applications.length;
2438
+ if (row.bypass_application_id || row.bypass_application_name) applications.push({
2439
+ role: `public exception for ${row.project_id}`,
2440
+ id: row.bypass_application_id,
2441
+ name: row.bypass_application_name,
2442
+ ...deleting ? { deleting } : {}
2443
+ });
2444
+ if (row.protected_application_id || row.protected_application_name) applications.push({
2445
+ role: `custom domain application for ${row.project_id}`,
2446
+ id: row.protected_application_id,
2447
+ name: row.protected_application_name,
2448
+ ...deleting ? { deleting } : {}
2449
+ });
2450
+ if (deleting && applications.length > owned) unfinishedDeletions.push(row.project_slug ?? row.project_id);
2451
+ }
2452
+ return {
2453
+ active,
2454
+ destinations,
2455
+ applications,
2456
+ unfinishedDeletions
2457
+ };
2458
+ }
2459
+ /** Read the project Zero Trust state from the platform database. */
2460
+ async function readPlatformProjectZeroTrustStateConnection(client) {
2461
+ const present = (await client.query(`SELECT to_regclass('public.platform_settings') IS NOT NULL AS "settings",
2462
+ to_regclass('public.project_zero_trust') IS NOT NULL AS "projects"`)).rows[0];
2463
+ if (!present) throw new Error("the database did not report its Zero Trust tables");
2464
+ return projectZeroTrustUninstallState(present.settings ? (await client.query("SELECT \"value\" FROM \"platform_settings\" WHERE \"key\" = $1", [ZERO_TRUST_SETTING])).rows[0]?.value : void 0, present.projects ? (await client.query(`SELECT z."project_id", p."slug" AS "project_slug", p."status" AS "project_status",
2465
+ z."bypass_application_id", z."bypass_application_name",
2466
+ z."protected_application_id", z."protected_application_name"
2467
+ FROM "project_zero_trust" z
2468
+ LEFT JOIN "projects" p ON p."id" = z."project_id"
2469
+ WHERE z."bypass_application_id" IS NOT NULL
2470
+ OR z."bypass_application_name" IS NOT NULL
2471
+ OR z."protected_application_id" IS NOT NULL
2472
+ OR z."protected_application_name" IS NOT NULL
2473
+ ORDER BY z."project_id"`)).rows : []);
2474
+ }
2475
+ /**
2476
+ * Take the Zero Trust configuration lock exclusively on this session. Every
2477
+ * API path that records or creates a Void-managed Access application holds
2478
+ * the same lock, so none can commit until the returned release runs.
2479
+ */
2480
+ async function lockPlatformProjectZeroTrustConnection(client) {
2481
+ await client.query(`SELECT pg_advisory_lock(${ZERO_TRUST_LOCK})`);
2482
+ let released = false;
2483
+ return async () => {
2484
+ if (released) return;
2485
+ released = true;
2486
+ await client.query(`SELECT pg_advisory_unlock(${ZERO_TRUST_LOCK})`);
2487
+ };
2488
+ }
2489
+ async function hasSettingsTable(client) {
2490
+ return (await client.query(`SELECT to_regclass('public.platform_settings') IS NOT NULL AS "present"`)).rows[0]?.present === true;
2491
+ }
2492
+ /**
2493
+ * Record that uninstall passed its final check. Runs while the configuration
2494
+ * lock is held. An API request admitted before traffic was disabled may be
2495
+ * waiting on that lock; once it gets the lock it reads this record and
2496
+ * refuses. Without the settings table the API has no Zero Trust writers.
2497
+ */
2498
+ async function fencePlatformProjectZeroTrustConnection(client) {
2499
+ if (!await hasSettingsTable(client)) return;
2500
+ await client.query(`INSERT INTO "platform_settings" ("key", "value") VALUES ($1, $2::jsonb)
2501
+ ON CONFLICT ("key") DO UPDATE SET "value" = EXCLUDED."value", "updated_at" = now()`, [UNINSTALL_SETTING, JSON.stringify({ startedAt: (/* @__PURE__ */ new Date()).toISOString() })]);
2502
+ }
2503
+ /**
2504
+ * Remove the uninstall record. Uninstall does this when it stops before
2505
+ * disabling traffic; installing or enabling does it before traffic opens.
2506
+ */
2507
+ async function liftPlatformProjectZeroTrustFenceConnection(client) {
2508
+ if (!await hasSettingsTable(client)) return;
2509
+ await client.query("DELETE FROM \"platform_settings\" WHERE \"key\" = $1", [UNINSTALL_SETTING]);
2510
+ }
2511
+ function formatApplications(applications) {
2512
+ const lines = applications.slice(0, LISTED_APPLICATIONS).map((application) => ` ${application.role}: ${application.name ?? "(name not recorded)"}${application.id ? ` (${application.id})` : ""}`);
2513
+ if (applications.length > LISTED_APPLICATIONS) lines.push(` …and ${applications.length - LISTED_APPLICATIONS} more`);
2514
+ return lines.join("\n");
2515
+ }
2516
+ const DASHBOARD_HINT = "To lift sign-in right away, delete these applications in the Cloudflare dashboard under Zero Trust → Access → Applications.";
2517
+ function unfinishedDeletionLines(state) {
2518
+ if (!state.unfinishedDeletions.length) return [];
2519
+ return [`Deletion of ${state.unfinishedDeletions.length === 1 ? "project" : "projects"} ${state.unfinishedDeletions.join(", ")} stopped before it removed their Access applications. Rerun \`void project delete <name>\` for each, or delete it again from the admin dashboard. This removes their applications, including ones already deleted in the Cloudflare dashboard, and Void's records of them.`];
2520
+ }
2521
+ /**
2522
+ * Refuse to uninstall while project Zero Trust still owns Access applications,
2523
+ * or while its state cannot be read. Runs before uninstall changes anything.
2524
+ */
2525
+ async function assertProjectZeroTrustAllowsUninstall(read) {
2526
+ let state;
2527
+ try {
2528
+ if (!read) throw new Error("no platform database connection is available");
2529
+ state = await read();
2530
+ } catch (error) {
2531
+ const reason = (error instanceof Error ? error.message : String(error)).replace(/^platform: /, "");
2532
+ throw new Error([
2533
+ `platform: Refusing to uninstall because the project Zero Trust state could not be read from the platform database: ${reason}`,
2534
+ "Uninstall turns off the platform API, which Void needs to remove the Cloudflare Access applications that project Zero Trust created.",
2535
+ "Make the installation database reachable (export VOID_PLATFORM_DATABASE_URL if this machine has not recorded it), then retry. If project Zero Trust was ever enabled, run `void platform zero-trust disable` first."
2536
+ ].join("\n"));
2537
+ }
2538
+ if (state.active) {
2539
+ const covered = state.destinations.length ? ` on ${state.destinations.join(" and ")}` : " on every hostname under the application domain";
2540
+ throw new Error([
2541
+ "platform: Refusing to uninstall while project Zero Trust is enabled or still changing.",
2542
+ `Uninstall turns off the platform API, so Void could no longer remove its Cloudflare Access applications, and Access would keep asking for sign-in${covered}, including hostnames Void does not serve.`,
2543
+ "Run `void platform zero-trust disable`, rerun it until `void platform zero-trust status` reports Zero Trust disabled with no operation running, then retry the uninstall.",
2544
+ "If the platform API does not answer, bring it back with `void platform enable` or `void platform repair` first.",
2545
+ ...state.applications.length ? ["Void-managed Access applications:", formatApplications(state.applications)] : [],
2546
+ ...unfinishedDeletionLines(state),
2547
+ DASHBOARD_HINT
2548
+ ].join("\n"));
2549
+ }
2550
+ if (state.applications.length) throw new Error([
2551
+ "platform: Refusing to uninstall while Void-managed Access applications from project Zero Trust remain.",
2552
+ "Zero Trust is disabled, but it could not remove the applications of some projects. Retry the uninstall once they are gone.",
2553
+ "Void-managed Access applications:",
2554
+ formatApplications(state.applications),
2555
+ ...state.applications.some((application) => !application.deleting) ? [`Hourly maintenance retries ${state.unfinishedDeletions.length ? "the other applications" : "them"} while the platform is enabled, and clears Void's records of any you delete in the dashboard.`] : [],
2556
+ ...unfinishedDeletionLines(state),
2557
+ DASHBOARD_HINT
2558
+ ].join("\n"));
2559
+ }
2560
+ //#endregion
2219
2561
  //#region src/cli/platform-installer.ts
2220
2562
  const REMOTE_INSTALLATION_MANIFEST_KEY = PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY;
2221
2563
  const PLATFORM_WORKER_SECRET_UPDATE_NAMES = [
@@ -2530,7 +2872,13 @@ async function preparePlatformDatabaseSchema(manifest, runtime, runtimeDirectory
2530
2872
  }
2531
2873
  async function recoverInterruptedPlatformWorkerCutover(manifest, interruptedCutover, workerActions, configs, previousConfigs, plan, dependencies, ids, zoneId, previousPublicEndpoints, hasEmailGatewayArtifact, runPinnedPlatformWrangler, now, writeRemoteInstallationCheckpoint, onManifestChange) {
2532
2874
  if (interruptedCutover) {
2533
- for (const key of [...interruptedCutover.staged.keys()].reverse()) {
2875
+ for (const key of [
2876
+ "dispatch",
2877
+ "tail",
2878
+ "emailGateway",
2879
+ "proxy",
2880
+ "api"
2881
+ ].filter((key) => interruptedCutover.staged.has(key))) {
2534
2882
  const snapshot = interruptedCutover.staged.get(key);
2535
2883
  const preservedRoutes = dependencies.recoverWorkerCutover ? [] : await inventoryUnmanagedPlatformWorkerTraffic(dependencies.client, plan, snapshot.workerName);
2536
2884
  const recoveryConfig = mergePlatformWorkerTrafficConfig({
@@ -2632,7 +2980,8 @@ async function installPlatform(plan, secrets, existing, dependencies) {
2632
2980
  if (plan.actions.some((action) => action.action === "inspect")) throw new Error("platform: AI Gateway access must be verified before provisioning. Provide AI Gateway: Edit on the runtime token, then retry.");
2633
2981
  const now = dependencies.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
2634
2982
  const cloudflareApiBaseUrl = dependencies.client.apiBaseUrl ?? resolvePinnedApiBaseUrl();
2635
- const runPinnedPlatformWrangler = (args, accountId, cwd, capture) => runPlatformWranglerAtApiBase(args, accountId, cwd, capture, cloudflareApiBaseUrl);
2983
+ const runPinnedPlatformWrangler = (operation, accountId, cwd, capture) => runPlatformWranglerAtApiBase(operation, accountId, cwd, capture, cloudflareApiBaseUrl);
2984
+ const runParallelPinnedPlatformWrangler = (operation, accountId, cwd, capture) => runPlatformWranglerAtApiBase(operation, accountId, cwd, capture, cloudflareApiBaseUrl, true);
2636
2985
  const resolvedRuntime = dependencies.runtime ?? resolvePlatformRuntime();
2637
2986
  const runtimeDirectory = resolvedRuntime.directory;
2638
2987
  const runtime = resolvedRuntime.manifest;
@@ -2724,6 +3073,10 @@ async function installPlatform(plan, secrets, existing, dependencies) {
2724
3073
  writeInstallationManifest(manifest);
2725
3074
  }
2726
3075
  assertRequiredPlatformSecrets(plan, manifest, secrets, !!dependencies.prepareAuthentication);
3076
+ if (hyperdriveAction?.action === "create") {
3077
+ parsePostgresConnectionString(secrets.databaseUrl);
3078
+ assertVoidCreatedHyperdriveTrustsDatabase(secrets.databaseUrl);
3079
+ }
2727
3080
  const pendingSecrets = secretCheckpoint(secrets, workerSecretUpdateIntent);
2728
3081
  writePendingInstallationSecrets(manifest, {
2729
3082
  ...pendingSecrets,
@@ -2846,6 +3199,12 @@ async function installPlatform(plan, secrets, existing, dependencies) {
2846
3199
  manifest = transitionInstallation(manifest, "provisioning", now());
2847
3200
  writeInstallationManifest(manifest);
2848
3201
  }
3202
+ if (runtime.deploymentExecution !== 1 && (manifest.deploymentExecution === 1 || dependencies.databaseRollbackSource?.manifest.deploymentExecution === 1)) {
3203
+ if (!releaseDatabaseLock.checkpoint) throw new Error("platform: The database lifecycle lock cannot fence deployment admission.");
3204
+ await releaseDatabaseLock.checkpoint(manifest);
3205
+ dependencies.onProgress?.("Verifying durable deployment operations have drained");
3206
+ await (dependencies.verifyDeploymentExecutionDrain ?? verifyPlatformDeploymentExecutionDrain)(plan.apiUrl, createPlatformHealthProbeToken(manifest.ownershipNonce), manifest.id);
3207
+ }
2849
3208
  if (dependencies.prepareAuthentication) {
2850
3209
  dependencies.onProgress?.("Preparing platform authentication");
2851
3210
  try {
@@ -2897,6 +3256,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
2897
3256
  client: dependencies.client,
2898
3257
  manifest,
2899
3258
  plan,
3259
+ dashboardUrl: platformDashboardUrl(plan.deployment),
2900
3260
  protection,
2901
3261
  checkpoint: secretCheckpoint(secrets, workerSecretUpdateIntent)
2902
3262
  });
@@ -2964,11 +3324,6 @@ async function installPlatform(plan, secrets, existing, dependencies) {
2964
3324
  if (targetSandboxDrainProtocol >= 1 && !prepareSandboxDrain) throw new Error("platform: The PostgreSQL lifecycle lock cannot establish the sandbox-drain safety checkpoint.");
2965
3325
  const sandboxDrainPreparation = targetSandboxDrainProtocol >= 1 ? await prepareSandboxDrain(targetSandboxDrainProtocol >= 3 ? 3 : targetSandboxDrainProtocol >= 2 ? 2 : 0) : null;
2966
3326
  if (sandboxDrainPreparation && sandboxDrainPreparation.admissionProtocol < targetSandboxDrainProtocol) throw new Error("platform: The database did not arm the Sandbox admission protocol required by this runtime.");
2967
- const sandboxDrainRoutingKvId = requireId(Object.fromEntries(manifest.resources.map((resource) => [resource.key, resource.id])), "routing-kv");
2968
- const writeSandboxDrainMirror = async (preparation) => {
2969
- await dependencies.client.putKvValue(plan.account.id, sandboxDrainRoutingKvId, PLATFORM_SANDBOX_DRAIN_KEY, preparation.mirrorValue);
2970
- };
2971
- if (sandboxDrainPreparation) await writeSandboxDrainMirror(sandboxDrainPreparation);
2972
3327
  const sandboxDrainState = sandboxDrainPreparation?.state ?? "complete";
2973
3328
  if (targetSandboxDrainProtocol >= 1 && (manifest.safetyProtocols?.sandboxDrain ?? 0) < targetSandboxDrainProtocol) {
2974
3329
  manifest = {
@@ -3027,6 +3382,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
3027
3382
  accountId: plan.account.id,
3028
3383
  apiUrl: plan.apiUrl,
3029
3384
  deployment: plan.deployment,
3385
+ deploymentExecution: runtime.deploymentExecution,
3030
3386
  healthProbeToken: createPlatformHealthProbeToken(manifest.ownershipNonce),
3031
3387
  installationId: manifest.id,
3032
3388
  ownershipNonce: manifest.ownershipNonce,
@@ -3075,7 +3431,59 @@ async function installPlatform(plan, secrets, existing, dependencies) {
3075
3431
  };
3076
3432
  writePendingInstallationSecrets(manifest, secretCheckpoint(secrets, workerSecretUpdateIntent));
3077
3433
  }
3078
- for (const key of workerKeys) {
3434
+ const parallelWorkerUploads = !dependencies.deployWorker && !requiresStagedCommit && !workerSecretUpdateIntent && !recoveringUnsafeTraffic && workerActions.length === workerKeys.length && workerActions.every((action) => action.action === "update");
3435
+ let workerManifestQueue = Promise.resolve();
3436
+ let workerManifestFailed = false;
3437
+ let workerManifestFailure;
3438
+ const checkpointWorkerManifest = (update, safetyRecovery = false) => {
3439
+ if (!parallelWorkerUploads) return update();
3440
+ const result = workerManifestQueue.then(async () => {
3441
+ if (workerManifestFailed && !safetyRecovery) throw workerManifestFailure;
3442
+ try {
3443
+ await update();
3444
+ } catch (error) {
3445
+ workerManifestFailed = true;
3446
+ workerManifestFailure = error;
3447
+ throw error;
3448
+ }
3449
+ });
3450
+ workerManifestQueue = result.then(() => void 0, () => void 0);
3451
+ return result;
3452
+ };
3453
+ let availableUploadSlots = 2;
3454
+ let uploadFailure;
3455
+ const uploadWaiters = [];
3456
+ const acquireUploadSlot = async () => {
3457
+ if (uploadFailure !== void 0) throw uploadFailure;
3458
+ if (availableUploadSlots === 0) await new Promise((resolve, reject) => uploadWaiters.push({
3459
+ resolve,
3460
+ reject
3461
+ }));
3462
+ else availableUploadSlots--;
3463
+ return (failure) => {
3464
+ if (failure !== void 0 && uploadFailure === void 0) {
3465
+ uploadFailure = failure;
3466
+ for (const waiter of uploadWaiters.splice(0)) waiter.reject(failure);
3467
+ }
3468
+ const next = uploadFailure === void 0 ? uploadWaiters.shift() : void 0;
3469
+ if (next) next.resolve();
3470
+ else availableUploadSlots++;
3471
+ };
3472
+ };
3473
+ const workerReady = (parallelWorkerUploads ? workerKeys : []).map(() => {
3474
+ let resolve;
3475
+ let reject;
3476
+ return {
3477
+ promise: new Promise((onResolve, onReject) => {
3478
+ resolve = onResolve;
3479
+ reject = onReject;
3480
+ }),
3481
+ resolve,
3482
+ reject
3483
+ };
3484
+ });
3485
+ const uploadBatch = Promise.all(workerReady.map((ready) => ready.promise)).then(() => ({ error: null }), (error) => ({ error }));
3486
+ const deployOneWorker = async (key, waitForPriorWorker, onReadyForActivation) => {
3079
3487
  dependencies.onProgress?.(`Deploying the ${key} service`);
3080
3488
  let workerStepStarted = performance.now();
3081
3489
  const markWorkerStep = (step) => {
@@ -3084,12 +3492,14 @@ async function installPlatform(plan, secrets, existing, dependencies) {
3084
3492
  };
3085
3493
  const action = workerActions.find((entry) => entry.key === (key === "emailGateway" ? "email-gateway-worker" : `${key}-worker`));
3086
3494
  const workerSecrets = platformWorkerSecretPayload(key, workerDeploymentSecrets, plan.account.id, zoneId, action.action !== "create");
3087
- const intended = recordProvisionIntent(manifest, action, now());
3088
- if (intended !== manifest) {
3089
- manifest = intended;
3090
- writeInstallationManifest(manifest);
3091
- await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3092
- }
3495
+ await checkpointWorkerManifest(async () => {
3496
+ const intended = recordProvisionIntent(manifest, action, now());
3497
+ if (intended !== manifest) {
3498
+ manifest = intended;
3499
+ writeInstallationManifest(manifest);
3500
+ await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3501
+ }
3502
+ });
3093
3503
  await dependencies.revalidateBeforeWorkerDeploy(manifest, action);
3094
3504
  markWorkerStep("revalidate");
3095
3505
  const preservedRoutes = dependencies.deployWorker ? [] : action.action === "create" ? [] : await inventoryUnmanagedPlatformWorkerTraffic(dependencies.client, plan, action.name);
@@ -3098,29 +3508,40 @@ async function installPlatform(plan, secrets, existing, dependencies) {
3098
3508
  const previousWorkerConfig = mergePlatformWorkerTrafficConfig(previousConfigs[key], preservedRoutes);
3099
3509
  const rollback = dependencies.deployWorker ? await dependencies.deployWorker(workerConfig, workerSecrets, previousWorkerConfig) : await deployPlatformWorker(workerConfig, workerSecrets, {
3100
3510
  apiBaseUrl: cloudflareApiBaseUrl,
3511
+ ...parallelWorkerUploads ? {
3512
+ acquireUploadSlot,
3513
+ onReadyForActivation,
3514
+ waitForPriorWorker
3515
+ } : {},
3101
3516
  ...dependencies.client.request ? { readCloudflare: (path) => dependencies.client.request(path) } : {},
3102
3517
  onStaged: async (previousVersionId, stagedVersionId, previousTriggers) => {
3103
- manifest = recordWorkerCutoverStage(manifest, workerCutoverGeneration, key, previousVersionId, stagedVersionId, action.name, previousTriggers, now());
3104
- writeInstallationManifest(manifest);
3105
- await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3518
+ await checkpointWorkerManifest(async () => {
3519
+ manifest = recordWorkerCutoverStage(manifest, workerCutoverGeneration, key, previousVersionId, stagedVersionId, action.name, previousTriggers, now());
3520
+ writeInstallationManifest(manifest);
3521
+ await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3522
+ });
3106
3523
  },
3107
3524
  onTrafficPublished: async () => {
3108
3525
  if (!dependencies.client.listWorkerDomains || !dependencies.client.listWorkerRoutes) throw new Error("platform: Cloudflare traffic inventory is unavailable.");
3109
3526
  const [customDomains, routes] = await Promise.all([dependencies.client.listWorkerDomains(plan.account.id), zoneId ? dependencies.client.listWorkerRoutes(zoneId) : Promise.resolve([])]);
3110
- const next = recordPlatformWorkerTraffic(manifest, action.name, {
3111
- customDomains,
3112
- routes
3113
- }, now());
3114
- if (JSON.stringify(next.resources) !== JSON.stringify(manifest.resources)) {
3115
- manifest = next;
3116
- writeInstallationManifest(manifest);
3117
- await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3118
- }
3527
+ await checkpointWorkerManifest(async () => {
3528
+ const next = recordPlatformWorkerTraffic(manifest, action.name, {
3529
+ customDomains,
3530
+ routes
3531
+ }, now());
3532
+ if (JSON.stringify(next.resources) !== JSON.stringify(manifest.resources)) {
3533
+ manifest = next;
3534
+ writeInstallationManifest(manifest);
3535
+ await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3536
+ }
3537
+ });
3119
3538
  },
3120
3539
  onUnsafeTraffic: async () => {
3121
- manifest = recordWorkerUnsafeTraffic(manifest, workerCutoverGeneration, key, action.name, now());
3122
- writeInstallationManifest(manifest);
3123
- await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3540
+ await checkpointWorkerManifest(async () => {
3541
+ manifest = recordWorkerUnsafeTraffic(manifest, workerCutoverGeneration, key, action.name, now());
3542
+ writeInstallationManifest(manifest);
3543
+ await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3544
+ }, true);
3124
3545
  },
3125
3546
  managedRoutes: preserveDisabled ? [] : managedRoutes.filter((route) => route.script === action.name).map((route) => route.pattern),
3126
3547
  preservedRoutes,
@@ -3140,22 +3561,40 @@ async function installPlatform(plan, secrets, existing, dependencies) {
3140
3561
  retainNewWorker: async () => {
3141
3562
  await dependencies.client.putKvValue(plan.account.id, requireId(ids, "routing-kv"), PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE);
3142
3563
  },
3143
- runner: runPinnedPlatformWrangler,
3564
+ runner: parallelWorkerUploads ? runParallelPinnedPlatformWrangler : runPinnedPlatformWrangler,
3144
3565
  workerExisted: action.action !== "create"
3145
3566
  });
3146
3567
  markWorkerStep("deploy");
3147
3568
  if (rollback) workerRollbacks.push(rollback);
3148
- const recorded = recordResource(manifest, action, action.name, "created", now());
3149
- if (rollback || dependencies.deployWorker || JSON.stringify(recorded.resources) !== JSON.stringify(manifest.resources)) {
3150
- manifest = recorded;
3151
- writeInstallationManifest(manifest);
3152
- await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3153
- }
3569
+ await checkpointWorkerManifest(async () => {
3570
+ const recorded = recordResource(manifest, action, action.name, "created", now());
3571
+ if (rollback || dependencies.deployWorker || JSON.stringify(recorded.resources) !== JSON.stringify(manifest.resources)) {
3572
+ manifest = recorded;
3573
+ writeInstallationManifest(manifest);
3574
+ await writeRemoteInstallationCheckpoint(dependencies.client, plan, manifest);
3575
+ }
3576
+ });
3154
3577
  markWorkerStep("record");
3155
- }
3578
+ };
3579
+ if (parallelWorkerUploads) {
3580
+ const tasks = [];
3581
+ for (const [index, key] of workerKeys.entries()) {
3582
+ const priorWorker = tasks.at(-1);
3583
+ const ready = workerReady[index];
3584
+ const task = deployOneWorker(key, priorWorker ? () => priorWorker : void 0, async () => {
3585
+ ready.resolve();
3586
+ const batch = await uploadBatch;
3587
+ if (batch.error !== null) throw batch.error;
3588
+ });
3589
+ task.then(() => ready.resolve(), (error) => ready.reject(error));
3590
+ tasks.push(task);
3591
+ }
3592
+ const firstFailure = (await Promise.allSettled(tasks)).find((result) => result.status === "rejected");
3593
+ if (firstFailure) throw firstFailure.reason;
3594
+ } else for (const key of workerKeys) await deployOneWorker(key);
3156
3595
  manifest = setInstallationEndpoints(manifest, {
3157
3596
  apiUrl: plan.apiUrl,
3158
- dashboardUrl: null,
3597
+ dashboardUrl: platformDashboardUrl(plan.deployment),
3159
3598
  proxyUrl: plan.proxyUrl
3160
3599
  }, now());
3161
3600
  if (preserveDisabled) {
@@ -3166,6 +3605,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
3166
3605
  manifest = {
3167
3606
  ...manifest,
3168
3607
  deployment: plan.deployment,
3608
+ deploymentExecution: runtime.deploymentExecution,
3169
3609
  platformVersion: version,
3170
3610
  runtime: runtimeProvenance,
3171
3611
  updatedAt: now(),
@@ -3264,9 +3704,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
3264
3704
  zoneNameServers: zone?.nameServers ?? []
3265
3705
  };
3266
3706
  }
3267
- const completedDrain = await prepareSandboxDrain();
3268
- if (completedDrain.state !== "complete") throw new Error("platform: Sandbox drain completion changed while it was being verified. Traffic remains disabled.");
3269
- await writeSandboxDrainMirror(completedDrain);
3707
+ if ((await prepareSandboxDrain()).state !== "complete") throw new Error("platform: Sandbox drain completion changed while it was being verified. Traffic remains disabled.");
3270
3708
  }
3271
3709
  if (requiresStagedCommit) {
3272
3710
  if (!authenticationCutoverCommitted) {
@@ -3338,6 +3776,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
3338
3776
  retainNewWorker: async () => {
3339
3777
  await dependencies.client.putKvValue(plan.account.id, requireId(ids, "routing-kv"), PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE);
3340
3778
  },
3779
+ reuseMatchingVersion: false,
3341
3780
  runner: runPinnedPlatformWrangler,
3342
3781
  workerExisted: true
3343
3782
  });
@@ -3361,6 +3800,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
3361
3800
  dependencies.onProgress?.("Preparing shared email routing");
3362
3801
  emailOperationId = await (dependencies.bootstrapEmail ?? bootstrapPlatformSharedEmail)(plan.apiUrl, createPlatformHealthProbeToken(manifest.ownershipNonce));
3363
3802
  }
3803
+ await releaseDatabaseLock.liftProjectZeroTrustFence?.();
3364
3804
  if (dependencies.client.deleteKvValue) await dependencies.client.deleteKvValue(plan.account.id, requireId(ids, "routing-kv"), PLATFORM_DISABLED_KEY);
3365
3805
  else if (wasDisabled || !previousPublicEndpoints) throw new Error("platform: Routing disablement removal is unavailable.");
3366
3806
  if (requiresStagedCommit) {
@@ -3375,6 +3815,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
3375
3815
  manifest = {
3376
3816
  ...manifest,
3377
3817
  deployment: plan.deployment,
3818
+ deploymentExecution: runtime.deploymentExecution,
3378
3819
  platformVersion: version,
3379
3820
  runtime: runtimeProvenance,
3380
3821
  safetyProtocols: runtime.safetyProtocols ?? { sandboxDrain: 0 },
@@ -3747,6 +4188,14 @@ function assertRequiredPlatformSecrets(plan, manifest, secrets, authenticationWi
3747
4188
  }
3748
4189
  if (missing.size > 0) throw new Error(`platform: Creating a missing platform Worker requires: ${[...missing].sort().join(", ")}. Restore these values from your secret manager and retry.`);
3749
4190
  }
4191
+ /**
4192
+ * A Hyperdrive that Void creates validates the server against public certificate authorities
4193
+ * only, so it cannot honor a private CA that the local database client would trust.
4194
+ */
4195
+ function assertVoidCreatedHyperdriveTrustsDatabase(databaseUrl) {
4196
+ const sslrootcert = new URL(databaseUrl).searchParams.getAll("sslrootcert").at(-1);
4197
+ if (sslrootcert && sslrootcert !== "system") throw new Error("platform: A Hyperdrive created by Void trusts only public certificate authorities, so it cannot use the certificate authority in DATABASE_URL sslrootcert. If the database certificate comes from a public certificate authority, use sslrootcert=system. Otherwise create a separately managed Hyperdrive that trusts it, then select it with \"Use an existing Hyperdrive\" when you start a new installation, or set VOID_PLATFORM_HYPERDRIVE_ID, VOID_PLATFORM_HYPERDRIVE_ORIGIN_HOST, and VOID_PLATFORM_HYPERDRIVE_ORIGIN_USER.");
4198
+ }
3750
4199
  /** Make pg's current strict TLS behavior explicit without rewriting saved connection details. */
3751
4200
  function platformDatabaseClientUrl(databaseUrl) {
3752
4201
  let url;
@@ -3755,7 +4204,13 @@ function platformDatabaseClientUrl(databaseUrl) {
3755
4204
  } catch {
3756
4205
  return databaseUrl;
3757
4206
  }
3758
- if (url.protocol !== "postgres:" && url.protocol !== "postgresql:" || url.searchParams.getAll("sslmode").at(-1) !== "require" || url.searchParams.getAll("uselibpqcompat").at(-1) === "true") return databaseUrl;
4207
+ if (url.protocol !== "postgres:" && url.protocol !== "postgresql:") return databaseUrl;
4208
+ if (url.searchParams.getAll("sslrootcert").at(-1) === "system") {
4209
+ url.searchParams.delete("sslrootcert");
4210
+ url.searchParams.set("sslmode", "verify-full");
4211
+ return url.toString();
4212
+ }
4213
+ if (url.searchParams.getAll("sslmode").at(-1) !== "require" || url.searchParams.getAll("uselibpqcompat").at(-1) === "true") return databaseUrl;
3759
4214
  url.searchParams.set("sslmode", "verify-full");
3760
4215
  return url.toString();
3761
4216
  }
@@ -3812,6 +4267,21 @@ async function readPlatformDatabaseLifecycleManifest(databaseUrl, installationId
3812
4267
  await pool.end();
3813
4268
  }
3814
4269
  }
4270
+ /** Read project Zero Trust state for a plan, which holds no lifecycle lock. */
4271
+ async function readPlatformProjectZeroTrustState(databaseUrl, installationId, ownershipNonce) {
4272
+ if (!databaseUrl) throw new Error("the installation database URL is not recorded on this machine");
4273
+ const { Pool } = await import("pg");
4274
+ const pool = new Pool({
4275
+ connectionString: platformDatabaseClientUrl(databaseUrl),
4276
+ max: 1
4277
+ });
4278
+ try {
4279
+ await readPlatformDatabaseLifecycleManifestConnection(pool, installationId, ownershipNonce);
4280
+ return await readPlatformProjectZeroTrustStateConnection(pool);
4281
+ } finally {
4282
+ await pool.end();
4283
+ }
4284
+ }
3815
4285
  async function inspectPlatformDatabaseStateConnection(client, database) {
3816
4286
  const relation = await client.query(`SELECT to_regclass('"drizzle"."__drizzle_migrations"')::text AS "relation"`);
3817
4287
  if (relation.rows.length === 0 || relation.rows[0]?.relation === null) return {
@@ -3939,6 +4409,10 @@ async function acquirePlatformDatabaseLifecycleLockConnection(client, installati
3939
4409
  protection: parsePlatformAccessProtection(settings.protection)
3940
4410
  } : null;
3941
4411
  };
4412
+ release.readProjectZeroTrust = () => readPlatformProjectZeroTrustStateConnection(client);
4413
+ release.lockProjectZeroTrust = () => lockPlatformProjectZeroTrustConnection(client);
4414
+ release.fenceProjectZeroTrust = () => fencePlatformProjectZeroTrustConnection(client);
4415
+ release.liftProjectZeroTrustFence = () => liftPlatformProjectZeroTrustFenceConnection(client);
3942
4416
  release.requiredSafetyProtocols = await readRequiredPlatformSafetyProtocols(client);
3943
4417
  release.checkpoint = async (manifest) => {
3944
4418
  if (released) throw new Error("platform: Cannot checkpoint lifecycle state after releasing its lock.");
@@ -3980,14 +4454,6 @@ async function preparePlatformSandboxDrainConnection(client, requiredAdmissionPr
3980
4454
  return {
3981
4455
  admissionProtocol: row.admission_protocol,
3982
4456
  generation: row.generation,
3983
- mirrorValue: JSON.stringify({
3984
- version: 1,
3985
- generation: row.generation,
3986
- state: row.state,
3987
- startedAt: row.started_at,
3988
- updatedAt: row.updated_at,
3989
- ...row.completed_at ? { completedAt: row.completed_at } : {}
3990
- }),
3991
4457
  state: row.state === "complete" ? "complete" : "pending"
3992
4458
  };
3993
4459
  };
@@ -4154,11 +4620,12 @@ let cachedPlatformUploaderHash;
4154
4620
  function platformWorkerUploaderHash() {
4155
4621
  if (cachedPlatformUploaderHash !== void 0) return cachedPlatformUploaderHash;
4156
4622
  try {
4157
- const launcher = resolveWranglerArgv(process.cwd())[1];
4623
+ const launcher = resolveCloudflareOperationArgv(process.cwd())[1];
4158
4624
  if (!launcher) return cachedPlatformUploaderHash = null;
4159
4625
  const digest = createHash("sha256");
4160
4626
  digest.update("platform-uploader:v1\0");
4161
4627
  digest.update(process.version);
4628
+ digest.update(readFileSync(launcher));
4162
4629
  digest.update(readFileSync(resolve(dirname(launcher), "..", "..", "package.json")));
4163
4630
  const cfDirectory = dirname(fileURLToPath(import.meta.resolve("cf/package.json")));
4164
4631
  digest.update(readFileSync(join(cfDirectory, "package.json")));
@@ -4268,32 +4735,34 @@ function snapshotPlatformWorkerUploadInputs(config, directory) {
4268
4735
  }
4269
4736
  function platformWorkerApiReadRunner(accountId, workerName, readCloudflare, fallback) {
4270
4737
  const base = `/accounts/${encodeURIComponent(accountId)}/workers/scripts/${encodeURIComponent(workerName)}`;
4271
- return async (args, account, cwd, capture) => {
4272
- if (args[0] === "deployments" && (args[1] === "status" || args[1] === "list")) {
4273
- const response = await readCloudflare(`${base}/deployments`);
4274
- const deployments = response && typeof response === "object" && !Array.isArray(response) ? response.deployments : null;
4275
- if (!Array.isArray(deployments)) throw new Error(`platform: Cloudflare returned malformed deployment history for ${workerName}.`);
4276
- return {
4277
- status: args[1] === "status" && deployments.length === 0 ? 1 : 0,
4278
- stderr: "",
4279
- stdout: JSON.stringify(args[1] === "status" ? deployments[0] : deployments)
4280
- };
4281
- }
4282
- if (args[0] === "versions" && args[1] === "view" && args[2]) {
4283
- const version = await readCloudflare(`${base}/versions/${encodeURIComponent(args[2])}`);
4284
- return {
4738
+ return async (operation, account, cwd, capture) => {
4739
+ try {
4740
+ if (operation.type === "deployments.status" || operation.type === "deployments.list") {
4741
+ const response = await readCloudflare(`${base}/deployments`);
4742
+ const deployments = response && typeof response === "object" && !Array.isArray(response) ? response.deployments : null;
4743
+ if (!Array.isArray(deployments)) throw new Error(`platform: Cloudflare returned malformed deployment history for ${workerName}.`);
4744
+ return {
4745
+ status: operation.type === "deployments.status" && deployments.length === 0 ? 1 : 0,
4746
+ stderr: "",
4747
+ stdout: "",
4748
+ data: operation.type === "deployments.status" ? deployments[0] : deployments
4749
+ };
4750
+ }
4751
+ if (operation.type === "versions.view") return {
4285
4752
  status: 0,
4286
4753
  stderr: "",
4287
- stdout: JSON.stringify(version)
4754
+ stdout: "",
4755
+ data: await readCloudflare(`${base}/versions/${encodeURIComponent(operation.versionId)}`)
4288
4756
  };
4289
- }
4290
- return fallback(args, account, cwd, capture);
4757
+ } catch {}
4758
+ return fallback(operation, account, cwd, capture);
4291
4759
  };
4292
4760
  }
4293
4761
  async function deployPlatformWorker(config, secrets, options) {
4294
4762
  const directory = mkdtempSync(join(tmpdir(), "void-platform-deploy-"));
4295
4763
  const workerName = String(config.name);
4296
4764
  const accountId = String(config.account_id);
4765
+ const readRunner = options.readCloudflare ? platformWorkerApiReadRunner(accountId, workerName, options.readCloudflare, options.runner) : options.runner;
4297
4766
  let activatedVersionId = null;
4298
4767
  let activatedDeploymentId = null;
4299
4768
  let uploadedVersionId = null;
@@ -4305,13 +4774,22 @@ async function deployPlatformWorker(config, secrets, options) {
4305
4774
  let uploadConfig = withoutPlatformWorkerTrafficAttachments(nextTraffic.wranglerConfig);
4306
4775
  const nextTriggers = readTriggerConfigFromObject(nextTraffic.wranglerConfig);
4307
4776
  const contentHash = secrets === null ? platformWorkerUploadFingerprint(config, options.managedRoutes) : null;
4777
+ const reuseMatchingVersion = options.reuseMatchingVersion !== false;
4308
4778
  let previousTriggers = readTriggerConfigFromObject(previousTraffic.wranglerConfig);
4309
4779
  try {
4310
4780
  const configPath = join(directory, "wrangler.json");
4311
4781
  writeFileSync(configPath, `${JSON.stringify(uploadConfig, null, 2)}\n`, { mode: 384 });
4312
4782
  const reconcileExistingVersion = async (versionId, triggerConfig, runner, deploymentId) => {
4313
4783
  try {
4314
- assertPlatformTriggerStabilization(await stabilizeCloudflareVersionTriggers({
4784
+ if (deploymentId && options.readCloudflare ? await platformWorkerLiveTriggersMatch({
4785
+ accountId,
4786
+ workerName,
4787
+ desired: nextTriggers,
4788
+ readCloudflare: options.readCloudflare
4789
+ }) : false) {
4790
+ const beforeTraffic = await readPlatformWorkerDeployments(configPath, accountId, workerName, runner);
4791
+ if (beforeTraffic[0]?.id !== deploymentId || !deploymentRunsOnlyVersion(beforeTraffic[0], versionId)) throw new Error(`platform: Worker ${workerName} changed deployment lineage during trigger verification.`);
4792
+ } else assertPlatformTriggerStabilization(await stabilizeCloudflareVersionTriggers({
4315
4793
  configPath,
4316
4794
  knownTriggerConfigs: { [versionId]: nextTriggers },
4317
4795
  manageRoutes: false,
@@ -4344,16 +4822,15 @@ async function deployPlatformWorker(config, secrets, options) {
4344
4822
  throw new PlatformWorkerRecoveryIncompleteError(`platform: Worker ${workerName} traffic reconciliation failed: ${errorMessage(error)}${failures.length > 0 ? ` Safety recovery also failed: ${failures.join("; ")}` : ""}`, { cause: error });
4345
4823
  }
4346
4824
  };
4347
- if (options.workerExisted && contentHash && options.readCloudflare) {
4348
- const directRunner = platformWorkerApiReadRunner(accountId, workerName, options.readCloudflare, options.runner);
4825
+ if (options.workerExisted && contentHash && reuseMatchingVersion && options.readCloudflare) {
4349
4826
  let candidate;
4350
4827
  let changedDuringRead = false;
4351
4828
  try {
4352
- const before = await readPlatformWorkerDeployments(configPath, accountId, workerName, directRunner);
4829
+ const before = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
4353
4830
  const activeVersion = before[0]?.versions.length === 1 && before[0].versions[0]?.percentage === 100 ? before[0].versions[0].versionId : null;
4354
4831
  if (!activeVersion) throw new Error(`platform: Existing Worker ${workerName} has no single active production version.`);
4355
- const snapshot = await readPlatformWorkerVersionSnapshot(configPath, accountId, workerName, readPlatformWorkerOwnershipMarker(config), activeVersion, directRunner);
4356
- const after = await readPlatformWorkerDeployments(configPath, accountId, workerName, directRunner);
4832
+ const snapshot = await readPlatformWorkerVersionSnapshot(configPath, accountId, workerName, readPlatformWorkerOwnershipMarker(config), activeVersion, readRunner);
4833
+ const after = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
4357
4834
  changedDuringRead = before[0]?.id !== after[0]?.id || !deploymentRunsOnlyVersion(after[0], snapshot.versionId);
4358
4835
  if (!changedDuringRead && snapshot.contentHash === contentHash) candidate = {
4359
4836
  deploymentId: after[0].id,
@@ -4363,16 +4840,24 @@ async function deployPlatformWorker(config, secrets, options) {
4363
4840
  if (error instanceof Error && (error.message.includes("changed concurrently") || error.message.includes("does not belong to this installation"))) throw error;
4364
4841
  }
4365
4842
  if (changedDuringRead) throw new Error(`platform: Worker ${workerName} changed concurrently while its deployment lineage was inspected.`);
4366
- if (candidate && platformWorkerUploadFingerprint(config, options.managedRoutes) === contentHash) return await reconcileExistingVersion(candidate.snapshot.versionId, candidate.snapshot.triggerConfig, directRunner, candidate.deploymentId);
4843
+ if (candidate && platformWorkerUploadFingerprint(config, options.managedRoutes) === contentHash) {
4844
+ await options.onReadyForActivation?.();
4845
+ await options.waitForPriorWorker?.();
4846
+ return await reconcileExistingVersion(candidate.snapshot.versionId, candidate.snapshot.triggerConfig, readRunner, candidate.deploymentId);
4847
+ }
4367
4848
  }
4368
4849
  if (options.workerExisted) {
4369
- const previous = await readActivePlatformWorkerSnapshot(configPath, accountId, workerName, readPlatformWorkerOwnershipMarker(config), options.runner);
4850
+ const previous = await readActivePlatformWorkerSnapshot(configPath, accountId, workerName, readPlatformWorkerOwnershipMarker(config), readRunner);
4370
4851
  previousVersionId = previous.versionId;
4371
- const deployments = await readPlatformWorkerDeployments(configPath, accountId, workerName, options.runner);
4852
+ const deployments = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
4372
4853
  if (!deploymentRunsOnlyVersion(deployments[0], previousVersionId)) throw new Error(`platform: Worker ${workerName} changed concurrently while its deployment lineage was inspected.`);
4373
4854
  previousDeployment = deployments[0];
4374
4855
  previousDeploymentId = previousDeployment.id;
4375
- if (contentHash && previous.contentHash === contentHash && platformWorkerUploadFingerprint(config, options.managedRoutes) === contentHash) return await reconcileExistingVersion(previousVersionId, previous.triggerConfig, options.runner);
4856
+ if (contentHash && reuseMatchingVersion && previous.contentHash === contentHash && platformWorkerUploadFingerprint(config, options.managedRoutes) === contentHash) {
4857
+ await options.onReadyForActivation?.();
4858
+ await options.waitForPriorWorker?.();
4859
+ return await reconcileExistingVersion(previousVersionId, previous.triggerConfig, readRunner);
4860
+ }
4376
4861
  uploadConfig = withoutCompletedPlatformWorkerMigrations(uploadConfig, previous.migrationTag);
4377
4862
  writeFileSync(configPath, `${JSON.stringify(uploadConfig, null, 2)}\n`, { mode: 384 });
4378
4863
  previousTriggers = resolvePlatformPreviousTriggerConfig(withoutPlatformCustomDomainTriggers(previous.triggerConfig), previousTriggers, options.previousPublicEndpoints);
@@ -4388,44 +4873,50 @@ async function deployPlatformWorker(config, secrets, options) {
4388
4873
  };
4389
4874
  writeFileSync(configPath, `${JSON.stringify(uploadConfig, null, 2)}\n`, { mode: 384 });
4390
4875
  }
4391
- const argv = [
4392
- ...options.workerExisted ? ["versions", "upload"] : ["deploy"],
4393
- "--config",
4876
+ const operation = {
4877
+ type: options.workerExisted ? "versions.upload" : "worker.deploy",
4394
4878
  configPath,
4395
- "--strict",
4396
- "--experimental-provision=false",
4397
- "--experimental-auto-create=false",
4398
- ...!options.workerExisted ? ["--autoconfig=false"] : [],
4399
- "--message",
4400
- encodeCloudflareVersionMessage([], nextTriggers, uploadedContentHash ?? void 0)
4401
- ];
4879
+ message: encodeCloudflareVersionMessage([], nextTriggers, uploadedContentHash ?? void 0)
4880
+ };
4402
4881
  if (secrets) {
4403
4882
  const secretsPath = join(directory, "secrets.json");
4404
4883
  writeFileSync(secretsPath, JSON.stringify(secrets), { mode: 384 });
4405
- argv.push("--secrets-file", secretsPath);
4884
+ operation.secretsFilePath = secretsPath;
4406
4885
  }
4407
- const uploaded = options.workerExisted ? await options.runner(argv, accountId, directory, true) : await runPlatformWranglerForConditionalCreate(argv, accountId, directory, true, workerName, options.apiBaseUrl);
4408
- uploadedVersionId = parseUploadedPlatformWorkerVersion(`${uploaded.stdout}\n${uploaded.stderr}`);
4886
+ const releaseUploadSlot = await options.acquireUploadSlot?.();
4887
+ let uploaded;
4888
+ let uploadError;
4889
+ try {
4890
+ uploaded = options.workerExisted ? await options.runner(operation, accountId, directory, true) : await runPlatformWranglerForConditionalCreate(operation, accountId, directory, true, workerName, options.apiBaseUrl);
4891
+ assertCleanPlatformWrangler(uploaded, `${options.workerExisted ? "upload" : "bootstrap"} ${workerName}`);
4892
+ } catch (error) {
4893
+ uploadError = error;
4894
+ throw error;
4895
+ } finally {
4896
+ releaseUploadSlot?.(uploadError);
4897
+ }
4898
+ const uploadData = uploaded.data;
4899
+ uploadedVersionId = typeof uploadData?.versionId === "string" ? uploadData.versionId : null;
4409
4900
  if (!options.workerExisted) activatedVersionId = uploadedVersionId;
4410
- assertCleanPlatformWrangler(uploaded, `${options.workerExisted ? "upload" : "bootstrap"} ${workerName}`);
4411
4901
  if (!uploadedVersionId) throw new Error(`platform: Wrangler uploaded ${workerName} without reporting its immutable version id.`);
4412
4902
  await options.onStaged(previousVersionId, uploadedVersionId, previousTriggers);
4903
+ await options.onReadyForActivation?.();
4904
+ await options.waitForPriorWorker?.();
4413
4905
  if (options.workerExisted) {
4414
- const deploymentsBeforeActivation = await readPlatformWorkerDeployments(configPath, accountId, workerName, options.runner);
4906
+ const deploymentsBeforeActivation = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
4415
4907
  if (deploymentsBeforeActivation[0]?.id !== previousDeploymentId || !deploymentRunsOnlyVersion(deploymentsBeforeActivation[0], previousVersionId)) throw new Error(`platform: Worker ${workerName} changed concurrently before activation; refusing to overwrite it.`);
4416
4908
  activatedVersionId = uploadedVersionId;
4417
- assertCleanPlatformWrangler(await options.runner([
4418
- "versions",
4419
- "deploy",
4420
- `${uploadedVersionId}@100`,
4421
- "--config",
4909
+ assertCleanPlatformWrangler(await options.runner({
4910
+ type: "versions.deploy",
4422
4911
  configPath,
4423
- "--message",
4424
- `Void platform activation ${uploadedVersionId}`,
4425
- "--yes"
4426
- ], accountId, directory, true), `activate ${workerName}`);
4427
- }
4428
- const deploymentsAfterActivation = await readPlatformWorkerDeployments(configPath, accountId, workerName, options.runner);
4912
+ versions: [{
4913
+ versionId: uploadedVersionId,
4914
+ percentage: 100
4915
+ }],
4916
+ message: `Void platform activation ${uploadedVersionId}`
4917
+ }, accountId, directory, true), `activate ${workerName}`);
4918
+ }
4919
+ const deploymentsAfterActivation = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
4429
4920
  const lineage = classifyPlatformWorkerDeploymentLineage(deploymentsAfterActivation, uploadedVersionId, previousDeploymentId);
4430
4921
  if (lineage.kind === "concurrent-after") {
4431
4922
  activatedVersionId = null;
@@ -4445,7 +4936,7 @@ async function deployPlatformWorker(config, secrets, options) {
4445
4936
  expectedActiveDeploymentId: lineage.deploymentId,
4446
4937
  expectedActiveVersionId: uploadedVersionId,
4447
4938
  restoration: interveningDeployment,
4448
- runner: options.runner,
4939
+ runner: readRunner,
4449
4940
  workerName
4450
4941
  });
4451
4942
  } catch (recoveryError) {
@@ -4471,7 +4962,7 @@ async function deployPlatformWorker(config, secrets, options) {
4471
4962
  await options.removeCustomDomains();
4472
4963
  return cleanPlatformCommandResult();
4473
4964
  },
4474
- runWrangler: platformWorkerCommandRunner(accountId, directory, options.runner),
4965
+ runWrangler: platformWorkerCommandRunner(accountId, directory, readRunner),
4475
4966
  targetTriggerConfig: nextTriggers,
4476
4967
  targetVersionId: uploadedVersionId,
4477
4968
  workerName
@@ -4495,7 +4986,7 @@ async function deployPlatformWorker(config, secrets, options) {
4495
4986
  removeCustomDomains: options.removeCustomDomains,
4496
4987
  publishCustomDomains: options.publishCustomDomains,
4497
4988
  retainNewWorker: options.retainNewWorker,
4498
- runner: options.runner,
4989
+ runner: readRunner,
4499
4990
  workerExisted: options.workerExisted,
4500
4991
  workerName
4501
4992
  });
@@ -4522,7 +5013,7 @@ async function deployPlatformWorker(config, secrets, options) {
4522
5013
  removeCustomDomains: options.removeCustomDomains,
4523
5014
  publishCustomDomains: options.publishCustomDomains,
4524
5015
  retainNewWorker: options.retainNewWorker,
4525
- runner: options.runner,
5016
+ runner: readRunner,
4526
5017
  workerExisted: options.workerExisted,
4527
5018
  workerName
4528
5019
  }) : null,
@@ -4617,27 +5108,24 @@ async function rollbackPlatformWorker(input) {
4617
5108
  }
4618
5109
  }
4619
5110
  function platformWorkerCommandRunner(accountId, cwd, runner) {
4620
- return (args, options) => runner(args, accountId, cwd, options.capture);
5111
+ return (operation, options) => runner(operation, accountId, cwd, options.capture);
4621
5112
  }
4622
- async function runPlatformWrangler(args, accountId, cwd, capture) {
4623
- return runPlatformWranglerAtApiBase(args, accountId, cwd, capture);
5113
+ async function runPlatformWrangler(operation, accountId, cwd, capture) {
5114
+ return runPlatformWranglerAtApiBase(operation, accountId, cwd, capture);
4624
5115
  }
4625
5116
  const PLATFORM_WRANGLER_TIMEOUT_MS = 6e5;
4626
- async function runPlatformWranglerAtApiBase(args, accountId, cwd, capture, apiBaseUrl = resolvePinnedApiBaseUrl()) {
5117
+ async function runPlatformWranglerAtApiBase(operation, accountId, cwd, capture, apiBaseUrl = resolvePinnedApiBaseUrl(), parallelWorkerUpload = false) {
4627
5118
  const startedAt = performance.now();
4628
5119
  const result = await createDefaultRunWrangler(process.cwd(), {
4629
- launcher: resolveWranglerArgv(process.cwd()),
5120
+ launcher: resolveCloudflareOperationArgv(process.cwd()),
5121
+ parallelWorkerUpload,
4630
5122
  timeoutMs: PLATFORM_WRANGLER_TIMEOUT_MS
4631
- })([...args], {
5123
+ })(operation, {
4632
5124
  accountId,
4633
5125
  apiBaseUrl,
4634
- captureJson: capture,
4635
5126
  cwd
4636
5127
  });
4637
- if (process.env.VOID_PLATFORM_TIMINGS === "1") {
4638
- const operation = args[0] === "versions" || args[0] === "deployments" ? `${args[0]} ${args[1] ?? ""}` : args[0] ?? "unknown";
4639
- process.stderr.write(`[platform timing] wrangler ${operation} ${(performance.now() - startedAt).toFixed(0)} ms\n`);
4640
- }
5128
+ if (process.env.VOID_PLATFORM_TIMINGS === "1") process.stderr.write(`[platform timing] Cloudflare ${operation.type} ${(performance.now() - startedAt).toFixed(0)} ms\n`);
4641
5129
  const stdout = result.stdout || "";
4642
5130
  const stderr = result.stderr || "";
4643
5131
  if (!capture && result.status !== 0) {
@@ -4645,7 +5133,7 @@ async function runPlatformWranglerAtApiBase(args, accountId, cwd, capture, apiBa
4645
5133
  if (stderr) process.stderr.write(stderr);
4646
5134
  }
4647
5135
  return {
4648
- status: result.status,
5136
+ ...result,
4649
5137
  stderr,
4650
5138
  stdout
4651
5139
  };
@@ -4676,8 +5164,11 @@ async function startConditionalWorkerCreateProxy(upstreamApiBaseUrl, accountId,
4676
5164
  const upstreamBase = new URL(upstreamApiBaseUrl);
4677
5165
  if (upstreamBase.protocol !== "http:" && upstreamBase.protocol !== "https:") throw new Error("platform: Cloudflare API base URL must use HTTP or HTTPS.");
4678
5166
  const basePath = upstreamBase.pathname.replace(/\/+$/, "");
4679
- const expectedPath = `${basePath}/accounts/${encodeURIComponent(accountId)}/workers/scripts/${encodeURIComponent(workerName)}`;
5167
+ const accountPath = `${basePath}/accounts/${encodeURIComponent(accountId)}`;
5168
+ const expectedPath = `${accountPath}/workers/scripts/${encodeURIComponent(workerName)}`;
5169
+ const assetUploadPath = `${accountPath}/workers/assets/upload`;
4680
5170
  const state = {
5171
+ assetSessionStarted: false,
4681
5172
  attempted: false,
4682
5173
  conflictStatus: null,
4683
5174
  created: false,
@@ -4697,7 +5188,9 @@ async function startConditionalWorkerCreateProxy(upstreamApiBaseUrl, accountId,
4697
5188
  const isConditionalCreate = incoming.method === "PUT" && incomingUrl.pathname === expectedPath && supportedCreateQuery;
4698
5189
  const isTopLevelWorkerUpload = incoming.method === "PUT" && new RegExp(`^${basePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/accounts/[^/]+/workers/scripts/[^/]+$`).test(incomingUrl.pathname);
4699
5190
  const isReadOnly = incoming.method === "GET" || incoming.method === "HEAD" || incoming.method === "OPTIONS";
4700
- if (isTopLevelWorkerUpload && !isConditionalCreate || !isReadOnly && !isConditionalCreate && !state.created) {
5191
+ const isAssetUploadSession = incoming.method === "POST" && incomingUrl.pathname === `${expectedPath}/assets-upload-session` && incomingUrl.search === "";
5192
+ const isAssetUpload = incoming.method === "POST" && state.assetSessionStarted && (incomingUrl.pathname === assetUploadPath && incomingUrl.search === "?base64=true" || incomingUrl.search === "" && incomingUrl.pathname.startsWith(`${assetUploadPath}/`) && /^[0-9a-f]{32}$/.test(incomingUrl.pathname.slice(assetUploadPath.length + 1)));
5193
+ if (isTopLevelWorkerUpload && !isConditionalCreate || !isReadOnly && !isConditionalCreate && !isAssetUploadSession && !isAssetUpload && !state.created) {
4701
5194
  state.rejectedMutation = `${incoming.method ?? "UNKNOWN"} ${incomingUrl.pathname}${incomingUrl.pathname === expectedPath && !supportedCreateQuery ? " (unsupported Worker create query)" : ""}`;
4702
5195
  incoming.resume();
4703
5196
  outgoing.writeHead(409, { "Content-Type": "application/json" });
@@ -4710,6 +5203,7 @@ async function startConditionalWorkerCreateProxy(upstreamApiBaseUrl, accountId,
4710
5203
  state.attempted = true;
4711
5204
  headers["if-none-match"] = "*";
4712
5205
  }
5206
+ if (isAssetUploadSession) state.assetSessionStarted = true;
4713
5207
  const upstreamUrl = new URL(`${incomingUrl.pathname}${incomingUrl.search}`, upstreamBase.origin);
4714
5208
  const request$2 = (upstreamUrl.protocol === "https:" ? request$1 : request)(upstreamUrl, {
4715
5209
  headers,
@@ -4760,48 +5254,30 @@ async function withConditionalPlatformWorkerCreate(accountId, workerName, operat
4760
5254
  if (proxy.state.conflictStatus !== null) throw new Error(`platform: Worker ${workerName} was created concurrently (HTTP ${proxy.state.conflictStatus}); no existing Worker was changed. Re-run to inspect the collision.`);
4761
5255
  return result;
4762
5256
  }
4763
- async function runPlatformWranglerForConditionalCreate(args, accountId, cwd, capture, workerName, upstreamApiBaseUrl) {
4764
- return withConditionalPlatformWorkerCreate(accountId, workerName, (apiBaseUrl) => runPlatformWranglerAtApiBase(args, accountId, cwd, capture, apiBaseUrl), upstreamApiBaseUrl);
5257
+ async function runPlatformWranglerForConditionalCreate(operation, accountId, cwd, capture, workerName, upstreamApiBaseUrl) {
5258
+ return withConditionalPlatformWorkerCreate(accountId, workerName, (apiBaseUrl) => runPlatformWranglerAtApiBase(operation, accountId, cwd, capture, apiBaseUrl), upstreamApiBaseUrl);
4765
5259
  }
4766
5260
  async function readActivePlatformWorkerVersion(configPath, accountId, workerName, runner = runPlatformWrangler) {
4767
- const result = await runner([
4768
- "deployments",
4769
- "status",
4770
- "--config",
5261
+ const result = await runner({
5262
+ type: "deployments.status",
4771
5263
  configPath,
4772
- "--name",
4773
- workerName,
4774
- "--json"
4775
- ], accountId, process.cwd(), true);
5264
+ workerName
5265
+ }, accountId, process.cwd(), true);
4776
5266
  if (result.status !== 0) assertCleanPlatformWrangler(result, `read active version for ${workerName}`);
4777
- let value;
4778
- try {
4779
- value = JSON.parse(result.stdout);
4780
- } catch {
4781
- throw new Error(`platform: Wrangler returned malformed deployment status for ${workerName}.`);
4782
- }
5267
+ const value = result.data;
4783
5268
  if (!value || typeof value !== "object" || !Array.isArray(value.versions)) throw new Error(`platform: Wrangler returned malformed deployment status for ${workerName}.`);
4784
5269
  const active = value.versions.filter((entry) => entry && typeof entry === "object" && Number(entry.percentage) === 100);
4785
5270
  if (active.length !== 1 || typeof active[0].version_id !== "string") return null;
4786
5271
  return active[0].version_id;
4787
5272
  }
4788
5273
  async function readPlatformWorkerDeployments(configPath, accountId, workerName, runner = runPlatformWrangler) {
4789
- const result = await runner([
4790
- "deployments",
4791
- "list",
4792
- "--config",
5274
+ const result = await runner({
5275
+ type: "deployments.list",
4793
5276
  configPath,
4794
- "--name",
4795
- workerName,
4796
- "--json"
4797
- ], accountId, process.cwd(), true);
5277
+ workerName
5278
+ }, accountId, process.cwd(), true);
4798
5279
  if (result.status !== 0) assertCleanPlatformWrangler(result, `read deployment lineage for ${workerName}`);
4799
- let value;
4800
- try {
4801
- value = JSON.parse(result.stdout);
4802
- } catch {
4803
- throw new Error(`platform: Wrangler returned malformed deployment history for ${workerName}.`);
4804
- }
5280
+ const value = result.data;
4805
5281
  if (!Array.isArray(value)) throw new Error(`platform: Wrangler returned malformed deployment history for ${workerName}.`);
4806
5282
  return value.map((entry, index) => {
4807
5283
  if (!entry || typeof entry !== "object" || typeof entry.id !== "string" || typeof entry.created_on !== "string" || !Array.isArray(entry.versions)) throw new Error(`platform: Wrangler returned malformed deployment history for ${workerName}.`);
@@ -4836,16 +5312,15 @@ async function restorePlatformWorkerDeployment(input) {
4836
5312
  if (restorationVersions.length === 0 || restorationVersions.length > 2 || restorationVersions.some((version, index) => version.percentage > 100 || restorationVersions.findIndex((entry) => entry.versionId === version.versionId) !== index) || Math.abs(totalPercentage - 100) > .001) throw new Error(`Worker ${input.workerName} deployment ${input.restoration.id} has an unsupported traffic distribution.`);
4837
5313
  const before = await readPlatformWorkerDeployments(input.configPath, input.accountId, input.workerName, input.runner);
4838
5314
  if (before[0]?.id !== input.expectedActiveDeploymentId || !deploymentRunsOnlyVersion(before[0], input.expectedActiveVersionId) || before[1]?.id !== input.restoration.id || !samePlatformWorkerTraffic(before[1], input.restoration)) throw new Error(`Worker ${input.workerName} changed concurrently before deployment restoration; refusing to overwrite the newer deployment.`);
4839
- assertCleanPlatformWrangler(await input.runner([
4840
- "versions",
4841
- "deploy",
4842
- ...restorationVersions.map((version) => `${version.versionId}@${version.percentage}`),
4843
- "--config",
4844
- input.configPath,
4845
- "--message",
4846
- `Void platform restoration ${input.restoration.id}`,
4847
- "--yes"
4848
- ], input.accountId, input.directory, true), `restore deployment ${input.restoration.id}`);
5315
+ assertCleanPlatformWrangler(await input.runner({
5316
+ type: "versions.deploy",
5317
+ configPath: input.configPath,
5318
+ versions: restorationVersions.map((version) => ({
5319
+ versionId: version.versionId,
5320
+ percentage: version.percentage
5321
+ })),
5322
+ message: `Void platform restoration ${input.restoration.id}`
5323
+ }, input.accountId, input.directory, true), `restore deployment ${input.restoration.id}`);
4849
5324
  const after = await readPlatformWorkerDeployments(input.configPath, input.accountId, input.workerName, input.runner);
4850
5325
  if (!samePlatformWorkerTraffic(after[0], input.restoration) || after[1]?.id !== input.expectedActiveDeploymentId) throw new Error(`Worker ${input.workerName} deployment restoration was superseded or did not restore deployment ${input.restoration.id}.`);
4851
5326
  }
@@ -4901,23 +5376,14 @@ async function readActivePlatformWorkerSnapshot(configPath, accountId, workerNam
4901
5376
  });
4902
5377
  }
4903
5378
  async function readPlatformWorkerVersionSnapshot(configPath, accountId, workerName, expectedOwnershipMarker, versionId, runner, validateCurrentVersion) {
4904
- const result = await runner([
4905
- "versions",
4906
- "view",
5379
+ const result = await runner({
5380
+ type: "versions.view",
4907
5381
  versionId,
4908
- "--config",
4909
5382
  configPath,
4910
- "--name",
4911
- workerName,
4912
- "--json"
4913
- ], accountId, process.cwd(), true);
5383
+ workerName
5384
+ }, accountId, process.cwd(), true);
4914
5385
  assertCleanPlatformWrangler(result, `read rollback metadata for ${workerName}`);
4915
- let value;
4916
- try {
4917
- value = JSON.parse(result.stdout);
4918
- } catch {
4919
- throw new Error(`platform: Wrangler returned malformed version metadata for ${workerName}.`);
4920
- }
5386
+ const value = result.data;
4921
5387
  if (!value || typeof value !== "object") throw new Error(`platform: Wrangler returned malformed version metadata for ${workerName}.`);
4922
5388
  const resources = value.resources;
4923
5389
  const bindings = resources && typeof resources === "object" && !Array.isArray(resources) ? resources.bindings : null;
@@ -4943,11 +5409,8 @@ function withoutCompletedPlatformWorkerMigrations(config, activeMigrationTag) {
4943
5409
  delete result.migrations;
4944
5410
  return result;
4945
5411
  }
4946
- function parseUploadedPlatformWorkerVersion(output) {
4947
- return /(?:Worker|Current) Version ID:\s*([0-9a-f]{8}-[0-9a-f-]{27})/i.exec(output)?.[1] ?? null;
4948
- }
4949
5412
  function assertCleanPlatformWrangler(result, operation) {
4950
- if (result.status !== 0) throw new Error(`platform: Wrangler failed to ${operation} (${result.stderr.trim() || `exit ${result.status}`}).`);
5413
+ if (result.status !== 0 || result.signal || result.error) throw new Error(`platform: Wrangler failed to ${operation} (${result.stderr.trim() || `exit ${result.status}`}).`);
4951
5414
  }
4952
5415
  function cleanPlatformCommandResult() {
4953
5416
  return {
@@ -5068,18 +5531,18 @@ function parseWorkerCutoverSnapshot(value) {
5068
5531
  return null;
5069
5532
  }
5070
5533
  }
5071
- function interruptedWorkerCutover(manifest) {
5534
+ function workerCutoverGenerations(manifest) {
5072
5535
  const generations = /* @__PURE__ */ new Map();
5073
5536
  for (const step of manifest.steps) {
5074
5537
  const resolution = /^worker-cutover:(\d+):(committed|restored)$/.exec(step.key);
5075
5538
  if (resolution) {
5076
5539
  const generation = Number(resolution[1]);
5077
5540
  const entry = generations.get(generation) ?? {
5078
- resolved: false,
5541
+ resolution: null,
5079
5542
  staged: /* @__PURE__ */ new Map(),
5080
5543
  unsafeTraffic: /* @__PURE__ */ new Set()
5081
5544
  };
5082
- entry.resolved = true;
5545
+ entry.resolution = resolution[2];
5083
5546
  generations.set(generation, entry);
5084
5547
  continue;
5085
5548
  }
@@ -5087,7 +5550,7 @@ function interruptedWorkerCutover(manifest) {
5087
5550
  if (unsafeTraffic) {
5088
5551
  const generation = Number(unsafeTraffic[1]);
5089
5552
  const entry = generations.get(generation) ?? {
5090
- resolved: false,
5553
+ resolution: null,
5091
5554
  staged: /* @__PURE__ */ new Map(),
5092
5555
  unsafeTraffic: /* @__PURE__ */ new Set()
5093
5556
  };
@@ -5101,14 +5564,17 @@ function interruptedWorkerCutover(manifest) {
5101
5564
  const snapshot = parseWorkerCutoverSnapshot(step.resultHash);
5102
5565
  if (!snapshot) throw new Error(`platform: Invalid Worker cutover checkpoint ${step.key}.`);
5103
5566
  const entry = generations.get(generation) ?? {
5104
- resolved: false,
5567
+ resolution: null,
5105
5568
  staged: /* @__PURE__ */ new Map(),
5106
5569
  unsafeTraffic: /* @__PURE__ */ new Set()
5107
5570
  };
5108
5571
  entry.staged.set(staged[2], snapshot);
5109
5572
  generations.set(generation, entry);
5110
5573
  }
5111
- const unresolved = [...generations.entries()].filter(([, entry]) => !entry.resolved && entry.staged.size > 0).sort(([left], [right]) => right - left)[0];
5574
+ return generations;
5575
+ }
5576
+ function interruptedWorkerCutover(manifest) {
5577
+ const unresolved = [...workerCutoverGenerations(manifest).entries()].filter(([, entry]) => entry.resolution === null && entry.staged.size > 0).sort(([left], [right]) => right - left)[0];
5112
5578
  return unresolved ? {
5113
5579
  generation: unresolved[0],
5114
5580
  staged: unresolved[1].staged,
@@ -5122,8 +5588,8 @@ async function verifyPlatformWorkerCutoverVersions(manifest, generation, configs
5122
5588
  "tail",
5123
5589
  "dispatch"
5124
5590
  ]) {
5125
- const cutover = interruptedWorkerCutover(manifest);
5126
- if (cutover?.generation !== generation || cutover.staged.size !== workerKeys.length) throw new Error(`platform: Worker cutover generation ${generation} is missing a complete immutable-version checkpoint.`);
5591
+ const cutover = workerCutoverGenerations(manifest).get(generation);
5592
+ if (!cutover || cutover.resolution === "restored" || cutover.staged.size !== workerKeys.length) throw new Error(`platform: Worker cutover generation ${generation} is missing a complete immutable-version checkpoint.`);
5127
5593
  const directory = mkdtempSync(join(tmpdir(), "void-platform-cutover-verify-"));
5128
5594
  try {
5129
5595
  for (const key of workerKeys) {
@@ -5214,7 +5680,7 @@ async function verifyPlatformHealth(apiUrl, installationId, proxyUrl, applicatio
5214
5680
  redirect: "manual",
5215
5681
  signal: AbortSignal.timeout(3e4)
5216
5682
  });
5217
- const dispatchUrl = dispatchOrigin ?? `https://test.${applicationBaseDomain}`;
5683
+ const dispatchUrl = dispatchOrigin ?? `https://${PLATFORM_HEALTH_HOSTNAME_LABEL}.${applicationBaseDomain}`;
5218
5684
  const [health, discovery, proxy, dispatch, emailGateway] = await Promise.all([
5219
5685
  request(`${apiUrl}/health`),
5220
5686
  request(`${apiUrl}/.well-known/void-platform`),
@@ -5259,9 +5725,8 @@ async function verifyPlatformSandboxDrain(apiUrl, healthProbeToken, fetcher = fe
5259
5725
  if (status.version !== 1 || !validState || typeof status.complete !== "boolean" || !validPhase || status.generation !== null && (typeof status.generation !== "string" || status.generation.length === 0) || status.updatedAt !== null && (typeof status.updatedAt !== "string" || !Number.isFinite(Date.parse(status.updatedAt))) || status.state === "required" && (status.complete || status.generation !== null || status.updatedAt !== null || status.phase !== null) || status.state === "draining" && (status.complete || typeof status.generation !== "string" || typeof status.updatedAt !== "string" || status.phase === null) || status.state === "complete" && (!status.complete || typeof status.generation !== "string" || typeof status.updatedAt !== "string" || status.phase === null)) throw new Error("platform: Sandbox drain probe returned an invalid DB-backed protocol v1 document. Traffic remains disabled.");
5260
5726
  return status.state === "complete";
5261
5727
  }
5262
- async function bootstrapPlatformSharedEmail(apiUrl, healthProbeToken, fetcher = fetch) {
5263
- const response = await fetcher(`${apiUrl}/internal/platform/email-bootstrap`, {
5264
- method: "POST",
5728
+ async function verifyPlatformDeploymentExecutionDrain(apiUrl, healthProbeToken, installationId, fetcher = fetch) {
5729
+ const response = await fetcher(`${apiUrl}/__void/platform/deployment-execution`, {
5265
5730
  headers: {
5266
5731
  "X-Void-Platform-Probe": healthProbeToken,
5267
5732
  ...cfAccessHeaders(process.env, apiUrl)
@@ -5269,18 +5734,104 @@ async function bootstrapPlatformSharedEmail(apiUrl, healthProbeToken, fetcher =
5269
5734
  signal: AbortSignal.timeout(3e4),
5270
5735
  redirect: "manual"
5271
5736
  });
5737
+ if (!response.ok) throw new Error(`platform: Deployment execution drain probe failed (HTTP ${response.status}). Keep the current runtime until its durable deployment operations have drained.`);
5272
5738
  let value;
5273
5739
  try {
5274
5740
  value = await response.json();
5275
5741
  } catch {
5276
- throw new Error(`platform: Shared email bootstrap failed at ${apiUrl} (HTTP ${response.status}). Traffic remains disabled.`);
5742
+ throw new Error("platform: Deployment execution drain probe returned an invalid document.");
5277
5743
  }
5278
- const status = value && typeof value === "object" && !Array.isArray(value) ? value : null;
5279
- if (!response.ok || status?.version !== 1 || status.state !== "ready" || typeof status.operationId !== "string" || status.operationId.length === 0) {
5280
- const detail = status && typeof status.operationId === "string" ? ` Operation ${status.operationId}: ${typeof status.operationStatus === "string" ? status.operationStatus : "incomplete"}.` : status && typeof status.operationStatus === "string" ? ` Operation status: ${status.operationStatus}.` : "";
5744
+ const status = value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
5745
+ if (status?.protocolVersion !== 1 || status.installationId !== installationId || typeof status.admissionClosed !== "boolean" || !Number.isSafeInteger(status.pendingDeployments) || Number(status.pendingDeployments) < 0) throw new Error("platform: Deployment execution drain probe returned an invalid document.");
5746
+ if (!status.admissionClosed || status.pendingDeployments !== 0) throw new Error("platform: Durable deployment operations are still admitted or unfinished. Keep the current runtime and retry this command after they finish.");
5747
+ }
5748
+ async function bootstrapPlatformSharedEmail(apiUrl, healthProbeToken, fetcher = fetch) {
5749
+ const deadline = Date.now() + 3e5;
5750
+ let operationId = null;
5751
+ const timedOut = () => /* @__PURE__ */ new Error(`platform: Shared email bootstrap did not finish within five minutes at ${apiUrl}.${operationId ? ` Operation ${operationId} remains incomplete.` : ""} Traffic remains disabled. Retry the installation's repair or resume command to reconcile the existing email operation.`);
5752
+ const waitForProgress = async () => {
5753
+ const remaining = deadline - Date.now();
5754
+ if (remaining <= 0) throw timedOut();
5755
+ await new Promise((resolve) => setTimeout(resolve, Math.min(1e3, remaining)));
5756
+ };
5757
+ let response;
5758
+ let value;
5759
+ let status;
5760
+ for (;;) {
5761
+ const remaining = deadline - Date.now();
5762
+ if (remaining <= 0) throw timedOut();
5763
+ const signal = AbortSignal.timeout(Math.min(12e4, remaining));
5764
+ try {
5765
+ response = await fetcher(`${apiUrl}/internal/platform/email-bootstrap`, {
5766
+ method: "POST",
5767
+ headers: {
5768
+ "X-Void-Platform-Probe": healthProbeToken,
5769
+ ...cfAccessHeaders(process.env, apiUrl)
5770
+ },
5771
+ signal,
5772
+ redirect: "manual"
5773
+ });
5774
+ } catch (error) {
5775
+ if (signal.aborted || error instanceof Error && error.name === "TimeoutError") {
5776
+ await waitForProgress();
5777
+ continue;
5778
+ }
5779
+ throw new Error(`platform: Shared email bootstrap request failed at ${apiUrl}. Traffic remains disabled. Retry the installation's repair or resume command to reconcile the existing email operation.`);
5780
+ }
5781
+ try {
5782
+ value = await response.json();
5783
+ } catch (error) {
5784
+ if (signal.aborted || error instanceof Error && error.name === "TimeoutError") {
5785
+ await waitForProgress();
5786
+ continue;
5787
+ }
5788
+ throw new Error(`platform: Shared email bootstrap failed at ${apiUrl} (HTTP ${response.status}). Traffic remains disabled.`);
5789
+ }
5790
+ status = value && typeof value === "object" && !Array.isArray(value) ? value : null;
5791
+ const observedOperationId = typeof status?.operationId === "string" && /^[A-Za-z0-9_-]{1,100}$/.test(status.operationId) ? status.operationId : null;
5792
+ if (status?.version === 1 && observedOperationId) {
5793
+ if (operationId && operationId !== observedOperationId) throw new Error("platform: Shared email bootstrap operation identity changed while waiting. Traffic remains disabled. Inspect the installation before retrying.");
5794
+ operationId = observedOperationId;
5795
+ }
5796
+ if ([
5797
+ 200,
5798
+ 202,
5799
+ 503
5800
+ ].includes(response.status) && status?.version === 1 && status.state === "incomplete" && observedOperationId && (status.operationStatus === "pending" || status.operationStatus === "running") && status.failureCode == null) {
5801
+ await waitForProgress();
5802
+ continue;
5803
+ }
5804
+ break;
5805
+ }
5806
+ if (!response.ok || status?.version !== 1 || status.state !== "ready" || operationId === null || status.operationId !== operationId || status.operationStatus != null && status.operationStatus !== "completed" || status.failureCode != null) {
5807
+ const code = (value) => typeof value === "string" && /^[a-z][a-z0-9_-]{0,63}$/.test(value) ? value : null;
5808
+ const operationStatus = code(status?.operationStatus);
5809
+ const operationId = typeof status?.operationId === "string" && /^[A-Za-z0-9_-]{1,100}$/.test(status.operationId) ? status.operationId : null;
5810
+ const details = operationId ? [`Operation ${operationId}: ${operationStatus ?? "incomplete"}.`] : operationStatus ? [`Operation status: ${operationStatus}.`] : [];
5811
+ const currentStep = code(status?.currentStep);
5812
+ const failureCode = code(status?.failureCode);
5813
+ if (currentStep) details.push(`Step: ${currentStep}.`);
5814
+ if (failureCode) details.push(`Failure: ${failureCode}.`);
5815
+ let domain = null;
5816
+ if (typeof status?.domain === "string") try {
5817
+ domain = normalizeApplicationDomain(status.domain);
5818
+ } catch {}
5819
+ const inboundStatus = code(status?.inboundStatus);
5820
+ if (domain) details.push(`Domain: ${domain}${inboundStatus ? ` (inbound: ${inboundStatus})` : ""}.`);
5821
+ const mxDetail = domain ? `MX records for ${domain} do not route mail to Cloudflare Email Routing` : null;
5822
+ const safeDetails = [
5823
+ ...domain ? [mxDetail, `enabled explicit Cloudflare email rules conflict with ${domain}`] : [],
5824
+ "zone catch-all does not point to this ingress",
5825
+ "another enabled catch-all owns this zone"
5826
+ ];
5827
+ if (typeof status?.failureDetail === "string" && safeDetails.includes(status.failureDetail)) {
5828
+ details.push(`${status.failureDetail}.`);
5829
+ if (currentStep === "inbound_probe" && status.failureDetail === mxDetail) details.push(`Enable Email Routing for ${domain} in the Cloudflare dashboard, then resume this installation.`);
5830
+ }
5831
+ const detail = details.length ? ` ${details.join(" ")}` : "";
5281
5832
  throw new Error(`platform: Shared email bootstrap did not reach inbound readiness at ${apiUrl} (HTTP ${response.status}).${detail} Traffic remains disabled.`);
5282
5833
  }
5283
- return status.operationId;
5834
+ return operationId;
5284
5835
  }
5285
5836
  function createPlatformHealthProbeToken(ownershipNonce) {
5286
5837
  return createHash("sha256").update("void-platform-health-probe:v1\0").update(ownershipNonce).digest("base64url");
@@ -5291,7 +5842,7 @@ function answer(value) {
5291
5842
  if (isCancel(value) || typeof value === "symbol") throw new Error("platform: Cancelled.");
5292
5843
  return value;
5293
5844
  }
5294
- async function selectInstallationAuthentication(file, recorded) {
5845
+ async function selectInstallationAuthentication(file, recorded, choices = {}) {
5295
5846
  if (file) {
5296
5847
  const input = JSON.parse(await readFile(file, "utf8"));
5297
5848
  if (!input || typeof input !== "object" || Array.isArray(input) || !("connections" in input) || !Array.isArray(input.connections) || !input.connections.length || input.connections.length > 32) throw new Error("platform: An authentication file must define between 1 and 32 connections.");
@@ -5310,9 +5861,12 @@ async function selectInstallationAuthentication(file, recorded) {
5310
5861
  file: selected
5311
5862
  };
5312
5863
  }
5313
- if (recorded) return { authentication: recorded };
5314
- if (!process.stdin.isTTY) return { authentication: { providers: ["github"] } };
5315
- const providers = answer(await multiselect({
5864
+ if (recorded) {
5865
+ if (choices.loginMethods && JSON.stringify(choices.loginMethods) !== JSON.stringify(recorded.providers) || choices.accessProtection !== void 0 && choices.accessProtection !== !!recorded.accessProtection) throw new Error("platform: Resume with the recorded login methods and protection settings.");
5866
+ return { authentication: recorded };
5867
+ }
5868
+ if (!process.stdin.isTTY && !choices.loginMethods && choices.accessProtection === void 0) return { authentication: { providers: ["github"] } };
5869
+ const providers = choices.loginMethods ?? (!process.stdin.isTTY ? ["github"] : answer(await multiselect({
5316
5870
  message: "Platform login methods:",
5317
5871
  initialValues: ["github"],
5318
5872
  required: true,
@@ -5334,11 +5888,11 @@ async function selectInstallationAuthentication(file, recorded) {
5334
5888
  label: "Cloudflare Access login"
5335
5889
  }
5336
5890
  ]
5337
- }));
5338
- const protection = answer(await confirm({
5891
+ })));
5892
+ const protection = choices.accessProtection ?? (!process.stdin.isTTY ? false : answer(await confirm({
5339
5893
  message: "Also protect the platform UI and APIs with Cloudflare Access?",
5340
5894
  initialValue: false
5341
- }));
5895
+ })));
5342
5896
  return { authentication: {
5343
5897
  providers,
5344
5898
  ...protection || providers.length !== 1 || providers[0] !== "github" ? { configurable: true } : {},
@@ -5375,6 +5929,7 @@ async function collectInstallationAuthentication(selection, plan, checkpoint, sa
5375
5929
  const accessEntry = entries.find((entry) => entry.configuration.kind === "cloudflare-access");
5376
5930
  const prepared = access && (selection.authentication.accessProtection || accessEntry && (!selection.file || selection.file.cloudflareAccess)) ? await preparePlatformAccessSetup({
5377
5931
  plan,
5932
+ ...platformDashboardUrl(plan.deployment) ? { additionalOrigins: [platformDashboardUrl(plan.deployment)] } : {},
5378
5933
  ownershipNonce: access.ownershipNonce,
5379
5934
  protection: !!selection.authentication.accessProtection,
5380
5935
  ...accessEntry ? { login: {
@@ -5582,13 +6137,13 @@ function platformSetupFieldHelp(key) {
5582
6137
  switch (key) {
5583
6138
  case "databaseUrl": return {
5584
6139
  placeholder: "postgresql://user:pw@…/database",
5585
- description: "Stores platform users, projects, and deployments; use an empty hosted database, e.g. " + platformSetupLink({
6140
+ description: "Stores platform users, projects, and deployments. Use an empty hosted database, e.g. " + platformSetupLink({
5586
6141
  title: "Neon",
5587
6142
  url: "https://neon.com/"
5588
6143
  }) + ". " + docs("prerequisites", "platform-database", "Database setup")
5589
6144
  };
5590
- case "jwtSecret": return { description: "Signs platform login tokens. Run openssl rand -base64 32; save the key in your password manager. " + docs("credentials", "signing-and-encryption-keys", "Key setup") };
5591
- case "projectSecretKey": return { description: "Encrypts app secrets. Run openssl rand -base64 32 separately; save this key for recovery. " + docs("credentials", "signing-and-encryption-keys", "Key setup") };
6145
+ case "jwtSecret": return { description: "Signs platform login tokens. Run openssl rand -base64 32. Save the key in your password manager. " + docs("credentials", "signing-and-encryption-keys", "Key setup") };
6146
+ case "projectSecretKey": return { description: "Encrypts app secrets. Run openssl rand -base64 32 separately. Save this key for recovery. " + docs("credentials", "signing-and-encryption-keys", "Key setup") };
5592
6147
  default: return;
5593
6148
  }
5594
6149
  }
@@ -5706,8 +6261,8 @@ function platformSetupPages(plan) {
5706
6261
  type: "account"
5707
6262
  }),
5708
6263
  instructions: [
5709
- "Select Object Read & Write (the form defaults to read-only).",
5710
- "Keep Apply to all buckets in this account (including newly created buckets) selected.",
6264
+ "Select Object Read & Write.",
6265
+ "Keep Apply to all buckets in this account selected.",
5711
6266
  "Create the token, then copy the Access Key ID and Secret Access Key into the CLI."
5712
6267
  ].join("\n")
5713
6268
  }
@@ -5716,16 +6271,20 @@ function platformSetupPages(plan) {
5716
6271
  function formatPlatformSetupPlan(plan) {
5717
6272
  const pages = platformSetupPages(plan);
5718
6273
  return [
5719
- "Credentials needed (paste them into the installer; keep a password-manager copy):",
6274
+ "Credentials needed during installation:",
6275
+ "PostgreSQL database URL",
5720
6276
  platformSetupLink(pages.runtime),
5721
- pages.runtime.instructions,
5722
- "",
5723
6277
  platformSetupLink(pages.r2),
5724
- pages.r2.instructions,
6278
+ ...plan.deployment.authentication.providers.includes("github") ? [platformSetupLink(pages.github)] : [],
6279
+ ...plan.deployment.authentication.providers.filter((provider) => provider !== "github").map((provider) => (provider === "google" ? "Google OAuth" : provider === "oidc" ? "OIDC" : "Cloudflare Access OIDC") + " client ID and secret"),
6280
+ ...plan.deployment.authentication.accessProtection ? ["Cloudflare Access application and installer credentials"] : [],
6281
+ platformSetupLink({
6282
+ title: "JWT signing key and project encryption key",
6283
+ url: "https://void.cloud/guide/platform/installation/credentials#signing-and-encryption-keys"
6284
+ }),
5725
6285
  "",
5726
- ...plan.deployment.authentication.providers.includes("github") ? [platformSetupLink(pages.github), pages.github.instructions] : [],
5727
- ...plan.deployment.authentication.providers.filter((provider) => provider !== "github").map((provider) => `${provider === "google" ? "Google OAuth client" : provider === "oidc" ? "OIDC connection" : "Cloudflare Access OIDC application"}: client ID and secret; callback ${plan.apiUrl}/auth/callback.`),
5728
- ...plan.deployment.authentication.accessProtection ? ["Cloudflare Access protection: configure API and proxy origins and admitted installer/CI credentials."] : []
6286
+ "Login callback: " + plan.apiUrl + "/auth/callback",
6287
+ "Installation collects and validates missing credentials after you confirm the plan."
5729
6288
  ].join("\n");
5730
6289
  }
5731
6290
  function formatPlatformInstallCommand(options) {
@@ -5753,10 +6312,18 @@ function formatPlatformInstallCommand(options) {
5753
6312
  flag("--application-domain", options.applicationDomain);
5754
6313
  flag("--zone", options.zoneName);
5755
6314
  if (options.dedicatedZone) parts.push("--dedicated-zone");
6315
+ else parts.push("--no-dedicated-zone");
5756
6316
  }
5757
6317
  flag("--control-plane-domain", options.controlPlaneDomain);
6318
+ if (!options.controlPlaneDomain) parts.push("--no-control-plane-domain");
5758
6319
  }
6320
+ flag("--hyperdrive", options.hyperdrive ?? "");
5759
6321
  flag("--auth-config", options.authConfig ?? "");
6322
+ if (!options.authConfig && !options.resumeId && options.authentication) {
6323
+ flag("--login-methods", options.authentication.providers.join(","));
6324
+ parts.push(options.authentication.accessProtection ? "--access-protection" : "--no-access-protection");
6325
+ }
6326
+ flag("--dashboard-url", options.dashboardUrl ?? "");
5760
6327
  flag("--runtime", options.runtime);
5761
6328
  if (options.yes) parts.push("--yes");
5762
6329
  return parts.join(" ");
@@ -5816,14 +6383,14 @@ function resolvePlatformInstallDatabaseUrl(checkpoint, manifest = null) {
5816
6383
  }
5817
6384
  async function runPlatformLifecycleCommand(args) {
5818
6385
  if (args.subcommand === "domain") {
5819
- const { runPlatformDomainCommand } = await import("./platform-domain-CU1JtJkW.mjs");
6386
+ const { runPlatformDomainCommand } = await import("./platform-domain-BNkcz0OB.mjs");
5820
6387
  await runPlatformDomainCommand(args);
5821
6388
  return;
5822
6389
  }
5823
6390
  if (args.subcommand === "discover") {
5824
6391
  const client = new PlatformCloudflareClient((await resolvePlatformCloudflareCredential()).token);
5825
6392
  const accountId = await selectAccount(await readAccounts(client), args.account);
5826
- const { recoverPlatformInstallations } = await import("./platform-recovery-dvUaptLr.mjs");
6393
+ const { recoverPlatformInstallations } = await import("./platform-recovery-D6KSpuFm.mjs");
5827
6394
  const recovered = await recoverPlatformInstallations(client, accountId, void 0, args.selector);
5828
6395
  if (recovered.length === 0) {
5829
6396
  log.info(`No recoverable Void platform installations were found in ${accountId}.`);
@@ -5833,7 +6400,7 @@ async function runPlatformLifecycleCommand(args) {
5833
6400
  return;
5834
6401
  }
5835
6402
  if (args.subcommand !== "install") {
5836
- const { runInstalledPlatformCommand } = await import("./platform-management-C8tt6D8h.mjs");
6403
+ const { runInstalledPlatformCommand } = await import("./platform-management-CnyTdcWX.mjs");
5837
6404
  await runInstalledPlatformCommand(args);
5838
6405
  return;
5839
6406
  }
@@ -5948,9 +6515,9 @@ async function runPlatformLifecycleCommand(args) {
5948
6515
  const applicationDomain = recorded?.application.baseDomain ?? (workersDev ? "" : normalizeApplicationDomain(args.applicationDomain || await promptText("Application base domain:", "example.app")));
5949
6516
  const zoneName = recorded?.application.zoneName ?? (workersDev ? "" : normalizeApplicationDomain(args.zone || await promptText("Cloudflare zone:", applicationDomain)));
5950
6517
  const displayName = recorded?.displayName ?? (args.displayName || await promptText("Platform display name:", `${installationSlug} Void`));
5951
- const controlPlaneDomain = recorded ? recorded.controlPlane.apiHostname ?? "" : args.controlPlaneDomain || (process.stdin.isTTY ? await promptText("Optional custom API hostname (leave empty for workers.dev):", "", true) : "");
6518
+ const controlPlaneDomain = recorded ? recorded.controlPlane.apiHostname ?? "" : args.controlPlaneDomain || (process.stdin.isTTY && !args.noControlPlaneDomain ? await promptText("Custom API hostname. Leave empty for workers.dev:", "", true) : "");
5952
6519
  let dedicatedZone = recorded?.application.dedicatedZone ?? args.dedicatedZone;
5953
- if (!workersDev && !recorded && !args.dedicatedZone && applicationDomain === zoneName && process.stdin.isTTY) {
6520
+ if (!workersDev && !recorded && !args.dedicatedZone && !args.dedicatedZoneSpecified && applicationDomain === zoneName && process.stdin.isTTY) {
5954
6521
  const catchAll = await confirm({
5955
6522
  message: `Dedicate all unmatched ${zoneName} traffic to Void apps?`,
5956
6523
  initialValue: false
@@ -5992,11 +6559,12 @@ async function runPlatformLifecycleCommand(args) {
5992
6559
  host: externalHyperdriveHost,
5993
6560
  user: externalHyperdriveUser
5994
6561
  } : void 0;
5995
- if (!externalHyperdrive && process.stdin.isTTY && (!existing || isPlatformSetupDraft(existing)) && !existing?.resources.some((resource) => resource.key === "hyperdrive")) {
6562
+ if (args.hyperdrive && externalHyperdrive && args.hyperdrive !== externalHyperdrive.id) throw new Error("platform: --hyperdrive conflicts with VOID_PLATFORM_HYPERDRIVE_ID.");
6563
+ if (!externalHyperdrive && args.hyperdrive !== "create" && (process.stdin.isTTY || args.hyperdrive) && (!existing || isPlatformSetupDraft(existing)) && !existing?.resources.some((resource) => resource.key === "hyperdrive")) {
5996
6564
  const suggestedName = existing?.deployment.resources.hyperdrive ?? createPlatformResourceNames(installationSlug).hyperdrive;
5997
6565
  const configurations = await client.listHyperdriveConfigs(accountId);
5998
6566
  const suggestedNameAvailable = !configurations.some((resource) => resource.name === suggestedName);
5999
- const choice = await select({
6567
+ const choice = args.hyperdrive ? "existing" : await select({
6000
6568
  message: "How should Void set up Hyperdrive?",
6001
6569
  options: [
6002
6570
  ...configurations.length > 0 ? [{
@@ -6016,12 +6584,12 @@ async function runPlatformLifecycleCommand(args) {
6016
6584
  });
6017
6585
  if (isCancel(choice)) throw new Error("platform: Cancelled.");
6018
6586
  if (choice === "setup") {
6019
- note(`Create a Hyperdrive in account ${accountId} with any unused name${suggestedNameAvailable ? ` (for example ${suggestedName})` : ""}. Point it at the same dedicated PostgreSQL database you will give Void, use a runtime database user, and disable SQL result caching. If your organization manages Hyperdrive, ask its administrator to create it. Then rerun this command and select it from the list.\nSetup: https://developers.cloudflare.com/hyperdrive/get-started/`, "Set up Hyperdrive first");
6587
+ note(`Create a Hyperdrive in account ${accountId} with any unused name${suggestedNameAvailable ? ` such as ${suggestedName}` : ""}. Point it at the same dedicated PostgreSQL database you will give Void, use a runtime database user, and disable SQL result caching. If your organization manages Hyperdrive, ask its administrator to create it. Then rerun this command and select it from the list.\nSetup: https://developers.cloudflare.com/hyperdrive/get-started/`, "Set up Hyperdrive first");
6020
6588
  outro("No Cloudflare or local installation state was changed.");
6021
6589
  return;
6022
6590
  }
6023
6591
  if (choice === "existing") {
6024
- const selectedId = await select({
6592
+ const selectedId = args.hyperdrive ?? await select({
6025
6593
  message: "Which Hyperdrive should Void use?",
6026
6594
  options: [...configurations].sort((a, b) => Number(b.name === suggestedName) - Number(a.name === suggestedName) || a.name.localeCompare(b.name) || a.id.localeCompare(b.id)).map((resource) => ({
6027
6595
  value: resource.id,
@@ -6036,7 +6604,7 @@ async function runPlatformLifecycleCommand(args) {
6036
6604
  if (live.id !== selected.id || live.name !== selected.name || !live.origin.user) throw new Error(`platform: Could not verify the database user for existing Hyperdrive ${selected.name}.`);
6037
6605
  note(`Database: ${live.origin.database}\nHost: ${live.origin.host}\nPort: ${live.origin.port}\nUser: ${live.origin.user}\nSQL result caching: ${live.cachingDisabled ? "disabled" : "enabled"}`, `Existing Hyperdrive ${live.name}`);
6038
6606
  if (!live.cachingDisabled) throw new Error(`platform: Disable SQL result caching on existing Hyperdrive ${live.name} before installing.`);
6039
- const confirmed = await confirm({
6607
+ const confirmed = args.hyperdrive ? true : await confirm({
6040
6608
  message: `Use ${live.name} for this platform?`,
6041
6609
  initialValue: true
6042
6610
  });
@@ -6057,7 +6625,7 @@ async function runPlatformLifecycleCommand(args) {
6057
6625
  progress?.stop("Read the saved installation.");
6058
6626
  progress = void 0;
6059
6627
  if (authoritative) {
6060
- const { selectAuthoritativePlatformManifest } = await import("./platform-management-C8tt6D8h.mjs");
6628
+ const { selectAuthoritativePlatformManifest } = await import("./platform-management-CnyTdcWX.mjs");
6061
6629
  const selected = selectAuthoritativePlatformManifest(existing, authoritative);
6062
6630
  authoritativeProvisionIntents = true;
6063
6631
  if (!args.plan && JSON.stringify(selected) !== JSON.stringify(existing)) writeInstallationManifest(selected);
@@ -6065,12 +6633,13 @@ async function runPlatformLifecycleCommand(args) {
6065
6633
  if (selectedInstall && !isPlatformInstallInProgress(existing)) throw new Error(`platform: Installation ${existing.id} is no longer unfinished. Run \`void platform status ${existing.id}\` to inspect it.`);
6066
6634
  }
6067
6635
  }
6068
- const authenticationSelection = await selectInstallationAuthentication(args.authConfig, existing?.deployment.authentication);
6636
+ const authenticationSelection = await selectInstallationAuthentication(args.authConfig, existing?.deployment.authentication, args);
6069
6637
  const planInput = {
6070
6638
  authentication: authenticationSelection.authentication,
6071
6639
  accountId,
6072
6640
  applicationDomain,
6073
6641
  controlPlaneDomain,
6642
+ ...args.dashboardUrl !== void 0 ? { dashboardUrl: args.dashboardUrl } : {},
6074
6643
  dedicatedZone,
6075
6644
  displayName,
6076
6645
  ...externalHyperdrive ? { externalHyperdrive } : {},
@@ -6084,13 +6653,15 @@ async function runPlatformLifecycleCommand(args) {
6084
6653
  authoritativeProvisionIntents,
6085
6654
  allowUnverifiedAiGateway: true,
6086
6655
  includeEmailGateway: runtime.manifest.artifacts.emailGateway !== void 0,
6087
- enableSandbox: runtime.manifest.safetyProtocols.sandboxDrain >= 3
6656
+ enableSandbox: runtime.manifest.safetyProtocols.sandboxDrain >= 3,
6657
+ deploymentExecution: runtime.manifest.deploymentExecution === 1
6088
6658
  });
6089
6659
  note(formatPlatformInstallPlan(plan), args.plan ? "Read-only installation plan" : "Installation plan");
6090
6660
  note(formatPlatformRuntime(runtime), "Platform runtime");
6091
6661
  if (args.plan) {
6092
6662
  console.log(formatPlatformSetupPlan(plan));
6093
- console.log(`\n${existing ? "To resume this installation later:" : "To install with these settings later:"}\n${formatPlatformInstallCommand({
6663
+ outro("Plan complete. No installation changes were made.");
6664
+ console.log(`\n${import_picocolors$1.default.bold(import_picocolors$1.default.cyan(existing ? "Next step: run this command to resume" : "Next step: run this command to install"))}\n\n${import_picocolors$1.default.cyan(formatPlatformInstallCommand({
6094
6665
  commandName: cliCommandName(),
6095
6666
  accountId,
6096
6667
  installationSlug,
@@ -6098,15 +6669,16 @@ async function runPlatformLifecycleCommand(args) {
6098
6669
  applicationDomain,
6099
6670
  zoneName,
6100
6671
  controlPlaneDomain,
6672
+ ...args.dashboardUrl !== void 0 ? { dashboardUrl: args.dashboardUrl } : {},
6101
6673
  dedicatedZone,
6102
6674
  workersDev,
6103
6675
  authConfig: args.authConfig,
6676
+ authentication: authenticationSelection.authentication,
6677
+ hyperdrive: !existing || isPlatformSetupDraft(existing) ? externalHyperdrive?.id ?? "create" : void 0,
6104
6678
  runtime: args.runtime,
6105
6679
  ...existing ? { resumeId: existing.id } : {},
6106
6680
  yes: args.yes || process.stdin.isTTY !== true
6107
- })}\n`);
6108
- if (!existing && !args.authConfig && process.stdin.isTTY) console.log("Choose the same login methods when prompted.\n");
6109
- outro("No Cloudflare or local installation state was changed.");
6681
+ }))}\n`);
6110
6682
  return;
6111
6683
  }
6112
6684
  assertPlatformOperatorCredentialCanApplyPlan(operatorCredential, plan);
@@ -6282,11 +6854,12 @@ async function runPlatformLifecycleCommand(args) {
6282
6854
  authoritativeProvisionIntents: true,
6283
6855
  repairMissing: true,
6284
6856
  includeEmailGateway: runtime.manifest.artifacts.emailGateway !== void 0,
6285
- enableSandbox: runtime.manifest.safetyProtocols.sandboxDrain >= 3
6857
+ enableSandbox: runtime.manifest.safetyProtocols.sandboxDrain >= 3,
6858
+ deploymentExecution: runtime.manifest.deploymentExecution === 1
6286
6859
  })).actions.filter((action) => action.kind === "worker"),
6287
6860
  revalidateBeforeWorkerDeploy: (manifest, action) => revalidatePlatformInstallAction(client, plan, manifest, action),
6288
6861
  verifyExistingOwnership: async (manifest, databaseCheckpointExists) => {
6289
- const { verifyPlatformInstallationOwnership } = await import("./platform-management-C8tt6D8h.mjs");
6862
+ const { verifyPlatformInstallationOwnership } = await import("./platform-management-CnyTdcWX.mjs");
6290
6863
  const retainedReplacementKeys = new Set(plan.actions.filter((action) => {
6291
6864
  const previous = manifest.resources.find((resource) => resource.key === action.key);
6292
6865
  return action.action === "create" && previous && previous.name !== action.name;
@@ -6298,7 +6871,7 @@ async function runPlatformLifecycleCommand(args) {
6298
6871
  });
6299
6872
  }
6300
6873
  });
6301
- progress?.stop(result.waitingForDns || result.waitingForSandboxDrain ? "Installation paused; progress is saved." : "Platform installed.");
6874
+ progress?.stop(result.waitingForDns || result.waitingForSandboxDrain ? "Installation paused. Progress is saved." : "Platform installed.");
6302
6875
  progress = void 0;
6303
6876
  if (result.waitingForDns) {
6304
6877
  note(result.zoneNameServers.length > 0 ? `Update the domain at your registrar to use:\n${result.zoneNameServers.join("\n")}\n\nThen rerun: void platform install --resume --name ${result.manifest.id}` : `Wait for ${zoneName} to become active, then rerun:\nvoid platform install --resume --name ${result.manifest.id}`, "Waiting for nameservers");
@@ -6306,13 +6879,13 @@ async function runPlatformLifecycleCommand(args) {
6306
6879
  return;
6307
6880
  }
6308
6881
  if (result.waitingForSandboxDrain) {
6309
- note("The compatible candidate runtime is live in sandbox-drain maintenance mode and application traffic remains disabled. Preview bounded work with `void platform system sandbox-drain --plan`; follow any returned `nextCursor` with `--cursor '<value>'`. Then apply with `void platform system sandbox-drain --yes` and rerun until it reports DB-backed `complete: true`. Rerun this install command afterward to enable traffic.", "Waiting for managed Sandbox drain");
6882
+ note("The compatible candidate runtime is live in sandbox-drain maintenance mode and application traffic remains disabled. Preview bounded work with `void platform system sandbox-drain --plan`. Follow any returned `nextCursor` with `--cursor '<value>'`. Then apply with `void platform system sandbox-drain --yes` and rerun until it reports DB-backed `complete: true`. Rerun this install command afterward to enable traffic.", "Waiting for managed Sandbox drain");
6310
6883
  outro(`Installation ${result.manifest.id} is safely checkpointed.`);
6311
6884
  return;
6312
6885
  }
6313
6886
  log.success(`${result.manifest.deployment.displayName} is installed at ${new URL("/admin", plan.apiUrl).href}.`);
6314
6887
  log.info(`API URL: ${plan.apiUrl}`);
6315
- if (result.emailOperationId) log.info(`Shared email routing ready (operation ${result.emailOperationId}).`);
6888
+ if (result.emailOperationId) log.info(`Shared email routing ready. Operation: ${result.emailOperationId}.`);
6316
6889
  const completedAuthCode = readInstallationRecoverySecrets(result.manifest)?.authBootstrapCode;
6317
6890
  if (result.setupPending && completedAuthCode) {
6318
6891
  log.info(`First administrator setup: ${plan.apiUrl}/setup`);
@@ -6352,7 +6925,8 @@ async function verifyPlatformAiGatewayPlan(client, input, existing, preview, aut
6352
6925
  verified = await createPlatformInstallPlan(client, input, existing, {
6353
6926
  authoritativeProvisionIntents,
6354
6927
  includeEmailGateway: preview.actions.some((entry) => entry.key === "email-gateway-worker"),
6355
- enableSandbox: preview.deployment.capabilities.sandbox
6928
+ enableSandbox: preview.deployment.capabilities.sandbox,
6929
+ deploymentExecution: preview.actions.some((entry) => entry.key === "deployment-queue")
6356
6930
  });
6357
6931
  } catch (error) {
6358
6932
  if (error instanceof PlatformCloudflareApiError && (error.status === 401 || error.status === 403) && error.requestPath?.includes("/ai-gateway/")) throw new Error("platform: The runtime token could not verify AI Gateway access. Include Account → AI Gateway → Edit for this account, then retry. No platform resources were created.", { cause: error });
@@ -6426,4 +7000,4 @@ async function promptSecret(message, masked = true, validate, placeholder) {
6426
7000
  return value;
6427
7001
  }
6428
7002
  //#endregion
6429
- export { hyperdriveOriginFromConnectionString as A, assertPlatformOperatorCredentialCanApplyPlan as C, preparePlatformAccessSetup as D, parsePlatformAccessSetupSpec as E, verifyPlatformRuntimeTokenPermissions as M, PlatformCloudflareClient as O, platformDatabaseNameFromOwnerUrl as S, checkpointPlatformAccessResources as T, createPlatformInstallPlan as _, REMOTE_INSTALLATION_MANIFEST_KEY as a, platformProvisionIntentHash as b, createPlatformHealthProbeToken as c, loadPlatformRuntime as d, platformDatabaseClientUrl as f, trackedPlatformWorkerTraffic as g, recordPlatformWorkerTraffic as h, verifyPlatformAiGatewayPlan as i, hyperdriveOriginMatches as j, hyperdriveOriginConfigHash as k, createPlatformInstallationSecrets as l, resolveBootstrapGitHubId as m, resolvePlatformInstallDatabaseUrl as n, acquirePlatformDatabaseLifecycleLock as o, recordResource as p, runPlatformLifecycleCommand as r, acquirePlatformDatabaseLifecycleLockConnection as s, assertNoninteractivePlatformMutationAcknowledged as t, installPlatform as u, formatPlatformInstallPlan as v, resolvePlatformCloudflareCredential as w, revalidatePlatformInstallAction as x, platformAdoptionIntentHash as y };
7003
+ export { PlatformCloudflareClient as A, revalidatePlatformInstallAction as C, checkpointPlatformAccessResources as D, resolvePlatformCloudflareCredential as E, hyperdriveOriginFromConnectionString as M, hyperdriveOriginMatches as N, parsePlatformAccessSetupSpec as O, verifyPlatformRuntimeTokenPermissions as P, platformProvisionIntentHash as S, assertPlatformOperatorCredentialCanApplyPlan as T, recordPlatformWorkerTraffic as _, REMOTE_INSTALLATION_MANIFEST_KEY as a, formatPlatformInstallPlan as b, createPlatformHealthProbeToken as c, loadPlatformRuntime as d, platformDatabaseClientUrl as f, assertProjectZeroTrustAllowsUninstall as g, resolveBootstrapGitHubId as h, verifyPlatformAiGatewayPlan as i, hyperdriveOriginConfigHash as j, preparePlatformAccessSetup as k, createPlatformInstallationSecrets as l, recordResource as m, resolvePlatformInstallDatabaseUrl as n, acquirePlatformDatabaseLifecycleLock as o, readPlatformProjectZeroTrustState as p, runPlatformLifecycleCommand as r, acquirePlatformDatabaseLifecycleLockConnection as s, assertNoninteractivePlatformMutationAcknowledged as t, installPlatform as u, trackedPlatformWorkerTraffic as v, platformDatabaseNameFromOwnerUrl as w, platformAdoptionIntentHash as x, createPlatformInstallPlan as y };