void 0.10.12 → 0.20.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 (431) hide show
  1. package/AGENTS_TEMPLATE.md +6 -0
  2. package/{skills/void/docs/node_modules/void/node_modules/pglite-server/LICENSE.md → LICENSE} +1 -1
  3. package/README.md +34 -8
  4. package/dist/{auth-qgMlYp7Z.d.mts → auth-DkcFflXV.d.mts} +10 -11
  5. package/dist/auth-W9WII-mN.mjs +630 -0
  6. package/dist/{auth-cmd-BqsdZJp5.mjs → auth-cmd-CYVhSNFy.mjs} +4 -3
  7. package/dist/{auth-migrations-BTZ-ATvQ.mjs → auth-migrations-9vif1uj8.mjs} +53 -12
  8. package/dist/{better-auth-shared-BQooDxbw.mjs → better-auth-shared-CYw1T3k4.mjs} +7 -10
  9. package/dist/{better-auth-shared-DealXecJ.d.mts → better-auth-shared-DSCeohOK.d.mts} +10 -5
  10. package/dist/{build-cmd-Bujrv5q-.mjs → build-cmd-CkVD1uOh.mjs} +6 -4
  11. package/dist/{cache-C11V8Fxq.mjs → cache-QcUfR-ff.mjs} +9 -5
  12. package/dist/{cancel-deploy-fwFYF04b.mjs → cancel-deploy-DsvWKFTe.mjs} +3 -2
  13. package/dist/cf-access-AJ1ehiFR.mjs +42 -0
  14. package/dist/cf-access-DsSsZUPr.mjs +67 -0
  15. package/dist/cli/cli.d.mts +1 -1
  16. package/dist/cli/cli.mjs +1592 -153
  17. package/dist/cli/env-schema-probe.d.mts +48 -61
  18. package/dist/cli/env-schema-probe.mjs +10 -58
  19. package/dist/{client-Gb71-XkG.mjs → client-CyCHWSO_.mjs} +61 -463
  20. package/dist/cloudflare-auth-DRkGe7-s.mjs +170 -0
  21. package/dist/cloudflare-cmd-D3ME2GAe.mjs +62 -0
  22. package/dist/cloudflare-connect-BivMefMA.mjs +56 -0
  23. package/dist/cloudflare-operations-CPTpRW6d.mjs +566 -0
  24. package/dist/{config-BkTvs43g.mjs → config-BQFq7QvD.mjs} +85 -41
  25. package/dist/{config-CutEMNGJ.mjs → config-NOG_U1aK.mjs} +10 -15
  26. package/dist/connect-D9Yr-T5f.mjs +79 -0
  27. package/dist/{create-project-DsYvl3TB.mjs → create-project-DMV-csEm.mjs} +26 -16
  28. package/dist/database-provider.d.mts +21 -0
  29. package/dist/database-provider.mjs +6 -0
  30. package/dist/{db-DOiJMRt2.mjs → db-BxoUWL0F.mjs} +600 -115
  31. package/dist/{delete-mh6p-zkQ.mjs → delete-iJOqxBz1.mjs} +9 -6
  32. package/dist/{deploy-u7Rv9q_q.mjs → deploy-CcDoeBIf.mjs} +3451 -1986
  33. package/dist/dev-inbox-DkgRWLkW.mjs +307 -0
  34. package/dist/{discover-CJHyvYfR.mjs → discover-xvfrgJeo.mjs} +3 -3
  35. package/dist/{magic-string.es-ZQjdJFFn.mjs → dist-BR1quN_w.mjs} +570 -216
  36. package/dist/{dist-DaKKDf8D.mjs → dist-BrsS7cai.mjs} +17 -2
  37. package/dist/{dist-BuiRJkTd.mjs → dist-m40_XgNh.mjs} +48 -33
  38. package/dist/{domain-DiaNQbrl.mjs → domain-gu_iaHmn.mjs} +41 -4
  39. package/dist/dotenv-VQxupEUv.mjs +181 -0
  40. package/dist/edge.d.mts +2 -0
  41. package/dist/edge.mjs +2 -0
  42. package/dist/email-Ce6SQq-i.mjs +182 -0
  43. package/dist/email-r6DHJyAB.mjs +262 -0
  44. package/dist/{entry-D7yy4xVH.mjs → entry-DU3oDoQ3.mjs} +2 -2
  45. package/dist/env-D4Emu-M_.mjs +95 -0
  46. package/dist/env-DmU2To0C.mjs +73 -0
  47. package/dist/env-helpers-CyKtOBpj.d.mts +22 -0
  48. package/dist/env-public-D_6u46fX.d.mts +135 -0
  49. package/dist/{env-types-QBj-ndax.mjs → env-types-BNPhro-M.mjs} +2 -2
  50. package/dist/{env-validation-Dea3v3ej.mjs → env-validation-ENpMy6Ez.mjs} +68 -122
  51. package/dist/{gen-o_w-8yI8.mjs → gen-BKw6qHIg.mjs} +70 -15
  52. package/dist/{github-cmd-DKcGUNsj.mjs → github-cmd-DJS5Ab-H.mjs} +39 -31
  53. package/dist/{handler-Cjh8uM3Y.d.mts → handler-D1hLsObx.d.mts} +124 -122
  54. package/dist/{head-nmvOgFjd.d.mts → head-Do8P4puT.d.mts} +9 -8
  55. package/dist/{headers-nsHIFixA.mjs → headers-D8QfRX9Y.mjs} +12 -10
  56. package/dist/inbound-BJ70in1n.d.mts +354 -0
  57. package/dist/inbound-CNKxb3FY.mjs +694 -0
  58. package/dist/index.d.mts +61 -34
  59. package/dist/index.mjs +1048 -321
  60. package/dist/{init-3rgHKBVi.mjs → init-Dx5cgKqK.mjs} +329 -271
  61. package/dist/link-D_rm5sRH.mjs +52 -0
  62. package/dist/{list-CPwFDZ_c.mjs → list-M8XShti7.mjs} +37 -5
  63. package/dist/{runner-kapo9aPs.mjs → local-d1-BE8KBbMy.mjs} +116 -6
  64. package/dist/{login-BT3H8PN3.mjs → login-C8-UuLnp.mjs} +17 -15
  65. package/dist/{logs-Bt313ax7.mjs → logs-DwFU7dMW.mjs} +16 -2
  66. package/dist/mime-BJD7d_qL.mjs +1216 -0
  67. package/dist/neon-DHwd2zvC.mjs +54 -0
  68. package/dist/{node-Da0UcsGA.mjs → node-BkaXcpAc.mjs} +29 -8
  69. package/dist/operator-cmd-DYWRbWUA.mjs +348 -0
  70. package/dist/{agents-CtgBYqld.mjs → output-tFQLLj26.mjs} +676 -211
  71. package/dist/{package-json-Cx1osYo6.mjs → package-json-CPoWX79C.mjs} +1 -1
  72. package/dist/pages/client.d.mts +43 -41
  73. package/dist/pages/client.mjs +40 -33
  74. package/dist/pages/head-client.d.mts +10 -12
  75. package/dist/pages/head.d.mts +1 -1
  76. package/dist/pages/index.d.mts +37 -14
  77. package/dist/pages/index.mjs +5 -5
  78. package/dist/pages/islands-plugin.d.mts +25 -27
  79. package/dist/pages/islands-plugin.mjs +6 -4
  80. package/dist/pages/prefetch.d.mts +6 -7
  81. package/dist/pages/protocol.d.mts +2 -2
  82. package/dist/pages/protocol.mjs +2 -1
  83. package/dist/pages/serialize.d.mts +7 -8
  84. package/dist/platform-cmd-BEOVv1PN.mjs +123 -0
  85. package/dist/platform-domain-BszUfiS8.mjs +228 -0
  86. package/dist/platform-lifecycle-CjSr6xf0.mjs +4450 -0
  87. package/dist/platform-management-W30iCaTL.mjs +698 -0
  88. package/dist/platform-recovery-pHHv4ZSg.mjs +99 -0
  89. package/dist/{plugin-inference-DMeavIJ6.mjs → plugin-inference-BDRfZngg.mjs} +35 -19
  90. package/dist/prepare-BfJvFUtJ.mjs +14 -0
  91. package/dist/{prepare-BoKHgMNx.mjs → prepare-CBetXvsN.mjs} +15 -24
  92. package/dist/{preset-BjyR3lzz.mjs → preset-lAy0B0BQ.mjs} +25 -212
  93. package/dist/{project-cmd-D_w-4w5B.mjs → project-cmd-DCpk1cDt.mjs} +25 -12
  94. package/dist/{project-paths-BQd7OmIo.mjs → project-paths-SK8nMHPp.mjs} +3 -1
  95. package/dist/{project-tsconfig-B-QtXjLQ.mjs → project-tsconfig-Ql2XsSQp.mjs} +2 -2
  96. package/dist/{protocol-Bnb0LFp3.d.mts → protocol-C-pqYJjE.d.mts} +3 -4
  97. package/dist/{provision-rShh6MKY.mjs → provision-Blnstcm2.mjs} +78 -45
  98. package/dist/r2-conditions-D1Wk8i7b.mjs +30 -0
  99. package/dist/{requests-B8sZxaFM.mjs → requests-Dn8Vheh1.mjs} +5 -3
  100. package/dist/{resolve-project-BBMtLLV9.mjs → resolve-project--Vxawf7z.mjs} +2 -2
  101. package/dist/rollback-CtlPXEBi.mjs +166 -0
  102. package/dist/{rolldown-runtime-DJK8HYOj.mjs → rolldown-runtime-rQ84J-ij.mjs} +1 -1
  103. package/dist/{route-types-COI2DsZv.mjs → route-types-Da-DpyUp.mjs} +85 -26
  104. package/dist/routes-stub.d.mts +22 -23
  105. package/dist/runner-mysql-CgRFl3s6.mjs +61 -0
  106. package/dist/{runner-pg-CHM76xuC.mjs → runner-pg-DCkWPsWS.mjs} +18 -6
  107. package/dist/runtime/ai.d.mts +21 -14
  108. package/dist/runtime/ai.mjs +5 -4
  109. package/dist/runtime/auth-client-react.d.mts +3 -5
  110. package/dist/runtime/auth-client-solid.d.mts +3 -5
  111. package/dist/runtime/auth-client-svelte.d.mts +3 -5
  112. package/dist/runtime/auth-client-vue.d.mts +3 -5
  113. package/dist/runtime/auth-client.d.mts +3 -5
  114. package/dist/runtime/auth.d.mts +1 -1
  115. package/dist/runtime/better-auth-mysql.d.mts +10 -0
  116. package/dist/runtime/better-auth-mysql.mjs +49 -0
  117. package/dist/runtime/better-auth-pg.d.mts +8 -9
  118. package/dist/runtime/better-auth-pg.mjs +2 -2
  119. package/dist/runtime/better-auth.d.mts +8 -9
  120. package/dist/runtime/better-auth.mjs +2 -2
  121. package/dist/runtime/client-react.d.mts +1 -1
  122. package/dist/runtime/client-solid.d.mts +1 -1
  123. package/dist/runtime/client-svelte.d.mts +1 -1
  124. package/dist/runtime/client-vue.d.mts +1 -1
  125. package/dist/runtime/client.d.mts +1 -1
  126. package/dist/runtime/db-mysql.d.mts +2 -0
  127. package/dist/runtime/db-mysql.mjs +1 -0
  128. package/dist/runtime/db.d.mts +10 -11
  129. package/dist/runtime/durable.d.mts +47 -0
  130. package/dist/runtime/durable.mjs +146 -0
  131. package/dist/runtime/email/testing.d.mts +112 -0
  132. package/dist/runtime/email/testing.mjs +283 -0
  133. package/dist/runtime/email.d.mts +30 -0
  134. package/dist/runtime/email.mjs +573 -0
  135. package/dist/runtime/env-helpers.d.mts +2 -2
  136. package/dist/runtime/env-helpers.mjs +5 -42
  137. package/dist/runtime/env-public-client.d.mts +10 -11
  138. package/dist/runtime/env-public-client.mjs +1 -1
  139. package/dist/runtime/env-public.d.mts +2 -2
  140. package/dist/runtime/env-public.mjs +104 -49
  141. package/dist/runtime/env.d.mts +19 -18
  142. package/dist/runtime/env.mjs +15 -2
  143. package/dist/runtime/fetch-stream.d.mts +20 -21
  144. package/dist/runtime/fetch.d.mts +15 -16
  145. package/dist/runtime/handler.d.mts +1 -1
  146. package/dist/runtime/isr.d.mts +21 -22
  147. package/dist/runtime/isr.mjs +26 -8
  148. package/dist/runtime/kv.d.mts +9 -10
  149. package/dist/runtime/live-client.d.mts +5 -7
  150. package/dist/runtime/live-client.mjs +9 -7
  151. package/dist/runtime/live-server.d.mts +4 -5
  152. package/dist/runtime/live.d.mts +22 -24
  153. package/dist/runtime/live.mjs +1 -1
  154. package/dist/runtime/log.d.mts +16 -17
  155. package/dist/runtime/migration-handler-mysql.d.mts +4 -0
  156. package/dist/runtime/migration-handler-mysql.mjs +81 -0
  157. package/dist/runtime/migration-handler-pg.d.mts +2 -4
  158. package/dist/runtime/migration-handler.d.mts +5 -6
  159. package/dist/runtime/migration-handler.mjs +4 -3
  160. package/dist/runtime/queues.d.mts +3 -4
  161. package/dist/runtime/queues.mjs +2 -1
  162. package/dist/runtime/remote/binding-handler.d.mts +10 -12
  163. package/dist/runtime/remote/binding-handler.mjs +24 -3
  164. package/dist/runtime/remote/index.d.mts +5 -6
  165. package/dist/runtime/remote/index.mjs +21 -18
  166. package/dist/runtime/response.d.mts +10 -11
  167. package/dist/runtime/sandbox.d.mts +56 -55
  168. package/dist/runtime/sandbox.mjs +57 -49
  169. package/dist/runtime/schema-mysql.d.mts +1 -0
  170. package/dist/runtime/schema-mysql.mjs +2 -0
  171. package/dist/runtime/seed.d.mts +14 -9
  172. package/dist/runtime/sse-client.d.mts +6 -7
  173. package/dist/runtime/sse.d.mts +11 -12
  174. package/dist/runtime/storage.d.mts +3 -4
  175. package/dist/runtime/validator.d.mts +1 -1
  176. package/dist/runtime/ws-server.d.mts +12 -12
  177. package/dist/runtime/ws-server.mjs +32 -4
  178. package/dist/runtime/ws.d.mts +19 -21
  179. package/dist/{scan-DYXkrasO.mjs → scan-BMH4rzlv.mjs} +53 -31
  180. package/dist/{scan-DEwlM_Xy.mjs → scan-CpK-57ug.mjs} +9 -5
  181. package/dist/{secret-Dt32J6RI.mjs → secret-BqTxGqki.mjs} +62 -5
  182. package/dist/{skills-CLjN0uUO.mjs → skills-Q46GZMO-.mjs} +6 -4
  183. package/dist/{standard-schema-DJ0HW7QP.d.mts → standard-schema-Fo_vCAZh.d.mts} +6 -6
  184. package/dist/{subcommand-prompt-BzV8iQZo.mjs → subcommand-prompt-WfySCQ7S.mjs} +67 -48
  185. package/dist/sveltekit.d.mts +12 -11
  186. package/dist/sveltekit.mjs +1 -1
  187. package/dist/types-BAp5AEBU.d.mts +79 -0
  188. package/dist/types-CKWnYgfy.d.mts +1 -0
  189. package/dist/{validate-Cw_RLeTj.mjs → validate-Bihr8WBi.mjs} +3 -3
  190. package/dist/wrangler--imS8n0d.mjs +1796 -0
  191. package/dist/{yarn-pnp-DJn3SAHF.mjs → yarn-pnp-DxSInkzL.mjs} +1 -1
  192. package/package.json +79 -65
  193. package/schema.json +35 -3
  194. package/skills/migrate-vite-cloudflare-to-void/SKILL.md +1 -1
  195. package/skills/void/SKILL.md +59 -2
  196. package/skills/void/docs/guide/ai.md +32 -14
  197. package/skills/void/docs/guide/app-types.md +6 -6
  198. package/skills/void/docs/guide/auth.md +14 -16
  199. package/skills/void/docs/guide/database/d1.md +6 -0
  200. package/skills/void/docs/guide/database/mysql.md +60 -0
  201. package/skills/void/docs/guide/database/postgresql.md +14 -9
  202. package/skills/void/docs/guide/database.md +39 -26
  203. package/skills/void/docs/guide/deployment.md +163 -22
  204. package/skills/void/docs/guide/durable-state.md +140 -0
  205. package/skills/void/docs/guide/edge/headers.md +5 -5
  206. package/skills/void/docs/guide/edge/prerendering.md +2 -0
  207. package/skills/void/docs/guide/edge/revalidation.md +21 -6
  208. package/skills/void/docs/guide/edge/rewrites.md +18 -14
  209. package/skills/void/docs/guide/edge/static-assets.md +23 -8
  210. package/skills/void/docs/guide/email.md +609 -0
  211. package/skills/void/docs/guide/env-migration.md +109 -0
  212. package/skills/void/docs/guide/env-vars.md +70 -248
  213. package/skills/void/docs/guide/index.md +15 -17
  214. package/skills/void/docs/guide/jobs.md +8 -5
  215. package/skills/void/docs/guide/live.md +7 -15
  216. package/skills/void/docs/guide/pages-routing/actions-and-forms.md +8 -4
  217. package/skills/void/docs/guide/pages-routing/islands.md +3 -3
  218. package/skills/void/docs/guide/pages-routing/loaders.md +6 -4
  219. package/skills/void/docs/guide/pages-routing/overview.md +7 -7
  220. package/skills/void/docs/guide/platform-administration.md +212 -0
  221. package/skills/void/docs/guide/platform-development.md +235 -0
  222. package/skills/void/docs/guide/queues.md +10 -10
  223. package/skills/void/docs/guide/quickstart.md +46 -67
  224. package/skills/void/docs/guide/remote-dev.md +8 -6
  225. package/skills/void/docs/guide/sandboxes.md +33 -16
  226. package/skills/void/docs/guide/self-hosted-platform.md +553 -0
  227. package/skills/void/docs/guide/server-routing.md +25 -5
  228. package/skills/void/docs/guide/sse.md +4 -4
  229. package/skills/void/docs/guide/ssg.md +5 -3
  230. package/skills/void/docs/guide/storage.md +2 -2
  231. package/skills/void/docs/guide/websockets.md +18 -7
  232. package/skills/void/docs/index.md +3 -3
  233. package/skills/void/docs/integrations/agents.md +6 -64
  234. package/skills/void/docs/integrations/cloudflare.md +165 -146
  235. package/skills/void/docs/integrations/frameworks/analog.md +4 -4
  236. package/skills/void/docs/integrations/frameworks/astro.md +5 -5
  237. package/skills/void/docs/integrations/frameworks/nuxt.md +5 -5
  238. package/skills/void/docs/integrations/frameworks/overview.md +3 -3
  239. package/skills/void/docs/integrations/frameworks/react-router.md +3 -3
  240. package/skills/void/docs/integrations/frameworks/sveltekit.md +3 -3
  241. package/skills/void/docs/integrations/frameworks/tanstack-start.md +2 -2
  242. package/skills/void/docs/integrations/nodejs-bun-deno.md +11 -4
  243. package/skills/void/docs/reference/api.md +42 -6
  244. package/skills/void/docs/reference/cli.md +637 -159
  245. package/skills/void/docs/reference/config.md +80 -25
  246. package/skills/void/docs/reference/resource-inference.md +13 -9
  247. package/skills/void/docs/reference/structure.md +10 -11
  248. package/AGENT_PROMPT.md +0 -19
  249. package/dist/cf-access-Bqw81xAf.mjs +0 -22
  250. package/dist/env-CZy5MorI.mjs +0 -299
  251. package/dist/env-helpers-z4stu8uc.d.mts +0 -52
  252. package/dist/env-mask-Dd47NbR6.mjs +0 -90
  253. package/dist/env-public-BfiLcMBk.d.mts +0 -140
  254. package/dist/link-CdGHSIy-.mjs +0 -45
  255. package/dist/mcp-DoM3_nhd.mjs +0 -377
  256. package/dist/project-paths-GpziKeQQ.d.mts +0 -25
  257. package/dist/providers-BNKRacMr.d.mts +0 -7
  258. package/dist/proxy-D-3_D-Gl.mjs +0 -5
  259. package/dist/rollback-CkvTFXx5.mjs +0 -90
  260. package/dist/runtime/isr-cache.d.mts +0 -207
  261. package/dist/runtime/isr-cache.mjs +0 -523
  262. package/dist/types-lLjNE9Qp.d.mts +0 -51
  263. package/getting-started-prompt.txt +0 -28
  264. package/skills/void/command/void.md +0 -7
  265. package/skills/void/docs/integrations/auth-providers.md +0 -0
  266. package/skills/void/docs/integrations/payment-processors.md +0 -0
  267. package/skills/void/docs/node_modules/@iconify/vue/README.md +0 -408
  268. package/skills/void/docs/node_modules/@iconify/vue/offline/readme.md +0 -5
  269. package/skills/void/docs/node_modules/@voidzero-dev/vitepress-theme/README.md +0 -103
  270. package/skills/void/docs/node_modules/oxc-minify/README.md +0 -78
  271. package/skills/void/docs/node_modules/reka-ui/README.md +0 -80
  272. package/skills/void/docs/node_modules/vitepress/README.md +0 -28
  273. package/skills/void/docs/node_modules/vitepress/template/api-examples.md +0 -49
  274. package/skills/void/docs/node_modules/vitepress/template/index.md +0 -28
  275. package/skills/void/docs/node_modules/vitepress/template/markdown-examples.md +0 -85
  276. package/skills/void/docs/node_modules/vitepress-plugin-group-icons/README.md +0 -101
  277. package/skills/void/docs/node_modules/void/AGENT_PROMPT.md +0 -19
  278. package/skills/void/docs/node_modules/void/CLAUDE.md +0 -219
  279. package/skills/void/docs/node_modules/void/README.md +0 -90
  280. package/skills/void/docs/node_modules/void/node_modules/@clack/prompts/CHANGELOG.md +0 -685
  281. package/skills/void/docs/node_modules/void/node_modules/@clack/prompts/README.md +0 -396
  282. package/skills/void/docs/node_modules/void/node_modules/@cloudflare/sandbox/README.md +0 -219
  283. package/skills/void/docs/node_modules/void/node_modules/@cloudflare/vite-plugin/README.md +0 -37
  284. package/skills/void/docs/node_modules/void/node_modules/@cloudflare/workers-types/README.md +0 -135
  285. package/skills/void/docs/node_modules/void/node_modules/@electric-sql/pglite/README.md +0 -189
  286. package/skills/void/docs/node_modules/void/node_modules/@hono/oauth-providers/CHANGELOG.md +0 -143
  287. package/skills/void/docs/node_modules/void/node_modules/@hono/oauth-providers/README.md +0 -1272
  288. package/skills/void/docs/node_modules/void/node_modules/@napi-rs/keyring/README.md +0 -19
  289. package/skills/void/docs/node_modules/void/node_modules/@types/better-sqlite3/README.md +0 -15
  290. package/skills/void/docs/node_modules/void/node_modules/@types/node/README.md +0 -15
  291. package/skills/void/docs/node_modules/void/node_modules/@types/pg/README.md +0 -15
  292. package/skills/void/docs/node_modules/void/node_modules/@types/proper-lockfile/README.md +0 -51
  293. package/skills/void/docs/node_modules/void/node_modules/@typescript/native-preview/README.md +0 -22
  294. package/skills/void/docs/node_modules/void/node_modules/@typescript/native-preview/vendor/vscode-jsonrpc/README.md +0 -69
  295. package/skills/void/docs/node_modules/void/node_modules/@void/md/README.md +0 -153
  296. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/@shikijs/engine-javascript/README.md +0 -9
  297. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/@shikijs/transformers/README.md +0 -9
  298. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/@types/node/README.md +0 -15
  299. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/gray-matter/CHANGELOG.md +0 -24
  300. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/gray-matter/README.md +0 -565
  301. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-exit/README.md +0 -127
  302. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-anchor/README.md +0 -600
  303. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-attrs/README.md +0 -386
  304. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-container/README.md +0 -95
  305. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-emoji/README.md +0 -101
  306. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-footnote/README.md +0 -135
  307. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/pathslash/README.md +0 -64
  308. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/shiki/README.md +0 -15
  309. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/tinyglobby/README.md +0 -25
  310. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/AGENTS.md +0 -16
  311. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/README.md +0 -220
  312. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/build.md +0 -21
  313. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/check.md +0 -35
  314. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/create.md +0 -70
  315. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/fmt.md +0 -20
  316. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/index.md +0 -35
  317. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/lint.md +0 -26
  318. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/pack.md +0 -17
  319. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/run.md +0 -364
  320. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/staged.md +0 -15
  321. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/test.md +0 -18
  322. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/automatic-data-tracking.md +0 -145
  323. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/build.md +0 -40
  324. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/cache.md +0 -107
  325. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/check.md +0 -60
  326. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/ci.md +0 -62
  327. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/commit-hooks.md +0 -60
  328. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/create.md +0 -341
  329. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/dev.md +0 -24
  330. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/docker.md +0 -175
  331. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/env.md +0 -167
  332. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/fmt.md +0 -41
  333. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/github-actions-cache.md +0 -165
  334. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/ide-integration.md +0 -101
  335. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/implode.md +0 -23
  336. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/index.md +0 -134
  337. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/install.md +0 -199
  338. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/lint.md +0 -50
  339. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/migrate-rules.md +0 -347
  340. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/migrate.md +0 -197
  341. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/monorepo.md +0 -176
  342. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/pack.md +0 -69
  343. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/run.md +0 -356
  344. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/test.md +0 -35
  345. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/troubleshooting.md +0 -108
  346. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/upgrade.md +0 -101
  347. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/vpx.md +0 -66
  348. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/why.md +0 -39
  349. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/index.md +0 -12
  350. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/team.md +0 -35
  351. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/templates/generator/README.md +0 -35
  352. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/templates/monorepo/README.md +0 -29
  353. package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vue/README.md +0 -58
  354. package/skills/void/docs/node_modules/void/node_modules/arktype/README.md +0 -165
  355. package/skills/void/docs/node_modules/void/node_modules/better-auth/LICENSE.md +0 -20
  356. package/skills/void/docs/node_modules/void/node_modules/better-auth/README.md +0 -32
  357. package/skills/void/docs/node_modules/void/node_modules/better-sqlite3/README.md +0 -99
  358. package/skills/void/docs/node_modules/void/node_modules/blake3-jit/README.md +0 -108
  359. package/skills/void/docs/node_modules/void/node_modules/drizzle-arktype/README.md +0 -51
  360. package/skills/void/docs/node_modules/void/node_modules/drizzle-kit/README.md +0 -79
  361. package/skills/void/docs/node_modules/void/node_modules/drizzle-orm/README.md +0 -44
  362. package/skills/void/docs/node_modules/void/node_modules/drizzle-valibot/README.md +0 -51
  363. package/skills/void/docs/node_modules/void/node_modules/drizzle-zod/README.md +0 -65
  364. package/skills/void/docs/node_modules/void/node_modules/es-module-lexer/README.md +0 -403
  365. package/skills/void/docs/node_modules/void/node_modules/estree-walker/README.md +0 -48
  366. package/skills/void/docs/node_modules/void/node_modules/hono/README.md +0 -85
  367. package/skills/void/docs/node_modules/void/node_modules/ignore/README.md +0 -452
  368. package/skills/void/docs/node_modules/void/node_modules/jsonc-parser/CHANGELOG.md +0 -76
  369. package/skills/void/docs/node_modules/void/node_modules/jsonc-parser/LICENSE.md +0 -21
  370. package/skills/void/docs/node_modules/void/node_modules/jsonc-parser/README.md +0 -364
  371. package/skills/void/docs/node_modules/void/node_modules/jsonc-parser/SECURITY.md +0 -41
  372. package/skills/void/docs/node_modules/void/node_modules/magic-string/README.md +0 -325
  373. package/skills/void/docs/node_modules/void/node_modules/ofetch/README.md +0 -398
  374. package/skills/void/docs/node_modules/void/node_modules/pathslash/README.md +0 -64
  375. package/skills/void/docs/node_modules/void/node_modules/pg/README.md +0 -96
  376. package/skills/void/docs/node_modules/void/node_modules/pglite-server/README.md +0 -135
  377. package/skills/void/docs/node_modules/void/node_modules/picocolors/README.md +0 -21
  378. package/skills/void/docs/node_modules/void/node_modules/proper-lockfile/CHANGELOG.md +0 -108
  379. package/skills/void/docs/node_modules/void/node_modules/proper-lockfile/README.md +0 -183
  380. package/skills/void/docs/node_modules/void/node_modules/tinyglobby/README.md +0 -25
  381. package/skills/void/docs/node_modules/void/node_modules/valibot/LICENSE.md +0 -9
  382. package/skills/void/docs/node_modules/void/node_modules/valibot/README.md +0 -94
  383. package/skills/void/docs/node_modules/void/node_modules/vite-plus/AGENTS.md +0 -16
  384. package/skills/void/docs/node_modules/void/node_modules/vite-plus/README.md +0 -220
  385. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/build.md +0 -21
  386. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/check.md +0 -35
  387. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/create.md +0 -70
  388. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/fmt.md +0 -20
  389. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/index.md +0 -35
  390. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/lint.md +0 -26
  391. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/pack.md +0 -17
  392. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/run.md +0 -364
  393. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/staged.md +0 -15
  394. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/test.md +0 -18
  395. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/automatic-data-tracking.md +0 -145
  396. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/build.md +0 -40
  397. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/cache.md +0 -107
  398. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/check.md +0 -60
  399. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/ci.md +0 -62
  400. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/commit-hooks.md +0 -60
  401. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/create.md +0 -341
  402. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/dev.md +0 -24
  403. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/docker.md +0 -175
  404. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/env.md +0 -167
  405. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/fmt.md +0 -41
  406. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/github-actions-cache.md +0 -165
  407. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/ide-integration.md +0 -101
  408. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/implode.md +0 -23
  409. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/index.md +0 -134
  410. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/install.md +0 -199
  411. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/lint.md +0 -50
  412. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/migrate-rules.md +0 -347
  413. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/migrate.md +0 -197
  414. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/monorepo.md +0 -176
  415. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/pack.md +0 -69
  416. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/run.md +0 -356
  417. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/test.md +0 -35
  418. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/troubleshooting.md +0 -108
  419. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/upgrade.md +0 -101
  420. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/vpx.md +0 -66
  421. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/why.md +0 -39
  422. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/index.md +0 -12
  423. package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/team.md +0 -35
  424. package/skills/void/docs/node_modules/void/node_modules/vite-plus/templates/generator/README.md +0 -35
  425. package/skills/void/docs/node_modules/void/node_modules/vite-plus/templates/monorepo/README.md +0 -29
  426. package/skills/void/docs/node_modules/void/node_modules/wrangler/README.md +0 -63
  427. package/skills/void/docs/node_modules/void/node_modules/zod/README.md +0 -191
  428. package/skills/void/docs/node_modules/void/skills/migrate-vite-cloudflare-to-void/SKILL.md +0 -175
  429. package/skills/void/docs/node_modules/void/skills/void/SKILL.md +0 -76
  430. package/skills/void/docs/node_modules/void/skills/void/command/void.md +0 -7
  431. package/skills/void/docs/node_modules/void/test/e2e/README.md +0 -85
