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.
- package/AGENTS_TEMPLATE.md +6 -0
- package/{skills/void/docs/node_modules/void/node_modules/pglite-server/LICENSE.md → LICENSE} +1 -1
- package/README.md +34 -8
- package/dist/{auth-qgMlYp7Z.d.mts → auth-DkcFflXV.d.mts} +10 -11
- package/dist/auth-W9WII-mN.mjs +630 -0
- package/dist/{auth-cmd-BqsdZJp5.mjs → auth-cmd-CYVhSNFy.mjs} +4 -3
- package/dist/{auth-migrations-BTZ-ATvQ.mjs → auth-migrations-9vif1uj8.mjs} +53 -12
- package/dist/{better-auth-shared-BQooDxbw.mjs → better-auth-shared-CYw1T3k4.mjs} +7 -10
- package/dist/{better-auth-shared-DealXecJ.d.mts → better-auth-shared-DSCeohOK.d.mts} +10 -5
- package/dist/{build-cmd-Bujrv5q-.mjs → build-cmd-CkVD1uOh.mjs} +6 -4
- package/dist/{cache-C11V8Fxq.mjs → cache-QcUfR-ff.mjs} +9 -5
- package/dist/{cancel-deploy-fwFYF04b.mjs → cancel-deploy-DsvWKFTe.mjs} +3 -2
- package/dist/cf-access-AJ1ehiFR.mjs +42 -0
- package/dist/cf-access-DsSsZUPr.mjs +67 -0
- package/dist/cli/cli.d.mts +1 -1
- package/dist/cli/cli.mjs +1592 -153
- package/dist/cli/env-schema-probe.d.mts +48 -61
- package/dist/cli/env-schema-probe.mjs +10 -58
- package/dist/{client-Gb71-XkG.mjs → client-CyCHWSO_.mjs} +61 -463
- package/dist/cloudflare-auth-DRkGe7-s.mjs +170 -0
- package/dist/cloudflare-cmd-D3ME2GAe.mjs +62 -0
- package/dist/cloudflare-connect-BivMefMA.mjs +56 -0
- package/dist/cloudflare-operations-CPTpRW6d.mjs +566 -0
- package/dist/{config-BkTvs43g.mjs → config-BQFq7QvD.mjs} +85 -41
- package/dist/{config-CutEMNGJ.mjs → config-NOG_U1aK.mjs} +10 -15
- package/dist/connect-D9Yr-T5f.mjs +79 -0
- package/dist/{create-project-DsYvl3TB.mjs → create-project-DMV-csEm.mjs} +26 -16
- package/dist/database-provider.d.mts +21 -0
- package/dist/database-provider.mjs +6 -0
- package/dist/{db-DOiJMRt2.mjs → db-BxoUWL0F.mjs} +600 -115
- package/dist/{delete-mh6p-zkQ.mjs → delete-iJOqxBz1.mjs} +9 -6
- package/dist/{deploy-u7Rv9q_q.mjs → deploy-CcDoeBIf.mjs} +3451 -1986
- package/dist/dev-inbox-DkgRWLkW.mjs +307 -0
- package/dist/{discover-CJHyvYfR.mjs → discover-xvfrgJeo.mjs} +3 -3
- package/dist/{magic-string.es-ZQjdJFFn.mjs → dist-BR1quN_w.mjs} +570 -216
- package/dist/{dist-DaKKDf8D.mjs → dist-BrsS7cai.mjs} +17 -2
- package/dist/{dist-BuiRJkTd.mjs → dist-m40_XgNh.mjs} +48 -33
- package/dist/{domain-DiaNQbrl.mjs → domain-gu_iaHmn.mjs} +41 -4
- package/dist/dotenv-VQxupEUv.mjs +181 -0
- package/dist/edge.d.mts +2 -0
- package/dist/edge.mjs +2 -0
- package/dist/email-Ce6SQq-i.mjs +182 -0
- package/dist/email-r6DHJyAB.mjs +262 -0
- package/dist/{entry-D7yy4xVH.mjs → entry-DU3oDoQ3.mjs} +2 -2
- package/dist/env-D4Emu-M_.mjs +95 -0
- package/dist/env-DmU2To0C.mjs +73 -0
- package/dist/env-helpers-CyKtOBpj.d.mts +22 -0
- package/dist/env-public-D_6u46fX.d.mts +135 -0
- package/dist/{env-types-QBj-ndax.mjs → env-types-BNPhro-M.mjs} +2 -2
- package/dist/{env-validation-Dea3v3ej.mjs → env-validation-ENpMy6Ez.mjs} +68 -122
- package/dist/{gen-o_w-8yI8.mjs → gen-BKw6qHIg.mjs} +70 -15
- package/dist/{github-cmd-DKcGUNsj.mjs → github-cmd-DJS5Ab-H.mjs} +39 -31
- package/dist/{handler-Cjh8uM3Y.d.mts → handler-D1hLsObx.d.mts} +124 -122
- package/dist/{head-nmvOgFjd.d.mts → head-Do8P4puT.d.mts} +9 -8
- package/dist/{headers-nsHIFixA.mjs → headers-D8QfRX9Y.mjs} +12 -10
- package/dist/inbound-BJ70in1n.d.mts +354 -0
- package/dist/inbound-CNKxb3FY.mjs +694 -0
- package/dist/index.d.mts +61 -34
- package/dist/index.mjs +1048 -321
- package/dist/{init-3rgHKBVi.mjs → init-Dx5cgKqK.mjs} +329 -271
- package/dist/link-D_rm5sRH.mjs +52 -0
- package/dist/{list-CPwFDZ_c.mjs → list-M8XShti7.mjs} +37 -5
- package/dist/{runner-kapo9aPs.mjs → local-d1-BE8KBbMy.mjs} +116 -6
- package/dist/{login-BT3H8PN3.mjs → login-C8-UuLnp.mjs} +17 -15
- package/dist/{logs-Bt313ax7.mjs → logs-DwFU7dMW.mjs} +16 -2
- package/dist/mime-BJD7d_qL.mjs +1216 -0
- package/dist/neon-DHwd2zvC.mjs +54 -0
- package/dist/{node-Da0UcsGA.mjs → node-BkaXcpAc.mjs} +29 -8
- package/dist/operator-cmd-DYWRbWUA.mjs +348 -0
- package/dist/{agents-CtgBYqld.mjs → output-tFQLLj26.mjs} +676 -211
- package/dist/{package-json-Cx1osYo6.mjs → package-json-CPoWX79C.mjs} +1 -1
- package/dist/pages/client.d.mts +43 -41
- package/dist/pages/client.mjs +40 -33
- package/dist/pages/head-client.d.mts +10 -12
- package/dist/pages/head.d.mts +1 -1
- package/dist/pages/index.d.mts +37 -14
- package/dist/pages/index.mjs +5 -5
- package/dist/pages/islands-plugin.d.mts +25 -27
- package/dist/pages/islands-plugin.mjs +6 -4
- package/dist/pages/prefetch.d.mts +6 -7
- package/dist/pages/protocol.d.mts +2 -2
- package/dist/pages/protocol.mjs +2 -1
- package/dist/pages/serialize.d.mts +7 -8
- package/dist/platform-cmd-BEOVv1PN.mjs +123 -0
- package/dist/platform-domain-BszUfiS8.mjs +228 -0
- package/dist/platform-lifecycle-CjSr6xf0.mjs +4450 -0
- package/dist/platform-management-W30iCaTL.mjs +698 -0
- package/dist/platform-recovery-pHHv4ZSg.mjs +99 -0
- package/dist/{plugin-inference-DMeavIJ6.mjs → plugin-inference-BDRfZngg.mjs} +35 -19
- package/dist/prepare-BfJvFUtJ.mjs +14 -0
- package/dist/{prepare-BoKHgMNx.mjs → prepare-CBetXvsN.mjs} +15 -24
- package/dist/{preset-BjyR3lzz.mjs → preset-lAy0B0BQ.mjs} +25 -212
- package/dist/{project-cmd-D_w-4w5B.mjs → project-cmd-DCpk1cDt.mjs} +25 -12
- package/dist/{project-paths-BQd7OmIo.mjs → project-paths-SK8nMHPp.mjs} +3 -1
- package/dist/{project-tsconfig-B-QtXjLQ.mjs → project-tsconfig-Ql2XsSQp.mjs} +2 -2
- package/dist/{protocol-Bnb0LFp3.d.mts → protocol-C-pqYJjE.d.mts} +3 -4
- package/dist/{provision-rShh6MKY.mjs → provision-Blnstcm2.mjs} +78 -45
- package/dist/r2-conditions-D1Wk8i7b.mjs +30 -0
- package/dist/{requests-B8sZxaFM.mjs → requests-Dn8Vheh1.mjs} +5 -3
- package/dist/{resolve-project-BBMtLLV9.mjs → resolve-project--Vxawf7z.mjs} +2 -2
- package/dist/rollback-CtlPXEBi.mjs +166 -0
- package/dist/{rolldown-runtime-DJK8HYOj.mjs → rolldown-runtime-rQ84J-ij.mjs} +1 -1
- package/dist/{route-types-COI2DsZv.mjs → route-types-Da-DpyUp.mjs} +85 -26
- package/dist/routes-stub.d.mts +22 -23
- package/dist/runner-mysql-CgRFl3s6.mjs +61 -0
- package/dist/{runner-pg-CHM76xuC.mjs → runner-pg-DCkWPsWS.mjs} +18 -6
- package/dist/runtime/ai.d.mts +21 -14
- package/dist/runtime/ai.mjs +5 -4
- package/dist/runtime/auth-client-react.d.mts +3 -5
- package/dist/runtime/auth-client-solid.d.mts +3 -5
- package/dist/runtime/auth-client-svelte.d.mts +3 -5
- package/dist/runtime/auth-client-vue.d.mts +3 -5
- package/dist/runtime/auth-client.d.mts +3 -5
- package/dist/runtime/auth.d.mts +1 -1
- package/dist/runtime/better-auth-mysql.d.mts +10 -0
- package/dist/runtime/better-auth-mysql.mjs +49 -0
- package/dist/runtime/better-auth-pg.d.mts +8 -9
- package/dist/runtime/better-auth-pg.mjs +2 -2
- package/dist/runtime/better-auth.d.mts +8 -9
- package/dist/runtime/better-auth.mjs +2 -2
- package/dist/runtime/client-react.d.mts +1 -1
- package/dist/runtime/client-solid.d.mts +1 -1
- package/dist/runtime/client-svelte.d.mts +1 -1
- package/dist/runtime/client-vue.d.mts +1 -1
- package/dist/runtime/client.d.mts +1 -1
- package/dist/runtime/db-mysql.d.mts +2 -0
- package/dist/runtime/db-mysql.mjs +1 -0
- package/dist/runtime/db.d.mts +10 -11
- package/dist/runtime/durable.d.mts +47 -0
- package/dist/runtime/durable.mjs +146 -0
- package/dist/runtime/email/testing.d.mts +112 -0
- package/dist/runtime/email/testing.mjs +283 -0
- package/dist/runtime/email.d.mts +30 -0
- package/dist/runtime/email.mjs +573 -0
- package/dist/runtime/env-helpers.d.mts +2 -2
- package/dist/runtime/env-helpers.mjs +5 -42
- package/dist/runtime/env-public-client.d.mts +10 -11
- package/dist/runtime/env-public-client.mjs +1 -1
- package/dist/runtime/env-public.d.mts +2 -2
- package/dist/runtime/env-public.mjs +104 -49
- package/dist/runtime/env.d.mts +19 -18
- package/dist/runtime/env.mjs +15 -2
- package/dist/runtime/fetch-stream.d.mts +20 -21
- package/dist/runtime/fetch.d.mts +15 -16
- package/dist/runtime/handler.d.mts +1 -1
- package/dist/runtime/isr.d.mts +21 -22
- package/dist/runtime/isr.mjs +26 -8
- package/dist/runtime/kv.d.mts +9 -10
- package/dist/runtime/live-client.d.mts +5 -7
- package/dist/runtime/live-client.mjs +9 -7
- package/dist/runtime/live-server.d.mts +4 -5
- package/dist/runtime/live.d.mts +22 -24
- package/dist/runtime/live.mjs +1 -1
- package/dist/runtime/log.d.mts +16 -17
- package/dist/runtime/migration-handler-mysql.d.mts +4 -0
- package/dist/runtime/migration-handler-mysql.mjs +81 -0
- package/dist/runtime/migration-handler-pg.d.mts +2 -4
- package/dist/runtime/migration-handler.d.mts +5 -6
- package/dist/runtime/migration-handler.mjs +4 -3
- package/dist/runtime/queues.d.mts +3 -4
- package/dist/runtime/queues.mjs +2 -1
- package/dist/runtime/remote/binding-handler.d.mts +10 -12
- package/dist/runtime/remote/binding-handler.mjs +24 -3
- package/dist/runtime/remote/index.d.mts +5 -6
- package/dist/runtime/remote/index.mjs +21 -18
- package/dist/runtime/response.d.mts +10 -11
- package/dist/runtime/sandbox.d.mts +56 -55
- package/dist/runtime/sandbox.mjs +57 -49
- package/dist/runtime/schema-mysql.d.mts +1 -0
- package/dist/runtime/schema-mysql.mjs +2 -0
- package/dist/runtime/seed.d.mts +14 -9
- package/dist/runtime/sse-client.d.mts +6 -7
- package/dist/runtime/sse.d.mts +11 -12
- package/dist/runtime/storage.d.mts +3 -4
- package/dist/runtime/validator.d.mts +1 -1
- package/dist/runtime/ws-server.d.mts +12 -12
- package/dist/runtime/ws-server.mjs +32 -4
- package/dist/runtime/ws.d.mts +19 -21
- package/dist/{scan-DYXkrasO.mjs → scan-BMH4rzlv.mjs} +53 -31
- package/dist/{scan-DEwlM_Xy.mjs → scan-CpK-57ug.mjs} +9 -5
- package/dist/{secret-Dt32J6RI.mjs → secret-BqTxGqki.mjs} +62 -5
- package/dist/{skills-CLjN0uUO.mjs → skills-Q46GZMO-.mjs} +6 -4
- package/dist/{standard-schema-DJ0HW7QP.d.mts → standard-schema-Fo_vCAZh.d.mts} +6 -6
- package/dist/{subcommand-prompt-BzV8iQZo.mjs → subcommand-prompt-WfySCQ7S.mjs} +67 -48
- package/dist/sveltekit.d.mts +12 -11
- package/dist/sveltekit.mjs +1 -1
- package/dist/types-BAp5AEBU.d.mts +79 -0
- package/dist/types-CKWnYgfy.d.mts +1 -0
- package/dist/{validate-Cw_RLeTj.mjs → validate-Bihr8WBi.mjs} +3 -3
- package/dist/wrangler--imS8n0d.mjs +1796 -0
- package/dist/{yarn-pnp-DJn3SAHF.mjs → yarn-pnp-DxSInkzL.mjs} +1 -1
- package/package.json +79 -65
- package/schema.json +35 -3
- package/skills/migrate-vite-cloudflare-to-void/SKILL.md +1 -1
- package/skills/void/SKILL.md +59 -2
- package/skills/void/docs/guide/ai.md +32 -14
- package/skills/void/docs/guide/app-types.md +6 -6
- package/skills/void/docs/guide/auth.md +14 -16
- package/skills/void/docs/guide/database/d1.md +6 -0
- package/skills/void/docs/guide/database/mysql.md +60 -0
- package/skills/void/docs/guide/database/postgresql.md +14 -9
- package/skills/void/docs/guide/database.md +39 -26
- package/skills/void/docs/guide/deployment.md +163 -22
- package/skills/void/docs/guide/durable-state.md +140 -0
- package/skills/void/docs/guide/edge/headers.md +5 -5
- package/skills/void/docs/guide/edge/prerendering.md +2 -0
- package/skills/void/docs/guide/edge/revalidation.md +21 -6
- package/skills/void/docs/guide/edge/rewrites.md +18 -14
- package/skills/void/docs/guide/edge/static-assets.md +23 -8
- package/skills/void/docs/guide/email.md +609 -0
- package/skills/void/docs/guide/env-migration.md +109 -0
- package/skills/void/docs/guide/env-vars.md +70 -248
- package/skills/void/docs/guide/index.md +15 -17
- package/skills/void/docs/guide/jobs.md +8 -5
- package/skills/void/docs/guide/live.md +7 -15
- package/skills/void/docs/guide/pages-routing/actions-and-forms.md +8 -4
- package/skills/void/docs/guide/pages-routing/islands.md +3 -3
- package/skills/void/docs/guide/pages-routing/loaders.md +6 -4
- package/skills/void/docs/guide/pages-routing/overview.md +7 -7
- package/skills/void/docs/guide/platform-administration.md +212 -0
- package/skills/void/docs/guide/platform-development.md +235 -0
- package/skills/void/docs/guide/queues.md +10 -10
- package/skills/void/docs/guide/quickstart.md +46 -67
- package/skills/void/docs/guide/remote-dev.md +8 -6
- package/skills/void/docs/guide/sandboxes.md +33 -16
- package/skills/void/docs/guide/self-hosted-platform.md +553 -0
- package/skills/void/docs/guide/server-routing.md +25 -5
- package/skills/void/docs/guide/sse.md +4 -4
- package/skills/void/docs/guide/ssg.md +5 -3
- package/skills/void/docs/guide/storage.md +2 -2
- package/skills/void/docs/guide/websockets.md +18 -7
- package/skills/void/docs/index.md +3 -3
- package/skills/void/docs/integrations/agents.md +6 -64
- package/skills/void/docs/integrations/cloudflare.md +165 -146
- package/skills/void/docs/integrations/frameworks/analog.md +4 -4
- package/skills/void/docs/integrations/frameworks/astro.md +5 -5
- package/skills/void/docs/integrations/frameworks/nuxt.md +5 -5
- package/skills/void/docs/integrations/frameworks/overview.md +3 -3
- package/skills/void/docs/integrations/frameworks/react-router.md +3 -3
- package/skills/void/docs/integrations/frameworks/sveltekit.md +3 -3
- package/skills/void/docs/integrations/frameworks/tanstack-start.md +2 -2
- package/skills/void/docs/integrations/nodejs-bun-deno.md +11 -4
- package/skills/void/docs/reference/api.md +42 -6
- package/skills/void/docs/reference/cli.md +637 -159
- package/skills/void/docs/reference/config.md +80 -25
- package/skills/void/docs/reference/resource-inference.md +13 -9
- package/skills/void/docs/reference/structure.md +10 -11
- package/AGENT_PROMPT.md +0 -19
- package/dist/cf-access-Bqw81xAf.mjs +0 -22
- package/dist/env-CZy5MorI.mjs +0 -299
- package/dist/env-helpers-z4stu8uc.d.mts +0 -52
- package/dist/env-mask-Dd47NbR6.mjs +0 -90
- package/dist/env-public-BfiLcMBk.d.mts +0 -140
- package/dist/link-CdGHSIy-.mjs +0 -45
- package/dist/mcp-DoM3_nhd.mjs +0 -377
- package/dist/project-paths-GpziKeQQ.d.mts +0 -25
- package/dist/providers-BNKRacMr.d.mts +0 -7
- package/dist/proxy-D-3_D-Gl.mjs +0 -5
- package/dist/rollback-CkvTFXx5.mjs +0 -90
- package/dist/runtime/isr-cache.d.mts +0 -207
- package/dist/runtime/isr-cache.mjs +0 -523
- package/dist/types-lLjNE9Qp.d.mts +0 -51
- package/getting-started-prompt.txt +0 -28
- package/skills/void/command/void.md +0 -7
- package/skills/void/docs/integrations/auth-providers.md +0 -0
- package/skills/void/docs/integrations/payment-processors.md +0 -0
- package/skills/void/docs/node_modules/@iconify/vue/README.md +0 -408
- package/skills/void/docs/node_modules/@iconify/vue/offline/readme.md +0 -5
- package/skills/void/docs/node_modules/@voidzero-dev/vitepress-theme/README.md +0 -103
- package/skills/void/docs/node_modules/oxc-minify/README.md +0 -78
- package/skills/void/docs/node_modules/reka-ui/README.md +0 -80
- package/skills/void/docs/node_modules/vitepress/README.md +0 -28
- package/skills/void/docs/node_modules/vitepress/template/api-examples.md +0 -49
- package/skills/void/docs/node_modules/vitepress/template/index.md +0 -28
- package/skills/void/docs/node_modules/vitepress/template/markdown-examples.md +0 -85
- package/skills/void/docs/node_modules/vitepress-plugin-group-icons/README.md +0 -101
- package/skills/void/docs/node_modules/void/AGENT_PROMPT.md +0 -19
- package/skills/void/docs/node_modules/void/CLAUDE.md +0 -219
- package/skills/void/docs/node_modules/void/README.md +0 -90
- package/skills/void/docs/node_modules/void/node_modules/@clack/prompts/CHANGELOG.md +0 -685
- package/skills/void/docs/node_modules/void/node_modules/@clack/prompts/README.md +0 -396
- package/skills/void/docs/node_modules/void/node_modules/@cloudflare/sandbox/README.md +0 -219
- package/skills/void/docs/node_modules/void/node_modules/@cloudflare/vite-plugin/README.md +0 -37
- package/skills/void/docs/node_modules/void/node_modules/@cloudflare/workers-types/README.md +0 -135
- package/skills/void/docs/node_modules/void/node_modules/@electric-sql/pglite/README.md +0 -189
- package/skills/void/docs/node_modules/void/node_modules/@hono/oauth-providers/CHANGELOG.md +0 -143
- package/skills/void/docs/node_modules/void/node_modules/@hono/oauth-providers/README.md +0 -1272
- package/skills/void/docs/node_modules/void/node_modules/@napi-rs/keyring/README.md +0 -19
- package/skills/void/docs/node_modules/void/node_modules/@types/better-sqlite3/README.md +0 -15
- package/skills/void/docs/node_modules/void/node_modules/@types/node/README.md +0 -15
- package/skills/void/docs/node_modules/void/node_modules/@types/pg/README.md +0 -15
- package/skills/void/docs/node_modules/void/node_modules/@types/proper-lockfile/README.md +0 -51
- package/skills/void/docs/node_modules/void/node_modules/@typescript/native-preview/README.md +0 -22
- package/skills/void/docs/node_modules/void/node_modules/@typescript/native-preview/vendor/vscode-jsonrpc/README.md +0 -69
- package/skills/void/docs/node_modules/void/node_modules/@void/md/README.md +0 -153
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/@shikijs/engine-javascript/README.md +0 -9
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/@shikijs/transformers/README.md +0 -9
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/@types/node/README.md +0 -15
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/gray-matter/CHANGELOG.md +0 -24
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/gray-matter/README.md +0 -565
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-exit/README.md +0 -127
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-anchor/README.md +0 -600
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-attrs/README.md +0 -386
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-container/README.md +0 -95
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-emoji/README.md +0 -101
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/markdown-it-footnote/README.md +0 -135
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/pathslash/README.md +0 -64
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/shiki/README.md +0 -15
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/tinyglobby/README.md +0 -25
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/AGENTS.md +0 -16
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/README.md +0 -220
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/build.md +0 -21
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/check.md +0 -35
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/create.md +0 -70
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/fmt.md +0 -20
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/index.md +0 -35
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/lint.md +0 -26
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/pack.md +0 -17
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/run.md +0 -364
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/staged.md +0 -15
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/config/test.md +0 -18
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/automatic-data-tracking.md +0 -145
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/build.md +0 -40
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/cache.md +0 -107
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/check.md +0 -60
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/ci.md +0 -62
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/commit-hooks.md +0 -60
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/create.md +0 -341
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/dev.md +0 -24
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/docker.md +0 -175
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/env.md +0 -167
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/fmt.md +0 -41
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/github-actions-cache.md +0 -165
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/ide-integration.md +0 -101
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/implode.md +0 -23
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/index.md +0 -134
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/install.md +0 -199
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/lint.md +0 -50
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/migrate-rules.md +0 -347
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/migrate.md +0 -197
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/monorepo.md +0 -176
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/pack.md +0 -69
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/run.md +0 -356
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/test.md +0 -35
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/troubleshooting.md +0 -108
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/upgrade.md +0 -101
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/vpx.md +0 -66
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/guide/why.md +0 -39
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/index.md +0 -12
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/docs/team.md +0 -35
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/templates/generator/README.md +0 -35
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vite-plus/templates/monorepo/README.md +0 -29
- package/skills/void/docs/node_modules/void/node_modules/@void/md/node_modules/vue/README.md +0 -58
- package/skills/void/docs/node_modules/void/node_modules/arktype/README.md +0 -165
- package/skills/void/docs/node_modules/void/node_modules/better-auth/LICENSE.md +0 -20
- package/skills/void/docs/node_modules/void/node_modules/better-auth/README.md +0 -32
- package/skills/void/docs/node_modules/void/node_modules/better-sqlite3/README.md +0 -99
- package/skills/void/docs/node_modules/void/node_modules/blake3-jit/README.md +0 -108
- package/skills/void/docs/node_modules/void/node_modules/drizzle-arktype/README.md +0 -51
- package/skills/void/docs/node_modules/void/node_modules/drizzle-kit/README.md +0 -79
- package/skills/void/docs/node_modules/void/node_modules/drizzle-orm/README.md +0 -44
- package/skills/void/docs/node_modules/void/node_modules/drizzle-valibot/README.md +0 -51
- package/skills/void/docs/node_modules/void/node_modules/drizzle-zod/README.md +0 -65
- package/skills/void/docs/node_modules/void/node_modules/es-module-lexer/README.md +0 -403
- package/skills/void/docs/node_modules/void/node_modules/estree-walker/README.md +0 -48
- package/skills/void/docs/node_modules/void/node_modules/hono/README.md +0 -85
- package/skills/void/docs/node_modules/void/node_modules/ignore/README.md +0 -452
- package/skills/void/docs/node_modules/void/node_modules/jsonc-parser/CHANGELOG.md +0 -76
- package/skills/void/docs/node_modules/void/node_modules/jsonc-parser/LICENSE.md +0 -21
- package/skills/void/docs/node_modules/void/node_modules/jsonc-parser/README.md +0 -364
- package/skills/void/docs/node_modules/void/node_modules/jsonc-parser/SECURITY.md +0 -41
- package/skills/void/docs/node_modules/void/node_modules/magic-string/README.md +0 -325
- package/skills/void/docs/node_modules/void/node_modules/ofetch/README.md +0 -398
- package/skills/void/docs/node_modules/void/node_modules/pathslash/README.md +0 -64
- package/skills/void/docs/node_modules/void/node_modules/pg/README.md +0 -96
- package/skills/void/docs/node_modules/void/node_modules/pglite-server/README.md +0 -135
- package/skills/void/docs/node_modules/void/node_modules/picocolors/README.md +0 -21
- package/skills/void/docs/node_modules/void/node_modules/proper-lockfile/CHANGELOG.md +0 -108
- package/skills/void/docs/node_modules/void/node_modules/proper-lockfile/README.md +0 -183
- package/skills/void/docs/node_modules/void/node_modules/tinyglobby/README.md +0 -25
- package/skills/void/docs/node_modules/void/node_modules/valibot/LICENSE.md +0 -9
- package/skills/void/docs/node_modules/void/node_modules/valibot/README.md +0 -94
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/AGENTS.md +0 -16
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/README.md +0 -220
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/build.md +0 -21
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/check.md +0 -35
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/create.md +0 -70
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/fmt.md +0 -20
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/index.md +0 -35
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/lint.md +0 -26
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/pack.md +0 -17
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/run.md +0 -364
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/staged.md +0 -15
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/config/test.md +0 -18
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/automatic-data-tracking.md +0 -145
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/build.md +0 -40
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/cache.md +0 -107
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/check.md +0 -60
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/ci.md +0 -62
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/commit-hooks.md +0 -60
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/create.md +0 -341
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/dev.md +0 -24
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/docker.md +0 -175
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/env.md +0 -167
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/fmt.md +0 -41
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/github-actions-cache.md +0 -165
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/ide-integration.md +0 -101
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/implode.md +0 -23
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/index.md +0 -134
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/install.md +0 -199
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/lint.md +0 -50
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/migrate-rules.md +0 -347
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/migrate.md +0 -197
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/monorepo.md +0 -176
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/pack.md +0 -69
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/run.md +0 -356
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/test.md +0 -35
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/troubleshooting.md +0 -108
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/upgrade.md +0 -101
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/vpx.md +0 -66
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/guide/why.md +0 -39
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/index.md +0 -12
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/docs/team.md +0 -35
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/templates/generator/README.md +0 -35
- package/skills/void/docs/node_modules/void/node_modules/vite-plus/templates/monorepo/README.md +0 -29
- package/skills/void/docs/node_modules/void/node_modules/wrangler/README.md +0 -63
- package/skills/void/docs/node_modules/void/node_modules/zod/README.md +0 -191
- package/skills/void/docs/node_modules/void/skills/migrate-vite-cloudflare-to-void/SKILL.md +0 -175
- package/skills/void/docs/node_modules/void/skills/void/SKILL.md +0 -76
- package/skills/void/docs/node_modules/void/skills/void/command/void.md +0 -7
- 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 };
|