@@ -0,0 +1,1796 @@
1
+ import { a as isAbsolute, n as dirname, o as join, s as normalize } from "./dist-BrsS7cai.mjs";
2
+ import { createRequire } from "node:module";
3
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { appendSqliteClassMigration } from "@void/deploy-core";
5
+ import { createHash } from "node:crypto";
6
+ import { SyntaxKind, applyEdits, createScanner, findNodeAtLocation, modify, parse, parseTree } from "jsonc-parser";
7
+ const SANDBOX_MIGRATION_TAG = "void-sandbox-v1";
8
+ const require = createRequire(import.meta.url);
9
+ function isSandboxEnabled(config) {
10
+ return config.sandbox !== void 0 && config.sandbox !== false;
11
+ }
12
+ function getDefaultSandboxImage() {
13
+ return getDefaultSandboxPlatformImage();
14
+ }
15
+ function getDefaultSandboxPlatformImage() {
16
+ return `docker.io/cloudflare/sandbox:${JSON.parse(readFileSync(join(getSandboxPackageRoot(), "package.json"), "utf-8")).version ?? "latest"}`;
17
+ }
18
+ function isLocalSandboxImage(image) {
19
+ const lowerImage = image.toLowerCase();
20
+ return isAbsolute(image) || image.startsWith("./") || image.startsWith("../") || lowerImage === "dockerfile" || lowerImage.includes("dockerfile") || lowerImage.endsWith("/dockerfile") || lowerImage.endsWith("\\dockerfile");
21
+ }
22
+ function requiresSandboxPlatformImage(config) {
23
+ if (typeof config.sandbox !== "object" || config.sandbox === null) return false;
24
+ return typeof config.sandbox.image === "string" && isLocalSandboxImage(config.sandbox.image) && config.sandbox.platformImage === void 0;
25
+ }
26
+ function resolveSandboxConfig(config, root = process.cwd()) {
27
+ const raw = typeof config.sandbox === "object" ? config.sandbox : {};
28
+ const image = raw.image ?? getDefaultSandboxImage();
29
+ const imageBuildContext = raw.imageBuildContext;
30
+ const platformImage = raw.platformImage ?? (raw.image && !isLocalSandboxImage(raw.image) ? raw.image : getDefaultSandboxPlatformImage());
31
+ return {
32
+ binding: raw.binding ?? "SANDBOX",
33
+ className: raw.className ?? "Sandbox",
34
+ containerName: raw.containerName ?? "void-sandbox",
35
+ image: resolveSandboxImage(image, root),
36
+ platformImage,
37
+ ...imageBuildContext && { imageBuildContext: isAbsolute(imageBuildContext) ? imageBuildContext : join(root, imageBuildContext) },
38
+ ...raw.instanceType && { instanceType: raw.instanceType },
39
+ ...raw.maxInstances != null && { maxInstances: raw.maxInstances }
40
+ };
41
+ }
42
+ function getSandboxPackageRoot() {
43
+ return join(dirname(require.resolve("@cloudflare/sandbox")), "..");
44
+ }
45
+ function resolveSandboxImage(image, root) {
46
+ if (!isLocalSandboxImage(image) || isAbsolute(image)) return image;
47
+ return join(root, image);
48
+ }
49
+ //#endregion
50
+ //#region src/cli/wrangler.ts
51
+ const COMPAT_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
52
+ /** UTF-8 byte-order mark. Node's utf-8 read preserves a leading one. */
53
+ const UTF8_BOM = "";
54
+ const NODEJS_ALS_FLAG = "nodejs_als";
55
+ const NODEJS_COMPAT_FLAG = "nodejs_compat";
56
+ const VOID_CONFIG_FILE = "void.json";
57
+ const MANAGED_QUEUES_MARKER = /^\s*\/\/ void:managed-queues:v1:([A-Za-z0-9_-]+)\s*$/m;
58
+ const PUBLISHED_TRAFFIC_MARKER = /^\s*\/\/ void:published-traffic:v1:([A-Za-z0-9_-]+)\s*$/m;
59
+ const DEFAULT_LIVE_BINDING = "VOID_LIVE";
60
+ const DEFAULT_LIVE_CLASS = "VoidLiveStreamDurableObject";
61
+ const LIVE_MIGRATION_TAG = "live-v1";
62
+ /** Read the root Wrangler config with the same JSONC parser used by binding sync. */
63
+ function readRootWranglerConfig(root) {
64
+ for (const name of ["wrangler.jsonc", "wrangler.json"]) {
65
+ const configPath = join(root, name);
66
+ if (!existsSync(configPath)) continue;
67
+ const parsed = parseJsonc(readFileSync(configPath, "utf-8"));
68
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new SyntaxError(`Failed to parse ${configPath}: expected a JSON object.`);
69
+ return {
70
+ configPath,
71
+ config: parsed
72
+ };
73
+ }
74
+ }
75
+ /** Read the last traffic attachment state Void successfully published remotely. */
76
+ function readWranglerPublishedTrafficSnapshot(configPath) {
77
+ if (!existsSync(configPath)) return;
78
+ const encoded = PUBLISHED_TRAFFIC_MARKER.exec(readFileSync(configPath, "utf-8"))?.[1];
79
+ if (!encoded) return;
80
+ try {
81
+ const value = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
82
+ if (!isRecord(value) || typeof value.workers_dev !== "boolean" || typeof value.preview_urls !== "boolean" || !Array.isArray(value.routes)) return;
83
+ return {
84
+ workers_dev: value.workers_dev,
85
+ preview_urls: value.preview_urls,
86
+ routes: value.routes
87
+ };
88
+ } catch {
89
+ return;
90
+ }
91
+ }
92
+ /** Persist traffic attachment provenance without adding unsupported Wrangler fields. */
93
+ function writeWranglerPublishedTrafficSnapshot(configPath, snapshot, options) {
94
+ if (!existsSync(configPath)) return;
95
+ const raw = readFileSync(configPath, "utf-8");
96
+ const marker = `// void:published-traffic:v1:${Buffer.from(JSON.stringify(snapshot), "utf8").toString("base64url")}`;
97
+ const existing = PUBLISHED_TRAFFIC_MARKER.exec(raw);
98
+ if (!existing && options?.create === false) return;
99
+ const next = existing ? raw.slice(0, existing.index) + marker + raw.slice(existing.index + existing[0].length) : `${marker}${raw.includes("\r\n") ? "\r\n" : "\n"}${raw}`;
100
+ if (next !== raw) writeFileSync(configPath, next);
101
+ }
102
+ /** Latest compatibility date the Void test suite is pinned against. */
103
+ const LATEST_KNOWN_COMPAT_DATE = "2026-02-24";
104
+ function normalizeWranglerCompatibilityFlags(flags, options) {
105
+ const existingFlags = flags ?? [];
106
+ if (options?.needsNodejsCompat && !existingFlags.includes(NODEJS_COMPAT_FLAG)) return [...existingFlags.filter((flag) => flag !== NODEJS_ALS_FLAG), NODEJS_COMPAT_FLAG];
107
+ if (existingFlags.length === 0 || !existingFlags.includes(NODEJS_ALS_FLAG) && !existingFlags.includes(NODEJS_COMPAT_FLAG)) return [...existingFlags, NODEJS_ALS_FLAG];
108
+ return flags;
109
+ }
110
+ function hasDurableObjectSqliteMigration(migrations, className) {
111
+ return migrations.some((migration) => migration.new_sqlite_classes?.includes(className));
112
+ }
113
+ function nextDurableObjectMigrationTag(migrations, baseTag) {
114
+ const existingTags = new Set(migrations.map((migration) => migration.tag).filter(Boolean));
115
+ if (!existingTags.has(baseTag)) return baseTag;
116
+ let index = 2;
117
+ while (existingTags.has(`${baseTag}-${index}`)) index++;
118
+ return `${baseTag}-${index}`;
119
+ }
120
+ function resolveLiveBindingConfig(root, options) {
121
+ const durableObjectBindings = [...readWranglerDurableObjectBindings(root), ...options?.existingBindings ?? []];
122
+ const generated = findGeneratedLiveBinding(durableObjectBindings);
123
+ if (generated) return {
124
+ bindingName: generated.name,
125
+ className: generated.class_name
126
+ };
127
+ const usedBindings = /* @__PURE__ */ new Set([...durableObjectBindings.map((binding) => binding.name), ...(options?.reservedBindings ?? []).filter((name) => typeof name === "string")]);
128
+ const usedClasses = /* @__PURE__ */ new Set([...durableObjectBindings.map((binding) => binding.class_name), ...(options?.reservedClasses ?? []).filter((name) => typeof name === "string")]);
129
+ return {
130
+ bindingName: resolveGeneratedName(DEFAULT_LIVE_BINDING, usedBindings, "binding"),
131
+ className: resolveGeneratedName(DEFAULT_LIVE_CLASS, usedClasses, "class")
132
+ };
133
+ }
134
+ function readExistingLiveBindingConfig(root) {
135
+ const generated = findGeneratedLiveBinding(readWranglerDurableObjectBindings(root));
136
+ return generated ? {
137
+ bindingName: generated.name,
138
+ className: generated.class_name
139
+ } : void 0;
140
+ }
141
+ function readWranglerDurableObjectBindings(root) {
142
+ for (const name of ["wrangler.jsonc", "wrangler.json"]) try {
143
+ const bindings = parseJsonc(readFileSync(join(root, name), "utf-8")).durable_objects?.bindings;
144
+ if (!Array.isArray(bindings)) return [];
145
+ return bindings.filter((binding) => typeof binding.name === "string" && typeof binding.class_name === "string");
146
+ } catch {}
147
+ return [];
148
+ }
149
+ function isGeneratedLiveName(name, base) {
150
+ return name === base || name.match(generatedLiveNamePattern(base)) !== null;
151
+ }
152
+ function generatedLiveNamePattern(base) {
153
+ return new RegExp(`^${escapeRegExp(base)}_[0-9a-f]{8}$`);
154
+ }
155
+ function escapeRegExp(value) {
156
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
157
+ }
158
+ function findGeneratedLiveBinding(bindings) {
159
+ return bindings.find((binding) => isGeneratedLiveName(binding.name, "VOID_LIVE") && isGeneratedLiveName(binding.class_name, "VoidLiveStreamDurableObject"));
160
+ }
161
+ function resolveGeneratedName(base, used, kind) {
162
+ if (!used.has(base)) return base;
163
+ for (let attempt = 0;; attempt++) {
164
+ const candidate = `${base}_${createHash("sha256").update(`void/live:${kind}:${base}:${attempt}`).digest("hex").slice(0, 8)}`;
165
+ if (!used.has(candidate)) return candidate;
166
+ }
167
+ }
168
+ /**
169
+ * Read compatibility settings from `wrangler.jsonc`/`wrangler.json`,
170
+ * `void.json`, and build output, with priority:
171
+ * void.json > wrangler.jsonc > build output > latest known-good date.
172
+ *
173
+ * This function is read-only: when no explicit `compatibility_date` exists, it
174
+ * returns the latest known-good date without writing it anywhere. Use
175
+ * `ensureWranglerCompat()` when the caller is in a real dev/build/deploy
176
+ * lifecycle and wants to persist the default in `void.json`.
177
+ *
178
+ * @param buildOutputDir - Optional path to the build output directory (e.g.
179
+ * `dist/ssr`) which may contain a plugin-generated `wrangler.json` with the
180
+ * correct compat settings for the built worker.
181
+ */
182
+ function readWranglerCompat(root, config, buildOutputDir) {
183
+ return resolveWranglerCompat(readWranglerCompatFields(root, config, buildOutputDir));
184
+ }
185
+ /**
186
+ * Read compatibility settings and ensure the default date is pinned in
187
+ * `void.json` when no explicit date exists in void.json, wrangler config, or
188
+ * build output.
189
+ */
190
+ function ensureWranglerCompat(root, config, buildOutputDir) {
191
+ const fields = readWranglerCompatFields(root, config, buildOutputDir);
192
+ const defaultDate = fields.voidDate ?? fields.wranglerDate ?? fields.buildDate ? void 0 : ensureVoidCompatibilityDate(root, fields.voidConfig);
193
+ return resolveWranglerCompat(fields, defaultDate ? { defaultDate } : void 0);
194
+ }
195
+ function readWranglerCompatFields(root, config, buildOutputDir) {
196
+ const voidConfig = config ?? readVoidConfigIfExists(root);
197
+ let raw;
198
+ for (const name of ["wrangler.jsonc", "wrangler.json"]) try {
199
+ raw = readFileSync(join(root, name), "utf-8");
200
+ break;
201
+ } catch {}
202
+ let wranglerDate;
203
+ let wranglerFlags;
204
+ if (raw) {
205
+ const parsed = parseJsonc(raw);
206
+ if (typeof parsed.compatibility_date === "string") {
207
+ validateCompatDate(parsed.compatibility_date, "wrangler config");
208
+ wranglerDate = parsed.compatibility_date;
209
+ }
210
+ if (Array.isArray(parsed.compatibility_flags) && parsed.compatibility_flags.every((f) => typeof f === "string")) wranglerFlags = parsed.compatibility_flags;
211
+ }
212
+ let buildDate;
213
+ let buildFlags;
214
+ if (buildOutputDir) try {
215
+ const buildRaw = readFileSync(join(buildOutputDir, "wrangler.json"), "utf-8");
216
+ const buildParsed = JSON.parse(buildRaw);
217
+ if (typeof buildParsed.compatibility_date === "string") {
218
+ validateCompatDate(buildParsed.compatibility_date, "build output wrangler.json");
219
+ buildDate = buildParsed.compatibility_date;
220
+ }
221
+ if (Array.isArray(buildParsed.compatibility_flags) && buildParsed.compatibility_flags.every((f) => typeof f === "string")) buildFlags = buildParsed.compatibility_flags;
222
+ } catch {}
223
+ const voidDate = voidConfig?.worker?.compatibility_date;
224
+ const voidFlags = voidConfig?.worker?.compatibility_flags;
225
+ if (voidDate !== void 0) validateCompatDate(voidDate, "void.json worker config");
226
+ return {
227
+ voidConfig,
228
+ voidDate,
229
+ voidFlags,
230
+ wranglerDate,
231
+ wranglerFlags,
232
+ buildDate,
233
+ buildFlags
234
+ };
235
+ }
236
+ function resolveWranglerCompat(fields, options) {
237
+ const result = { compatibilityDate: fields.voidDate ?? fields.wranglerDate ?? fields.buildDate ?? options?.defaultDate ?? "2026-02-24" };
238
+ const flags = fields.voidFlags ?? fields.wranglerFlags ?? fields.buildFlags;
239
+ if (flags && flags.length > 0) result.compatibilityFlags = flags;
240
+ return result;
241
+ }
242
+ function validateCompatDate(value, source) {
243
+ if (!COMPAT_DATE_RE.test(value)) throw new Error(`config: "compatibility_date" in ${source} must use YYYY-MM-DD format, got '${value}'.`);
244
+ }
245
+ function readVoidConfigIfExists(root) {
246
+ const filePath = join(root, VOID_CONFIG_FILE);
247
+ if (!existsSync(filePath)) return;
248
+ try {
249
+ const parsed = JSON.parse(readFileSync(filePath, "utf-8"));
250
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`config: ${VOID_CONFIG_FILE} must be a JSON object.`);
251
+ return parsed;
252
+ } catch (error) {
253
+ if (error instanceof SyntaxError) throw new Error(`config: Invalid JSON in ${VOID_CONFIG_FILE}.`);
254
+ throw error;
255
+ }
256
+ }
257
+ function ensureVoidCompatibilityDate(root, config) {
258
+ const filePath = join(root, VOID_CONFIG_FILE);
259
+ let nextConfig = {};
260
+ if (existsSync(filePath)) try {
261
+ const parsed = JSON.parse(readFileSync(filePath, "utf-8"));
262
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error(`config: ${VOID_CONFIG_FILE} must be a JSON object.`);
263
+ nextConfig = parsed;
264
+ } catch (error) {
265
+ if (error instanceof SyntaxError) throw new Error(`config: Invalid JSON in ${VOID_CONFIG_FILE}.`);
266
+ throw error;
267
+ }
268
+ else if (config) nextConfig = { ...config };
269
+ nextConfig.worker = {
270
+ ...nextConfig.worker ?? {},
271
+ compatibility_date: LATEST_KNOWN_COMPAT_DATE
272
+ };
273
+ writeFileSync(filePath, `${JSON.stringify(nextConfig, null, 2)}\n`);
274
+ return LATEST_KNOWN_COMPAT_DATE;
275
+ }
276
+ /**
277
+ * Sync inferred/configured bindings into the project's wrangler.jsonc for local dev.
278
+ * Only adds bindings that are missing — existing bindings are preserved.
279
+ * Returns true if the file was modified.
280
+ */
281
+ function syncWranglerBindings(root, bindings, options) {
282
+ let configPath;
283
+ let parsed;
284
+ let created = false;
285
+ for (const name of ["wrangler.jsonc", "wrangler.json"]) {
286
+ const p = join(root, name);
287
+ if (existsSync(p)) {
288
+ configPath = p;
289
+ try {
290
+ parsed = parseJsonc(readFileSync(p, "utf-8"));
291
+ } catch {}
292
+ break;
293
+ }
294
+ }
295
+ if (!configPath || !parsed) {
296
+ configPath = join(root, "wrangler.jsonc");
297
+ parsed = {};
298
+ created = true;
299
+ }
300
+ let modified = false;
301
+ const d1Name = options?.bindingNames?.d1 ?? "DB";
302
+ if (bindings.needsD1) {
303
+ const existing = parsed.d1_databases ?? [];
304
+ if (!existing.some((b) => b.binding === d1Name)) {
305
+ parsed.d1_databases = [...existing, {
306
+ binding: d1Name,
307
+ database_name: "default",
308
+ database_id: "local"
309
+ }];
310
+ modified = true;
311
+ }
312
+ }
313
+ const kvName = options?.bindingNames?.kv ?? "KV";
314
+ if (bindings.needsKV) {
315
+ const existing = parsed.kv_namespaces ?? [];
316
+ if (!existing.some((b) => b.binding === kvName)) {
317
+ parsed.kv_namespaces = [...existing, {
318
+ binding: kvName,
319
+ id: "local"
320
+ }];
321
+ modified = true;
322
+ }
323
+ }
324
+ const r2Name = options?.bindingNames?.r2 ?? "STORAGE";
325
+ if (bindings.needsR2) {
326
+ const existing = parsed.r2_buckets ?? [];
327
+ if (!existing.some((b) => b.binding === r2Name)) {
328
+ parsed.r2_buckets = [...existing, {
329
+ binding: r2Name,
330
+ bucket_name: "default"
331
+ }];
332
+ modified = true;
333
+ }
334
+ }
335
+ if (options?.websockets) {
336
+ const existing = parsed.durable_objects?.bindings ?? [];
337
+ const migrations = Array.isArray(parsed.migrations) ? parsed.migrations : [];
338
+ const retained = existing;
339
+ for (const route of options.websockets) {
340
+ const conflict = retained.find((binding) => binding.name === route.bindingName && binding.class_name !== route.className);
341
+ if (conflict) throw new Error(`websocket: binding '${route.bindingName}' is already configured for class '${conflict.class_name}', but '${route.filePath}' requires '${route.className}'.`);
342
+ }
343
+ const additions = options.websockets.filter((route) => !retained.some((binding) => binding.name === route.bindingName)).map((route) => ({
344
+ name: route.bindingName,
345
+ class_name: route.className
346
+ }));
347
+ const nextBindings = [...retained, ...additions];
348
+ if (nextBindings.length !== existing.length || nextBindings.some((binding, index) => binding.name !== existing[index]?.name || binding.class_name !== existing[index]?.class_name)) {
349
+ parsed.durable_objects = {
350
+ ...parsed.durable_objects ?? {},
351
+ bindings: nextBindings
352
+ };
353
+ modified = true;
354
+ }
355
+ const planned = appendSqliteClassMigration(migrations, options.websockets.map((route) => route.className), "void-ws-v1", retained.map((binding) => binding.class_name));
356
+ if (planned.added) {
357
+ parsed.migrations = planned.migrations;
358
+ modified = true;
359
+ }
360
+ }
361
+ if (options?.durableObjects && options.durableObjects.length > 0) {
362
+ const existingBindings = parsed.durable_objects?.bindings ?? [];
363
+ for (const module of options.durableObjects) {
364
+ const conflict = existingBindings.find((binding) => binding.name === module.bindingName && binding.class_name !== module.className);
365
+ if (conflict) throw new Error(`durable: binding '${module.bindingName}' is already configured for class '${conflict.class_name}', but durable-objects/ requires '${module.className}'.`);
366
+ }
367
+ const additions = options.durableObjects.filter((module) => !existingBindings.some((binding) => binding.name === module.bindingName)).map((module) => ({
368
+ name: module.bindingName,
369
+ class_name: module.className
370
+ }));
371
+ if (additions.length > 0) {
372
+ parsed.durable_objects = { bindings: [...existingBindings, ...additions] };
373
+ modified = true;
374
+ }
375
+ const migrations = Array.isArray(parsed.migrations) ? parsed.migrations : [];
376
+ for (const module of options.durableObjects) {
377
+ if (migrations.find((migration) => migration.tag === module.migrationTag && !migration.new_sqlite_classes?.includes(module.className))) throw new Error(`durable: migration tag '${module.migrationTag}' is already used by another Durable Object migration.`);
378
+ if (!hasDurableObjectSqliteMigration(migrations, module.className)) {
379
+ migrations.push({
380
+ tag: module.migrationTag,
381
+ new_sqlite_classes: [module.className]
382
+ });
383
+ modified = true;
384
+ }
385
+ }
386
+ if (modified) parsed.migrations = migrations;
387
+ }
388
+ if (bindings.needsSandbox) {
389
+ const sandbox = options?.sandbox ?? resolveSandboxConfig({ sandbox: true }, root);
390
+ const bindingName = options?.bindingNames?.sandbox ?? sandbox?.binding ?? "SANDBOX";
391
+ const className = sandbox?.className ?? "Sandbox";
392
+ const existingDurableObjectBindings = parsed.durable_objects?.bindings ?? [];
393
+ if (!existingDurableObjectBindings.some((binding) => binding.name === bindingName)) {
394
+ parsed.durable_objects = { bindings: [...existingDurableObjectBindings, {
395
+ name: bindingName,
396
+ class_name: className
397
+ }] };
398
+ modified = true;
399
+ }
400
+ const existingContainers = parsed.containers ?? [];
401
+ if (!existingContainers.some((container) => container.class_name === className)) {
402
+ parsed.containers = [...existingContainers, {
403
+ name: sandbox?.containerName ?? "void-sandbox",
404
+ class_name: className,
405
+ image: sandbox?.image,
406
+ ...sandbox?.imageBuildContext && { image_build_context: sandbox.imageBuildContext },
407
+ ...sandbox?.instanceType && { instance_type: sandbox.instanceType },
408
+ ...sandbox?.maxInstances != null && { max_instances: sandbox.maxInstances }
409
+ }];
410
+ modified = true;
411
+ }
412
+ const migrations = Array.isArray(parsed.migrations) ? parsed.migrations : [];
413
+ if (!migrations.some((migration) => migration.tag === "void-sandbox-v1" || migration.new_sqlite_classes?.includes(className))) {
414
+ parsed.migrations = [...migrations, {
415
+ tag: SANDBOX_MIGRATION_TAG,
416
+ new_sqlite_classes: [className]
417
+ }];
418
+ modified = true;
419
+ }
420
+ }
421
+ if (bindings.needsLive || options?.live) {
422
+ const existingDurableObjectBindings = parsed.durable_objects?.bindings ?? [];
423
+ const liveConfig = typeof options?.live === "object" ? options.live : resolveLiveBindingConfig(root, {
424
+ existingBindings: existingDurableObjectBindings,
425
+ reservedBindings: options?.websockets?.map((route) => route.bindingName),
426
+ reservedClasses: [...options?.websockets?.map((route) => route.className) ?? [], options?.sandbox?.className]
427
+ });
428
+ if (!existingDurableObjectBindings.some((binding) => binding.name === liveConfig.bindingName)) {
429
+ parsed.durable_objects = { bindings: [...existingDurableObjectBindings, {
430
+ name: liveConfig.bindingName,
431
+ class_name: liveConfig.className
432
+ }] };
433
+ modified = true;
434
+ }
435
+ const migrations = Array.isArray(parsed.migrations) ? parsed.migrations : [];
436
+ if (!hasDurableObjectSqliteMigration(migrations, liveConfig.className)) {
437
+ parsed.migrations = [...migrations, {
438
+ tag: nextDurableObjectMigrationTag(migrations, LIVE_MIGRATION_TAG),
439
+ new_sqlite_classes: [liveConfig.className]
440
+ }];
441
+ modified = true;
442
+ }
443
+ }
444
+ if (created || modified) writeFileSync(configPath, JSON.stringify(parsed, null, 2) + "\n");
445
+ return created || modified;
446
+ }
447
+ const NO_INFERRED_BINDINGS = {
448
+ needsD1: false,
449
+ needsKV: false,
450
+ needsR2: false,
451
+ needsAuth: false,
452
+ needsQueues: false,
453
+ needsAI: false,
454
+ needsSandbox: false,
455
+ needsLive: false
456
+ };
457
+ /** Persist convention-derived Durable Object bindings and append-only migration history. */
458
+ function syncWranglerDurableObjects(root, durableObjects) {
459
+ if (durableObjects.length === 0) return false;
460
+ return syncWranglerBindings(root, NO_INFERRED_BINDINGS, { durableObjects });
461
+ }
462
+ /** Persist WebSocket Durable Object bindings and SQLite migration history. */
463
+ function syncWranglerWebSockets(root, websockets) {
464
+ if (websockets.length === 0 && !["wrangler.jsonc", "wrangler.json"].some((name) => existsSync(join(root, name)))) return false;
465
+ return writeProvisionedWranglerConfig(root, { webSockets: websockets }).modified;
466
+ }
467
+ /**
468
+ * Return registered WebSocket classes that no longer have a source route.
469
+ * These classes must remain exported while their data-bearing bindings remain
470
+ * in Wrangler configuration. Arbitrary user Durable Objects are excluded by
471
+ * requiring provenance from Void's WebSocket migration tag.
472
+ */
473
+ function readWranglerWebSocketTombstones(root, websockets) {
474
+ const configPath = ["wrangler.jsonc", "wrangler.json"].map((name) => join(root, name)).find((path) => existsSync(path));
475
+ if (!configPath) return [];
476
+ const parsed = parseJsonc(readFileSync(configPath, "utf-8"));
477
+ const migrations = Array.isArray(parsed.migrations) ? parsed.migrations : [];
478
+ const historicalClasses = new Set(migrations.filter((migration) => /^void-ws-v1(?:-\d+)?$/.test(migration.tag ?? "")).flatMap((migration) => [...migration.new_classes ?? [], ...migration.new_sqlite_classes ?? []]));
479
+ const currentClasses = new Set(websockets.map((route) => route.className));
480
+ const bindings = parsed.durable_objects?.bindings;
481
+ return [...new Set((Array.isArray(bindings) ? bindings : []).flatMap((binding) => typeof binding.class_name === "string" && historicalClasses.has(binding.class_name) && !currentClasses.has(binding.class_name) ? [binding.class_name] : []))].sort();
482
+ }
483
+ /**
484
+ * Ensure a wrangler config has an explicit compatibility date.
485
+ * Used when `worker.compatibility_date` is configured in void.json but the
486
+ * active framework reads Cloudflare settings from wrangler.jsonc/json.
487
+ */
488
+ function ensureWranglerCompatibilityDate(root, compatibilityDate) {
489
+ validateCompatDate(compatibilityDate, "void.json worker config");
490
+ let configPath;
491
+ let parsed;
492
+ for (const name of ["wrangler.jsonc", "wrangler.json"]) {
493
+ const p = join(root, name);
494
+ if (existsSync(p)) {
495
+ configPath = p;
496
+ try {
497
+ parsed = parseJsonc(readFileSync(p, "utf-8"));
498
+ } catch {}
499
+ break;
500
+ }
501
+ }
502
+ if (!configPath || !parsed) {
503
+ configPath = join(root, "wrangler.jsonc");
504
+ parsed = {};
505
+ }
506
+ if (parsed.compatibility_date === compatibilityDate) return false;
507
+ parsed.compatibility_date = compatibilityDate;
508
+ writeFileSync(configPath, JSON.stringify(parsed, null, 2) + "\n");
509
+ return true;
510
+ }
511
+ /**
512
+ * Ensure the `nodejs_als` compatibility flag exists in wrangler config.
513
+ * Returns true if the config file was modified.
514
+ */
515
+ function ensureWranglerNodejsAlsFlag(root) {
516
+ let configPath;
517
+ let parsed;
518
+ for (const name of ["wrangler.jsonc", "wrangler.json"]) {
519
+ const p = join(root, name);
520
+ if (existsSync(p)) {
521
+ configPath = p;
522
+ try {
523
+ parsed = parseJsonc(readFileSync(p, "utf-8"));
524
+ } catch {}
525
+ break;
526
+ }
527
+ }
528
+ if (!configPath || !parsed) {
529
+ configPath = join(root, "wrangler.jsonc");
530
+ parsed = {};
531
+ }
532
+ const compatibilityFlags = Array.isArray(parsed.compatibility_flags) ? parsed.compatibility_flags : [];
533
+ if (compatibilityFlags.includes(NODEJS_ALS_FLAG)) return false;
534
+ parsed.compatibility_flags = [...compatibilityFlags, NODEJS_ALS_FLAG];
535
+ writeFileSync(configPath, JSON.stringify(parsed, null, 2) + "\n");
536
+ return true;
537
+ }
538
+ function readManagedQueueProvenance(text) {
539
+ const encoded = MANAGED_QUEUES_MARKER.exec(text)?.[1];
540
+ if (!encoded) return {
541
+ producerBindings: [],
542
+ consumerQueues: [],
543
+ pendingConsumerRemovals: []
544
+ };
545
+ try {
546
+ const parsed = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
547
+ if (!isRecord(parsed)) return {
548
+ producerBindings: [],
549
+ consumerQueues: [],
550
+ pendingConsumerRemovals: []
551
+ };
552
+ return {
553
+ producerBindings: Array.isArray(parsed.producerBindings) ? parsed.producerBindings.filter((value) => typeof value === "string") : [],
554
+ consumerQueues: Array.isArray(parsed.consumerQueues) ? parsed.consumerQueues.filter((value) => typeof value === "string") : [],
555
+ pendingConsumerRemovals: Array.isArray(parsed.pendingConsumerRemovals) ? parsed.pendingConsumerRemovals.filter((value) => typeof value === "string") : []
556
+ };
557
+ } catch {
558
+ return {
559
+ producerBindings: [],
560
+ consumerQueues: [],
561
+ pendingConsumerRemovals: []
562
+ };
563
+ }
564
+ }
565
+ function writeManagedQueueProvenance(text, provenance) {
566
+ const normalized = {
567
+ producerBindings: [...new Set(provenance.producerBindings)].sort(),
568
+ consumerQueues: [...new Set(provenance.consumerQueues)].sort(),
569
+ pendingConsumerRemovals: [...new Set(provenance.pendingConsumerRemovals)].sort()
570
+ };
571
+ const existing = MANAGED_QUEUES_MARKER.exec(text);
572
+ if (normalized.producerBindings.length === 0 && normalized.consumerQueues.length === 0 && normalized.pendingConsumerRemovals.length === 0) {
573
+ if (!existing) return text;
574
+ const start = existing.index;
575
+ const end = start + existing[0].length;
576
+ const trailingNewline = text.slice(end).match(/^(\r?\n)/)?.[1] ?? "";
577
+ return text.slice(0, start) + text.slice(end + trailingNewline.length);
578
+ }
579
+ const marker = `// void:managed-queues:v1:${Buffer.from(JSON.stringify(normalized), "utf8").toString("base64url")}`;
580
+ if (existing) return text.slice(0, existing.index) + marker + text.slice(existing.index + existing[0].length);
581
+ return `${marker}${text.includes("\r\n") ? "\r\n" : "\n"}${text}`;
582
+ }
583
+ /**
584
+ * Return explicitly Void-owned consumer names that may need remote cleanup.
585
+ *
586
+ * Current managed consumers are included for schema-1 upgrades; the activation layer subtracts
587
+ * consumers still present in the desired config. Pending removals survive failed deploys so a
588
+ * later retry can finish cleanup without inferring ownership from queue naming conventions.
589
+ */
590
+ function readWranglerManagedQueueCleanupCandidates(configPath) {
591
+ if (!existsSync(configPath)) return [];
592
+ const raw = readFileSync(configPath, "utf-8");
593
+ const provenance = readManagedQueueProvenance(raw.startsWith(UTF8_BOM) ? raw.slice(1) : raw);
594
+ return [.../* @__PURE__ */ new Set([...provenance.consumerQueues, ...provenance.pendingConsumerRemovals])].sort();
595
+ }
596
+ /** Clear only cleanup tombstones confirmed by a successful trigger reconciliation. */
597
+ function acknowledgeWranglerManagedQueueRemovals(configPath, removedQueues) {
598
+ if (!existsSync(configPath) || removedQueues.length === 0) return;
599
+ const raw = readFileSync(configPath, "utf-8");
600
+ const hasBom = raw.startsWith(UTF8_BOM);
601
+ const text = hasBom ? raw.slice(1) : raw;
602
+ const provenance = readManagedQueueProvenance(text);
603
+ const removed = new Set(removedQueues);
604
+ const updated = writeManagedQueueProvenance(text, {
605
+ ...provenance,
606
+ pendingConsumerRemovals: provenance.pendingConsumerRemovals.filter((queue) => !removed.has(queue))
607
+ });
608
+ const finalText = hasBom ? `${UTF8_BOM}${updated}` : updated;
609
+ if (finalText !== raw) writeFileSync(configPath, finalText);
610
+ }
611
+ /**
612
+ * Fill root Wrangler config fields required by generated/provisioned resources, including
613
+ * append-only WebSocket Durable Object bindings and migrations, and Mode B email fields
614
+ * (`send_email`, `addresses`, `vars`) that have no Wrangler command.
615
+ *
616
+ * Comment-preserving: uses `jsonc-parser` `modify()` + `applyEdits()` so
617
+ * comments and unrelated content survive (unlike `syncWranglerBindings`, which
618
+ * round-trips through `JSON.stringify` and strips comments). Every edit is an
619
+ * idempotent upsert — running twice produces byte-identical output.
620
+ */
621
+ function writeProvisionedWranglerConfig(root, patch, options) {
622
+ let configPath = options?.configPath;
623
+ if (configPath === void 0) {
624
+ for (const name of ["wrangler.jsonc", "wrangler.json"]) {
625
+ const p = join(root, name);
626
+ if (existsSync(p)) {
627
+ configPath = p;
628
+ break;
629
+ }
630
+ }
631
+ configPath ??= join(root, "wrangler.jsonc");
632
+ }
633
+ const rawContent = existsSync(configPath) ? readFileSync(configPath, "utf-8") : "{}";
634
+ const hasBom = rawContent.startsWith(UTF8_BOM);
635
+ const original = hasBom ? rawContent.slice(1) : rawContent;
636
+ let parsed;
637
+ try {
638
+ parsed = parseJsonc(original);
639
+ } catch (error) {
640
+ throw new Error(`deploy: cannot update "${configPath}" — it is not valid JSON/JSONC. Fix the file and retry. (${error.message})`);
641
+ }
642
+ assertNoDuplicateMembers(original, configPath);
643
+ const formattingOptions = detectFormattingOptions(original);
644
+ let text = original;
645
+ const scalarSetPaths = [];
646
+ const set = (path, value) => {
647
+ scalarSetPaths.push(path);
648
+ text = applyMemberEdit(text, path, value, { formattingOptions });
649
+ };
650
+ if (patch.accountId !== void 0) set(["account_id"], patch.accountId);
651
+ if (patch.name !== void 0 && parsed.name === void 0) set(["name"], patch.name);
652
+ if (patch.compatibilityDate !== void 0 && parsed.compatibility_date === void 0) set(["compatibility_date"], patch.compatibilityDate);
653
+ if (patch.compatibilityFlags !== void 0) {
654
+ const existingFlags = parsed.compatibility_flags;
655
+ if (existingFlags !== void 0 && (!Array.isArray(existingFlags) || !existingFlags.every((flag) => typeof flag === "string"))) throw new Error(`deploy: cannot update "${configPath}" — "compatibility_flags" must be an array of strings.`);
656
+ const flags = existingFlags ?? [];
657
+ for (const flag of patch.compatibilityFlags) if (!flags.includes(flag)) {
658
+ text = applyMemberEdit(text, ["compatibility_flags", flags.length], flag, {
659
+ formattingOptions,
660
+ isArrayInsertion: true
661
+ });
662
+ flags.push(flag);
663
+ }
664
+ }
665
+ let workerBindingOwners;
666
+ if (patch.webSockets && patch.webSockets.length > 0) {
667
+ const requested = /* @__PURE__ */ new Map();
668
+ for (const route of patch.webSockets) {
669
+ const existing = requested.get(route.bindingName);
670
+ if (existing && existing.className !== route.className) throw new Error(`websocket: binding '${route.bindingName}' resolves to both '${existing.className}' and '${route.className}'. Rename one of the route files.`);
671
+ requested.set(route.bindingName, route);
672
+ }
673
+ const knownExistingClasses = bindingEntriesForField("durable_objects", parseJsonc(text).durable_objects).map(({ entry }, index) => {
674
+ if (typeof entry.class_name !== "string" || entry.class_name.length === 0) throw new Error(`deploy: cannot update "${configPath}" — "durable_objects.bindings" entry at index ${index} is missing a non-empty string "class_name".`);
675
+ return entry.class_name;
676
+ });
677
+ for (const route of requested.values()) {
678
+ const currentConfig = parseJsonc(text);
679
+ const durableObjectBindings = bindingEntriesForField("durable_objects", currentConfig.durable_objects);
680
+ const matches = durableObjectBindings.filter(({ name }) => name === route.bindingName);
681
+ if (matches.length > 1) throw new Error(`deploy: cannot update "${configPath}" — Durable Object binding '${route.bindingName}' is declared more than once.`);
682
+ if (matches.length === 1) {
683
+ const entry = matches[0].entry;
684
+ if (entry.class_name !== route.className || entry.script_name !== void 0 || entry.environment !== void 0) throw new Error(`websocket: binding '${route.bindingName}' is already configured for another class, script, or environment, but '${route.filePath}' requires the local class '${route.className}'.`);
685
+ continue;
686
+ }
687
+ const owners = collectWorkerBindingNames(configWithoutField(currentConfig, "durable_objects"));
688
+ addQueueProducerOwners(owners, currentConfig);
689
+ const owner = owners.get(route.bindingName);
690
+ if (owner) throw new Error(`deploy: cannot update "${configPath}" — WebSocket binding '${route.bindingName}' is already used by a ${owner} binding.`);
691
+ text = applyMemberEdit(text, [
692
+ "durable_objects",
693
+ "bindings",
694
+ durableObjectBindings.length
695
+ ], {
696
+ name: route.bindingName,
697
+ class_name: route.className
698
+ }, {
699
+ formattingOptions,
700
+ isArrayInsertion: true
701
+ });
702
+ }
703
+ const rawMigrations = parseJsonc(text).migrations;
704
+ const migrations = validateDurableObjectMigrations(rawMigrations, configPath);
705
+ const planned = appendSqliteClassMigration(migrations, [...requested.values()].map((route) => route.className), "void-ws-v1", knownExistingClasses);
706
+ if (planned.added) text = applyMemberEdit(text, ["migrations", migrations.length], planned.migrations[planned.migrations.length - 1], {
707
+ formattingOptions,
708
+ isArrayInsertion: true
709
+ });
710
+ }
711
+ const upsertAdoptedBinding = (field, binding, properties, identityKey = "binding") => {
712
+ const currentConfig = parseJsonc(text);
713
+ const rawList = currentConfig[field];
714
+ bindingNamesForField(field, rawList);
715
+ const currentList = Array.isArray(rawList) ? [...rawList] : [];
716
+ const matches = [];
717
+ for (let i = 0; i < currentList.length; i++) {
718
+ const entry = currentList[i];
719
+ if (isRecord(entry) && entry[identityKey] === binding) matches.push(i);
720
+ }
721
+ if (matches.length > 1) throw new Error(`deploy: cannot update "${configPath}" — binding ${JSON.stringify(binding)} is declared ${matches.length} times in "${field}". Void cannot choose which entry to update, and wrangler rejects duplicate binding names. Remove the duplicate and retry.`);
722
+ const owners = collectWorkerBindingNames(configWithoutField(currentConfig, field));
723
+ addQueueProducerOwners(owners, currentConfig);
724
+ const owner = owners.get(binding);
725
+ if (owner !== void 0) throw new Error(`deploy: cannot update "${configPath}" — cannot adopt ${JSON.stringify(binding)} into "${field}" because that binding name is already used by a ${owner} binding. Wrangler requires every binding name to be unique across the whole worker, so this would make the config undeployable. Rename the conflicting binding and retry.`);
726
+ if (matches.length === 1) {
727
+ const index = matches[0];
728
+ const existing = isRecord(currentList[index]) ? currentList[index] : {};
729
+ for (const [key, value] of Object.entries(properties)) if (!jsonEqual(existing[key], value)) set([
730
+ field,
731
+ index,
732
+ key
733
+ ], value);
734
+ } else text = applyMemberEdit(text, [field, currentList.length], {
735
+ [identityKey]: binding,
736
+ ...properties
737
+ }, {
738
+ formattingOptions,
739
+ isArrayInsertion: true
740
+ });
741
+ };
742
+ if (patch.d1) upsertAdoptedBinding("d1_databases", patch.d1.binding, {
743
+ database_name: patch.d1.databaseName,
744
+ database_id: patch.d1.databaseId
745
+ });
746
+ if (patch.kv) upsertAdoptedBinding("kv_namespaces", patch.kv.binding, { id: patch.kv.id });
747
+ if (patch.r2) upsertAdoptedBinding("r2_buckets", patch.r2.binding, { bucket_name: patch.r2.bucketName });
748
+ if (patch.hyperdrive) upsertAdoptedBinding("hyperdrive", patch.hyperdrive.binding, { id: patch.hyperdrive.id });
749
+ if (patch.sendEmail) upsertAdoptedBinding("send_email", patch.sendEmail.name, {}, "name");
750
+ let d1MigrationsDirSkipped = false;
751
+ if (patch.d1MigrationsDir) {
752
+ const currentD1 = parseJsonc(text).d1_databases;
753
+ const d1Entries = Array.isArray(currentD1) ? currentD1 : [];
754
+ const matchIndices = [];
755
+ d1Entries.forEach((entry, i) => {
756
+ if (entry.binding === patch.d1MigrationsDir.binding) matchIndices.push(i);
757
+ });
758
+ if (matchIndices.length > 1) throw new Error(`deploy: cannot update "${configPath}" — binding ${JSON.stringify(patch.d1MigrationsDir.binding)} is declared ${matchIndices.length} times in "d1_databases". A binding name must be unique across the worker (wrangler rejects the config otherwise), so migrations_dir cannot be attached unambiguously. Remove the duplicate D1 entry and retry.`);
759
+ const index = matchIndices.length === 1 ? matchIndices[0] : -1;
760
+ if (index >= 0) {
761
+ const existingPattern = d1Entries[index].migrations_pattern;
762
+ if (existingPattern !== void 0 && typeof existingPattern !== "string") throw new Error(`deploy: cannot update "${configPath}" — the D1 entry for binding ${JSON.stringify(patch.d1MigrationsDir.binding)} has a non-string "migrations_pattern" value; expected a string. Wrangler rejects such a config, so filling "migrations_dir" beside it would commit an invalid file. Fix the config and retry.`);
763
+ const existingDir = d1Entries[index].migrations_dir;
764
+ if (existingDir !== void 0 && typeof existingDir !== "string") throw new Error(`deploy: cannot update "${configPath}" — the D1 entry for binding ${JSON.stringify(patch.d1MigrationsDir.binding)} has a non-string "migrations_dir" value; expected a string. Fix the config and retry.`);
765
+ if (typeof existingDir === "string") {
766
+ if (normalizeMigrationsPath(existingDir) !== normalizeMigrationsPath(patch.d1MigrationsDir.dir)) throw new Error(`deploy: cannot update "${configPath}" — the D1 entry for binding ${JSON.stringify(patch.d1MigrationsDir.binding)} already sets "migrations_dir": ${JSON.stringify(existingDir)}, but Void keeps this database's migrations in ${JSON.stringify(patch.d1MigrationsDir.dir)}. Refusing to overwrite an explicit different migrations directory. Either remove "migrations_dir" from the entry (or set it to ${JSON.stringify(patch.d1MigrationsDir.dir)}) to let Void manage it, or move the migrations and keep your directory, then retry.`);
767
+ } else {
768
+ if (existingPattern !== void 0 && !migrationsPatternMatchesDir(existingPattern, patch.d1MigrationsDir.dir)) throw new Error(`deploy: cannot update "${configPath}" — the D1 entry for binding ${JSON.stringify(patch.d1MigrationsDir.binding)} has "migrations_pattern": ${JSON.stringify(existingPattern)}, which is not under the requested "migrations_dir": ${JSON.stringify(patch.d1MigrationsDir.dir)}. Wrangler requires the pattern to start with the migrations dir, so this change would break \`wrangler d1 migrations\`. Align "migrations_pattern" with the dir (or remove it) and retry.`);
769
+ set([
770
+ "d1_databases",
771
+ index,
772
+ "migrations_dir"
773
+ ], patch.d1MigrationsDir.dir);
774
+ }
775
+ } else d1MigrationsDirSkipped = true;
776
+ }
777
+ const reconcileManagedQueues = patch.queues?.reconcileVoidOwned === true;
778
+ const managedQueueProvenance = readManagedQueueProvenance(text);
779
+ const managedProducerBindings = new Set(managedQueueProvenance.producerBindings);
780
+ const managedConsumerQueues = new Set(managedQueueProvenance.consumerQueues);
781
+ const pendingConsumerRemovals = new Set(managedQueueProvenance.pendingConsumerRemovals);
782
+ const upsertQueueEntries = (section, entries) => {
783
+ if (!entries || entries.length === 0) return;
784
+ const currentQueues = assertObjectContainer(parseJsonc(text).queues, "queues");
785
+ if (section === "producers") assertValidQueueProducers(currentQueues?.producers);
786
+ const currentList = Array.isArray(currentQueues?.[section]) ? [...currentQueues[section]] : [];
787
+ for (const entry of entries) {
788
+ let index;
789
+ if (section === "producers") {
790
+ const matches = [];
791
+ for (let i = 0; i < currentList.length; i++) {
792
+ const existing = currentList[i];
793
+ if (isRecord(existing) && existing.binding === entry.binding) matches.push(i);
794
+ }
795
+ if (matches.length > 1) throw new Error(`deploy: cannot update "${configPath}" — queue producer binding ${JSON.stringify(entry.binding)} is declared ${matches.length} times across producers. A producer binding must map to exactly one queue, so wrangler rejects this config. Remove the duplicate producers and retry.`);
796
+ if (matches.length === 1) {
797
+ const existing = currentList[matches[0]];
798
+ if (isRecord(existing) && existing.queue !== entry.queue) throw new Error(`deploy: cannot update "${configPath}" — queue producer binding ${JSON.stringify(entry.binding)} is already bound to queue ${JSON.stringify(existing.queue)}, but queue ${JSON.stringify(entry.queue)} also requests it. A producer binding must map to exactly one queue. Rename or remove the conflicting producer and retry.`);
799
+ }
800
+ index = matches.length === 1 ? matches[0] : -1;
801
+ workerBindingOwners ??= collectWorkerBindingNames(parseJsonc(text));
802
+ const bindingName = String(entry.binding);
803
+ const owner = workerBindingOwners.get(bindingName);
804
+ if (owner !== void 0) throw new Error(`deploy: cannot update "${configPath}" — queue producer binding ${JSON.stringify(bindingName)} is already used by a ${owner} binding. Wrangler requires every binding name to be unique across the whole worker, so this producer would make the config undeployable. Rename the conflicting binding (or the queue) and retry.`);
805
+ } else index = currentList.findIndex((existing) => isRecord(existing) && existing.queue === entry.queue);
806
+ if (index >= 0) {
807
+ const existing = isRecord(currentList[index]) ? currentList[index] : {};
808
+ for (const [key, value] of Object.entries(entry)) {
809
+ if (value === void 0) continue;
810
+ if (!jsonEqual(existing[key], value)) set([
811
+ "queues",
812
+ section,
813
+ index,
814
+ key
815
+ ], value);
816
+ }
817
+ currentList[index] = {
818
+ ...existing,
819
+ ...entry
820
+ };
821
+ } else {
822
+ text = applyMemberEdit(text, [
823
+ "queues",
824
+ section,
825
+ currentList.length
826
+ ], entry, {
827
+ formattingOptions,
828
+ isArrayInsertion: true
829
+ });
830
+ currentList.push(entry);
831
+ if (reconcileManagedQueues) {
832
+ if (section === "producers" && typeof entry.binding === "string") managedProducerBindings.add(entry.binding);
833
+ else if (section === "consumers" && typeof entry.queue === "string") managedConsumerQueues.add(entry.queue);
834
+ }
835
+ }
836
+ }
837
+ };
838
+ if (patch.queues) {
839
+ if (patch.queues.reconcileVoidOwned) {
840
+ const queues = assertObjectContainer(parseJsonc(text).queues, "queues");
841
+ assertValidQueueProducers(queues?.producers);
842
+ const producers = Array.isArray(queues?.producers) ? queues.producers : [];
843
+ const desiredBindings = new Set((patch.queues.producers ?? []).map((producer) => producer.binding));
844
+ const staleProducerIndexes = [];
845
+ for (let index = 0; index < producers.length; index++) {
846
+ const producer = producers[index];
847
+ if (typeof producer.binding === "string" && managedProducerBindings.has(producer.binding) && !desiredBindings.has(producer.binding)) {
848
+ staleProducerIndexes.push(index);
849
+ managedProducerBindings.delete(producer.binding);
850
+ }
851
+ }
852
+ for (const index of staleProducerIndexes.reverse()) set([
853
+ "queues",
854
+ "producers",
855
+ index
856
+ ], void 0);
857
+ const consumers = Array.isArray(queues?.consumers) ? queues.consumers : [];
858
+ const desiredConsumerQueues = new Set((patch.queues.consumers ?? []).map((consumer) => consumer.queue));
859
+ const staleConsumerIndexes = consumers.flatMap((consumer, index) => {
860
+ if (typeof consumer.queue === "string" && managedConsumerQueues.has(consumer.queue) && !desiredConsumerQueues.has(consumer.queue)) {
861
+ managedConsumerQueues.delete(consumer.queue);
862
+ pendingConsumerRemovals.add(consumer.queue);
863
+ return [index];
864
+ }
865
+ return [];
866
+ });
867
+ for (const index of staleConsumerIndexes.reverse()) set([
868
+ "queues",
869
+ "consumers",
870
+ index
871
+ ], void 0);
872
+ }
873
+ upsertQueueEntries("producers", patch.queues.producers);
874
+ upsertQueueEntries("consumers", patch.queues.consumers);
875
+ for (const consumer of patch.queues.consumers ?? []) pendingConsumerRemovals.delete(consumer.queue);
876
+ }
877
+ if (patch.vars) {
878
+ const currentConfig = parseJsonc(text);
879
+ bindingNamesForField("vars", currentConfig.vars);
880
+ const currentVars = isRecord(currentConfig.vars) ? currentConfig.vars : {};
881
+ const owners = collectWorkerBindingNames(configWithoutField(currentConfig, "vars"));
882
+ addQueueProducerOwners(owners, currentConfig);
883
+ for (const [key, value] of Object.entries(patch.vars)) {
884
+ const owner = owners.get(key);
885
+ if (owner !== void 0) throw new Error(`deploy: cannot update "${configPath}" — cannot write var ${JSON.stringify(key)} because that binding name is already used by a ${owner} binding. Wrangler requires every binding name to be unique across the whole worker, so this would make the config undeployable. Rename the conflicting binding and retry.`);
886
+ if (!jsonEqual(currentVars[key], value)) set(["vars", key], value);
887
+ }
888
+ }
889
+ if (patch.addresses !== void 0) {
890
+ const currentAddresses = parseJsonc(text).addresses;
891
+ if (patch.addresses === null) text = removeMember(text, ["addresses"], { formattingOptions });
892
+ else if (!jsonEqual(currentAddresses, patch.addresses)) text = applyMemberEdit(text, ["addresses"], patch.addresses, { formattingOptions });
893
+ }
894
+ const droppedComment = findDroppedComment(extractCommentTokens(original), extractCommentTokens(text));
895
+ if (droppedComment !== void 0) {
896
+ const originalRoot = parseTree(original, void 0, { allowTrailingComma: true });
897
+ const compositeField = originalRoot ? scalarSetPaths.find((path) => {
898
+ const node = findNodeAtLocation(originalRoot, path);
899
+ return node?.type === "object" || node?.type === "array";
900
+ }) : void 0;
901
+ if (compositeField) {
902
+ const field = String(compositeField[compositeField.length - 1]);
903
+ throw new Error(`deploy: cannot update "${configPath}" — "${field}" has an object/array value; expected a string. Fix the config and retry.`);
904
+ }
905
+ throw new Error(`deploy: cannot update "${configPath}" — this edit would drop a comment (${droppedComment}); the writer refuses to corrupt the file — simplify the surrounding structure and retry.`);
906
+ }
907
+ if (reconcileManagedQueues) {
908
+ const finalQueues = assertObjectContainer(parseJsonc(text).queues, "queues");
909
+ const finalProducerBindings = new Set((Array.isArray(finalQueues?.producers) ? finalQueues.producers : []).flatMap((entry) => isRecord(entry) && typeof entry.binding === "string" ? [entry.binding] : []));
910
+ const finalConsumerQueues = new Set((Array.isArray(finalQueues?.consumers) ? finalQueues.consumers : []).flatMap((entry) => isRecord(entry) && typeof entry.queue === "string" ? [entry.queue] : []));
911
+ text = writeManagedQueueProvenance(text, {
912
+ producerBindings: [...managedProducerBindings].filter((binding) => finalProducerBindings.has(binding)),
913
+ consumerQueues: [...managedConsumerQueues].filter((queue) => finalConsumerQueues.has(queue)),
914
+ pendingConsumerRemovals: [...pendingConsumerRemovals]
915
+ });
916
+ }
917
+ const finalText = hasBom ? `${UTF8_BOM}${text}` : text;
918
+ const modified = finalText !== rawContent;
919
+ if (modified) writeFileSync(configPath, finalText);
920
+ return {
921
+ configPath,
922
+ modified,
923
+ d1MigrationsDirSkipped
924
+ };
925
+ }
926
+ /** Read the custom-domain routes owned by the root Wrangler config. */
927
+ function readWranglerCustomDomains(root) {
928
+ const configPath = discoverWranglerConfigPath(root);
929
+ const raw = existsSync(configPath) ? readFileSync(configPath, "utf-8") : "{}";
930
+ const text = raw.startsWith(UTF8_BOM) ? raw.slice(1) : raw;
931
+ const parsed = parseJsonc(text);
932
+ assertNoDuplicateMembers(text, configPath);
933
+ return {
934
+ configPath,
935
+ domains: parseCustomDomains(parsed.routes, configPath),
936
+ modified: false
937
+ };
938
+ }
939
+ /**
940
+ * Add or remove one Wrangler `custom_domain` route without rewriting unrelated
941
+ * configuration. The returned domain list is the post-edit source of truth used
942
+ * by `void domain list|status` on direct Cloudflare projects.
943
+ */
944
+ function updateWranglerCustomDomain(root, hostname, action) {
945
+ const configPath = discoverWranglerConfigPath(root);
946
+ if (!existsSync(configPath)) throw new Error(`domain: No Wrangler config found at "${configPath}". Run \`void init\` first.`);
947
+ const raw = readFileSync(configPath, "utf-8");
948
+ const hasBom = raw.startsWith(UTF8_BOM);
949
+ const original = hasBom ? raw.slice(1) : raw;
950
+ const parsed = parseJsonc(original);
951
+ assertNoDuplicateMembers(original, configPath);
952
+ if (parsed.route !== void 0) throw new Error(`domain: "${configPath}" uses the singular "route" field. Convert it to a "routes" array before managing custom domains so the existing route cannot be shadowed.`);
953
+ const routes = parsed.routes;
954
+ if (routes !== void 0 && !Array.isArray(routes)) throw new Error(`domain: "routes" in "${configPath}" must be an array.`);
955
+ const routeArray = routes ?? [];
956
+ const matches = [];
957
+ for (let index = 0; index < routeArray.length; index++) {
958
+ const route = routeArray[index];
959
+ if (typeof route === "string") {
960
+ if (route === hostname) throw new Error(`domain: ${JSON.stringify(hostname)} already exists as a non-custom route in "${configPath}". Remove or convert that route first.`);
961
+ continue;
962
+ }
963
+ if (typeof route !== "object" || route === null || Array.isArray(route)) throw new Error(`domain: "routes[${index}]" in "${configPath}" is malformed.`);
964
+ const entry = route;
965
+ if (entry.pattern !== hostname) continue;
966
+ if (entry.custom_domain !== true) throw new Error(`domain: ${JSON.stringify(hostname)} already exists as a non-custom route in "${configPath}". Remove or convert that route first.`);
967
+ matches.push(index);
968
+ }
969
+ if (matches.length > 1) throw new Error(`domain: ${JSON.stringify(hostname)} is declared more than once in "${configPath}". Remove the duplicate before continuing.`);
970
+ let text = original;
971
+ const formattingOptions = detectFormattingOptions(original);
972
+ if (action === "add" && matches.length === 0) {
973
+ text = applyMemberEdit(text, ["routes", routeArray.length], {
974
+ pattern: hostname,
975
+ custom_domain: true
976
+ }, {
977
+ formattingOptions,
978
+ isArrayInsertion: true
979
+ });
980
+ const dropped = findDroppedComment(extractCommentTokens(original), extractCommentTokens(text));
981
+ if (dropped !== void 0) throw new Error(`domain: cannot update "${configPath}" because the edit would drop a comment (${dropped}).`);
982
+ } else if (action === "delete" && matches.length === 1) text = applyEdits(text, modify(text, ["routes", matches[0]], void 0, { formattingOptions }));
983
+ const finalText = hasBom ? `${UTF8_BOM}${text}` : text;
984
+ const modified = finalText !== raw;
985
+ if (modified) writeFileSync(configPath, finalText);
986
+ return {
987
+ configPath,
988
+ domains: parseCustomDomains(parseJsonc(text).routes, configPath),
989
+ modified
990
+ };
991
+ }
992
+ function discoverWranglerConfigPath(root) {
993
+ for (const name of ["wrangler.jsonc", "wrangler.json"]) {
994
+ const path = join(root, name);
995
+ if (existsSync(path)) return path;
996
+ }
997
+ return join(root, "wrangler.jsonc");
998
+ }
999
+ function parseCustomDomains(value, configPath) {
1000
+ if (value === void 0) return [];
1001
+ if (!Array.isArray(value)) throw new Error(`domain: "routes" in "${configPath}" must be an array.`);
1002
+ const domains = [];
1003
+ for (let index = 0; index < value.length; index++) {
1004
+ const route = value[index];
1005
+ if (typeof route === "string") continue;
1006
+ if (typeof route !== "object" || route === null || Array.isArray(route)) throw new Error(`domain: "routes[${index}]" in "${configPath}" is malformed.`);
1007
+ const entry = route;
1008
+ if (entry.custom_domain !== true) continue;
1009
+ if (typeof entry.pattern !== "string" || entry.pattern.length === 0) throw new Error(`domain: custom domain route "routes[${index}]" in "${configPath}" needs a non-empty pattern.`);
1010
+ domains.push({
1011
+ pattern: entry.pattern,
1012
+ customDomain: true
1013
+ });
1014
+ }
1015
+ return domains;
1016
+ }
1017
+ /**
1018
+ * Extract the multiset of comment TOKENS (line + block) from JSONC source using
1019
+ * jsonc-parser's scanner — exact by construction, no regex. Each token is its
1020
+ * verbatim source slice, so a relocated comment keeps identical text (its
1021
+ * multiset entry is unchanged) while a dropped comment removes one.
1022
+ */
1023
+ function extractCommentTokens(text) {
1024
+ const scanner = createScanner(text, false);
1025
+ const tokens = [];
1026
+ let kind = scanner.scan();
1027
+ while (kind !== SyntaxKind.EOF) {
1028
+ if (kind === SyntaxKind.LineCommentTrivia || kind === SyntaxKind.BlockCommentTrivia) {
1029
+ const offset = scanner.getTokenOffset();
1030
+ tokens.push(text.slice(offset, offset + scanner.getTokenLength()));
1031
+ }
1032
+ kind = scanner.scan();
1033
+ }
1034
+ return tokens;
1035
+ }
1036
+ /**
1037
+ * Multiset-subset check: return the first comment token present in `before` but
1038
+ * missing (accounting for repeats) from `after`, or `undefined` when `after` is
1039
+ * a superset of `before`. The final gate uses this to prove no comment TEXT was
1040
+ * dropped by the edits.
1041
+ */
1042
+ function findDroppedComment(before, after) {
1043
+ const counts = /* @__PURE__ */ new Map();
1044
+ for (const token of after) counts.set(token, (counts.get(token) ?? 0) + 1);
1045
+ for (const token of before) {
1046
+ const remaining = counts.get(token) ?? 0;
1047
+ if (remaining <= 0) return token;
1048
+ counts.set(token, remaining - 1);
1049
+ }
1050
+ }
1051
+ /**
1052
+ * Detect indentation from an existing config so `jsonc-parser` formats inserted
1053
+ * content to match. Falls back to two-space indentation (the repo default).
1054
+ */
1055
+ function detectFormattingOptions(text) {
1056
+ const match = text.match(/^([ \t]+)\S/m);
1057
+ if (match) {
1058
+ const indent = match[1];
1059
+ if (indent.startsWith(" ")) return {
1060
+ tabSize: 1,
1061
+ insertSpaces: false
1062
+ };
1063
+ return {
1064
+ tabSize: indent.length,
1065
+ insertSpaces: true
1066
+ };
1067
+ }
1068
+ return {
1069
+ tabSize: 2,
1070
+ insertSpaces: true
1071
+ };
1072
+ }
1073
+ function parseJsonc(text) {
1074
+ const errors = [];
1075
+ const result = parse(text, errors, { allowTrailingComma: true });
1076
+ if (errors.length > 0) throw new SyntaxError("Failed to parse JSONC");
1077
+ return result;
1078
+ }
1079
+ /**
1080
+ * `modify()` + `applyEdits()`, but when the edit APPENDS a new member after a
1081
+ * sibling that carries a trailing inline comment, keep that comment attached to
1082
+ * the sibling it documented.
1083
+ *
1084
+ * jsonc-parser's formatting `modify()` reflows the whole enclosing member/entry
1085
+ * region and re-emits a trailing inline comment at the END of that region — so
1086
+ * inserting `migrations_dir` after `"database_id": "id" // production db` (or
1087
+ * appending a queue entry after `{ … } // jobs producer`) silently reassociates
1088
+ * the comment onto the NEWLY inserted member. That violates the writer's
1089
+ * comment-preservation contract. Here we detect that exact case, run the
1090
+ * (comment-free) formatting edit on a stripped copy, then splice the comment
1091
+ * back right after the sibling's separating comma so it stays put.
1092
+ *
1093
+ * Every other edit (value replacement, append after a comment-free sibling,
1094
+ * first insertion into a missing container) falls through to the plain path, so
1095
+ * behavior is byte-identical to before outside the trailing-comment case.
1096
+ */
1097
+ function applyMemberEdit(text, path, value, options) {
1098
+ const plain = () => applyEdits(text, modify(text, path, value, options));
1099
+ const root = parseTree(text, void 0, { allowTrailingComma: true });
1100
+ if (!root) return plain();
1101
+ if (!options.isArrayInsertion && findNodeAtLocation(root, path) !== void 0) return plain();
1102
+ let receivingPath = path.slice(0, -1);
1103
+ let container = findNodeAtLocation(root, receivingPath);
1104
+ while (!container && receivingPath.length > 0) {
1105
+ receivingPath = receivingPath.slice(0, -1);
1106
+ container = findNodeAtLocation(root, receivingPath);
1107
+ }
1108
+ const members = container?.children;
1109
+ if (!container || !members || members.length === 0) return plain();
1110
+ const last = members[members.length - 1];
1111
+ const trailing = readTrailingComment(text, last.offset + last.length);
1112
+ if (!trailing) return plain();
1113
+ const stripped = text.slice(0, last.offset + last.length) + text.slice(trailing.end);
1114
+ const rewritten = applyEdits(stripped, modify(stripped, path, value, options));
1115
+ const newRoot = parseTree(rewritten, void 0, { allowTrailingComma: true });
1116
+ const newContainer = newRoot ? findNodeAtLocation(newRoot, receivingPath) : void 0;
1117
+ const newMembers = newContainer?.children;
1118
+ if (!newRoot || !newContainer || !newMembers || newMembers.length < 2) return plain();
1119
+ const sibling = newMembers[newMembers.length - 2];
1120
+ const commaPos = rewritten.indexOf(",", sibling.offset + sibling.length);
1121
+ if (commaPos === -1) return plain();
1122
+ const relocated = `${rewritten.slice(0, commaPos + 1)} ${trailing.text}${rewritten.slice(commaPos + 1)}`;
1123
+ const plainResult = plain();
1124
+ let relocatedParsed;
1125
+ try {
1126
+ relocatedParsed = parseJsonc(relocated);
1127
+ } catch {
1128
+ return plainResult;
1129
+ }
1130
+ if (!jsonEqual(relocatedParsed, parseJsonc(plainResult))) return plainResult;
1131
+ return relocated;
1132
+ }
1133
+ /**
1134
+ * Remove one object member (the property at `path`), keeping every comment.
1135
+ *
1136
+ * jsonc-parser's `modify(text, path, undefined)` deletes from the END of the
1137
+ * previous member to the end of the removed one — so the previous sibling's
1138
+ * trailing comment, a full-line comment above the member, and the member's own
1139
+ * trailing comment all vanish (measured on 4.x). The final comment gate would
1140
+ * then refuse the write for the common `"name": "app", // worker` shape. Here
1141
+ * the property node's exact source range is spliced out instead, plus ONE
1142
+ * separating comma (the following one when present, else the preceding one so
1143
+ * a strict `.json` file never gains a trailing comma) and the now-blank line it
1144
+ * sat on. Comments are never inside those ranges, so they all stay put; a
1145
+ * comment that was attached to the removed member is left orphaned on its line.
1146
+ *
1147
+ * Validate-before-return backstop: the spliced text must parse and equal the
1148
+ * plain `modify()` result's data; otherwise the plain edit is returned and the
1149
+ * final gate decides. An absent member is a no-op.
1150
+ */
1151
+ function removeMember(text, path, options) {
1152
+ const root = parseTree(text, void 0, { allowTrailingComma: true });
1153
+ const valueNode = root ? findNodeAtLocation(root, path) : void 0;
1154
+ const property = valueNode?.parent;
1155
+ if (!root || !valueNode || !property || property.type !== "property") return valueNode ? applyEdits(text, modify(text, path, void 0, options)) : text;
1156
+ const start = property.offset;
1157
+ const end = property.offset + property.length;
1158
+ const scanner = createScanner(text, false);
1159
+ const tokens = [];
1160
+ for (let kind = scanner.scan(); kind !== SyntaxKind.EOF; kind = scanner.scan()) tokens.push({
1161
+ kind,
1162
+ offset: scanner.getTokenOffset(),
1163
+ length: scanner.getTokenLength()
1164
+ });
1165
+ const isTrivia = (kind) => kind === SyntaxKind.Trivia || kind === SyntaxKind.LineBreakTrivia || kind === SyntaxKind.LineCommentTrivia || kind === SyntaxKind.BlockCommentTrivia;
1166
+ const following = tokens.find((token) => token.offset >= end && !isTrivia(token.kind));
1167
+ const preceding = [...tokens].reverse().find((token) => token.offset + token.length <= start && !isTrivia(token.kind));
1168
+ const comma = following?.kind === SyntaxKind.CommaToken ? following : preceding?.kind === SyntaxKind.CommaToken ? preceding : void 0;
1169
+ const ranges = [{
1170
+ offset: start,
1171
+ length: end - start
1172
+ }];
1173
+ if (comma) ranges.push({
1174
+ offset: comma.offset,
1175
+ length: comma.length
1176
+ });
1177
+ ranges.sort((a, b) => b.offset - a.offset);
1178
+ let spliced = text;
1179
+ for (const range of ranges) spliced = spliced.slice(0, range.offset) + spliced.slice(range.offset + range.length);
1180
+ const cursor = comma && comma.offset < start ? start - comma.length : start;
1181
+ let lineStart = cursor;
1182
+ while (lineStart > 0 && spliced[lineStart - 1] !== "\n" && spliced[lineStart - 1] !== "\r") lineStart--;
1183
+ let lineEnd = cursor;
1184
+ while (lineEnd < spliced.length && spliced[lineEnd] !== "\n" && spliced[lineEnd] !== "\r") lineEnd++;
1185
+ if (spliced.slice(lineStart, lineEnd).trim() === "") {
1186
+ const terminator = spliced.startsWith("\r\n", lineEnd) ? 2 : lineEnd < spliced.length ? 1 : 0;
1187
+ spliced = spliced.slice(0, lineStart) + spliced.slice(lineEnd + terminator);
1188
+ }
1189
+ const plainResult = applyEdits(text, modify(text, path, void 0, options));
1190
+ let splicedParsed;
1191
+ try {
1192
+ splicedParsed = parseJsonc(spliced);
1193
+ } catch {
1194
+ return plainResult;
1195
+ }
1196
+ if (!jsonEqual(splicedParsed, parseJsonc(plainResult))) return plainResult;
1197
+ return spliced;
1198
+ }
1199
+ /**
1200
+ * If a trailing inline comment sits on the same line right after `offset`,
1201
+ * return its text and end offset. Only whitespace and an OPTIONAL single
1202
+ * separator comma may sit between the sibling's value and the comment — so both
1203
+ * `"id": "x" // c` and `"id": "x", // c` (a JSONC trailing comma, or the
1204
+ * separator once a sibling follows) are recognized. A `//` line comment or a
1205
+ * single-line `/* *\/` block comment counts; a newline or closing bracket first
1206
+ * means there is no trailing comment (returns undefined). `offset` points just
1207
+ * past the sibling's value, and the sibling is always its container's LAST
1208
+ * member, so any comma here is a trailing/separator comma — never a boundary to
1209
+ * an existing following member.
1210
+ */
1211
+ function readTrailingComment(text, offset) {
1212
+ let i = offset;
1213
+ while (i < text.length && (text[i] === " " || text[i] === " ")) i++;
1214
+ if (text[i] === ",") {
1215
+ i++;
1216
+ while (i < text.length && (text[i] === " " || text[i] === " ")) i++;
1217
+ }
1218
+ if (text[i] === "/" && text[i + 1] === "/") {
1219
+ let end = i;
1220
+ while (end < text.length && text[end] !== "\n" && text[end] !== "\r") end++;
1221
+ return {
1222
+ text: text.slice(i, end),
1223
+ end
1224
+ };
1225
+ }
1226
+ if (text[i] === "/" && text[i + 1] === "*") {
1227
+ const close = text.indexOf("*/", i + 2);
1228
+ if (close === -1) return;
1229
+ const end = close + 2;
1230
+ const slice = text.slice(i, end);
1231
+ if (slice.includes("\n") || slice.includes("\r")) return;
1232
+ return {
1233
+ text: slice,
1234
+ end
1235
+ };
1236
+ }
1237
+ }
1238
+ /**
1239
+ * Throw (without writing) if any object in the config declares the same member
1240
+ * name twice. See the call site for why a duplicate makes property-level upserts
1241
+ * silently ineffective. Walks the jsonc AST so nested objects (queue consumers,
1242
+ * D1 entries, …) are covered, not just the top level.
1243
+ */
1244
+ function assertNoDuplicateMembers(text, configPath) {
1245
+ const root = parseTree(text, void 0, { allowTrailingComma: true });
1246
+ if (!root) return;
1247
+ const walk = (node) => {
1248
+ if (node.type === "object" && node.children) {
1249
+ const seen = /* @__PURE__ */ new Set();
1250
+ for (const property of node.children) {
1251
+ const keyNode = property.children?.[0];
1252
+ const key = keyNode?.value;
1253
+ if (typeof key !== "string") continue;
1254
+ if (seen.has(key)) {
1255
+ const { line, column } = offsetToLineColumn(text, keyNode.offset);
1256
+ throw new Error(`deploy: cannot update "${configPath}" — duplicate key ${JSON.stringify(key)} at line ${line}, column ${column}. Wrangler reads the last of two duplicate members, but this writer edits the first, so the update would silently not apply. Remove the duplicate and retry.`);
1257
+ }
1258
+ seen.add(key);
1259
+ }
1260
+ }
1261
+ if (node.children) for (const child of node.children) walk(child);
1262
+ };
1263
+ walk(root);
1264
+ }
1265
+ /** 1-based line/column for a byte offset — used in duplicate-key error messages. */
1266
+ function offsetToLineColumn(text, offset) {
1267
+ let line = 1;
1268
+ let column = 1;
1269
+ const limit = Math.min(offset, text.length);
1270
+ for (let i = 0; i < limit; i++) if (text[i] === "\n") {
1271
+ line += 1;
1272
+ column = 1;
1273
+ } else column += 1;
1274
+ return {
1275
+ line,
1276
+ column
1277
+ };
1278
+ }
1279
+ function isRecord(value) {
1280
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1281
+ }
1282
+ function validateDurableObjectMigrations(value, configPath) {
1283
+ if (value === void 0) return [];
1284
+ if (!Array.isArray(value)) throw new Error(`deploy: cannot update "${configPath}" — "migrations" must be an array of Durable Object migration steps.`);
1285
+ const tags = /* @__PURE__ */ new Set();
1286
+ for (const [index, migration] of value.entries()) {
1287
+ if (!isRecord(migration) || typeof migration.tag !== "string" || migration.tag.length === 0) throw new Error(`deploy: cannot update "${configPath}" — migration at index ${index} must be an object with a non-empty string "tag".`);
1288
+ if (tags.has(migration.tag)) throw new Error(`deploy: cannot update "${configPath}" — migration tag '${migration.tag}' is declared more than once.`);
1289
+ tags.add(migration.tag);
1290
+ for (const field of [
1291
+ "new_classes",
1292
+ "new_sqlite_classes",
1293
+ "deleted_classes"
1294
+ ]) {
1295
+ const classes = migration[field];
1296
+ if (classes !== void 0 && (!Array.isArray(classes) || classes.some((className) => typeof className !== "string"))) throw new Error(`deploy: cannot update "${configPath}" — "migrations[${index}].${field}" must be an array of class names.`);
1297
+ }
1298
+ const renamed = migration.renamed_classes;
1299
+ if (renamed !== void 0 && (!Array.isArray(renamed) || renamed.some((entry) => !isRecord(entry) || typeof entry.from !== "string" || typeof entry.to !== "string"))) throw new Error(`deploy: cannot update "${configPath}" — "migrations[${index}].renamed_classes" has an invalid entry.`);
1300
+ const transferred = migration.transferred_classes;
1301
+ if (transferred !== void 0 && (!Array.isArray(transferred) || transferred.some((entry) => !isRecord(entry) || typeof entry.from !== "string" || typeof entry.from_script !== "string" || typeof entry.to !== "string"))) throw new Error(`deploy: cannot update "${configPath}" — "migrations[${index}].transferred_classes" has an invalid entry.`);
1302
+ }
1303
+ return value;
1304
+ }
1305
+ /**
1306
+ * wrangler's friendly label for each binding namespace. Mirrors
1307
+ * `friendlyBindingNames` in `wrangler-dist/cli.js` — the map
1308
+ * `validateBindingsHaveUniqueNames` iterates to gather every worker-wide binding
1309
+ * name. Drives the owner name in the worker-wide producer-collision error.
1310
+ *
1311
+ * This is a 1:1 copy of wrangler's `friendlyBindingNames` EXCEPT `queues`, which
1312
+ * is intentionally ABSENT: wrangler folds in `queues.producers`, but here producer
1313
+ * identity is handled by the upsert's own binding-match logic (and consumers carry
1314
+ * no binding), so including it would flag the very producer we are about to append
1315
+ * as a self-collision. Every OTHER field wrangler enumerates is present (including
1316
+ * `unsafe_hello_world`). `secrets.required` — which wrangler folds in separately
1317
+ * under the label "Secret", not via this map — is handled in
1318
+ * `collectWorkerBindingNames`.
1319
+ */
1320
+ const FRIENDLY_BINDING_NAMES = {
1321
+ data_blobs: "Data Blob",
1322
+ durable_objects: "Durable Object",
1323
+ kv_namespaces: "KV Namespace",
1324
+ send_email: "Send Email",
1325
+ d1_databases: "D1 Database",
1326
+ vectorize: "Vectorize Index",
1327
+ ai_search_namespaces: "AI Search Namespace",
1328
+ ai_search: "AI Search Instance",
1329
+ websearch: "Web Search",
1330
+ agent_memory: "Agent Memory",
1331
+ hyperdrive: "Hyperdrive Config",
1332
+ r2_buckets: "R2 Bucket",
1333
+ logfwdr: "logfwdr",
1334
+ services: "Worker",
1335
+ analytics_engine_datasets: "Analytics Engine Dataset",
1336
+ text_blobs: "Text Blob",
1337
+ browser: "Browser Run",
1338
+ ai: "AI",
1339
+ images: "Images",
1340
+ stream: "Stream",
1341
+ media: "Media",
1342
+ version_metadata: "Worker Version Metadata",
1343
+ unsafe: "Unsafe Metadata",
1344
+ vars: "Environment Variable",
1345
+ wasm_modules: "Wasm Module",
1346
+ dispatch_namespaces: "Dispatch Namespace",
1347
+ mtls_certificates: "mTLS Certificate",
1348
+ workflows: "Workflow",
1349
+ pipelines: "Pipeline",
1350
+ secrets_store_secrets: "Secrets Store Secret",
1351
+ artifacts: "Artifacts",
1352
+ ratelimits: "Rate Limit",
1353
+ assets: "Assets",
1354
+ unsafe_hello_world: "Hello World",
1355
+ flagship: "Flagship",
1356
+ worker_loaders: "Worker Loader",
1357
+ vpc_services: "VPC Service",
1358
+ vpc_networks: "VPC Network"
1359
+ };
1360
+ /**
1361
+ * Collect every binding NAME already used across the worker config, mapped to
1362
+ * wrangler's friendly owner label (first writer wins on ties). Mirrors the exact
1363
+ * name set `validateBindingsHaveUniqueNames` builds in `wrangler-dist/cli.js`:
1364
+ * `getBindingNames(config[field])` for every `friendlyBindingNames` field, PLUS
1365
+ * `config.secrets.required` under the label "Secret" — minus `queues` (see
1366
+ * FRIENDLY_BINDING_NAMES for why). Used to fail closed before appending a queue
1367
+ * producer whose binding collides with any other worker-wide binding.
1368
+ */
1369
+ function collectWorkerBindingNames(config) {
1370
+ const owners = /* @__PURE__ */ new Map();
1371
+ const claim = (name, friendly) => {
1372
+ if (!owners.has(name)) owners.set(name, friendly);
1373
+ };
1374
+ for (const [field, friendly] of Object.entries(FRIENDLY_BINDING_NAMES)) for (const name of bindingNamesForField(field, config[field])) claim(name, friendly);
1375
+ const secrets = assertObjectContainer(config.secrets, "secrets");
1376
+ if (secrets && secrets.required !== void 0) {
1377
+ if (!Array.isArray(secrets.required)) throw new Error("deploy: cannot verify binding-name uniqueness — \"secrets.required\" must be an array of secret names. Fix the wrangler config and retry.");
1378
+ secrets.required.forEach((name, index) => {
1379
+ if (typeof name !== "string" || name.length === 0) throw new Error(`deploy: cannot verify binding-name uniqueness — "secrets.required" contains a malformed entry at index ${index} (expected a non-empty string). Refusing to proceed on a partially-readable binding set (a dropped entry could hide a real collision). Fix the wrangler config and retry.`);
1380
+ claim(name, "Secret");
1381
+ });
1382
+ }
1383
+ return owners;
1384
+ }
1385
+ /** A shallow copy of `config` with one field omitted (never mutates the input). */
1386
+ function configWithoutField(config, field) {
1387
+ const { [field]: _omit, ...rest } = config;
1388
+ return rest;
1389
+ }
1390
+ /**
1391
+ * Add every `queues.producers[].binding` to an existing worker-binding owner map,
1392
+ * under the friendly label "Queue" (first-writer-wins). The base
1393
+ * `collectWorkerBindingNames` deliberately OMITS queues so the producer-append
1394
+ * path does not flag the producer it is adding as a self-collision — but for the
1395
+ * ADOPT path a producer binding sharing the adopted binding IS a real cross-type
1396
+ * collision wrangler rejects at deploy, so the adopt guard layers producers on
1397
+ * top of the reused collector.
1398
+ */
1399
+ function addQueueProducerOwners(owners, config) {
1400
+ const queues = assertObjectContainer(config.queues, "queues");
1401
+ if (!queues || queues.producers === void 0) return;
1402
+ for (const producer of assertValidQueueProducers(queues.producers)) {
1403
+ const binding = producer.binding;
1404
+ if (!owners.has(binding)) owners.set(binding, "Queue");
1405
+ }
1406
+ }
1407
+ /**
1408
+ * The ONE strict `queues.producers` validator, shared by BOTH the adopt-guard's
1409
+ * owner collection ({@link addQueueProducerOwners}) and the producer-upsert path (which
1410
+ * otherwise clones the list and silently skips non-record members, committing a new
1411
+ * producer beside a corrupt list). A non-array, or any member that is not a record with
1412
+ * a non-empty string `binding`, THROWS (fail closed) rather than being dropped — a
1413
+ * dropped/hidden producer could mask a real binding collision. Returns the validated
1414
+ * producer records (each guaranteed to carry a non-empty string `binding`).
1415
+ */
1416
+ function assertValidQueueProducers(producers) {
1417
+ if (producers === void 0) return [];
1418
+ if (!Array.isArray(producers)) throw new Error("deploy: cannot verify binding-name uniqueness — \"queues.producers\" must be an array. Fix the wrangler config and retry.");
1419
+ return producers.map((producer, index) => {
1420
+ if (!isRecord(producer) || typeof producer.binding !== "string" || producer.binding.length === 0) throw new Error(`deploy: cannot verify binding-name uniqueness — "queues.producers" contains a malformed entry at index ${index} (missing a non-empty string "binding"). Refusing to proceed on a partially-readable binding set (a dropped producer could hide a real collision). Fix the wrangler config and retry.`);
1421
+ return producer;
1422
+ });
1423
+ }
1424
+ /**
1425
+ * Validate the OUTER shape of a wrangler container field that wrangler 4.131
1426
+ * requires to be an OBJECT (`queues`, `secrets`). A PRESENT-but-non-object container
1427
+ * (array/string/number/null) is malformed for wrangler AND would bypass the binding
1428
+ * collectors, HIDING a real cross-type binding collision — so it THROWS (fail closed).
1429
+ * An ABSENT container (undefined) returns undefined (a no-op, NOT an error).
1430
+ * Returns the container as a record when present-and-object.
1431
+ *
1432
+ * NOTE: `null` is `typeof 'object'` but `isRecord(null)` is false, so `secrets: null` /
1433
+ * `queues: null` (present but null) THROWS — correct (wrangler requires an object).
1434
+ * Absent (`undefined`) is the only no-op.
1435
+ */
1436
+ function assertObjectContainer(value, field) {
1437
+ if (value === void 0) return;
1438
+ if (!isRecord(value)) throw new Error(`deploy: cannot verify binding-name uniqueness — "${field}" must be an object. Refusing to proceed on a partially-readable binding set (a wrong-shaped container could hide a real collision). Fix the wrangler config and retry.`);
1439
+ return value;
1440
+ }
1441
+ /**
1442
+ * The worker-wide OWNER of a binding name as the writer's adopt / `vars` guards see it:
1443
+ * validates `field`'s own outer shape (throws on a malformed target, fail closed), then
1444
+ * looks `name` up among every OTHER binding type — `queues.producers` included — and
1445
+ * returns wrangler's friendly label for the owner, or `undefined` when the name is free
1446
+ * (or held only by `field` itself). Lets a caller refuse a doomed write BEFORE side
1447
+ * effects the write would otherwise follow (the Mode B email step). Reuses the ONE
1448
+ * collector — never fork it.
1449
+ */
1450
+ function findBindingNameOwner(config, field, name) {
1451
+ bindingNamesForField(field, config[field]);
1452
+ const owners = collectWorkerBindingNames(configWithoutField(config, field));
1453
+ addQueueProducerOwners(owners, config);
1454
+ return owners.get(name);
1455
+ }
1456
+ /**
1457
+ * The STRICT canonical reader for a single provisioned binding entry in a wrangler
1458
+ * config field (`d1_databases` / `kv_namespaces` / `r2_buckets` / `hyperdrive`). Replaces
1459
+ * the provisioner's loose first-match `.find()` — which validated no outer shape,
1460
+ * silently picked among duplicates, and bypassed the worker-wide collision check, so a
1461
+ * duplicate/cross-type-collision config could be reported `reused` or pass a create
1462
+ * postcondition though it is undeployable. Fails closed on:
1463
+ * 1. a malformed TARGET field (reuses {@link bindingNamesForField}: a non-array outer
1464
+ * shape, or any member without a non-empty string `binding`, throws);
1465
+ * 2. MORE THAN ONE entry in the target field sharing `binding` (ambiguous identity —
1466
+ * wrangler also rejects duplicate binding names);
1467
+ * 3. a worker-wide CROSS-TYPE collision — `binding` is ALSO owned by any OTHER binding
1468
+ * type (reuses the {@link collectWorkerBindingNames} Task-3 collector, EXCLUDING the
1469
+ * target field so the entry never self-collides, and INCLUDING `queues.producers`).
1470
+ * Returns the single matching entry, or `undefined` when the binding is absent.
1471
+ */
1472
+ function readProvisionedBindingEntry(config, field, binding, configPath) {
1473
+ bindingNamesForField(field, config[field]);
1474
+ const rawList = config[field];
1475
+ const matches = (Array.isArray(rawList) ? rawList : []).filter((entry) => isRecord(entry) && entry.binding === binding);
1476
+ if (matches.length > 1) throw new Error(`provision: cannot read "${configPath}" — binding ${JSON.stringify(binding)} is declared ${matches.length} times in "${field}". Cannot pick one to reuse/verify, and wrangler rejects duplicate binding names. Remove the duplicate and retry.`);
1477
+ const owners = collectWorkerBindingNames(configWithoutField(config, field));
1478
+ addQueueProducerOwners(owners, config);
1479
+ const owner = owners.get(binding);
1480
+ if (owner !== void 0) throw new Error(`provision: cannot read "${configPath}" — binding ${JSON.stringify(binding)} in "${field}" is also used by a ${owner} binding. Wrangler requires every binding name to be unique across the whole worker, so this config is undeployable. Rename the conflicting binding and retry.`);
1481
+ return matches[0];
1482
+ }
1483
+ /**
1484
+ * Explicit per-field shape metadata (see {@link BindingFieldShape}), transcribed
1485
+ * from wrangler 4.131's config schema. Any `FRIENDLY_BINDING_NAMES` field absent here is
1486
+ * a `namespace-array` (`[{ binding }]`) — the shape wrangler's `getBindingNames` reads
1487
+ * via its `isNamespaceList` branch.
1488
+ */
1489
+ const BINDING_FIELD_SHAPES = {
1490
+ vars: { kind: "key-map" },
1491
+ wasm_modules: { kind: "key-map" },
1492
+ text_blobs: { kind: "key-map" },
1493
+ data_blobs: { kind: "key-map" },
1494
+ send_email: { kind: "name-keyed-array" },
1495
+ ratelimits: { kind: "name-keyed-array" },
1496
+ addresses: { kind: "whole-object" },
1497
+ ai: {
1498
+ kind: "singleton",
1499
+ bindingRequired: true
1500
+ },
1501
+ browser: {
1502
+ kind: "singleton",
1503
+ bindingRequired: true
1504
+ },
1505
+ images: {
1506
+ kind: "singleton",
1507
+ bindingRequired: true
1508
+ },
1509
+ stream: {
1510
+ kind: "singleton",
1511
+ bindingRequired: true
1512
+ },
1513
+ media: {
1514
+ kind: "singleton",
1515
+ bindingRequired: true
1516
+ },
1517
+ websearch: {
1518
+ kind: "singleton",
1519
+ bindingRequired: true
1520
+ },
1521
+ version_metadata: {
1522
+ kind: "singleton",
1523
+ bindingRequired: true
1524
+ },
1525
+ assets: {
1526
+ kind: "singleton",
1527
+ bindingRequired: false
1528
+ },
1529
+ durable_objects: {
1530
+ kind: "binding-list",
1531
+ bindingsRequired: true
1532
+ },
1533
+ logfwdr: {
1534
+ kind: "binding-list",
1535
+ bindingsRequired: true
1536
+ },
1537
+ unsafe: {
1538
+ kind: "binding-list",
1539
+ bindingsRequired: false
1540
+ }
1541
+ };
1542
+ /**
1543
+ * The ONE field-aware strict binding-name reader for a single config field. Routes
1544
+ * each field to its KNOWN shape (see {@link BINDING_FIELD_SHAPES}) and FAILS CLOSED on
1545
+ * any malformed/missing REQUIRED identity or wrong outer shape — never content-guessing.
1546
+ * wrangler is lenient and would silently drop such a member, letting the worker-wide
1547
+ * uniqueness guard miss (or invent) a collision.
1548
+ *
1549
+ * Exported so the deploy orchestrator's pass-through assert-set check reads a resource field's
1550
+ * per-entry identity keys through the SAME wrangler-schema shapes (handling `send_email` /
1551
+ * `ratelimits` name-keying and `logfwdr`'s nested `.bindings`, among others).
1552
+ */
1553
+ function bindingNamesForField(field, value) {
1554
+ const shape = BINDING_FIELD_SHAPES[field] ?? { kind: "namespace-array" };
1555
+ switch (shape.kind) {
1556
+ case "key-map": return keyMapBindingNames(field, value);
1557
+ case "name-keyed-array": return nameKeyedBindingNames(field, value);
1558
+ case "namespace-array": return namespaceArrayBindingNames(field, value);
1559
+ case "singleton": return singletonBindingName(field, value, shape.bindingRequired);
1560
+ case "binding-list": return bindingListNames(field, value, shape.bindingsRequired);
1561
+ case "whole-object": return [];
1562
+ }
1563
+ }
1564
+ /**
1565
+ * The entry-shaped sibling of {@link bindingNamesForField}: routes each field to its KNOWN
1566
+ * shape and returns every binding's NAME **paired with its full config ENTRY object**, so a
1567
+ * caller can compare not just WHICH bindings a field declares but the TARGET each one points
1568
+ * at (a `services` binding's `service`, an `unsafe` binding's `type`, …). Same fail-closed
1569
+ * philosophy as {@link bindingNamesForField} — a malformed/missing REQUIRED identity or wrong
1570
+ * outer shape THROWS. By construction `bindingEntriesForField(f, v).map((e) => e.name)` equals
1571
+ * `bindingNamesForField(f, v)` for every field (both read identity through the same shapes).
1572
+ *
1573
+ * Exported so the deploy orchestrator's pass-through assert-set check can pin a legitimately
1574
+ * hand-declared binding's ENTRY pre-build and reject a build that KEEPS the name but REWRITES
1575
+ * the target.
1576
+ */
1577
+ function bindingEntriesForField(field, value) {
1578
+ const shape = BINDING_FIELD_SHAPES[field] ?? { kind: "namespace-array" };
1579
+ switch (shape.kind) {
1580
+ case "key-map": return keyMapBindingEntries(field);
1581
+ case "name-keyed-array": return nameKeyedBindingEntries(field, value);
1582
+ case "namespace-array": return namespaceArrayBindingEntries(field, value);
1583
+ case "singleton": return singletonBindingEntry(field, value, shape.bindingRequired);
1584
+ case "binding-list": return bindingListEntries(field, value, shape.bindingsRequired);
1585
+ case "whole-object": return [];
1586
+ }
1587
+ }
1588
+ /**
1589
+ * Entry-shaped reader for a KEY-MAP field. Key-map fields (vars/wasm_modules/text_blobs/
1590
+ * data_blobs) carry no per-binding config ENTRY object — the binding name is the KEY and the
1591
+ * VALUE is arbitrary content — and all four are excluded from the pass-through assert-set, so
1592
+ * this branch is never reached in the normal flow. It THROWS the same wrong-shape message the
1593
+ * name reader raises, so a FUTURE misclassification that routed a key-map field here fails
1594
+ * CLOSED rather than silently reading no entries.
1595
+ */
1596
+ function keyMapBindingEntries(field) {
1597
+ throw new Error(`deploy: cannot verify binding-name uniqueness — "${field}" must be an object mapping names to values. Refusing to proceed on a partially-readable binding set (a wrong-shaped field could hide a real collision). Fix the wrangler config and retry.`);
1598
+ }
1599
+ /**
1600
+ * Entry-shaped reader for a NAMESPACE-array field (mirror of {@link namespaceArrayBindingNames}):
1601
+ * a PRESENT non-array THROWS; every member is strictly validated for a non-empty string
1602
+ * `.binding` and returned with its full entry object.
1603
+ */
1604
+ function namespaceArrayBindingEntries(field, value) {
1605
+ if (value === void 0) return [];
1606
+ if (!Array.isArray(value)) throw new Error(`deploy: cannot verify binding-name uniqueness — "${field}" must be an array of binding entries. Refusing to proceed on a partially-readable binding set (a wrong-shaped field could hide a real collision). Fix the wrangler config and retry.`);
1607
+ return strictBindingEntries(value, "binding", field);
1608
+ }
1609
+ /**
1610
+ * Entry-shaped reader for a NAME-keyed array field (mirror of {@link nameKeyedBindingNames}):
1611
+ * identity is `.name` only; a PRESENT non-array THROWS; every member is strictly validated.
1612
+ */
1613
+ function nameKeyedBindingEntries(field, value) {
1614
+ if (value === void 0) return [];
1615
+ if (!Array.isArray(value)) throw new Error(`deploy: cannot verify binding-name uniqueness — "${field}" must be an array of binding entries. Refusing to proceed on a partially-readable binding set (a wrong-shaped field could hide a real collision). Fix the wrangler config and retry.`);
1616
+ return strictBindingEntries(value, "name", field);
1617
+ }
1618
+ /**
1619
+ * Entry-shaped reader for a binding-LIST field (mirror of {@link bindingListNames}): the entry
1620
+ * of each member is the member itself (`{ name, ... }`), keyed on `.name`. Same throw semantics
1621
+ * as the name reader — a wrong outer shape, or a required-but-missing `bindings`, THROWS; a
1622
+ * `bindings`-less `unsafe` (optional) contributes nothing.
1623
+ */
1624
+ function bindingListEntries(field, value, bindingsRequired) {
1625
+ if (value === void 0) return [];
1626
+ if (!isRecord(value)) throw new Error(`deploy: cannot verify binding-name uniqueness — "${field}" must be an object with a "bindings" array. Refusing to proceed on a partially-readable binding set (a wrong-shaped field could hide a real collision). Fix the wrangler config and retry.`);
1627
+ if (value.bindings === void 0) {
1628
+ if (bindingsRequired) throw new Error(`deploy: cannot verify binding-name uniqueness — "${field}" requires a "bindings" array. Refusing to proceed on a partially-readable binding set (a wrong-shaped field could hide a real collision). Fix the wrangler config and retry.`);
1629
+ return [];
1630
+ }
1631
+ if (!Array.isArray(value.bindings)) throw new Error(`deploy: cannot verify binding-name uniqueness — "${field}.bindings" must be an array. Refusing to proceed on a partially-readable binding set (a wrong-shaped field could hide a real collision). Fix the wrangler config and retry.`);
1632
+ return strictBindingEntries(value.bindings, "name", `${field}.bindings`);
1633
+ }
1634
+ /**
1635
+ * Entry-shaped reader for a SINGLETON field (mirror of {@link singletonBindingName}): the entry
1636
+ * is the singleton value itself. A PRESENT non-object THROWS; a present-but-malformed `binding`
1637
+ * THROWS; a required-but-absent `binding` THROWS; a binding-optional `assets` with no binding
1638
+ * contributes nothing.
1639
+ */
1640
+ function singletonBindingEntry(field, value, bindingRequired) {
1641
+ if (value === void 0) return [];
1642
+ if (!isRecord(value)) throw new Error(`deploy: cannot verify binding-name uniqueness — "${field}" must be an object with a "binding". Refusing to proceed on a partially-readable binding set (a wrong-shaped field could hide a real collision). Fix the wrangler config and retry.`);
1643
+ if (typeof value.binding === "string" && value.binding.length > 0) return [{
1644
+ name: value.binding,
1645
+ entry: value
1646
+ }];
1647
+ if (value.binding !== void 0) throw new Error(`deploy: cannot verify binding-name uniqueness — "${field}" has a malformed "binding" (expected a non-empty string). Refusing to proceed on a partially-readable binding set (a dropped binding could hide a real collision). Fix the wrangler config and retry.`);
1648
+ if (bindingRequired) throw new Error(`deploy: cannot verify binding-name uniqueness — "${field}" requires a "binding" (a non-empty string). Refusing to proceed on a partially-readable binding set (a dropped binding could hide a real collision). Fix the wrangler config and retry.`);
1649
+ return [];
1650
+ }
1651
+ /**
1652
+ * Read binding names from a KEY-MAP field (`vars`/`wasm_modules`/`text_blobs`/
1653
+ * `data_blobs`): the object's KEYS are the binding names. Reads ONLY the keys — never
1654
+ * the values — so a value that happens to look like a binding list (`{bindings:[…]}`)
1655
+ * or a singleton (`{binding:'x'}`) is NOT misread (the old generic path returned the
1656
+ * VALUE, or a nested `name`, instead of the real KEY). A PRESENT value that is not a
1657
+ * plain object is the wrong outer shape and THROWS (fail closed). An explicitly
1658
+ * `undefined`-valued key is not a real binding and is skipped.
1659
+ */
1660
+ function keyMapBindingNames(field, value) {
1661
+ if (value === void 0) return [];
1662
+ if (!isRecord(value)) throw new Error(`deploy: cannot verify binding-name uniqueness — "${field}" must be an object mapping names to values. Refusing to proceed on a partially-readable binding set (a wrong-shaped field could hide a real collision). Fix the wrangler config and retry.`);
1663
+ return Object.keys(value).filter((key) => value[key] !== void 0);
1664
+ }
1665
+ /**
1666
+ * Read binding names from a NAMESPACE-array field (`[{ binding }]`: kv_namespaces,
1667
+ * d1_databases, r2_buckets, hyperdrive, services, vectorize, …). A PRESENT value that
1668
+ * is not an array is the wrong outer shape and THROWS rather than being re-read as a
1669
+ * key-map (which could under-collect and hide a real collision); every member is
1670
+ * strictly validated for a non-empty string `.binding`.
1671
+ */
1672
+ function namespaceArrayBindingNames(field, value) {
1673
+ if (value === void 0) return [];
1674
+ if (!Array.isArray(value)) throw new Error(`deploy: cannot verify binding-name uniqueness — "${field}" must be an array of binding entries. Refusing to proceed on a partially-readable binding set (a wrong-shaped field could hide a real collision). Fix the wrangler config and retry.`);
1675
+ return strictBindingArray(value, "binding", field);
1676
+ }
1677
+ /**
1678
+ * Read binding names from a NAME-keyed array field (`send_email` / `ratelimits`:
1679
+ * an array of `{ name }`). Encodes the field's REQUIRED outer shape: a PRESENT value that
1680
+ * is not an array THROWS (fail closed) rather than falling back to generic key collection —
1681
+ * wrangler validates these via `validateBindingArray`, so a non-array is malformed and
1682
+ * could hide a real collision if silently re-read as a key-map. An ABSENT value contributes
1683
+ * nothing. Every array member is strictly validated (a malformed one rejects the whole
1684
+ * config).
1685
+ *
1686
+ * Identity is the `.name` ONLY. Verified against wrangler 4.131's bundle +
1687
+ * `config-schema.json`: NEITHER `send_email` NOR `ratelimits` has a `.binding` property in
1688
+ * the schema, and on UPLOAD wrangler keys them by `.name` (send_email:
1689
+ * `output[name] = { type: "send_email", ...}`; ratelimits push `{ name: binding.name, … }`).
1690
+ * An optional `.binding` that happens to sit on such an entry is NOT a binding identity
1691
+ * wrangler recognizes, so unioning it invents a PHANTOM collision — e.g. a
1692
+ * `send_email:[{name:"EMAIL_A",binding:"QUEUE_JOBS"}]` would falsely block a legitimate
1693
+ * `QUEUE_JOBS` queue producer / adopt / read-first. We therefore collect ONLY the `.name`
1694
+ * of every member (a genuine same-`name` collision is still caught).
1695
+ */
1696
+ function nameKeyedBindingNames(field, value) {
1697
+ if (value === void 0) return [];
1698
+ if (!Array.isArray(value)) throw new Error(`deploy: cannot verify binding-name uniqueness — "${field}" must be an array of binding entries. Refusing to proceed on a partially-readable binding set (a wrong-shaped field could hide a real collision). Fix the wrangler config and retry.`);
1699
+ return strictBindingArray(value, "name", field);
1700
+ }
1701
+ /**
1702
+ * Read the binding names from a binding-LIST field (`{ bindings: [{ name }] }`:
1703
+ * durable_objects / logfwdr / unsafe). A PRESENT value that is not an object is the
1704
+ * wrong outer shape and THROWS. When `bindingsRequired` (durable_objects / logfwdr per
1705
+ * schema), a MISSING `bindings` array THROWS (never a silent empty read); `unsafe` is
1706
+ * optional, so a `bindings`-less `{ metadata }` contributes nothing. A PRESENT
1707
+ * `bindings` array has EVERY member strictly validated for a non-empty string `name`.
1708
+ */
1709
+ function bindingListNames(field, value, bindingsRequired) {
1710
+ if (value === void 0) return [];
1711
+ if (!isRecord(value)) throw new Error(`deploy: cannot verify binding-name uniqueness — "${field}" must be an object with a "bindings" array. Refusing to proceed on a partially-readable binding set (a wrong-shaped field could hide a real collision). Fix the wrangler config and retry.`);
1712
+ if (value.bindings === void 0) {
1713
+ if (bindingsRequired) throw new Error(`deploy: cannot verify binding-name uniqueness — "${field}" requires a "bindings" array. Refusing to proceed on a partially-readable binding set (a wrong-shaped field could hide a real collision). Fix the wrangler config and retry.`);
1714
+ return [];
1715
+ }
1716
+ if (!Array.isArray(value.bindings)) throw new Error(`deploy: cannot verify binding-name uniqueness — "${field}.bindings" must be an array. Refusing to proceed on a partially-readable binding set (a wrong-shaped field could hide a real collision). Fix the wrangler config and retry.`);
1717
+ return strictBindingArray(value.bindings, "name", `${field}.bindings`);
1718
+ }
1719
+ /**
1720
+ * Read the binding name from a SINGLETON field (`{ binding }`: ai / browser /
1721
+ * images / stream / media / websearch / version_metadata / assets). A PRESENT value
1722
+ * that is not an object is the wrong outer shape and THROWS. A PRESENT-but-malformed
1723
+ * `binding` (non-string / empty) THROWS. When `bindingRequired` (every singleton except
1724
+ * `assets`, per schema), an ABSENT `binding` also THROWS — never a silent empty read,
1725
+ * and never a fall-through that would mis-collect the object's OTHER keys (e.g.
1726
+ * `{ staging: true }`) as bogus binding names. `assets` is binding-optional, so an
1727
+ * absent binding (e.g. `{ directory }`) contributes nothing.
1728
+ */
1729
+ function singletonBindingName(field, value, bindingRequired) {
1730
+ if (value === void 0) return [];
1731
+ if (!isRecord(value)) throw new Error(`deploy: cannot verify binding-name uniqueness — "${field}" must be an object with a "binding". Refusing to proceed on a partially-readable binding set (a wrong-shaped field could hide a real collision). Fix the wrangler config and retry.`);
1732
+ if (typeof value.binding === "string" && value.binding.length > 0) return [value.binding];
1733
+ if (value.binding !== void 0) throw new Error(`deploy: cannot verify binding-name uniqueness — "${field}" has a malformed "binding" (expected a non-empty string). Refusing to proceed on a partially-readable binding set (a dropped binding could hide a real collision). Fix the wrangler config and retry.`);
1734
+ if (bindingRequired) throw new Error(`deploy: cannot verify binding-name uniqueness — "${field}" requires a "binding" (a non-empty string). Refusing to proceed on a partially-readable binding set (a dropped binding could hide a real collision). Fix the wrangler config and retry.`);
1735
+ return [];
1736
+ }
1737
+ /**
1738
+ * Strictly read `keyField` (a non-empty string) from EVERY member of a
1739
+ * binding-bearing array. THROWS (fail closed) on ANY malformed member — never
1740
+ * silently drops one. Dropping a malformed member could convert a mixed-validity
1741
+ * array (e.g. `[null, { binding: 'DB', id: '…' }]`) into an empty/partial binding
1742
+ * set, so the worker-wide uniqueness guard would MISS the existing `DB` and commit
1743
+ * a conflicting binding — a fail-open in a documented invariant. Mirrors the
1744
+ * writer's existing fail-closed-on-malformed-config philosophy.
1745
+ */
1746
+ function strictBindingArray(members, keyField, field) {
1747
+ return strictBindingEntries(members, keyField, field).map((e) => e.name);
1748
+ }
1749
+ /**
1750
+ * The entry-shaped sibling of {@link strictBindingArray}: reads `keyField` (a non-empty string)
1751
+ * from EVERY member and returns it PAIRED with the member's full entry object. Same fail-closed
1752
+ * validation — a non-record member, or one missing a non-empty string `keyField`, THROWS (never
1753
+ * silently dropped). {@link strictBindingArray} is defined in terms of this, so the two agree on
1754
+ * names and error messages for every field.
1755
+ */
1756
+ function strictBindingEntries(members, keyField, field) {
1757
+ return members.map((member, index) => {
1758
+ if (!isRecord(member) || typeof member[keyField] !== "string" || member[keyField].length === 0) throw new Error(`deploy: cannot verify binding-name uniqueness — "${field}" contains a malformed entry at index ${index} (missing a non-empty string "${keyField}"). Refusing to proceed on a partially-readable binding set (a dropped entry could hide a real collision). Fix the wrangler config and retry.`);
1759
+ return {
1760
+ name: member[keyField],
1761
+ entry: member
1762
+ };
1763
+ });
1764
+ }
1765
+ /**
1766
+ * True when a D1 `migrations_pattern` is valid under `dir` per wrangler's rule:
1767
+ * the normalized pattern must start with `${normalized dir}/` (or the dir
1768
+ * normalizes to "."). Mirrors wrangler's `resolveMigrationsConfig` /
1769
+ * `stripDirPrefix`.
1770
+ */
1771
+ function migrationsPatternMatchesDir(pattern, dir) {
1772
+ const normalizedDir = normalizeMigrationsPath(dir);
1773
+ if (normalizedDir === ".") return true;
1774
+ return normalizeMigrationsPath(pattern).startsWith(`${normalizedDir}/`);
1775
+ }
1776
+ /**
1777
+ * Mirror wrangler's `normalizeRelativePath`: forward-slash, posix-normalize, then
1778
+ * drop a trailing slash. Keeps the migrations validity check byte-identical with
1779
+ * wrangler's own.
1780
+ */
1781
+ function normalizeMigrationsPath(p) {
1782
+ const normalized = normalize(p.replace(/\\/g, "/"));
1783
+ return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
1784
+ }
1785
+ /** Order-insensitive structural equality — avoids rewriting an entry that only
1786
+ * differs in key order (preserves the file's bytes/comments on that entry). */
1787
+ function jsonEqual(a, b) {
1788
+ return JSON.stringify(canonicalize(a)) === JSON.stringify(canonicalize(b));
1789
+ }
1790
+ function canonicalize(value) {
1791
+ if (Array.isArray(value)) return value.map(canonicalize);
1792
+ if (isRecord(value)) return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonicalize(value[key])]));
1793
+ return value;
1794
+ }
1795
+ //#endregion
1796
+ export { writeWranglerPublishedTrafficSnapshot as A, readWranglerWebSocketTombstones as C, syncWranglerWebSockets as D, syncWranglerDurableObjects as E, isSandboxEnabled as M, requiresSandboxPlatformImage as N, updateWranglerCustomDomain as O, resolveSandboxConfig as P, readWranglerPublishedTrafficSnapshot as S, syncWranglerBindings as T, readProvisionedBindingEntry as _, LIVE_MIGRATION_TAG as a, readWranglerCustomDomains as b, bindingEntriesForField as c, ensureWranglerNodejsAlsFlag as d, findBindingNameOwner as f, readExistingLiveBindingConfig as g, normalizeWranglerCompatibilityFlags as h, LATEST_KNOWN_COMPAT_DATE as i, SANDBOX_MIGRATION_TAG as j, writeProvisionedWranglerConfig as k, ensureWranglerCompat as l, nextDurableObjectMigrationTag as m, DEFAULT_LIVE_CLASS as n, acknowledgeWranglerManagedQueueRemovals as o, hasDurableObjectSqliteMigration as p, FRIENDLY_BINDING_NAMES as r, assertNoDuplicateMembers as s, DEFAULT_LIVE_BINDING as t, ensureWranglerCompatibilityDate as u, readRootWranglerConfig as v, resolveLiveBindingConfig as w, readWranglerManagedQueueCleanupCandidates as x, readWranglerCompat as y };