void 0.27.0 → 0.28.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/README.md +3 -1
- package/dist/{account-cmd-l-FM7Ioq.mjs → account-cmd-Ci00cncT.mjs} +5 -5
- package/dist/{agents-B_pX_HmQ.mjs → agents-CuNtT0D_.mjs} +5 -2
- package/dist/{application-analysis-DKy02UY0.mjs → application-analysis-D72ZQfMZ.mjs} +118 -135
- package/dist/{application-routing-DQyUh_5S.mjs → application-routing-Dy589Vgn.mjs} +1 -2
- package/dist/atomic-write-Dq6R0BPN.mjs +18 -0
- package/dist/{auth-Dn2vw-SG.mjs → auth-B6iOCjKI.mjs} +23 -56
- package/dist/{auth-link-vz9Z2kmG.mjs → auth-link-DWr6Afao.mjs} +10 -10
- package/dist/{auth-migrations-BwHgeEsD.mjs → auth-migrations-Cgs0k6lS.mjs} +28 -5
- package/dist/{auth-router-DvsCUVw4.mjs → auth-router-DP14wVkZ.mjs} +4 -4
- package/dist/auth-wn6F8dB8.mjs +2 -0
- package/dist/base64-DVrOndmI.mjs +16 -0
- package/dist/{better-auth-shared-CPvWD3nG.d.mts → better-auth-shared-B74NJIus.d.mts} +8 -13
- package/dist/{better-auth-shared-DPpj8zxc.mjs → better-auth-shared-CMXlZG7W.mjs} +56 -68
- package/dist/{build-cmd-D6we0VjO.mjs → build-cmd-Cr3FJxv0.mjs} +5 -5
- package/dist/{cache-CZRdzKx1.mjs → cache-D0Tt28cn.mjs} +5 -5
- package/dist/{cancel-deploy-CQ3NnMvf.mjs → cancel-deploy-CJKvk7AN.mjs} +4 -4
- package/dist/{cf-access-TWNzXltp.mjs → cf-access-BM9G0lhO.mjs} +4 -3
- package/dist/{cf-access-BcIBSKqG.mjs → cf-access-CWP4avfP.mjs} +3 -7
- package/dist/cf-build-output-2u583yJK.mjs +2 -0
- package/dist/{cf-build-output-DjvB_872.mjs → cf-build-output-Bc8i3dzE.mjs} +2 -2
- package/dist/cf-user-agent-ChjQp2MU.mjs +33 -0
- package/dist/cli/cli.mjs +198 -311
- package/dist/cli/cloudflare-operation-process.mjs +41 -15
- package/dist/cli/env-schema-probe.d.mts +5 -43
- package/dist/cli/env-schema-probe.mjs +14 -80
- package/dist/client-C1BjRn3g.mjs +2 -0
- package/dist/{client-DlDJuY0s.mjs → client-DM9Kcf4e.mjs} +161 -415
- package/dist/{cloudflare-auth-CuxfF6OA.mjs → cloudflare-auth-CqFBDo52.mjs} +12 -39
- package/dist/{cloudflare-cmd-lHqNlJGW.mjs → cloudflare-cmd-CTrn-lXJ.mjs} +6 -5
- package/dist/{cloudflare-config-BmhBv3HV.mjs → cloudflare-config-DV60XFFK.mjs} +16 -28
- package/dist/{cloudflare-connect-2G3rL2rP.mjs → cloudflare-connect-BosmCNlq.mjs} +3 -3
- package/dist/{cloudflare-operations-CwI1bChl.mjs → cloudflare-operations-BKdSfNBN.mjs} +26 -34
- package/dist/{cloudflare-operations-BlYLlowZ.mjs → cloudflare-operations-Ds-lX3Ts.mjs} +1 -1
- package/dist/{cloudflare-process-2-TfIEzB.mjs → cloudflare-process-CeDWJRhj.mjs} +12 -7
- package/dist/collect-NUlJMpLY.mjs +2 -0
- package/dist/{collect-DiTn9yr6.mjs → collect-xjffdCdk.mjs} +1 -1
- package/dist/{config-CSa15FvD.d.mts → config-7qoq2p9v.d.mts} +2 -2
- package/dist/{config-CLYAosnQ.mjs → config-BEm9vRvS.mjs} +11 -5
- package/dist/{config-DiowPT80.mjs → config-Bx_1SpnO.mjs} +87 -177
- package/dist/config-CNnrSMYm.mjs +2 -0
- package/dist/{config-DPByhVck.mjs → config-D0GY5ItR.mjs} +1 -1
- package/dist/config-entry.d.mts +1 -1
- package/dist/{connect-BfPBHHm-.mjs → connect-nukQSoEO.mjs} +8 -8
- package/dist/{create-project-BdXEFKal.mjs → create-project-D4APeBwX.mjs} +7 -7
- package/dist/{create-project-qFDC45zg.mjs → create-project-DL7YXpjj.mjs} +1 -1
- package/dist/{db-Xs80hcaA.mjs → db-27RztQLy.mjs} +173 -263
- package/dist/{delete-d2vuou3j.mjs → delete-BbsZFZIw.mjs} +5 -5
- package/dist/deploy-B06EQq4t.mjs +6009 -0
- package/dist/deploy-Db2ALnQP.mjs +3 -0
- package/dist/deploy-managed-static-CGswbcay.mjs +2277 -0
- package/dist/deploy-transport-BiicfoQW.mjs +180 -0
- package/dist/deploy.d.mts +10 -0
- package/dist/deploy.mjs +82 -0
- package/dist/dev-inbox-Rd6j_8vM.mjs +217 -0
- package/dist/discover-CTUiSsfG.mjs +2 -0
- package/dist/{discover-CkCYv9T_.mjs → discover-D2PGT63u.mjs} +2 -2
- package/dist/{dist-BVqXeR_h.mjs → dist-ipQi0Q8y.mjs} +2 -3
- package/dist/{domain-BsDUBAvt.mjs → domain-Bfq9n03H.mjs} +7 -22
- package/dist/{dotenv-BfU6eqFt.mjs → dotenv-CNazY3-w.mjs} +4 -16
- package/dist/{email-d_Aux1vM.mjs → email-BcjSyAL0.mjs} +4 -20
- package/dist/{email-_lGAHOhp.mjs → email-DIwaQU4P.mjs} +99 -191
- package/dist/email-cloudflare-CH053RxK.mjs +1096 -0
- package/dist/email-protocol-D9nTvhCp.mjs +82 -0
- package/dist/{entry-CMAkqBmU.mjs → entry-ehmBvs5w.mjs} +2 -2
- package/dist/{env-BxocywBB.mjs → env-BJjULvL0.mjs} +2 -2
- package/dist/{env-DMA3cXZt.mjs → env-BhdWMrNy.mjs} +2 -13
- package/dist/{env-C74QepDE.mjs → env-C6ipBLp3.mjs} +8 -8
- package/dist/{env-public-DR7ZM6d-.d.mts → env-public-CCdykygU.d.mts} +6 -23
- package/dist/{env-raw-jZpP5n47.mjs → env-raw-Cklzcde3.mjs} +3 -20
- package/dist/{env-types-CwXgeMgL.mjs → env-types-DS2_y4X8.mjs} +2 -2
- package/dist/{env-validation-CrVNjsPm.mjs → env-validation-CTn-HhG1.mjs} +1 -1
- package/dist/env-validation-cE0zcmzO.mjs +486 -0
- package/dist/{gen-DtkQhvQg.mjs → gen-CnN1Xidx.mjs} +131 -253
- package/dist/gen-F8pSZnhc.mjs +2 -0
- package/dist/{generate-GDbL7sUr.mjs → generate-Dat0UTry.mjs} +38 -4
- package/dist/generate-Rii9klHB.mjs +2 -0
- package/dist/generated-wrangler-config-DeWj5F9Y.mjs +18 -0
- package/dist/{github-cmd-DaDdBbSS.mjs → github-cmd-BdkU4Nhe.mjs} +27 -71
- package/dist/{handler-BHAn2fZJ.mjs → handler-BZ1t7-Rv.mjs} +2 -2
- package/dist/{handler-CNAwjRcW.d.mts → handler-_PqY82xe.d.mts} +1 -1
- package/dist/{headers-DlfR78Nu.mjs → headers-Di1FsD3e.mjs} +26 -81
- package/dist/{help-0bTe_lID.mjs → help-Bug7pyK_.mjs} +18 -10
- package/dist/help-s9q7LTcu.mjs +2 -0
- package/dist/{inbound-PfQDLXc5.mjs → inbound-DmkTQeWp.mjs} +71 -222
- package/dist/{inbound-CH5Mksyy.d.mts → inbound-brj2-TM5.d.mts} +19 -58
- package/dist/index.d.mts +2 -18
- package/dist/index.mjs +200 -496
- package/dist/{info-PYWwkWyt.mjs → info-CujPwtMe.mjs} +5 -5
- package/dist/{init-DaBGmaXC.mjs → init-ZWOJvc-Z.mjs} +179 -388
- package/dist/isr-config-DPjXM8J2.mjs +194 -0
- package/dist/{link-C6fjafep.mjs → link-UEc7bnF4.mjs} +8 -8
- package/dist/{list-BTFBlqKl.mjs → list-CuWeOnzk.mjs} +6 -17
- package/dist/{live-CgQuTpqD.mjs → live-BxLX-JqJ.mjs} +19 -17
- package/dist/{live-Du8jXwOW.d.mts → live-DJONVlQK.d.mts} +1 -1
- package/dist/load-user-env-DD5qveNU.mjs +49 -0
- package/dist/{local-d1-ga5O_idV.mjs → local-d1-BM3p74mm.mjs} +6 -6
- package/dist/{login-GKcNUmtI.mjs → login-CZ2Cqa-D.mjs} +11 -11
- package/dist/login-DhK7o1oJ.mjs +2 -0
- package/dist/{logs-BiMSULmc.mjs → logs-TihRcLTk.mjs} +5 -5
- package/dist/migrate-BgT-hmu8.mjs +2 -0
- package/dist/{migrate-NLsjBbuM.mjs → migrate-uFwojusJ.mjs} +69 -21
- package/dist/migration-handler-Cg0Y4EPu.mjs +470 -0
- package/dist/{migration-handler-DM4clYj5.d.mts → migration-handler-CwlEQUoo.d.mts} +17 -26
- package/dist/{mime-DxCznlJq.mjs → mime-srOklVVv.mjs} +92 -395
- package/dist/{node-DJicPvPj.mjs → node-DoKbo1SZ.mjs} +8 -24
- package/dist/{operator-args-DOBhIaIg.mjs → operator-args-UnE8Y2eK.mjs} +27 -26
- package/dist/{operator-auth-BEZ_u0Cz.mjs → operator-auth-Cr2zCc-J.mjs} +2 -2
- package/dist/{operator-client-DbDv4vtE.mjs → operator-client-C-ROjk1I.mjs} +4 -4
- package/dist/{operator-cmd-BT4hgavL.mjs → operator-cmd-C5lCctnc.mjs} +8 -8
- package/dist/operator-cmd-Cjk5DiRx.mjs +2 -0
- package/dist/{output-C50ag2r3.mjs → output-FNHKw5Ac.mjs} +36 -5
- package/dist/{package-json-DXfiT1jV.mjs → package-json-BjYqR-So.mjs} +1 -1
- package/dist/pages/client.d.mts +1 -1
- package/dist/pages/client.mjs +91 -5
- package/dist/pages/index.d.mts +1 -2
- package/dist/pages/index.mjs +6 -6
- package/dist/pages/islands-plugin.mjs +5 -83
- package/dist/pages/protocol.d.mts +2 -2
- package/dist/pages/protocol.mjs +1 -1
- package/dist/pages/serialize.mjs +1 -1
- package/dist/{platform-args-DUeXY9Om.mjs → platform-args-C6Mb_I3a.mjs} +34 -58
- package/dist/platform-args-Cm7x-fot.mjs +2 -0
- package/dist/{platform-auth-config-CkSx3usH.mjs → platform-auth-config-CJmNkFT2.mjs} +10 -11
- package/dist/{platform-auth-protection-BJimB_p9.mjs → platform-auth-protection-D5wvsI-Y.mjs} +36 -26
- package/dist/{platform-auth-recovery-BU6izFAU.mjs → platform-auth-recovery-D_0I3n-1.mjs} +8 -9
- package/dist/{platform-cmd-BbmJiq5O.mjs → platform-cmd-Cuj4oUVo.mjs} +1 -1
- package/dist/{platform-cmd-BbY1dWiM.mjs → platform-cmd-DxxZO6e5.mjs} +15 -15
- package/dist/{platform-discovery-DIYz8V63.mjs → platform-discovery-CHhNs8Nn.mjs} +1 -1
- package/dist/{platform-domain-CHVS6cV0.mjs → platform-domain-zLXnW1G_.mjs} +5 -5
- package/dist/{platform-lifecycle-DzCwqhFq.mjs → platform-lifecycle-DOW-wLfx.mjs} +1398 -2043
- package/dist/{platform-lifecycle-ByAlpkhV.mjs → platform-lifecycle-_gfnjUBr.mjs} +1 -1
- package/dist/{platform-management-CBOT4BSM.mjs → platform-management-CYrLvTGH.mjs} +1 -1
- package/dist/{platform-management-CuuVDhyt.mjs → platform-management-t7HKy4-U.mjs} +632 -124
- package/dist/{platform-plans-config-CN-heS4q.mjs → platform-plans-config-CNH6zbPX.mjs} +15 -11
- package/dist/{platform-recovery-CskSJgLh.mjs → platform-recovery-DeucI7A9.mjs} +6 -9
- package/dist/{platform-registry-Cjv2Qf0r.mjs → platform-registry-B9mWX9LO.mjs} +13 -18
- package/dist/{platform-setup-BBxRxmFC.mjs → platform-setup-CTf6MMT9.mjs} +19 -26
- package/dist/{plugin-inference-BlQbOL0P.mjs → plugin-inference-DsARdQKE.mjs} +18 -33
- package/dist/{prepare-Dq9hTIbo.mjs → prepare-C4zfwks4.mjs} +3 -3
- package/dist/{prepare-Do96654b.mjs → prepare-CSH9dKTI.mjs} +11 -13
- package/dist/prepare-dr9uxQSV.mjs +2 -0
- package/dist/{prerender-render-Ct20jftc.mjs → prerender-render-Bfsumdkv.mjs} +8 -2
- package/dist/prerender-render.d.mts +2 -0
- package/dist/prerender-render.mjs +2 -2
- package/dist/project-DiiOBZPF.mjs +2 -0
- package/dist/{project-cmd-DaoRrn-8.mjs → project-cmd-Dq75AXzy.mjs} +19 -19
- package/dist/{project-paths-GgcMozYz.mjs → project-paths-DMvEeSau.mjs} +9 -14
- package/dist/{project-BAm2aB6Q.mjs → project-rqDnVsJN.mjs} +8 -4
- package/dist/{project-team-Q_lCA7Io.mjs → project-team-B3_QcStG.mjs} +6 -6
- package/dist/{project-token-BArgK9o1.mjs → project-token-CvsRvv2I.mjs} +5 -5
- package/dist/{project-tsconfig-CVJhNMxs.mjs → project-tsconfig-bveqhlYA.mjs} +3 -5
- package/dist/{project-zero-trust-KO9Hu2Pq.mjs → project-zero-trust-D1ICiwN_.mjs} +5 -5
- package/dist/{protocol-BPZaNqBo.d.mts → protocol-4H7V_3Os.d.mts} +1 -1
- package/dist/{protocol-CfaUwNs8.mjs → protocol-RNP0u7UY.mjs} +9 -3
- package/dist/{provision-BfaLiWbj.mjs → provision-DDh7ITVS.mjs} +118 -865
- package/dist/{requests-C7Oeoh5u.mjs → requests-3CL3BuZh.mjs} +4 -4
- package/dist/resolve-project-B9Hr0D3n.mjs +2 -0
- package/dist/resolve-project-C_Cu7E8a.mjs +48 -0
- package/dist/{rollback-DVAtrLAl.mjs → rollback-Ce1d_0J9.mjs} +7 -17
- package/dist/{route-types-CbtsIeFn.mjs → route-types-IrBuwJ7u.mjs} +35 -70
- package/dist/routing-assets-VkrKIbxE.mjs +139 -0
- package/dist/{runner-9wjsSZx-.mjs → runner-B5huK2eC.mjs} +1 -1
- package/dist/{runner-DMBKSrRk.mjs → runner-DcVDrzuR.mjs} +7 -28
- package/dist/{runner-mysql-CEzKtmFb.mjs → runner-mysql-CCtyI0p6.mjs} +4 -3
- package/dist/runner-mysql-CaJq-hd6.mjs +2 -0
- package/dist/runner-pg-BJhFZZG4.mjs +2 -0
- package/dist/{runner-pg-DV8p_zjA.mjs → runner-pg-CPd-CB_F.mjs} +1 -1
- package/dist/runtime/ai.mjs +9 -22
- package/dist/runtime/better-auth-mysql.d.mts +2 -5
- package/dist/runtime/better-auth-mysql.mjs +6 -31
- package/dist/runtime/better-auth-pg.d.mts +2 -5
- package/dist/runtime/better-auth-pg.mjs +6 -32
- package/dist/runtime/better-auth.d.mts +2 -4
- package/dist/runtime/better-auth.mjs +9 -19
- package/dist/runtime/db.mjs +1 -1
- package/dist/runtime/durable.d.mts +16 -0
- package/dist/runtime/durable.mjs +26 -2
- package/dist/runtime/email/testing.d.mts +1 -1
- package/dist/runtime/email/testing.mjs +9 -23
- package/dist/runtime/email-protocol.d.mts +12 -0
- package/dist/runtime/email-protocol.mjs +2 -70
- package/dist/runtime/email.d.mts +1 -1
- package/dist/runtime/email.mjs +79 -221
- package/dist/runtime/env-public.d.mts +1 -1
- package/dist/runtime/env-public.mjs +12 -38
- package/dist/runtime/env.d.mts +1 -12
- package/dist/runtime/env.mjs +1 -1
- package/dist/runtime/handler.d.mts +1 -1
- package/dist/runtime/handler.mjs +1 -1
- package/dist/runtime/isr.mjs +1 -1
- package/dist/runtime/kv.mjs +1 -1
- package/dist/runtime/live-client.d.mts +1 -1
- package/dist/runtime/live-server.mjs +1 -16
- package/dist/runtime/live.d.mts +1 -1
- package/dist/runtime/live.mjs +1 -1
- package/dist/runtime/migration-handler-mysql.d.mts +2 -2
- package/dist/runtime/migration-handler-mysql.mjs +3 -2
- package/dist/runtime/migration-handler-pg.d.mts +2 -2
- package/dist/runtime/migration-handler-pg.mjs +1 -1
- package/dist/runtime/migration-handler.d.mts +2 -2
- package/dist/runtime/migration-handler.mjs +2 -470
- package/dist/runtime/queues.mjs +1 -1
- package/dist/runtime/remote/binding-handler.mjs +62 -62
- package/dist/runtime/remote/index.mjs +16 -77
- package/dist/runtime/routing.d.mts +1 -4
- package/dist/runtime/routing.mjs +82 -218
- package/dist/runtime/sandbox-container.d.mts +2 -2
- package/dist/runtime/sandbox-container.mjs +2 -2
- package/dist/runtime/sandbox.d.mts +2 -2
- package/dist/runtime/sandbox.mjs +13 -6
- package/dist/runtime/storage.mjs +1 -1
- package/dist/runtime/validator.d.mts +1 -1
- package/dist/runtime/ws-server.d.mts +2 -3
- package/dist/runtime/ws-server.mjs +6 -28
- package/dist/runtime/ws.d.mts +1 -1
- package/dist/{sandbox-container-Bo0eiFKz.d.mts → sandbox-container-ChQRum-j.d.mts} +3 -1
- package/dist/{sandbox-container-C6ItmVuN.mjs → sandbox-container-DBKIqDEK.mjs} +12 -1
- package/dist/{sandbox-C5LFoJMf.d.mts → sandbox-q3waMc2T.d.mts} +1 -1
- package/dist/{scan-C3uDzz6J.mjs → scan-C0BMdmEd.mjs} +84 -7
- package/dist/{secret-BvtB7IjY.mjs → secret-B5J8bBsI.mjs} +62 -80
- package/dist/{serialize-BPvnNQuA.mjs → serialize-BtQyaYoC.mjs} +10 -4
- package/dist/{skills-BgVMvQdy.mjs → skills-BJsan98C.mjs} +2 -2
- package/dist/{subcommand-prompt-BSlnD1fI.mjs → subcommand-prompt-gQ-NDA-u.mjs} +38 -43
- package/dist/sveltekit.mjs +1 -1
- package/dist/validate-C9xtFOYR.mjs +2 -0
- package/dist/{validate-C9qJArv7.mjs → validate-CheZGRE7.mjs} +4 -8
- package/dist/version-metadata-DIrqEHQo.mjs +73 -0
- package/dist/{wrangler-D9KTuqnv.mjs → wrangler-BYs07Rxy.mjs} +119 -378
- package/dist/{ws-CChrrAU-.d.mts → ws-aKVYB-fA.d.mts} +1 -1
- package/dist/yarn-pnp-DLbEFMT0.mjs +92 -0
- package/docs/guide/auth.md +2 -0
- package/docs/guide/database/postgresql.md +2 -0
- package/docs/guide/database.md +2 -0
- package/docs/guide/durable-state.md +24 -0
- package/docs/guide/platform/administration/access.md +4 -0
- package/docs/guide/platform/administration/connected-apps.md +55 -0
- package/docs/guide/platform/installation/credentials.md +3 -1
- package/docs/guide/quickstart.md +2 -0
- package/docs/guide/sandboxes.md +2 -0
- package/docs/reference/api/deploy.md +23 -0
- package/docs/reference/api/pages.md +2 -0
- package/docs/reference/api.md +2 -1
- package/docs/reference/cli/database.md +9 -3
- package/docs/reference/cli/platform-installation.md +1 -1
- package/docs/reference/cli/setup.md +2 -0
- package/package.json +13 -16
- package/skills/void/SKILL.md +1 -0
- package/dist/auth-LMJcNESe.mjs +0 -2
- package/dist/cf-build-output-H9a97vME.mjs +0 -2
- package/dist/cf-user-agent-lKblH3zU.mjs +0 -17
- package/dist/client-tso2OfcG.mjs +0 -2
- package/dist/collect-DLyFDiKW.mjs +0 -2
- package/dist/config-5r-YpMC-.mjs +0 -2
- package/dist/config-B_ggY-IO.mjs +0 -89
- package/dist/config-BtSFvXZx.mjs +0 -2
- package/dist/config-write-AHGesphx.mjs +0 -61
- package/dist/deploy-Bqe1PYBw.mjs +0 -10458
- package/dist/deploy-VsWsVQkw.mjs +0 -2
- package/dist/dev-inbox-CK-UuPob.mjs +0 -297
- package/dist/discover-EDUf6JJw.mjs +0 -2
- package/dist/dist-_UUlL6bg.mjs +0 -2
- package/dist/env-validation-Dxdx_5wo.mjs +0 -951
- package/dist/gen-dA9hW7AV.mjs +0 -2
- package/dist/head-client-DzmJGN4C.mjs +0 -90
- package/dist/help-DfQYVfGY.mjs +0 -2
- package/dist/login-mx_ydhmp.mjs +0 -2
- package/dist/migrate-BHp07yJE.mjs +0 -2
- package/dist/operator-cmd-CwBB-1yb.mjs +0 -2
- package/dist/pages/head-client.d.mts +0 -13
- package/dist/pages/head-client.mjs +0 -2
- package/dist/platform-args-D4E1PjVw.mjs +0 -2
- package/dist/prepare-DRUV-g3X.mjs +0 -2
- package/dist/project-DedqCp59.mjs +0 -2
- package/dist/provision-Wu9ZBk9L.mjs +0 -2
- package/dist/resolve-project-BIeYs9Cw.mjs +0 -2
- package/dist/resolve-project-CP8rZs_p.mjs +0 -28
- package/dist/runner-mysql-CkFqZ6CE.mjs +0 -2
- package/dist/runner-pg-BGYRFdRp.mjs +0 -2
- package/dist/runtime/env-helpers.d.mts +0 -2
- package/dist/runtime/env-helpers.mjs +0 -2
- package/dist/validate-BJr9LfXy.mjs +0 -2
- package/dist/yarn-pnp-CUX_iTPA.mjs +0 -188
|
@@ -0,0 +1,2277 @@
|
|
|
1
|
+
import { a as join, c as resolve, n as dirname, r as extname, s as relative } from "./dist-ipQi0Q8y.mjs";
|
|
2
|
+
import { h as needsApplicationDatabase } from "./plugin-inference-DsARdQKE.mjs";
|
|
3
|
+
import { n as readProjectPaths } from "./project-paths-DMvEeSau.mjs";
|
|
4
|
+
import { c as isNodeTarget } from "./config-Bx_1SpnO.mjs";
|
|
5
|
+
import { n as isIdentifier, r as isLiteral } from "./ast-CPGPjb0V.mjs";
|
|
6
|
+
import { p as scanDurableObjectModules, t as analyzeApplicationSync } from "./application-analysis-D72ZQfMZ.mjs";
|
|
7
|
+
import { t as collectMigrations } from "./collect-xjffdCdk.mjs";
|
|
8
|
+
import { t as MagicString } from "./dist-Cegxq8gs.mjs";
|
|
9
|
+
import { i as collectFiles, n as collectRevalidate, s as generateQueueResolverSource, t as collectPages } from "./isr-config-DPjXM8J2.mjs";
|
|
10
|
+
import { n as mergeRoutingRules, t as lintRoutingPhases } from "./headers-Di1FsD3e.mjs";
|
|
11
|
+
import { s as shouldLinkDeployProject, t as attachSelectedPlatform, u as writeProjectConfig } from "./project-rqDnVsJN.mjs";
|
|
12
|
+
import { D as syncWranglerBindings, E as resolveLiveBindingConfig, a as LIVE_MIGRATION_TAG, g as normalizeWranglerCompatibilityFlags, u as ensureWranglerCompat, v as readExistingLiveBindingConfig } from "./wrangler-BYs07Rxy.mjs";
|
|
13
|
+
import { n as buildMigrationPlanBundle } from "./plan-BhLO2p4w.mjs";
|
|
14
|
+
import { l as getTokenSource } from "./auth-B6iOCjKI.mjs";
|
|
15
|
+
import { L as log$1, N as intro, X as text, tt as isCancel } from "./dist-D3m88sjf.mjs";
|
|
16
|
+
import { f as import_picocolors, h as sanitizeTerminalText, m as printSuccess, o as formatCount, p as note, r as createSpinner, u as getErrorMessage } from "./output-FNHKw5Ac.mjs";
|
|
17
|
+
import { t as discoverSchema } from "./discover-D2PGT63u.mjs";
|
|
18
|
+
import { t as findEnvFile } from "./env-types-DS2_y4X8.mjs";
|
|
19
|
+
import { a as buildEnv } from "./version-metadata-DIrqEHQo.mjs";
|
|
20
|
+
import { r as writeDrizzleConfig, t as PLACEHOLDER_DB_URL } from "./config-BEm9vRvS.mjs";
|
|
21
|
+
import { i as validateMigrations, r as listDrizzleArtifacts, t as assertJournalCoherence } from "./validate-CheZGRE7.mjs";
|
|
22
|
+
import { t as formatCloudflareUserMessage } from "./cloudflare-user-output-Q-k0QoNQ.mjs";
|
|
23
|
+
import { c as platformErrorMessage, i as isCliOutdatedError, n as PlatformApiError, o as isHeadersTimeoutError, s as parsePlatformErrorBody } from "./client-DM9Kcf4e.mjs";
|
|
24
|
+
import { a as promptProjectSelection, i as reportProjectCreationWarnings, o as promptProjectSetupAction, r as promptAndCreateProject } from "./create-project-D4APeBwX.mjs";
|
|
25
|
+
import { o as detectPreset, s as formatProjectCommand, u as resolveProjectCommand } from "./cloudflare-process-CeDWJRhj.mjs";
|
|
26
|
+
import { d as formatEnvReport, g as validateProdEnv, u as fetchRemoteSecretNames } from "./env-validation-cE0zcmzO.mjs";
|
|
27
|
+
import { n as drizzleKitBin } from "./yarn-pnp-DLbEFMT0.mjs";
|
|
28
|
+
import { t as MANAGED_DEPLOY_BUILD_ENV } from "./deploy-transport-BiicfoQW.mjs";
|
|
29
|
+
import { createRequire } from "node:module";
|
|
30
|
+
import { copyFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
31
|
+
import { fileURLToPath } from "node:url";
|
|
32
|
+
import { build, parseSync } from "vite";
|
|
33
|
+
import { createDeployArtifact, createDeployUploadPayload } from "@void/deploy-core";
|
|
34
|
+
import { createHash } from "node:crypto";
|
|
35
|
+
import { execSync, spawnSync } from "node:child_process";
|
|
36
|
+
import { getContentType } from "@void/edge";
|
|
37
|
+
import { hash } from "blake3-jit";
|
|
38
|
+
import ignore from "ignore";
|
|
39
|
+
//#region src/cli/pg-bundle-transform.ts
|
|
40
|
+
function nodes(root) {
|
|
41
|
+
const found = [];
|
|
42
|
+
const pending = [root];
|
|
43
|
+
while (pending.length > 0) {
|
|
44
|
+
const node = pending.pop();
|
|
45
|
+
found.push(node);
|
|
46
|
+
for (const value of Object.values(node)) if (Array.isArray(value)) {
|
|
47
|
+
for (const child of value) if (child && typeof child === "object" && typeof child.type === "string") pending.push(child);
|
|
48
|
+
} else if (value && typeof value === "object" && typeof value.type === "string") pending.push(value);
|
|
49
|
+
}
|
|
50
|
+
return found;
|
|
51
|
+
}
|
|
52
|
+
/** Rewrite the two CommonJS constructs emitted by the PG migration bundle. */
|
|
53
|
+
function transformPgWorkerBundle(code, filename, requireShim) {
|
|
54
|
+
if (!code.includes("createRequire") && !code.includes("pgpass")) return code;
|
|
55
|
+
const program = parseSync(filename, code).program;
|
|
56
|
+
const edits = [];
|
|
57
|
+
let hasRequireBinding = false;
|
|
58
|
+
let pgpassCalls = 0;
|
|
59
|
+
for (const statement of program.body ?? []) {
|
|
60
|
+
if (statement.type === "ImportDeclaration" && statement.source?.value === "node:module") {
|
|
61
|
+
const specifiers = statement.specifiers;
|
|
62
|
+
if (specifiers?.length !== 1 || !isIdentifier(specifiers[0]?.imported) || specifiers[0].imported.name !== "createRequire") throw new Error(`deploy: Unsupported node:module import in PostgreSQL bundle ${filename}.`);
|
|
63
|
+
edits.push({
|
|
64
|
+
start: statement.start,
|
|
65
|
+
end: statement.end,
|
|
66
|
+
replacement: ""
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (statement.type === "VariableDeclaration") {
|
|
70
|
+
const declarations = statement.declarations;
|
|
71
|
+
if (declarations?.some((declaration) => isIdentifier(declaration.id) && declaration.id.name === "__require")) {
|
|
72
|
+
if (declarations.length !== 1 || hasRequireBinding) throw new Error(`deploy: Unsupported require binding in PostgreSQL bundle ${filename}.`);
|
|
73
|
+
hasRequireBinding = true;
|
|
74
|
+
edits.push({
|
|
75
|
+
start: statement.start,
|
|
76
|
+
end: statement.end,
|
|
77
|
+
replacement: requireShim
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
for (const node of nodes(program)) {
|
|
83
|
+
if (node.type !== "CallExpression" || !isIdentifier(node.callee) || node.callee.name !== "__require") continue;
|
|
84
|
+
const args = node.arguments;
|
|
85
|
+
if (args?.length !== 1 || !isLiteral(args[0]) || args[0].value !== "pgpass") continue;
|
|
86
|
+
pgpassCalls++;
|
|
87
|
+
edits.push({
|
|
88
|
+
start: node.start,
|
|
89
|
+
end: node.end,
|
|
90
|
+
replacement: "__voidPgpassNoop"
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (code.includes("createRequire") && !hasRequireBinding) throw new Error(`deploy: Could not locate the generated require binding in PostgreSQL bundle ${filename}.`);
|
|
94
|
+
if (code.includes("pgpass") && pgpassCalls === 0 && code.includes("__require")) throw new Error(`deploy: Could not locate the pgpass require in PostgreSQL bundle ${filename}.`);
|
|
95
|
+
const ms = new MagicString(code);
|
|
96
|
+
for (const edit of edits.sort((a, b) => b.start - a.start)) ms.overwrite(edit.start, edit.end, edit.replacement);
|
|
97
|
+
const transformed = ms.toString();
|
|
98
|
+
if (transformed.includes("createRequire") || transformed.includes("__require(\"pgpass\")")) throw new Error(`deploy: PostgreSQL bundle ${filename} still contains an unsupported require.`);
|
|
99
|
+
return pgpassCalls > 0 ? `const __voidPgpassNoop = Object.assign(function pgpass(_connectionInfo, callback) { callback(undefined); }, { warnTo() {} });\n${transformed}` : transformed;
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/cli/wrapper.ts
|
|
103
|
+
const BUNDLED_ENTRY_RE = /^index\.(?:m?js|cjs)$/;
|
|
104
|
+
const WRAPPER_MARKER = "// Generated by Void: deployment wrapper.";
|
|
105
|
+
function isGeneratedWrapper(source, originalName) {
|
|
106
|
+
return source.startsWith(WRAPPER_MARKER) || source.startsWith(`import original from "./${originalName}";\n`);
|
|
107
|
+
}
|
|
108
|
+
function selectBundledEntryFilename(outFiles) {
|
|
109
|
+
const entryFilename = outFiles.find((file) => BUNDLED_ENTRY_RE.test(file));
|
|
110
|
+
if (!entryFilename) throw new Error(`deploy: Bundled worker entry not found. Expected index.js, index.mjs, or index.cjs in bundle output. Found: ${outFiles.join(", ") || "(none)"}`);
|
|
111
|
+
return entryFilename;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Determine whether a wrapper is needed based on the features.
|
|
115
|
+
*/
|
|
116
|
+
function needsWrapper(features) {
|
|
117
|
+
return features.crons.length > 0 || features.queues.length > 0 || !!features.hasMigrations || !!features.readiness || !!features.isr;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Generate a wrapper entry in-place in the worker directory.
|
|
121
|
+
*
|
|
122
|
+
* - Preserves the original entry (e.g. `index.js` → `__original.js`)
|
|
123
|
+
* - Compiles cron/queue handlers via a small Vite build
|
|
124
|
+
* - Writes a wrapper module as the original entry name that imports
|
|
125
|
+
* the renamed original + compiled handlers
|
|
126
|
+
*
|
|
127
|
+
* The original worker code is never re-bundled.
|
|
128
|
+
*/
|
|
129
|
+
async function generateWrapper(workerDir, workerMain, root, features) {
|
|
130
|
+
const renamedName = `__original${extname(workerMain) || ".js"}`;
|
|
131
|
+
const mainPath = join(workerDir, workerMain);
|
|
132
|
+
const originalPath = join(workerDir, renamedName);
|
|
133
|
+
const alreadyWrapped = isGeneratedWrapper(readFileSync(mainPath, "utf8"), renamedName);
|
|
134
|
+
if (alreadyWrapped && (!existsSync(originalPath) || isGeneratedWrapper(readFileSync(originalPath, "utf8"), renamedName))) throw new Error("deploy: Original framework entry is missing or already wrapped. Rebuild the application before deploying.");
|
|
135
|
+
let handlerFilename = "__void_handlers.mjs";
|
|
136
|
+
if (features.crons.length > 0 || features.queues.length > 0) {
|
|
137
|
+
const tmpDir = join(root, ".void", "handlers");
|
|
138
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
139
|
+
const handlersSource = generateHandlersSource(root, features);
|
|
140
|
+
const handlersEntry = join(tmpDir, "handlers.ts");
|
|
141
|
+
writeFileSync(handlersEntry, handlersSource);
|
|
142
|
+
const outDir = join(tmpDir, "out");
|
|
143
|
+
await build({
|
|
144
|
+
root: tmpDir,
|
|
145
|
+
logLevel: "silent",
|
|
146
|
+
resolve: { alias: [{
|
|
147
|
+
find: /^void$/,
|
|
148
|
+
replacement: fileURLToPath(import.meta.resolve("void/handler"))
|
|
149
|
+
}] },
|
|
150
|
+
build: {
|
|
151
|
+
outDir,
|
|
152
|
+
emptyOutDir: true,
|
|
153
|
+
lib: {
|
|
154
|
+
entry: handlersEntry,
|
|
155
|
+
formats: ["es"],
|
|
156
|
+
fileName: "__void_handlers"
|
|
157
|
+
},
|
|
158
|
+
rollupOptions: { external: [/^cloudflare:/, /^node:/] },
|
|
159
|
+
minify: false,
|
|
160
|
+
target: "esnext"
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
handlerFilename = readdirSync(outDir).filter((f) => f.startsWith("__void_handlers"))[0] ?? "__void_handlers.mjs";
|
|
164
|
+
copyFileSync(join(outDir, handlerFilename), join(workerDir, handlerFilename));
|
|
165
|
+
}
|
|
166
|
+
let migrations;
|
|
167
|
+
let migrationPlanBundle;
|
|
168
|
+
if (features.hasMigrations) {
|
|
169
|
+
const { collectMigrations } = await import("./collect-NUlJMpLY.mjs");
|
|
170
|
+
const { buildMigrationPlanBundle } = await import("./plan-CH2G51je.mjs");
|
|
171
|
+
const migrationsDir = readProjectPaths(root).migrationsDir;
|
|
172
|
+
migrations = collectMigrations(migrationsDir);
|
|
173
|
+
migrationPlanBundle = features.migrationDialect === "sqlite" ? buildMigrationPlanBundle(migrations) : void 0;
|
|
174
|
+
const handlerName = features.migrationDialect === "postgresql" ? "migration-handler-pg" : features.migrationDialect === "mysql" ? "migration-handler-mysql" : "migration-handler";
|
|
175
|
+
const handlerFilename_ = `${handlerName}.mjs`;
|
|
176
|
+
let handlerPath = join(import.meta.dirname, "..", "runtime", `${handlerName}.ts`);
|
|
177
|
+
if (!existsSync(handlerPath)) handlerPath = join(import.meta.dirname, "runtime", handlerFilename_);
|
|
178
|
+
if (!existsSync(handlerPath)) handlerPath = join(import.meta.dirname, "..", "runtime", handlerFilename_);
|
|
179
|
+
if (!existsSync(handlerPath)) throw new Error(`Migration handler not found: ${handlerFilename_}. Run "pnpm -C packages/void build" first.`);
|
|
180
|
+
await bundleMigrationHandler(root, workerDir, handlerPath, features.migrationDialect ?? "sqlite");
|
|
181
|
+
}
|
|
182
|
+
if (features.isr) await bundleFrameworkIsr(root, workerDir);
|
|
183
|
+
const wrapperSource = generateWrapperSource(renamedName, handlerFilename, features, migrations, migrationPlanBundle);
|
|
184
|
+
const stagedPath = `${mainPath}.${crypto.randomUUID()}.tmp`;
|
|
185
|
+
try {
|
|
186
|
+
writeFileSync(stagedPath, wrapperSource);
|
|
187
|
+
if (!alreadyWrapped) copyFileSync(mainPath, originalPath);
|
|
188
|
+
renameSync(stagedPath, mainPath);
|
|
189
|
+
} finally {
|
|
190
|
+
rmSync(stagedPath, { force: true });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async function bundleFrameworkIsr(root, workerDir) {
|
|
194
|
+
const entry = fileURLToPath(import.meta.resolve("@void/isr/runtime"));
|
|
195
|
+
const tmpDir = join(root, ".void", "isr");
|
|
196
|
+
const outDir = join(tmpDir, "out");
|
|
197
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
198
|
+
await build({
|
|
199
|
+
root: tmpDir,
|
|
200
|
+
logLevel: "silent",
|
|
201
|
+
ssr: { noExternal: true },
|
|
202
|
+
build: {
|
|
203
|
+
outDir,
|
|
204
|
+
emptyOutDir: true,
|
|
205
|
+
minify: false,
|
|
206
|
+
ssr: entry,
|
|
207
|
+
target: "esnext",
|
|
208
|
+
rollupOptions: {
|
|
209
|
+
input: entry,
|
|
210
|
+
external: [/^cloudflare:/, /^node:/],
|
|
211
|
+
output: {
|
|
212
|
+
entryFileNames: "__void_isr.mjs",
|
|
213
|
+
chunkFileNames: "__void_isr-[hash].mjs"
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
for (const file of readdirSync(outDir)) copyFileSync(join(outDir, file), join(workerDir, file));
|
|
219
|
+
}
|
|
220
|
+
async function bundleMigrationHandler(root, workerDir, handlerPath, dialect) {
|
|
221
|
+
const tmpDir = join(root, ".void", "migration-handler");
|
|
222
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
223
|
+
const outDir = join(tmpDir, "out");
|
|
224
|
+
const isPostgresql = dialect === "postgresql";
|
|
225
|
+
const isMysql = dialect === "mysql";
|
|
226
|
+
await build({
|
|
227
|
+
root: tmpDir,
|
|
228
|
+
logLevel: "silent",
|
|
229
|
+
resolve: isPostgresql ? {
|
|
230
|
+
alias: {
|
|
231
|
+
"pg-cloudflare": resolvePgCloudflareWorkerdEntry(handlerPath),
|
|
232
|
+
"pg-pool": resolvePgCjsDependency(handlerPath, "pg-pool")
|
|
233
|
+
},
|
|
234
|
+
conditions: [
|
|
235
|
+
"workerd",
|
|
236
|
+
"worker",
|
|
237
|
+
"browser",
|
|
238
|
+
"module",
|
|
239
|
+
"import",
|
|
240
|
+
"default"
|
|
241
|
+
]
|
|
242
|
+
} : void 0,
|
|
243
|
+
ssr: isPostgresql ? {
|
|
244
|
+
noExternal: ["pg", "pg-cloudflare"],
|
|
245
|
+
resolve: { conditions: [
|
|
246
|
+
"workerd",
|
|
247
|
+
"worker",
|
|
248
|
+
"browser",
|
|
249
|
+
"module",
|
|
250
|
+
"import",
|
|
251
|
+
"default"
|
|
252
|
+
] }
|
|
253
|
+
} : isMysql ? { noExternal: ["mysql2"] } : void 0,
|
|
254
|
+
build: {
|
|
255
|
+
outDir,
|
|
256
|
+
emptyOutDir: true,
|
|
257
|
+
minify: false,
|
|
258
|
+
ssr: handlerPath,
|
|
259
|
+
target: "esnext",
|
|
260
|
+
rollupOptions: {
|
|
261
|
+
input: handlerPath,
|
|
262
|
+
external: [
|
|
263
|
+
/^cloudflare:/,
|
|
264
|
+
/^node:/,
|
|
265
|
+
/^pgpass$/
|
|
266
|
+
],
|
|
267
|
+
output: {
|
|
268
|
+
entryFileNames: "__void_migration_handler.mjs",
|
|
269
|
+
chunkFileNames: "__void_migration_handler-[hash].mjs"
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
if (isPostgresql) patchPgWorkerBundle(outDir);
|
|
275
|
+
for (const file of readdirSync(outDir)) copyFileSync(join(outDir, file), join(workerDir, file));
|
|
276
|
+
}
|
|
277
|
+
function patchPgWorkerBundle(outDir) {
|
|
278
|
+
for (const file of readdirSync(outDir)) {
|
|
279
|
+
if (!file.endsWith(".mjs") && !file.endsWith(".js")) continue;
|
|
280
|
+
const path = join(outDir, file);
|
|
281
|
+
const code = readFileSync(path, "utf-8");
|
|
282
|
+
const transformed = transformPgWorkerBundle(code, file, pgWorkerRequireShim());
|
|
283
|
+
if (transformed !== code) writeFileSync(path, transformed);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function createPgPackageRequire(fromPath) {
|
|
287
|
+
const fromRequire = createRequire(fromPath);
|
|
288
|
+
return createRequire(fromRequire.resolve("pg/package.json"));
|
|
289
|
+
}
|
|
290
|
+
function resolvePgCjsDependency(fromPath, specifier) {
|
|
291
|
+
return createPgPackageRequire(fromPath).resolve(specifier);
|
|
292
|
+
}
|
|
293
|
+
function resolvePgCloudflareWorkerdEntry(fromPath) {
|
|
294
|
+
const packageJsonPath = createPgPackageRequire(fromPath).resolve("pg-cloudflare/package.json");
|
|
295
|
+
return join(dirname(packageJsonPath), "dist", "index.js");
|
|
296
|
+
}
|
|
297
|
+
function pgWorkerRequireShim() {
|
|
298
|
+
return [
|
|
299
|
+
"import * as __voidNodeCrypto from \"node:crypto\";",
|
|
300
|
+
"import * as __voidNodeDns from \"node:dns\";",
|
|
301
|
+
"import * as __voidNodeEvents from \"node:events\";",
|
|
302
|
+
"import * as __voidNodeFs from \"node:fs\";",
|
|
303
|
+
"import * as __voidNodeNet from \"node:net\";",
|
|
304
|
+
"import * as __voidNodeTls from \"node:tls\";",
|
|
305
|
+
"import * as __voidNodeUtil from \"node:util\";",
|
|
306
|
+
"import * as __voidNodeUtilTypes from \"node:util/types\";",
|
|
307
|
+
"var __voidNodeRequireModules = {",
|
|
308
|
+
" crypto: __voidNodeCrypto,",
|
|
309
|
+
" dns: __voidNodeDns,",
|
|
310
|
+
" events: __voidNodeEvents,",
|
|
311
|
+
" fs: __voidNodeFs,",
|
|
312
|
+
" net: __voidNodeNet,",
|
|
313
|
+
" tls: __voidNodeTls,",
|
|
314
|
+
" util: __voidNodeUtil,",
|
|
315
|
+
" \"util/types\": __voidNodeUtilTypes,",
|
|
316
|
+
"};",
|
|
317
|
+
"var __require = (id) => {",
|
|
318
|
+
" if (id.startsWith(\"node:\")) id = id.slice(5);",
|
|
319
|
+
" const mod = __voidNodeRequireModules[id];",
|
|
320
|
+
" if (mod) return mod;",
|
|
321
|
+
" throw new Error(`Unsupported require(\"${id}\") in Void PG migration bundle`);",
|
|
322
|
+
"};"
|
|
323
|
+
].join("\n");
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Bundle a worker entry into a self-contained module.
|
|
327
|
+
*
|
|
328
|
+
* Used for frameworks whose build output has unbundled bare imports
|
|
329
|
+
* (e.g. SvelteKit's `_worker.js` imports from `@sveltejs/kit`).
|
|
330
|
+
*/
|
|
331
|
+
async function bundleEntry(root, entry) {
|
|
332
|
+
const tmpDir = join(root, ".void", "bundle");
|
|
333
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
334
|
+
const outDir = join(tmpDir, "out");
|
|
335
|
+
await build({
|
|
336
|
+
root: tmpDir,
|
|
337
|
+
logLevel: "silent",
|
|
338
|
+
build: {
|
|
339
|
+
outDir,
|
|
340
|
+
emptyOutDir: true,
|
|
341
|
+
lib: {
|
|
342
|
+
entry,
|
|
343
|
+
formats: ["es"],
|
|
344
|
+
fileName: "index"
|
|
345
|
+
},
|
|
346
|
+
rollupOptions: {
|
|
347
|
+
external: [/^cloudflare:/, /^node:/],
|
|
348
|
+
output: { banner: [
|
|
349
|
+
"const __noopCache = {",
|
|
350
|
+
" match() { return Promise.resolve(undefined); },",
|
|
351
|
+
" put() { return Promise.resolve(); },",
|
|
352
|
+
" delete() { return Promise.resolve(false); },",
|
|
353
|
+
"};",
|
|
354
|
+
"globalThis.caches = { default: __noopCache, open() { return Promise.resolve(__noopCache); } };"
|
|
355
|
+
].join("\n") }
|
|
356
|
+
},
|
|
357
|
+
minify: false,
|
|
358
|
+
target: "esnext"
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
return {
|
|
362
|
+
entryFilename: selectBundledEntryFilename(readdirSync(outDir)),
|
|
363
|
+
outputDir: outDir
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
/** Clean up the temporary directories created while generating a deploy wrapper. */
|
|
367
|
+
function cleanupWrapper(root) {
|
|
368
|
+
const voidDir = join(root, ".void");
|
|
369
|
+
for (const directory of [
|
|
370
|
+
"handlers",
|
|
371
|
+
"migration-handler",
|
|
372
|
+
"isr",
|
|
373
|
+
"bundle"
|
|
374
|
+
]) rmSync(join(voidDir, directory), {
|
|
375
|
+
recursive: true,
|
|
376
|
+
force: true
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
function generateWrapperSource(renamedEntry, handlersModule, features, migrations, migrationPlanBundle) {
|
|
380
|
+
const lines = [];
|
|
381
|
+
lines.push(WRAPPER_MARKER);
|
|
382
|
+
lines.push(`import original from "./${renamedEntry}";`);
|
|
383
|
+
if (features.isr) {
|
|
384
|
+
lines.push("import { handleFrameworkIsr } from \"./__void_isr.mjs\";");
|
|
385
|
+
lines.push(`const __voidIsrDeploymentId = ${JSON.stringify(crypto.randomUUID())};`);
|
|
386
|
+
lines.push(`const __voidIsrPolicy = ${JSON.stringify({
|
|
387
|
+
revalidate: features.isr.revalidate,
|
|
388
|
+
queryAllowlist: features.isr.queryAllowlist
|
|
389
|
+
})};`);
|
|
390
|
+
}
|
|
391
|
+
if (features.crons.length > 0) lines.push(`import { handleScheduled } from "./${handlersModule}";`);
|
|
392
|
+
if (features.queues.length > 0) lines.push(`import { handleQueue, handleQueueRelay } from "./${handlersModule}";`);
|
|
393
|
+
if (migrations && migrations.length > 0) {
|
|
394
|
+
lines.push(`import { createMigrationHandler } from "./__void_migration_handler.mjs";`);
|
|
395
|
+
lines.push(`const __migrations = ${JSON.stringify(migrations)};`);
|
|
396
|
+
lines.push(`const __migrationPlanBundle = ${JSON.stringify(migrationPlanBundle)};`);
|
|
397
|
+
}
|
|
398
|
+
lines.push("");
|
|
399
|
+
const needsFetchWrap = features.readiness || features.crons.length > 0 || features.queues.length > 0 || migrations && migrations.length > 0 || !!features.isr;
|
|
400
|
+
if (needsFetchWrap) {
|
|
401
|
+
lines.push("async function wrappedFetch(request, env, ctx) {");
|
|
402
|
+
lines.push(" const url = new URL(request.url);");
|
|
403
|
+
if (features.isr) {
|
|
404
|
+
lines.push(" env.__VOID_ISR_DEPLOYMENT_ID ??= __voidIsrDeploymentId;");
|
|
405
|
+
lines.push(" env.__VOID_ISR_HOST = url.hostname;");
|
|
406
|
+
}
|
|
407
|
+
if (features.readiness || features.crons.length > 0 || features.queues.length > 0 || features.hasMigrations) {
|
|
408
|
+
lines.push(" if (request.method === \"GET\" && url.pathname === \"/__void/ready\") {");
|
|
409
|
+
lines.push(" if (typeof original?.fetch !== \"function\") {");
|
|
410
|
+
lines.push(" return Response.json({ ready: false, error: \"worker entry has no fetch handler\" }, { status: 503, headers: { \"cache-control\": \"no-store\" } });");
|
|
411
|
+
lines.push(" }");
|
|
412
|
+
lines.push(` return Response.json({ ready: true${features.versionMetadataBinding ? `, versionId: env[${JSON.stringify(features.versionMetadataBinding)}]?.id` : ""} }${features.versionMetadataBinding ? ", { headers: { \"cache-control\": \"no-store\" } }" : ""});`);
|
|
413
|
+
lines.push(" }");
|
|
414
|
+
}
|
|
415
|
+
if (features.queues.length > 0) {
|
|
416
|
+
lines.push(" if (request.method === \"POST\" && url.pathname === \"/__void/queue\") {");
|
|
417
|
+
lines.push(" const expected = env?.__VOID_PROXY_TOKEN;");
|
|
418
|
+
lines.push(" if (!expected || request.headers.get(\"x-void-internal\") !== expected) {");
|
|
419
|
+
lines.push(" return Response.json({ error: \"unauthorized\" }, { status: 401 });");
|
|
420
|
+
lines.push(" }");
|
|
421
|
+
lines.push(" try {");
|
|
422
|
+
lines.push(" return Response.json(await handleQueueRelay(await request.json(), env, ctx));");
|
|
423
|
+
lines.push(" } catch {");
|
|
424
|
+
lines.push(" return Response.json({ error: \"Invalid queue payload\" }, { status: 400 });");
|
|
425
|
+
lines.push(" }");
|
|
426
|
+
lines.push(" }");
|
|
427
|
+
}
|
|
428
|
+
if (migrations && migrations.length > 0) {
|
|
429
|
+
lines.push(" if (request.method === \"POST\" && url.pathname === \"/__void/migrate\") {");
|
|
430
|
+
lines.push(" const handler = createMigrationHandler(env, __migrations);");
|
|
431
|
+
lines.push(" return handler(request);");
|
|
432
|
+
lines.push(" }");
|
|
433
|
+
lines.push(" if (request.method === \"GET\" && url.pathname === \"/__void/migrate/capabilities\") {");
|
|
434
|
+
lines.push(" const handler = createMigrationHandler(env, __migrations, undefined, __migrationPlanBundle);");
|
|
435
|
+
lines.push(" return handler(request);");
|
|
436
|
+
lines.push(" }");
|
|
437
|
+
lines.push(" if (request.method === \"POST\" && url.pathname === \"/__void/migrate/app\") {");
|
|
438
|
+
lines.push(" const handler = createMigrationHandler(env, __migrations, undefined, __migrationPlanBundle);");
|
|
439
|
+
lines.push(" return handler(request);");
|
|
440
|
+
lines.push(" }");
|
|
441
|
+
lines.push(" if (request.method === \"GET\" && url.pathname.startsWith(\"/__void/migrate/app/attempt/\")) {");
|
|
442
|
+
lines.push(" const handler = createMigrationHandler(env, __migrations, undefined, __migrationPlanBundle);");
|
|
443
|
+
lines.push(" return handler(request);");
|
|
444
|
+
lines.push(" }");
|
|
445
|
+
}
|
|
446
|
+
if (features.crons.length > 0) {
|
|
447
|
+
lines.push(" if (request.method === \"POST\" && url.pathname === \"/__void/scheduled\") {");
|
|
448
|
+
lines.push(" const __token = request.headers.get(\"x-void-internal\");");
|
|
449
|
+
lines.push(" const __expected = env?.__VOID_PROXY_TOKEN || env?.CRON_SECRET;");
|
|
450
|
+
lines.push(" if (!__expected || __token !== __expected) {");
|
|
451
|
+
lines.push(" return new Response(JSON.stringify({ error: 'unauthorized' }), {");
|
|
452
|
+
lines.push(" status: 401,");
|
|
453
|
+
lines.push(" headers: { \"Content-Type\": \"application/json\" },");
|
|
454
|
+
lines.push(" });");
|
|
455
|
+
lines.push(" }");
|
|
456
|
+
lines.push(" try {");
|
|
457
|
+
lines.push(" const { cron, scheduledTime } = await request.json();");
|
|
458
|
+
lines.push(" const controller = { cron, scheduledTime, noRetry() {} };");
|
|
459
|
+
lines.push(" await handleScheduled(controller, env, ctx);");
|
|
460
|
+
lines.push(" return new Response(JSON.stringify({ ok: true }), {");
|
|
461
|
+
lines.push(" headers: { \"Content-Type\": \"application/json\" },");
|
|
462
|
+
lines.push(" });");
|
|
463
|
+
lines.push(" } catch (err) {");
|
|
464
|
+
lines.push(" return new Response(JSON.stringify({ error: String(err) }), {");
|
|
465
|
+
lines.push(" status: 500,");
|
|
466
|
+
lines.push(" headers: { \"Content-Type\": \"application/json\" },");
|
|
467
|
+
lines.push(" });");
|
|
468
|
+
lines.push(" }");
|
|
469
|
+
lines.push(" }");
|
|
470
|
+
}
|
|
471
|
+
if (features.isr) {
|
|
472
|
+
lines.push(" if (env.ISR_CACHE && request.headers.get(\"upgrade\")?.toLowerCase() !== \"websocket\") {");
|
|
473
|
+
lines.push(" const cached = await handleFrameworkIsr(request, env, ctx, { ...__voidIsrPolicy, deploymentId: __voidIsrDeploymentId }, (req) => original.fetch(req, env, ctx));");
|
|
474
|
+
lines.push(" if (cached) return cached;");
|
|
475
|
+
lines.push(" }");
|
|
476
|
+
}
|
|
477
|
+
lines.push(" return original.fetch(request, env, ctx);");
|
|
478
|
+
lines.push("}");
|
|
479
|
+
lines.push("");
|
|
480
|
+
}
|
|
481
|
+
lines.push("export default {");
|
|
482
|
+
if (needsFetchWrap) lines.push(" fetch: wrappedFetch,");
|
|
483
|
+
else lines.push(" fetch: original.fetch,");
|
|
484
|
+
if (features.crons.length > 0) lines.push(features.isr ? " scheduled(controller, env, ctx) { env.__VOID_ISR_DEPLOYMENT_ID ??= __voidIsrDeploymentId; return handleScheduled(controller, env, ctx); }," : " scheduled: handleScheduled,");
|
|
485
|
+
if (features.queues.length > 0) lines.push(features.isr ? " queue(batch, env, ctx) { env.__VOID_ISR_DEPLOYMENT_ID ??= __voidIsrDeploymentId; return handleQueue(batch, env, ctx); }," : " queue: handleQueue,");
|
|
486
|
+
lines.push("};");
|
|
487
|
+
return lines.join("\n");
|
|
488
|
+
}
|
|
489
|
+
function generateHandlersSource(root, features) {
|
|
490
|
+
const lines = [];
|
|
491
|
+
const paths = readProjectPaths(root);
|
|
492
|
+
if (features.crons.length > 0) {
|
|
493
|
+
for (let i = 0; i < features.crons.length; i++) {
|
|
494
|
+
const absPath = join(paths.cronsDir, features.crons[i].filePath);
|
|
495
|
+
lines.push(`import cronHandler${i} from "${absPath}";`);
|
|
496
|
+
}
|
|
497
|
+
lines.push("");
|
|
498
|
+
lines.push("export async function handleScheduled(controller, env, ctx) {");
|
|
499
|
+
lines.push(" const tasks = [];");
|
|
500
|
+
for (let i = 0; i < features.crons.length; i++) {
|
|
501
|
+
lines.push(` if (${JSON.stringify(features.crons[i].crons)}.includes(controller.cron)) {`);
|
|
502
|
+
lines.push(" tasks.push((async () => {");
|
|
503
|
+
lines.push(` if (typeof cronHandler${i} === "function") {`);
|
|
504
|
+
lines.push(` await cronHandler${i}(controller, env, ctx);`);
|
|
505
|
+
lines.push(` } else if (cronHandler${i} && typeof cronHandler${i}.default === "function") {`);
|
|
506
|
+
lines.push(` await cronHandler${i}.default(controller, env, ctx);`);
|
|
507
|
+
lines.push(" }");
|
|
508
|
+
lines.push(" })());");
|
|
509
|
+
lines.push(" }");
|
|
510
|
+
}
|
|
511
|
+
lines.push(" const results = await Promise.allSettled(tasks);");
|
|
512
|
+
lines.push(" const errors = results.filter(result => result.status === \"rejected\").map(result => result.reason);");
|
|
513
|
+
lines.push(" if (errors.length) throw new AggregateError(errors, \"Scheduled jobs failed\");");
|
|
514
|
+
lines.push("}");
|
|
515
|
+
}
|
|
516
|
+
if (features.queues.length > 0) {
|
|
517
|
+
lines.push("");
|
|
518
|
+
for (let i = 0; i < features.queues.length; i++) {
|
|
519
|
+
const absPath = join(paths.queuesDir, features.queues[i].filePath);
|
|
520
|
+
lines.push(`import queueHandler${i} from "${absPath}";`);
|
|
521
|
+
}
|
|
522
|
+
lines.push("");
|
|
523
|
+
lines.push(generateQueueResolverSource(features.queues.map((queue) => queue.name)));
|
|
524
|
+
lines.push("");
|
|
525
|
+
lines.push("export async function handleQueue(batch, env, ctx) {");
|
|
526
|
+
lines.push(" const queueSuffix = batch.queue;");
|
|
527
|
+
lines.push(" const __queueIndex = __resolveQueueIndex(queueSuffix);");
|
|
528
|
+
lines.push(" if (__queueIndex === -1) {");
|
|
529
|
+
lines.push(" batch.retryAll();");
|
|
530
|
+
lines.push(" throw new Error(`No unambiguous queue handler matches \"${String(queueSuffix)}\"`);");
|
|
531
|
+
lines.push(" }");
|
|
532
|
+
lines.push(" switch (__queueIndex) {");
|
|
533
|
+
for (let i = 0; i < features.queues.length; i++) {
|
|
534
|
+
lines.push(` case ${i}:`);
|
|
535
|
+
lines.push(` if (typeof queueHandler${i} === "function") {`);
|
|
536
|
+
lines.push(` await queueHandler${i}(batch, env);`);
|
|
537
|
+
lines.push(` } else if (queueHandler${i} && typeof queueHandler${i}.default === "function") {`);
|
|
538
|
+
lines.push(` await queueHandler${i}.default(batch, env);`);
|
|
539
|
+
lines.push(` }`);
|
|
540
|
+
lines.push(` return;`);
|
|
541
|
+
}
|
|
542
|
+
lines.push(" }");
|
|
543
|
+
lines.push("}");
|
|
544
|
+
lines.push(`
|
|
545
|
+
export async function handleQueueRelay(payload, env, ctx) {
|
|
546
|
+
if (!payload || typeof payload.queue !== "string" || !Array.isArray(payload.messages) ||
|
|
547
|
+
payload.messages.some(m => !m || typeof m.id !== "string" || typeof m.timestamp !== "string" || !Number.isFinite(Date.parse(m.timestamp)))) {
|
|
548
|
+
throw new Error("Invalid queue payload");
|
|
549
|
+
}
|
|
550
|
+
const decisions = new Map();
|
|
551
|
+
const messages = payload.messages.map(m => {
|
|
552
|
+
const decide = (decision) => { if (!decisions.has(m.id)) decisions.set(m.id, decision); };
|
|
553
|
+
return {
|
|
554
|
+
id: m.id, timestamp: new Date(m.timestamp), body: m.body, attempts: m.attempts,
|
|
555
|
+
ack() { decide({ action: "ack" }); },
|
|
556
|
+
retry(options) { decide({ action: "retry", ...(options?.delaySeconds != null ? { delaySeconds: options.delaySeconds } : {}) }); },
|
|
557
|
+
};
|
|
558
|
+
});
|
|
559
|
+
const batch = {
|
|
560
|
+
queue: payload.queue, messages,
|
|
561
|
+
ackAll() { for (const message of messages) message.ack(); },
|
|
562
|
+
retryAll(options) { for (const message of messages) message.retry(options); },
|
|
563
|
+
};
|
|
564
|
+
let ok = true;
|
|
565
|
+
try { await handleQueue(batch, env, ctx); } catch { ok = false; }
|
|
566
|
+
return { ok, ...(decisions.size ? { decisions: Object.fromEntries(decisions) } : {}) };
|
|
567
|
+
}`);
|
|
568
|
+
}
|
|
569
|
+
return lines.join("\n");
|
|
570
|
+
}
|
|
571
|
+
//#endregion
|
|
572
|
+
//#region src/cli/deploy-helpers.ts
|
|
573
|
+
const log = {
|
|
574
|
+
error: (message) => log$1.error(formatCloudflareUserMessage(message)),
|
|
575
|
+
info: (message) => log$1.info(formatCloudflareUserMessage(message)),
|
|
576
|
+
message: (message) => log$1.message(formatCloudflareUserMessage(message)),
|
|
577
|
+
step: (message) => log$1.step(formatCloudflareUserMessage(message)),
|
|
578
|
+
warn: (message) => log$1.warn(formatCloudflareUserMessage(message))
|
|
579
|
+
};
|
|
580
|
+
var DeployEventError = class extends Error {};
|
|
581
|
+
async function generateDeployWrapperIfNeeded(root, workerDir, workerMain, features) {
|
|
582
|
+
if (!needsWrapper(features)) return false;
|
|
583
|
+
await generateWrapper(workerDir, workerMain, root, features);
|
|
584
|
+
return true;
|
|
585
|
+
}
|
|
586
|
+
const EMPTY_BINDINGS = {
|
|
587
|
+
needsD1: false,
|
|
588
|
+
needsKV: false,
|
|
589
|
+
needsR2: false,
|
|
590
|
+
needsAuth: false,
|
|
591
|
+
needsQueues: false,
|
|
592
|
+
needsAI: false,
|
|
593
|
+
needsSandbox: false
|
|
594
|
+
};
|
|
595
|
+
function readLiveBindingConfigFromDeployment(deployment) {
|
|
596
|
+
if (!deployment?.manifest) return;
|
|
597
|
+
let manifest;
|
|
598
|
+
try {
|
|
599
|
+
manifest = JSON.parse(deployment.manifest);
|
|
600
|
+
} catch {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
if (typeof manifest.live?.bindingName !== "string" || typeof manifest.live.className !== "string") return;
|
|
604
|
+
return {
|
|
605
|
+
bindingName: manifest.live.bindingName,
|
|
606
|
+
className: manifest.live.className
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
async function ensurePreviouslyDeployedLiveBindingConfig(root, client, projectId, cliLog) {
|
|
610
|
+
if (readExistingLiveBindingConfig(root)) return false;
|
|
611
|
+
const { deployments, activeDeploymentId } = await client.listDeployments(projectId);
|
|
612
|
+
const live = readLiveBindingConfigFromDeployment(deployments.find((deployment) => deployment.id === activeDeploymentId));
|
|
613
|
+
if (!live) return false;
|
|
614
|
+
const synced = syncWranglerBindings(root, {
|
|
615
|
+
...EMPTY_BINDINGS,
|
|
616
|
+
needsLive: true
|
|
617
|
+
}, { live });
|
|
618
|
+
if (synced) cliLog?.info("live_binding_restored", {
|
|
619
|
+
bindingName: live.bindingName,
|
|
620
|
+
className: live.className
|
|
621
|
+
});
|
|
622
|
+
return synced;
|
|
623
|
+
}
|
|
624
|
+
async function resolveDeployLiveBindingConfig(root, client, projectId, opts) {
|
|
625
|
+
if (opts.isFrameworkMode || !opts.needsLive) return;
|
|
626
|
+
await ensurePreviouslyDeployedLiveBindingConfig(root, client, projectId, opts.cliLog);
|
|
627
|
+
return resolveLiveBindingConfig(root, {
|
|
628
|
+
reservedBindings: opts.webSockets.map((route) => route.bindingName),
|
|
629
|
+
reservedClasses: [...opts.webSockets.map((route) => route.className), opts.sandboxClassName]
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
function getExitCode(error) {
|
|
633
|
+
if (typeof error === "object" && error !== null && "status" in error && typeof error.status === "number") return error.status;
|
|
634
|
+
return 1;
|
|
635
|
+
}
|
|
636
|
+
function resolveDeployWranglerCompat(root, config, buildOutputDir, requirements) {
|
|
637
|
+
if (isNodeTarget(config.target)) return;
|
|
638
|
+
const compat = ensureWranglerCompat(root, config, buildOutputDir);
|
|
639
|
+
const compatibilityFlags = normalizeWranglerCompatibilityFlags(compat.compatibilityFlags, { needsNodejsCompat: requirements?.authEnabled === true || requirements?.dialect !== void 0 && requirements.dialect !== "sqlite" || requirements?.needsSandbox === true });
|
|
640
|
+
return compatibilityFlags === compat.compatibilityFlags ? compat : {
|
|
641
|
+
...compat,
|
|
642
|
+
compatibilityFlags
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
function resolveDeployWorkerMain(config, buildOutputDir) {
|
|
646
|
+
if (isNodeTarget(config.target)) return;
|
|
647
|
+
try {
|
|
648
|
+
const wrangler = JSON.parse(readFileSync(join(buildOutputDir, "wrangler.json"), "utf-8"));
|
|
649
|
+
return typeof wrangler.main === "string" && wrangler.main.length > 0 ? wrangler.main : void 0;
|
|
650
|
+
} catch {
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
function assertWorkerMainUploaded(workerFiles, workerMain) {
|
|
655
|
+
if (!workerMain || workerFiles.size === 0) return;
|
|
656
|
+
if (!workerFiles.has(workerMain)) throw new Error(`deploy: Worker entry '${workerMain}' from wrangler.json was not found in uploaded worker modules.`);
|
|
657
|
+
}
|
|
658
|
+
function assertProjectDeployAccess(project) {
|
|
659
|
+
if (project?.role === "reader") throw new Error(`deploy: You have reader access to "${project.slug}" and cannot deploy. Ask a project admin or owner to grant collaborator access, or choose another project with \`--project <slug>\`.`);
|
|
660
|
+
}
|
|
661
|
+
async function promptDeployProjectConfig(root, client, cliLog) {
|
|
662
|
+
const projects = await client.listProjectChoices();
|
|
663
|
+
const link = shouldLinkDeployProject(root, client.apiUrl);
|
|
664
|
+
const deployableProjects = projects.filter((project) => project.role !== "reader");
|
|
665
|
+
if (deployableProjects.length !== projects.length) log.warn("Projects with reader access are hidden from deploy selection. Ask a project admin or owner to grant collaborator access to deploy to them.");
|
|
666
|
+
if (await promptProjectSetupAction("Set up a Void project for deploy:", {
|
|
667
|
+
includeLink: deployableProjects.length > 0,
|
|
668
|
+
...link ? {} : { link: false }
|
|
669
|
+
}) === "create") return promptAndCreateProject(root, client, { link });
|
|
670
|
+
const result = await promptProjectSelection(deployableProjects, "Select a project to deploy to:", root, {
|
|
671
|
+
confirmExisting: true,
|
|
672
|
+
includeCreate: false,
|
|
673
|
+
...link ? {} : { link: false }
|
|
674
|
+
});
|
|
675
|
+
if (result.action === "create") throw new Error("Unexpected project selection result.");
|
|
676
|
+
const config = {
|
|
677
|
+
projectId: result.project.id,
|
|
678
|
+
slug: result.project.slug
|
|
679
|
+
};
|
|
680
|
+
if (link) {
|
|
681
|
+
writeProjectConfig(root, attachSelectedPlatform(config, client.apiUrl));
|
|
682
|
+
log.step(`Linked to ${import_picocolors.default.blue(config.slug)}`);
|
|
683
|
+
cliLog?.info("project_linked", {
|
|
684
|
+
projectId: config.projectId,
|
|
685
|
+
slug: config.slug
|
|
686
|
+
});
|
|
687
|
+
} else log.step(`Using ${import_picocolors.default.blue(config.slug)} for this deployment. Saved destination unchanged.`);
|
|
688
|
+
return config;
|
|
689
|
+
}
|
|
690
|
+
function resolveStaticBuildCommand(preset, buildOverride, hasExplicitDir = false) {
|
|
691
|
+
if (hasExplicitDir) return null;
|
|
692
|
+
return buildOverride ?? preset.buildCommand;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Resolve the build command for a FULL (Class A / Void app) deploy.
|
|
696
|
+
*
|
|
697
|
+
* `inference.build` wins when set, matching `runFrameworkDeploy` and the static
|
|
698
|
+
* path's `resolveStaticBuildCommand`, so an app that needs more than one build
|
|
699
|
+
* step (an SSG that emits the site, then Void's build packaging it via
|
|
700
|
+
* `publicDir` alongside the worker) can express it.
|
|
701
|
+
*
|
|
702
|
+
* The two returned strings are deliberately different for the FALLBACK: `label`
|
|
703
|
+
* goes to the user (`formatProjectCommand` rewrites `vite` → `vp` without
|
|
704
|
+
* expanding an absolute bin path), `command` is what actually runs
|
|
705
|
+
* (`resolveProjectCommand` expands to the local bin). A configured command is
|
|
706
|
+
* the user's own text and is used verbatim for both, so what is printed is
|
|
707
|
+
* exactly what is executed.
|
|
708
|
+
*/
|
|
709
|
+
function resolveFullDeployBuildCommand(root, buildOverride) {
|
|
710
|
+
if (buildOverride) return {
|
|
711
|
+
label: buildOverride,
|
|
712
|
+
command: buildOverride
|
|
713
|
+
};
|
|
714
|
+
return {
|
|
715
|
+
label: formatProjectCommand(root, "vite build"),
|
|
716
|
+
command: resolveProjectCommand(root, "vite build")
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Whether the built worker's `__voidCollectPrerenderPaths` can return anything
|
|
721
|
+
* at all. When it cannot, booting it is pure cost and the deploy skips it.
|
|
722
|
+
*
|
|
723
|
+
* These three are exactly — and only — the sources `compilePrerenderCollector`
|
|
724
|
+
* (`router/compile.ts`) emits a `paths.push(...)` for:
|
|
725
|
+
* 1. `scan.pages.pages` — static patterns + dynamic pages' `getPrerenderPaths()`
|
|
726
|
+
* 2. the custom SSR entry's top-level `getPrerenderPaths()` (`ssr` argument)
|
|
727
|
+
* 3. `options.explicitPrerenderPaths` — void.config.ts `routing.prerender`
|
|
728
|
+
* With none of them present the collector is a generated function whose body
|
|
729
|
+
* returns `[...new Set([])]`, so skipping is result-identical.
|
|
730
|
+
*
|
|
731
|
+
* Skipping matters beyond speed. `collectPrerenderPaths` boots the worker in
|
|
732
|
+
* Miniflare without remote production secrets, and the generated fetch handler
|
|
733
|
+
* validates `env.ts` at boot. An app with nothing to prerender should not fail
|
|
734
|
+
* merely because that unnecessary worker boot cannot access the remote store.
|
|
735
|
+
*/
|
|
736
|
+
function hasPrerenderSources(inputs) {
|
|
737
|
+
return inputs.hasPages || inputs.hasSsrEntry || (inputs.explicitPrerenderPaths?.length ?? 0) > 0;
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Whether the project needs a worker for something other than its pages.
|
|
741
|
+
*
|
|
742
|
+
* An `output: "static"` project whose pages all prerender takes the static-site
|
|
743
|
+
* path, which uploads assets and NO worker script. Every worker surface that is
|
|
744
|
+
* not a page has to be listed here, because a missing one is not a failed
|
|
745
|
+
* deploy — it is a successful deploy with that feature silently absent.
|
|
746
|
+
*
|
|
747
|
+
* `email` belongs here for the same reason `hasRoutes` does: an `email/`
|
|
748
|
+
* handler compiles to the worker's `email()` export (`router/compile.ts`), and
|
|
749
|
+
* the platform dispatches inbound mail through that export. With no worker, the
|
|
750
|
+
* mail bounces at the proxy with `project has no email handler`.
|
|
751
|
+
*
|
|
752
|
+
* Schedules and queues are deliberately NOT folded in. They are checked at the
|
|
753
|
+
* call site against their own resolved lists, not against a scan flag.
|
|
754
|
+
*/
|
|
755
|
+
function hasNonPageBackend(inputs) {
|
|
756
|
+
return inputs.hasRoutes || inputs.hasMiddleware || inputs.hasWebSockets || inputs.hasEmail || inputs.authEnabled || inputs.hasLive;
|
|
757
|
+
}
|
|
758
|
+
function resolveStaticDeployPreset(root, voidConfig, options) {
|
|
759
|
+
if (options?.dir) return detectPreset(root, {
|
|
760
|
+
dir: options.dir,
|
|
761
|
+
spa: options.spa
|
|
762
|
+
});
|
|
763
|
+
const appType = voidConfig.inference?.appType;
|
|
764
|
+
if (appType === "static" || appType === "spa") {
|
|
765
|
+
const outputDir = voidConfig.inference?.outputDir;
|
|
766
|
+
const preset = {
|
|
767
|
+
buildCommand: null,
|
|
768
|
+
outputDir: outputDir ? resolve(root, outputDir) : join(root, "dist"),
|
|
769
|
+
appType
|
|
770
|
+
};
|
|
771
|
+
const detectedPreset = detectPreset(root, { spa: options?.spa });
|
|
772
|
+
if (detectedPreset) {
|
|
773
|
+
preset.buildCommand = detectedPreset.buildCommand;
|
|
774
|
+
if (!outputDir) preset.outputDir = detectedPreset.outputDir;
|
|
775
|
+
}
|
|
776
|
+
return preset;
|
|
777
|
+
}
|
|
778
|
+
return null;
|
|
779
|
+
}
|
|
780
|
+
async function createProjectFromSlug(root, client, slug, cliLog) {
|
|
781
|
+
log.step(`Creating project ${import_picocolors.default.blue(slug)}...`);
|
|
782
|
+
cliLog?.info("project_create_start", { slug });
|
|
783
|
+
const project = await client.createProject(slug);
|
|
784
|
+
const config = {
|
|
785
|
+
projectId: project.id,
|
|
786
|
+
slug: project.slug
|
|
787
|
+
};
|
|
788
|
+
if (shouldLinkDeployProject(root, client.apiUrl)) writeProjectConfig(root, attachSelectedPlatform(config, client.apiUrl));
|
|
789
|
+
log.step(`Project created: ${import_picocolors.default.blue(config.slug)}`);
|
|
790
|
+
reportProjectCreationWarnings(project);
|
|
791
|
+
cliLog?.info("project_create_success", {
|
|
792
|
+
projectId: config.projectId,
|
|
793
|
+
slug: config.slug
|
|
794
|
+
});
|
|
795
|
+
return config;
|
|
796
|
+
}
|
|
797
|
+
function getGitCommit(root) {
|
|
798
|
+
try {
|
|
799
|
+
const githubSha = process.env.GITHUB_SHA?.trim();
|
|
800
|
+
if (githubSha && /^[0-9a-f]{7,64}$/i.test(githubSha)) return githubSha;
|
|
801
|
+
const gitEnv = buildEnv();
|
|
802
|
+
execSync("git rev-parse --is-inside-work-tree", {
|
|
803
|
+
cwd: root,
|
|
804
|
+
stdio: "ignore",
|
|
805
|
+
env: gitEnv
|
|
806
|
+
});
|
|
807
|
+
if (execSync("git status --porcelain", {
|
|
808
|
+
cwd: root,
|
|
809
|
+
encoding: "utf-8",
|
|
810
|
+
env: gitEnv
|
|
811
|
+
}).trim() !== "") return null;
|
|
812
|
+
return execSync("git rev-parse HEAD", {
|
|
813
|
+
cwd: root,
|
|
814
|
+
encoding: "utf-8",
|
|
815
|
+
env: gitEnv
|
|
816
|
+
}).trim();
|
|
817
|
+
} catch {
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Check journal coherence and validate the checked-in migrations of a managed
|
|
823
|
+
* deploy. Logs and exits on failure; returns `undefined` when there are none.
|
|
824
|
+
*/
|
|
825
|
+
function validateDeployMigrations(migrationsDir, rawMigrations) {
|
|
826
|
+
try {
|
|
827
|
+
assertJournalCoherence(migrationsDir, rawMigrations);
|
|
828
|
+
} catch (err) {
|
|
829
|
+
log.error(getErrorMessage(err));
|
|
830
|
+
process.exit(1);
|
|
831
|
+
}
|
|
832
|
+
if (rawMigrations.length === 0) return;
|
|
833
|
+
try {
|
|
834
|
+
const validated = validateMigrations(rawMigrations);
|
|
835
|
+
log.info(`Found ${formatCount(validated.length, "migration")}`);
|
|
836
|
+
return validated;
|
|
837
|
+
} catch (err) {
|
|
838
|
+
log.error(getErrorMessage(err));
|
|
839
|
+
process.exit(1);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Ensure a managed external SQL project has Hyperdrive, prompting for the
|
|
844
|
+
* connection string when it is missing.
|
|
845
|
+
*/
|
|
846
|
+
async function ensureManagedHyperdrive(client, projectId, dialect) {
|
|
847
|
+
let resources = null;
|
|
848
|
+
try {
|
|
849
|
+
resources = await client.getResources(projectId);
|
|
850
|
+
} catch {}
|
|
851
|
+
if (!resources || resources.some((resource) => resource.type === "hyperdrive")) return;
|
|
852
|
+
const connectionString = await text({
|
|
853
|
+
message: `Your project uses ${dialect === "mysql" ? "MySQL" : "PostgreSQL"}. Enter your connection string:`,
|
|
854
|
+
placeholder: dialect === "mysql" ? "mysql://user:pass@host:3306/dbname" : "postgresql://user:pass@host:5432/dbname",
|
|
855
|
+
validate(value) {
|
|
856
|
+
if (!(dialect === "mysql" ? value?.startsWith("mysql://") : value?.startsWith("postgres://") || value?.startsWith("postgresql://"))) return dialect === "mysql" ? "Must start with \"mysql://\"" : "Must start with \"postgres://\" or \"postgresql://\"";
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
if (isCancel(connectionString)) {
|
|
860
|
+
log.info("Deploy cancelled.");
|
|
861
|
+
process.exit(0);
|
|
862
|
+
}
|
|
863
|
+
await client.provisionDatabase(projectId, connectionString);
|
|
864
|
+
log.step("Hyperdrive provisioned.");
|
|
865
|
+
}
|
|
866
|
+
/** Convert scanned cron jobs and queues into deploy-manifest entries. */
|
|
867
|
+
function toManifestSchedulesAndQueues(jobs, queues) {
|
|
868
|
+
return {
|
|
869
|
+
schedules: jobs.flatMap((job) => job.crons.map((cron) => ({
|
|
870
|
+
name: job.name,
|
|
871
|
+
cron
|
|
872
|
+
}))),
|
|
873
|
+
queues: queues.map((q) => ({
|
|
874
|
+
name: q.name,
|
|
875
|
+
bindingName: q.bindingName,
|
|
876
|
+
...q.maxBatchSize != null && { maxBatchSize: q.maxBatchSize },
|
|
877
|
+
...q.maxBatchTimeout != null && { maxBatchTimeout: q.maxBatchTimeout }
|
|
878
|
+
}))
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* If the project has an `env.ts`, validate its schema against the union of
|
|
883
|
+
* production env files and remote secret names. Hard-error before upload so
|
|
884
|
+
* deploys never ship missing required env vars.
|
|
885
|
+
*/
|
|
886
|
+
async function assertEnvSchemaValid(root, client, projectId) {
|
|
887
|
+
if (findEnvFile(root) == null) return;
|
|
888
|
+
const remoteSecrets = await fetchRemoteSecretNames(client, projectId);
|
|
889
|
+
const { hasSchema, report } = await validateProdEnv(root, {
|
|
890
|
+
productionOnly: true,
|
|
891
|
+
remoteSecrets
|
|
892
|
+
});
|
|
893
|
+
if (!hasSchema || report.valid) return;
|
|
894
|
+
const formatted = formatEnvReport(report);
|
|
895
|
+
log.error(`Env validation failed:\n${formatted}`);
|
|
896
|
+
if (report.missing.length > 0) log.info("Upload missing server values with `void secret put <NAME>`; set VITE_* values in the build shell or schema defaults.");
|
|
897
|
+
throw new DeployEventError("deploy: Aborting because env.ts schema is not satisfied.");
|
|
898
|
+
}
|
|
899
|
+
//#endregion
|
|
900
|
+
//#region src/cli/deploy-drift.ts
|
|
901
|
+
const deployDriftCheckDir = join(".void", "deploy-drift-check");
|
|
902
|
+
/** Top-level entries never copied into the isolated drift-check project. */
|
|
903
|
+
const DRIFT_PROJECT_SKIPPED_ENTRIES = /* @__PURE__ */ new Set([
|
|
904
|
+
".git",
|
|
905
|
+
".void",
|
|
906
|
+
"node_modules",
|
|
907
|
+
"dist",
|
|
908
|
+
"build",
|
|
909
|
+
".wrangler",
|
|
910
|
+
".turbo",
|
|
911
|
+
".next",
|
|
912
|
+
".svelte-kit",
|
|
913
|
+
".output"
|
|
914
|
+
]);
|
|
915
|
+
function assertDeploySchemaDriftFree(root, dialect) {
|
|
916
|
+
try {
|
|
917
|
+
if (!hasDeploySchemaDrift(root, dialect)) return;
|
|
918
|
+
} catch (error) {
|
|
919
|
+
log.error(getErrorMessage(error));
|
|
920
|
+
process.exit(1);
|
|
921
|
+
}
|
|
922
|
+
log.error(getDeploySchemaDriftMessage(root));
|
|
923
|
+
process.exit(1);
|
|
924
|
+
}
|
|
925
|
+
function hasDeploySchemaDrift(root, dialect) {
|
|
926
|
+
const authSchemaPath = join(root, ".void", "better-auth-schema.ts");
|
|
927
|
+
if (!discoverSchema(root, readProjectPaths(root)) && !existsSync(authSchemaPath)) return false;
|
|
928
|
+
const driftRoot = mkDeployDriftCheckRoot(root);
|
|
929
|
+
const driftProjectRoot = join(driftRoot, "project");
|
|
930
|
+
createDeployDriftProject(root, driftProjectRoot);
|
|
931
|
+
try {
|
|
932
|
+
const migrationsDir = readProjectPaths(driftProjectRoot).migrationsDir;
|
|
933
|
+
const before = snapshotDir(migrationsDir);
|
|
934
|
+
const driftAuthSchemaPath = join(driftProjectRoot, ".void", "better-auth-schema.ts");
|
|
935
|
+
const output = runDrizzleKitGenerate(driftProjectRoot, writeDrizzleConfig(driftProjectRoot, PLACEHOLDER_DB_URL[dialect], dialect, { additionalSchemas: existsSync(driftAuthSchemaPath) ? [driftAuthSchemaPath] : void 0 }));
|
|
936
|
+
const snapshotsEqual = migrationSnapshotsEqual(before, snapshotDir(migrationsDir));
|
|
937
|
+
if (snapshotsEqual && !output.includes("No schema changes, nothing to migrate")) throw new Error(output || "drizzle-kit generate finished without reporting schema status.");
|
|
938
|
+
return !snapshotsEqual;
|
|
939
|
+
} catch (error) {
|
|
940
|
+
throw new Error(formatDeploySchemaDriftCheckError(error));
|
|
941
|
+
} finally {
|
|
942
|
+
rmSync(driftRoot, {
|
|
943
|
+
recursive: true,
|
|
944
|
+
force: true
|
|
945
|
+
});
|
|
946
|
+
cleanupDeployDriftCheckRoot(root);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
function runDrizzleKitGenerate(root, configPath) {
|
|
950
|
+
const result = spawnSync(process.execPath, [
|
|
951
|
+
drizzleKitBin,
|
|
952
|
+
"generate",
|
|
953
|
+
"--config",
|
|
954
|
+
configPath
|
|
955
|
+
], {
|
|
956
|
+
cwd: root,
|
|
957
|
+
encoding: "utf-8",
|
|
958
|
+
env: buildEnv()
|
|
959
|
+
});
|
|
960
|
+
const stderr = (result.stderr ?? "").trim();
|
|
961
|
+
const output = [stderr, (result.stdout ?? "").trim()].filter(Boolean).join("\n").trim();
|
|
962
|
+
if (result.error) throw result.error;
|
|
963
|
+
if (stderr) throw new Error(output);
|
|
964
|
+
if (result.status !== 0) throw new Error(output || `drizzle-kit generate exited with code ${result.status}.`);
|
|
965
|
+
return output;
|
|
966
|
+
}
|
|
967
|
+
function getDeploySchemaDriftMessage(root) {
|
|
968
|
+
const migrationsDir = readProjectPaths(root).migrationsDir;
|
|
969
|
+
if (collectMigrations(migrationsDir).length > 0 && !existsSync(join(migrationsDir, "meta", "_journal.json")) && listDrizzleArtifacts(migrationsDir).length === 0) return [`Missing Drizzle metadata in ${`${relative(root, migrationsDir)}/`}.`, "See https://void.cloud/guide/database#hand-written-migrations"].join("\n");
|
|
970
|
+
return ["Schema drift detected between your Drizzle schema and db/migrations/.", "Run `void db generate`, review the generated migration, commit it yourself, then rerun `void deploy`."].join("\n");
|
|
971
|
+
}
|
|
972
|
+
function formatDeploySchemaDriftCheckError(error) {
|
|
973
|
+
const detail = getErrorMessage(error);
|
|
974
|
+
return [
|
|
975
|
+
"Failed to check Drizzle schema drift during deploy.",
|
|
976
|
+
"Run `void db generate` locally, fix any reported issues, review the generated migration, commit it yourself, then rerun `void deploy`.",
|
|
977
|
+
...detail ? [detail] : []
|
|
978
|
+
].join("\n\n");
|
|
979
|
+
}
|
|
980
|
+
function createDeployDriftProject(root, driftProjectRoot) {
|
|
981
|
+
mkdirSync(driftProjectRoot, { recursive: true });
|
|
982
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
983
|
+
if (DRIFT_PROJECT_SKIPPED_ENTRIES.has(entry.name)) continue;
|
|
984
|
+
const source = join(root, entry.name);
|
|
985
|
+
const target = join(driftProjectRoot, entry.name);
|
|
986
|
+
cpSync(source, target, { recursive: true });
|
|
987
|
+
}
|
|
988
|
+
const authSchemaPath = join(root, ".void", "better-auth-schema.ts");
|
|
989
|
+
if (existsSync(authSchemaPath)) {
|
|
990
|
+
const target = join(driftProjectRoot, ".void", "better-auth-schema.ts");
|
|
991
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
992
|
+
cpSync(authSchemaPath, target);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
function mkDeployDriftCheckRoot(root) {
|
|
996
|
+
const driftCheckDir = join(root, deployDriftCheckDir);
|
|
997
|
+
rmSync(driftCheckDir, {
|
|
998
|
+
recursive: true,
|
|
999
|
+
force: true
|
|
1000
|
+
});
|
|
1001
|
+
mkdirSync(driftCheckDir, { recursive: true });
|
|
1002
|
+
return mkdtempSync(join(driftCheckDir, "check-"));
|
|
1003
|
+
}
|
|
1004
|
+
function cleanupDeployDriftCheckRoot(root) {
|
|
1005
|
+
const driftCheckDir = join(root, deployDriftCheckDir);
|
|
1006
|
+
try {
|
|
1007
|
+
if (existsSync(driftCheckDir) && readdirSync(driftCheckDir).length === 0) rmSync(driftCheckDir, {
|
|
1008
|
+
recursive: true,
|
|
1009
|
+
force: true
|
|
1010
|
+
});
|
|
1011
|
+
} catch {}
|
|
1012
|
+
}
|
|
1013
|
+
function snapshotDir(root) {
|
|
1014
|
+
if (!existsSync(root)) return {};
|
|
1015
|
+
const snapshot = {};
|
|
1016
|
+
collectSnapshotFiles(root, root, snapshot);
|
|
1017
|
+
return snapshot;
|
|
1018
|
+
}
|
|
1019
|
+
function collectSnapshotFiles(current, root, snapshot) {
|
|
1020
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
1021
|
+
const fullPath = join(current, entry.name);
|
|
1022
|
+
if (entry.isDirectory()) {
|
|
1023
|
+
collectSnapshotFiles(fullPath, root, snapshot);
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
snapshot[relative(root, fullPath)] = readFileSync(fullPath, "utf-8");
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
function migrationSnapshotsEqual(left, right) {
|
|
1030
|
+
const leftEntries = Object.entries(left).sort(([a], [b]) => a.localeCompare(b));
|
|
1031
|
+
const rightEntries = Object.entries(right).sort(([a], [b]) => a.localeCompare(b));
|
|
1032
|
+
if (leftEntries.length !== rightEntries.length) return false;
|
|
1033
|
+
return leftEntries.every(([leftPath, leftContent], index) => {
|
|
1034
|
+
const [rightPath, rightContent] = rightEntries[index] ?? [];
|
|
1035
|
+
return leftPath === rightPath && leftContent === rightContent;
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
//#endregion
|
|
1039
|
+
//#region src/cli/deploy-errors.ts
|
|
1040
|
+
const BODY_SNIPPET_MAX = 200;
|
|
1041
|
+
function trimSnippet(body) {
|
|
1042
|
+
const collapsed = body.replace(/\s+/g, " ").trim();
|
|
1043
|
+
if (collapsed.length <= BODY_SNIPPET_MAX) return collapsed;
|
|
1044
|
+
return `${collapsed.slice(0, BODY_SNIPPET_MAX)}...`;
|
|
1045
|
+
}
|
|
1046
|
+
function appendDeploymentLine(message, deploymentId) {
|
|
1047
|
+
return deploymentId ? `${message}\nDeployment: ${deploymentId}` : message;
|
|
1048
|
+
}
|
|
1049
|
+
function appendLogLine(message, logPath) {
|
|
1050
|
+
return logPath ? `${message}\nDetailed log: ${logPath}` : message;
|
|
1051
|
+
}
|
|
1052
|
+
function appendTrailingLines(message, deploymentId, logPath) {
|
|
1053
|
+
return appendLogLine(appendDeploymentLine(message, deploymentId), logPath);
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Describe the deepest `cause` of an error in one short line, e.g.
|
|
1057
|
+
* `getaddrinfo ENOTFOUND api.example.com` under `fetch failed`.
|
|
1058
|
+
*/
|
|
1059
|
+
function describeRootCause(err) {
|
|
1060
|
+
let root = err;
|
|
1061
|
+
for (let depth = 0; depth < 6; depth++) {
|
|
1062
|
+
const cause = root.cause;
|
|
1063
|
+
if (cause === null || typeof cause !== "object") break;
|
|
1064
|
+
root = cause;
|
|
1065
|
+
}
|
|
1066
|
+
if (root === err) return null;
|
|
1067
|
+
const { code, message } = root;
|
|
1068
|
+
const text = typeof message === "string" ? message.trim() : "";
|
|
1069
|
+
const codeText = typeof code === "string" ? code : "";
|
|
1070
|
+
if (!text) return codeText || null;
|
|
1071
|
+
return codeText && !text.includes(codeText) ? `${codeText}: ${text}` : text;
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Format a server-emitted `event: 'error'` payload with an optional deployment id.
|
|
1075
|
+
*/
|
|
1076
|
+
function formatDeployFailureMessage(message, deploymentId, logPath) {
|
|
1077
|
+
return appendTrailingLines(message, deploymentId, logPath);
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Format the "stream ended cleanly but no terminal event arrived" case.
|
|
1081
|
+
*
|
|
1082
|
+
* This is the only call site that should emit the bare "Connection lost during
|
|
1083
|
+
* deploy" message — the server closed the NDJSON stream without emitting
|
|
1084
|
+
* `done` or `error`, so there is no underlying error to carry.
|
|
1085
|
+
*/
|
|
1086
|
+
function formatConnectionLostMessage(deploymentId, logPath) {
|
|
1087
|
+
return appendTrailingLines("deploy: Connection lost during deploy.", deploymentId, logPath);
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Convert an exception thrown during the deploy NDJSON stream into a
|
|
1091
|
+
* user-facing `Error` with a readable message.
|
|
1092
|
+
*
|
|
1093
|
+
* Four cases:
|
|
1094
|
+
* 1. `PlatformApiError` with 409 + a JSON body carrying `{error, deployment}`
|
|
1095
|
+
* — surface the server-supplied error and prefer the body's deployment id.
|
|
1096
|
+
* 2. Any other `PlatformApiError` — retain the client's actionable message and
|
|
1097
|
+
* structured error fields; opaque provider failures use a trimmed body snippet.
|
|
1098
|
+
* 3. A headers timeout on the last deploy POST attempt — the platform sent
|
|
1099
|
+
* no response in time. Explain that the deploy may still finish and how
|
|
1100
|
+
* to check and retry.
|
|
1101
|
+
* 4. Anything else — preserve the original message, plus its root cause when
|
|
1102
|
+
* there is one, under a `Connection lost during deploy: <original>`
|
|
1103
|
+
* prefix so the caller can still tell network, parse, and transport
|
|
1104
|
+
* errors apart.
|
|
1105
|
+
*/
|
|
1106
|
+
function formatStreamDeployError(err, deploymentId, logPath) {
|
|
1107
|
+
if (err instanceof PlatformApiError) {
|
|
1108
|
+
const parsed = parsePlatformErrorBody(err.body);
|
|
1109
|
+
const parsedError = platformErrorMessage(parsed) ?? parsed?.error ?? null;
|
|
1110
|
+
const effectiveDeploymentId = parsed?.deployment ?? null ?? deploymentId;
|
|
1111
|
+
if (isCliOutdatedError(err) && parsed?.message) {
|
|
1112
|
+
err.message = appendTrailingLines(err.message, effectiveDeploymentId, logPath);
|
|
1113
|
+
return err;
|
|
1114
|
+
}
|
|
1115
|
+
if (parsedError || err.status === 401 || err.status === 403 || parsed !== null) {
|
|
1116
|
+
err.message = appendTrailingLines(`deploy: ${err.message}`, effectiveDeploymentId, logPath);
|
|
1117
|
+
return err;
|
|
1118
|
+
}
|
|
1119
|
+
const detail = trimSnippet(err.body);
|
|
1120
|
+
err.message = appendTrailingLines(detail ? `deploy: Deploy failed (HTTP ${err.status}): ${detail}` : `deploy: Deploy failed (HTTP ${err.status}).`, effectiveDeploymentId, logPath);
|
|
1121
|
+
return err;
|
|
1122
|
+
}
|
|
1123
|
+
if (isHeadersTimeoutError(err)) {
|
|
1124
|
+
const lines = [`deploy: The Void platform did not respond while preparing the deploy. Tried 4 times; the last try waited 5 minutes.`, "It may still finish. Check `void project status`, then run `void deploy` again to retry."];
|
|
1125
|
+
if (logPath) lines.push("If this keeps happening, share the detailed log below.");
|
|
1126
|
+
return new Error(appendTrailingLines(lines.join("\n"), deploymentId, logPath));
|
|
1127
|
+
}
|
|
1128
|
+
const original = err instanceof Error ? err.message : String(err);
|
|
1129
|
+
const rootCause = err instanceof Error ? describeRootCause(err) : null;
|
|
1130
|
+
const detail = rootCause && rootCause !== original ? ` (${rootCause})` : "";
|
|
1131
|
+
return new Error(appendTrailingLines(`deploy: Connection lost during deploy: '${original}'${detail}.`, deploymentId, logPath));
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Maximum retry attempts for a single R2 PUT before giving up. Matched to
|
|
1135
|
+
* the design doc 0068 spec: 5xx triggers retry, 4xx surfaces immediately.
|
|
1136
|
+
*/
|
|
1137
|
+
const MAX_R2_PUT_RETRIES = 3;
|
|
1138
|
+
/**
|
|
1139
|
+
* Retry backoff delays in milliseconds. After attempt `i` fails (5xx) we
|
|
1140
|
+
* sleep `RETRY_BACKOFF_MS[i]` before retrying. Indexed from 0; once the
|
|
1141
|
+
* array is exhausted we surface the error.
|
|
1142
|
+
*/
|
|
1143
|
+
const RETRY_BACKOFF_MS = [
|
|
1144
|
+
250,
|
|
1145
|
+
500,
|
|
1146
|
+
1e3
|
|
1147
|
+
];
|
|
1148
|
+
var DirectR2PutError = class extends Error {
|
|
1149
|
+
status;
|
|
1150
|
+
hash;
|
|
1151
|
+
path;
|
|
1152
|
+
/**
|
|
1153
|
+
* `body` is the response body text (may be empty / non-text). Useful in
|
|
1154
|
+
* the surface error so users can correlate against R2-side errors like
|
|
1155
|
+
* `BadDigest` (Content-MD5 mismatch) or signature errors.
|
|
1156
|
+
*/
|
|
1157
|
+
body;
|
|
1158
|
+
constructor(status, hash, path, body) {
|
|
1159
|
+
super(`R2 PUT failed for ${path} (${hash}): ${status}${body ? ` ${body}` : ""}`);
|
|
1160
|
+
this.name = "DirectR2PutError";
|
|
1161
|
+
this.status = status;
|
|
1162
|
+
this.hash = hash;
|
|
1163
|
+
this.path = path;
|
|
1164
|
+
this.body = body;
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1167
|
+
/**
|
|
1168
|
+
* Issue direct-to-R2 PUTs for every item in `items`, bounded to
|
|
1169
|
+
* `R2_PUT_CONCURRENCY`. Surfaces the first failure (4xx immediately, 5xx
|
|
1170
|
+
* after retry exhaustion) as a `DirectR2PutError` carrying the asset path
|
|
1171
|
+
* and hash so the user can correlate against the JSONL log.
|
|
1172
|
+
*
|
|
1173
|
+
* Logs `r2_put_start` / `r2_put_end` per asset, plus `r2_put_retry` on
|
|
1174
|
+
* each retry attempt. Per-asset durations help identify slow uploads.
|
|
1175
|
+
*
|
|
1176
|
+
* Memory: each successful PUT clears `item.body` and invokes `onAfterPut`
|
|
1177
|
+
* so the caller can drop its own ref (e.g. delete from the in-memory
|
|
1178
|
+
* `assetFiles` cache). For huge deploys this keeps peak memory bounded by
|
|
1179
|
+
* the upload concurrency rather than the total bundle size.
|
|
1180
|
+
*/
|
|
1181
|
+
async function uploadAssetsToR2(items, cliLog, fetchImpl = fetch, onAfterPut) {
|
|
1182
|
+
if (items.length === 0) return;
|
|
1183
|
+
let firstError = null;
|
|
1184
|
+
let nextIndex = 0;
|
|
1185
|
+
const workerCount = Math.min(10, items.length);
|
|
1186
|
+
const workers = [];
|
|
1187
|
+
for (let w = 0; w < workerCount; w++) workers.push((async () => {
|
|
1188
|
+
while (firstError === null) {
|
|
1189
|
+
const i = nextIndex++;
|
|
1190
|
+
if (i >= items.length) return;
|
|
1191
|
+
try {
|
|
1192
|
+
await uploadOne(items[i], cliLog, fetchImpl);
|
|
1193
|
+
} catch (err) {
|
|
1194
|
+
if (firstError === null) firstError = err;
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
items[i].body = void 0;
|
|
1198
|
+
onAfterPut?.(items[i]);
|
|
1199
|
+
}
|
|
1200
|
+
})());
|
|
1201
|
+
await Promise.all(workers);
|
|
1202
|
+
if (firstError !== null) throw firstError;
|
|
1203
|
+
}
|
|
1204
|
+
async function uploadOne(item, cliLog, fetchImpl) {
|
|
1205
|
+
if (!item.body) throw new Error(`internal: uploadOne invoked for ${item.path} with no body`);
|
|
1206
|
+
const body = item.body;
|
|
1207
|
+
const startedAt = Date.now();
|
|
1208
|
+
cliLog?.info("r2_put_start", {
|
|
1209
|
+
hash: item.hash,
|
|
1210
|
+
path: item.path,
|
|
1211
|
+
sizeBytes: body.length
|
|
1212
|
+
});
|
|
1213
|
+
let attempt = 0;
|
|
1214
|
+
while (true) {
|
|
1215
|
+
let res;
|
|
1216
|
+
try {
|
|
1217
|
+
res = await fetchImpl(item.url, {
|
|
1218
|
+
method: "PUT",
|
|
1219
|
+
body: new Uint8Array(body),
|
|
1220
|
+
headers: item.headers
|
|
1221
|
+
});
|
|
1222
|
+
} catch (err) {
|
|
1223
|
+
if (attempt < MAX_R2_PUT_RETRIES) {
|
|
1224
|
+
cliLog?.warn("r2_put_retry", {
|
|
1225
|
+
hash: item.hash,
|
|
1226
|
+
path: item.path,
|
|
1227
|
+
attempt: attempt + 1,
|
|
1228
|
+
status: 0,
|
|
1229
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1230
|
+
});
|
|
1231
|
+
await sleep(RETRY_BACKOFF_MS[attempt]);
|
|
1232
|
+
attempt++;
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1235
|
+
cliLog?.error("r2_put_end", err, {
|
|
1236
|
+
hash: item.hash,
|
|
1237
|
+
path: item.path,
|
|
1238
|
+
durationMs: Date.now() - startedAt,
|
|
1239
|
+
status: 0
|
|
1240
|
+
});
|
|
1241
|
+
throw new DirectR2PutError(0, item.hash, item.path, err instanceof Error ? err.message : String(err));
|
|
1242
|
+
}
|
|
1243
|
+
if (res.ok) {
|
|
1244
|
+
cliLog?.info("r2_put_end", {
|
|
1245
|
+
hash: item.hash,
|
|
1246
|
+
path: item.path,
|
|
1247
|
+
durationMs: Date.now() - startedAt,
|
|
1248
|
+
status: res.status
|
|
1249
|
+
});
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
const errBody = await safeReadText(res);
|
|
1253
|
+
if (res.status >= 500 && attempt < MAX_R2_PUT_RETRIES) {
|
|
1254
|
+
cliLog?.warn("r2_put_retry", {
|
|
1255
|
+
hash: item.hash,
|
|
1256
|
+
path: item.path,
|
|
1257
|
+
attempt: attempt + 1,
|
|
1258
|
+
status: res.status,
|
|
1259
|
+
body: truncate(errBody)
|
|
1260
|
+
});
|
|
1261
|
+
await sleep(RETRY_BACKOFF_MS[attempt]);
|
|
1262
|
+
attempt++;
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
cliLog?.error("r2_put_end", void 0, {
|
|
1266
|
+
hash: item.hash,
|
|
1267
|
+
path: item.path,
|
|
1268
|
+
durationMs: Date.now() - startedAt,
|
|
1269
|
+
status: res.status,
|
|
1270
|
+
body: truncate(errBody)
|
|
1271
|
+
});
|
|
1272
|
+
throw new DirectR2PutError(res.status, item.hash, item.path, truncate(errBody));
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
async function safeReadText(res) {
|
|
1276
|
+
try {
|
|
1277
|
+
return await res.text();
|
|
1278
|
+
} catch {
|
|
1279
|
+
return "";
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
function truncate(s) {
|
|
1283
|
+
return s.length > 500 ? `${s.slice(0, 500)}...` : s;
|
|
1284
|
+
}
|
|
1285
|
+
function sleep(ms) {
|
|
1286
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1287
|
+
}
|
|
1288
|
+
//#endregion
|
|
1289
|
+
//#region src/cli/deploy-managed-events.ts
|
|
1290
|
+
/**
|
|
1291
|
+
* Apply the visible side effects of a streamed deploy event (spinner
|
|
1292
|
+
* message or log warning). Returns a discriminated result so the caller
|
|
1293
|
+
* advances its own state (deploymentId capture, final return, error
|
|
1294
|
+
* throw). Pure function of (event, s) so the switch stays unit-testable.
|
|
1295
|
+
*/
|
|
1296
|
+
function applyDeployEvent(event, s) {
|
|
1297
|
+
switch (event.event) {
|
|
1298
|
+
case "start":
|
|
1299
|
+
s.message("Deploying...");
|
|
1300
|
+
return {
|
|
1301
|
+
kind: "start",
|
|
1302
|
+
deploymentId: event.deploymentId
|
|
1303
|
+
};
|
|
1304
|
+
case "heartbeat":
|
|
1305
|
+
if (event.status === "recovering") s.message("Recovering interrupted deployment...");
|
|
1306
|
+
return { kind: "continue" };
|
|
1307
|
+
case "asset_upload":
|
|
1308
|
+
if (event.new > 0) s.message(`Uploading assets (${event.new} new, ${event.cached} cached)`);
|
|
1309
|
+
else s.message(`Uploading assets (${event.total} cached)`);
|
|
1310
|
+
return { kind: "continue" };
|
|
1311
|
+
case "provisioning":
|
|
1312
|
+
s.message(`Provisioning ${event.bindings.join(", ")}`);
|
|
1313
|
+
return { kind: "continue" };
|
|
1314
|
+
case "worker_readiness":
|
|
1315
|
+
s.message("Waiting for worker to become reachable...");
|
|
1316
|
+
return { kind: "continue" };
|
|
1317
|
+
case "migration_start":
|
|
1318
|
+
s.message(`Checking ${event.pending} bundled migration${event.pending === 1 ? "" : "s"}...`);
|
|
1319
|
+
return { kind: "continue" };
|
|
1320
|
+
case "migration_warning":
|
|
1321
|
+
log.warn(event.message);
|
|
1322
|
+
return { kind: "continue" };
|
|
1323
|
+
case "migration":
|
|
1324
|
+
s.message(`Applied migration: ${event.name} (${event.durationMs}ms)`);
|
|
1325
|
+
return { kind: "continue" };
|
|
1326
|
+
case "worker_upload":
|
|
1327
|
+
s.message("Uploading worker...");
|
|
1328
|
+
return { kind: "continue" };
|
|
1329
|
+
case "queue_setup":
|
|
1330
|
+
s.message(`Configuring queue consumers: ${event.queues.join(", ")}`);
|
|
1331
|
+
return { kind: "continue" };
|
|
1332
|
+
case "queue_consumer_conflict":
|
|
1333
|
+
log.warn(`Queue consumer conflict on "${event.queue}": owned by "${event.ownerScript}". Messages for this queue will not be delivered to this deployment until the owning script is removed.`);
|
|
1334
|
+
return { kind: "continue" };
|
|
1335
|
+
case "prerender":
|
|
1336
|
+
s.message(`Prerendering ${event.pages} pages...`);
|
|
1337
|
+
return { kind: "continue" };
|
|
1338
|
+
case "done":
|
|
1339
|
+
if (event.published === false) s.stop("Deployed, but routing was not published — the project may be unreachable or still serving the previous deploy. Re-run `void deploy` to publish routing.");
|
|
1340
|
+
else s.clear();
|
|
1341
|
+
return {
|
|
1342
|
+
kind: "done",
|
|
1343
|
+
event
|
|
1344
|
+
};
|
|
1345
|
+
case "error":
|
|
1346
|
+
s.stop("Deploy failed");
|
|
1347
|
+
return {
|
|
1348
|
+
kind: "error",
|
|
1349
|
+
message: event.message
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
return { kind: "continue" };
|
|
1353
|
+
}
|
|
1354
|
+
/**
|
|
1355
|
+
* Apply a streamed `DeployEvent` iterator to the spinner / cliLog and
|
|
1356
|
+
* resolve with the terminal `done` event (or throw on terminal error /
|
|
1357
|
+
* connection loss).
|
|
1358
|
+
*/
|
|
1359
|
+
async function streamDeployEvents(projectId, events, s, cliLog) {
|
|
1360
|
+
const startedAt = Date.now();
|
|
1361
|
+
let deploymentId = null;
|
|
1362
|
+
let lastProgressAt = startedAt;
|
|
1363
|
+
let waitNotice;
|
|
1364
|
+
let interrupted = false;
|
|
1365
|
+
cliLog?.info("stream_start", { projectId });
|
|
1366
|
+
const onSigint = () => {
|
|
1367
|
+
interrupted = true;
|
|
1368
|
+
s.stop("Deploy interrupted");
|
|
1369
|
+
if (deploymentId) log.info(`Deployment: ${deploymentId}`);
|
|
1370
|
+
else log.info("Deployment id was not received before the interrupt.");
|
|
1371
|
+
cliLog?.warn("stream_interrupted", { deploymentId });
|
|
1372
|
+
cliLog?.close().catch(() => {});
|
|
1373
|
+
process.exit(130);
|
|
1374
|
+
};
|
|
1375
|
+
process.once("SIGINT", onSigint);
|
|
1376
|
+
try {
|
|
1377
|
+
for await (const event of events) {
|
|
1378
|
+
if (event.event === "heartbeat") {
|
|
1379
|
+
const fields = {
|
|
1380
|
+
deploymentId,
|
|
1381
|
+
step: event.step,
|
|
1382
|
+
status: event.status,
|
|
1383
|
+
recoveryAt: event.recoveryAt
|
|
1384
|
+
};
|
|
1385
|
+
if (event.status === "recovering" && waitNotice !== "recovering") {
|
|
1386
|
+
log.info(`Deployment ${deploymentId ?? event.deploymentId ?? ""} is recovering; waiting for its result.`);
|
|
1387
|
+
cliLog?.info("deploy_recovering", fields);
|
|
1388
|
+
waitNotice = "recovering";
|
|
1389
|
+
} else if (Date.now() - lastProgressAt >= 9e4 && waitNotice === void 0) {
|
|
1390
|
+
log.warn(`Deployment ${deploymentId ?? event.deploymentId ?? ""} has not reported new progress for 90 seconds (last reported progress: ${event.step.replaceAll("_", " ")}).` + (event.recoveryAt ? ` Automatic recovery can start after ${event.recoveryAt} if execution was interrupted.` : "") + " Inspect with `void project status`.");
|
|
1391
|
+
cliLog?.warn("deploy_progress_stalled", fields);
|
|
1392
|
+
waitNotice = "stalled";
|
|
1393
|
+
}
|
|
1394
|
+
} else {
|
|
1395
|
+
lastProgressAt = Date.now();
|
|
1396
|
+
waitNotice = void 0;
|
|
1397
|
+
}
|
|
1398
|
+
const result = applyDeployEvent(event, s);
|
|
1399
|
+
if (result.kind === "start") {
|
|
1400
|
+
deploymentId = result.deploymentId;
|
|
1401
|
+
cliLog?.info("stream_deployment_id", { deploymentId });
|
|
1402
|
+
} else if (result.kind === "done") {
|
|
1403
|
+
const degraded = result.event.published === false;
|
|
1404
|
+
cliLog?.info(degraded ? "deploy_degraded" : "deploy_success", {
|
|
1405
|
+
deploymentId,
|
|
1406
|
+
url: result.event.url,
|
|
1407
|
+
assets: result.event.assets,
|
|
1408
|
+
workers: result.event.workers,
|
|
1409
|
+
durationMs: Date.now() - startedAt,
|
|
1410
|
+
degraded
|
|
1411
|
+
});
|
|
1412
|
+
return result.event;
|
|
1413
|
+
} else if (result.kind === "error") {
|
|
1414
|
+
cliLog?.error("deploy_server_error", void 0, {
|
|
1415
|
+
deploymentId,
|
|
1416
|
+
message: result.message
|
|
1417
|
+
});
|
|
1418
|
+
throw new DeployEventError(formatDeployFailureMessage(result.message, deploymentId, cliLog?.path ?? null));
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
} catch (err) {
|
|
1422
|
+
if (err instanceof DeployEventError || interrupted) throw err;
|
|
1423
|
+
cliLog?.error("stream_deploy_caught", err, { deploymentId });
|
|
1424
|
+
s.stop("Deploy failed");
|
|
1425
|
+
throw formatStreamDeployError(err, deploymentId, cliLog?.path ?? null);
|
|
1426
|
+
} finally {
|
|
1427
|
+
process.removeListener("SIGINT", onSigint);
|
|
1428
|
+
}
|
|
1429
|
+
cliLog?.warn("stream_no_terminal_event", { deploymentId });
|
|
1430
|
+
s.stop("Deploy failed");
|
|
1431
|
+
throw new Error(formatConnectionLostMessage(deploymentId, cliLog?.path ?? null));
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Direct-to-R2 deploy orchestration. Issues presigned PUT URLs for each
|
|
1435
|
+
* needed asset, uploads bytes directly to R2, then calls the manifest-only
|
|
1436
|
+
* deploy endpoint to finalize.
|
|
1437
|
+
*
|
|
1438
|
+
* Key invariants:
|
|
1439
|
+
* 1. No needed assets → skip presign + R2 PUT phase entirely (saves a
|
|
1440
|
+
* round-trip when every asset was already present from a prior deploy).
|
|
1441
|
+
* 2. Asset bytes come exclusively from the in-memory `assetFiles` cache
|
|
1442
|
+
* computed by `collectAndHashAssets` — files are NEVER re-read from
|
|
1443
|
+
* disk during this flow.
|
|
1444
|
+
* 3. R2 PUT failures (4xx, 5xx, network) are surfaced with the asset
|
|
1445
|
+
* path + hash so the user can correlate against the JSONL trace.
|
|
1446
|
+
*/
|
|
1447
|
+
async function streamDirectR2Deploy({ client, projectId, manifest, assetManifest, assetFiles, needed, gitMetadata, preflightUsed, workerFiles, spinner: s, cliLog }) {
|
|
1448
|
+
if (needed.length > 0) {
|
|
1449
|
+
const neededPaths = new Set(needed);
|
|
1450
|
+
const preBufferCount = assetFiles.size;
|
|
1451
|
+
for (const path of assetFiles.keys()) if (!neededPaths.has(path)) assetFiles.delete(path);
|
|
1452
|
+
if (assetFiles.size !== preBufferCount) cliLog?.info("asset_cache_pruned", {
|
|
1453
|
+
before: preBufferCount,
|
|
1454
|
+
after: assetFiles.size
|
|
1455
|
+
});
|
|
1456
|
+
s.message(`Requesting upload URLs for ${formatCount(needed.length, "asset")}...`);
|
|
1457
|
+
const seenBlake3 = /* @__PURE__ */ new Set();
|
|
1458
|
+
const uniqueNeeded = [];
|
|
1459
|
+
for (const path of needed) {
|
|
1460
|
+
const entry = assetManifest[path];
|
|
1461
|
+
if (!entry) throw new Error(`internal: preflight returned ${path} but assetManifest has no entry`);
|
|
1462
|
+
if (seenBlake3.has(entry.blake3)) continue;
|
|
1463
|
+
seenBlake3.add(entry.blake3);
|
|
1464
|
+
uniqueNeeded.push(path);
|
|
1465
|
+
}
|
|
1466
|
+
const assetsToUpload = uniqueNeeded.map((path) => {
|
|
1467
|
+
const entry = assetManifest[path];
|
|
1468
|
+
return {
|
|
1469
|
+
blake3: entry.blake3,
|
|
1470
|
+
md5: entry.md5,
|
|
1471
|
+
size: entry.size,
|
|
1472
|
+
contentType: getContentType(path)
|
|
1473
|
+
};
|
|
1474
|
+
});
|
|
1475
|
+
cliLog?.info("presign_request_start", {
|
|
1476
|
+
assetCount: assetsToUpload.length,
|
|
1477
|
+
dedupedFrom: needed.length
|
|
1478
|
+
});
|
|
1479
|
+
const presignStartedAt = Date.now();
|
|
1480
|
+
const urls = await client.requestUploadUrls(projectId, assetsToUpload);
|
|
1481
|
+
cliLog?.info("presign_request_end", {
|
|
1482
|
+
urlsReceived: Object.keys(urls).length,
|
|
1483
|
+
durationMs: Date.now() - presignStartedAt
|
|
1484
|
+
});
|
|
1485
|
+
const items = uniqueNeeded.map((path) => {
|
|
1486
|
+
const entry = assetManifest[path];
|
|
1487
|
+
const presigned = urls[entry.blake3];
|
|
1488
|
+
if (!presigned) throw new Error(`Platform did not return an upload URL for ${path} (blake3=${entry.blake3}). This indicates a server-side bug or a stale client.`);
|
|
1489
|
+
const body = assetFiles.get(path);
|
|
1490
|
+
if (!body) throw new Error(`internal: asset ${path} declared in manifest but missing from buffer cache`);
|
|
1491
|
+
return {
|
|
1492
|
+
path,
|
|
1493
|
+
hash: entry.blake3,
|
|
1494
|
+
body,
|
|
1495
|
+
url: presigned.url,
|
|
1496
|
+
headers: presigned.headers
|
|
1497
|
+
};
|
|
1498
|
+
});
|
|
1499
|
+
s.message(`Uploading ${formatCount(items.length, "asset")} to R2...`);
|
|
1500
|
+
await uploadAssetsToR2(items, cliLog, void 0, (item) => {
|
|
1501
|
+
assetFiles.delete(item.path);
|
|
1502
|
+
});
|
|
1503
|
+
} else {
|
|
1504
|
+
assetFiles.clear();
|
|
1505
|
+
cliLog?.info("presign_skipped", { reason: "no_needed_assets" });
|
|
1506
|
+
}
|
|
1507
|
+
let encodedWorkerFiles;
|
|
1508
|
+
if (workerFiles && workerFiles.size > 0) {
|
|
1509
|
+
encodedWorkerFiles = {};
|
|
1510
|
+
for (const [relPath, buf] of workerFiles) encodedWorkerFiles[relPath] = buf.toString("base64");
|
|
1511
|
+
}
|
|
1512
|
+
const artifact = createDeployArtifact({
|
|
1513
|
+
manifest,
|
|
1514
|
+
assetManifest,
|
|
1515
|
+
workerFiles: encodedWorkerFiles
|
|
1516
|
+
});
|
|
1517
|
+
const payload = createDeployUploadPayload(artifact, {
|
|
1518
|
+
source: getTokenSource(),
|
|
1519
|
+
commit: gitMetadata?.commit ?? null,
|
|
1520
|
+
repositoryUrl: gitMetadata?.repositoryUrl ?? null,
|
|
1521
|
+
commitUrl: gitMetadata?.commitUrl ?? null,
|
|
1522
|
+
gitRef: gitMetadata?.gitRef ?? null,
|
|
1523
|
+
preflight: preflightUsed ? "true" : null
|
|
1524
|
+
});
|
|
1525
|
+
s.message("Finalizing deploy...");
|
|
1526
|
+
cliLog?.info("finalize_start", {
|
|
1527
|
+
workerFileCount: workerFiles?.size ?? 0,
|
|
1528
|
+
assetCount: Object.keys(assetManifest).length
|
|
1529
|
+
});
|
|
1530
|
+
const finalizeStartedAt = Date.now();
|
|
1531
|
+
try {
|
|
1532
|
+
return await streamDeployEvents(projectId, client.finalizeDeploy(projectId, payload), s, cliLog);
|
|
1533
|
+
} finally {
|
|
1534
|
+
cliLog?.info("finalize_end", { durationMs: Date.now() - finalizeStartedAt });
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
/**
|
|
1538
|
+
* Surface a persistent warning when a deploy committed live but its routing
|
|
1539
|
+
* KV was not published. Uses `log.warn` (not the spinner) so the warning
|
|
1540
|
+
* survives the following `note`/`outro` calls and the user sees it after the
|
|
1541
|
+
* deploy summary. The deploy is still committed — this is a warning, not a
|
|
1542
|
+
* failure, so the process exit code is unaffected.
|
|
1543
|
+
*/
|
|
1544
|
+
function warnUnpublishedRouting(result) {
|
|
1545
|
+
if (result.published !== false) return;
|
|
1546
|
+
log.warn("Routing was not published for this deploy. The project may be unreachable (first deploy) or still serving the previous deploy. Re-run `void deploy` to publish routing.");
|
|
1547
|
+
}
|
|
1548
|
+
/** Merge `_headers` / `_redirects` in `dir` with `void.config.ts` routing rules, then lint them. */
|
|
1549
|
+
function mergeAndLintRoutingRules(dir, routing) {
|
|
1550
|
+
const merged = mergeRoutingRules({
|
|
1551
|
+
dir,
|
|
1552
|
+
routing
|
|
1553
|
+
});
|
|
1554
|
+
warnRedundantForceOn3xx(merged.redundantForceOn3xx);
|
|
1555
|
+
lintRoutingPhases(merged);
|
|
1556
|
+
return merged;
|
|
1557
|
+
}
|
|
1558
|
+
/** Warn once about `!` suffixes on 3xx `_redirects` entries, which are no-ops. */
|
|
1559
|
+
function warnRedundantForceOn3xx(count) {
|
|
1560
|
+
if (count === 0) return;
|
|
1561
|
+
log.warn(`Found ${count} redundant "!" on 3xx entries in _redirects (these always force by nature; safe to remove).`);
|
|
1562
|
+
}
|
|
1563
|
+
/**
|
|
1564
|
+
* Class B/C framework deploys pin `not_found_handling: 'none'`, so warn when
|
|
1565
|
+
* `routing.notFound` is set. Honoring it would turn framework 404s into a 200
|
|
1566
|
+
* `index.html` or take the 404 from the framework's own error route.
|
|
1567
|
+
*/
|
|
1568
|
+
function warnIgnoredNotFoundForFramework(notFound, frameworkName) {
|
|
1569
|
+
if (notFound === void 0) return;
|
|
1570
|
+
log.warn(`"routing.notFound" in void.config.ts is ignored for ${frameworkName} deploys (not_found_handling is pinned to "none" — the framework's own worker owns unmatched HTML).`);
|
|
1571
|
+
}
|
|
1572
|
+
//#endregion
|
|
1573
|
+
//#region src/cli/deploy-summary.ts
|
|
1574
|
+
function printDeployContext(project, destination) {
|
|
1575
|
+
intro(`${import_picocolors.default.dim(sanitizeTerminalText(project))} ${import_picocolors.default.dim("→")} ${import_picocolors.default.blue(sanitizeTerminalText(destination))}`);
|
|
1576
|
+
}
|
|
1577
|
+
function printDeploySuccess(published = true) {
|
|
1578
|
+
if (published) printSuccess("Successfully deployed!");
|
|
1579
|
+
}
|
|
1580
|
+
/** Only include counts confirmed by the deployment target. */
|
|
1581
|
+
function formatDeploySummary(details) {
|
|
1582
|
+
const lines = [];
|
|
1583
|
+
if (details.workerName) lines.push(`Worker: ${details.workerName}`);
|
|
1584
|
+
const output = [];
|
|
1585
|
+
if (details.workers !== void 0) output.push(formatCount(details.workers, "worker module"));
|
|
1586
|
+
if (details.assets !== void 0) {
|
|
1587
|
+
let assets = formatCount(details.assets, "static asset");
|
|
1588
|
+
if (details.unchangedAssets) assets += ` (${details.unchangedAssets} unchanged, ${details.assets - details.unchangedAssets} uploaded)`;
|
|
1589
|
+
output.push(assets);
|
|
1590
|
+
}
|
|
1591
|
+
if (details.typeLabel) lines.push([details.typeLabel, output.join(", ")].filter(Boolean).join(" — "));
|
|
1592
|
+
else if (output.length) lines.push(output.join(", "));
|
|
1593
|
+
if (details.migrations) lines.push(`${formatCount(details.migrations, "migration")} applied`);
|
|
1594
|
+
if (details.d1MigrationsCurrent) lines.push("D1 migrations up to date");
|
|
1595
|
+
if (details.cronJobs) lines.push(`${formatCount(details.cronJobs, "cron job")} scheduled`);
|
|
1596
|
+
if (details.queues) lines.push(`${formatCount(details.queues, "queue")} configured`);
|
|
1597
|
+
if (details.prerenderedPages) lines.push(`${formatCount(details.prerenderedPages, "page")} prerendered`);
|
|
1598
|
+
if (details.ssr) lines.push("SSR enabled");
|
|
1599
|
+
if (details.revalidate) lines.push(typeof details.revalidate === "number" ? `ISR revalidate: ${details.revalidate}s` : `ISR revalidate: ${formatCount(Object.keys(details.revalidate).length, "path rule")}`);
|
|
1600
|
+
if (details.framework) lines.push(details.framework === true ? "Framework mode" : `Framework: ${details.framework}`);
|
|
1601
|
+
return lines.join("\n");
|
|
1602
|
+
}
|
|
1603
|
+
//#endregion
|
|
1604
|
+
//#region src/cli/package.ts
|
|
1605
|
+
const textEncoder = new TextEncoder();
|
|
1606
|
+
/**
|
|
1607
|
+
* Strip dev-only / internal-only fields from each rule before shipping the
|
|
1608
|
+
* manifest. The resulting entries contain only `{ source, destination, status }`.
|
|
1609
|
+
*
|
|
1610
|
+
* - `origin` is used to build the `X-Void-Routing` trace header during local
|
|
1611
|
+
* dev; the deployed platform doesn't consume it.
|
|
1612
|
+
* - `force` is used internally by the CLI/router to split rules into the
|
|
1613
|
+
* `redirectRules` (pre-asset, always-fires) and `fallbackRules` (post-asset,
|
|
1614
|
+
* only when the asset resolver 404s) buckets and to label the trace header
|
|
1615
|
+
* on `node`/`bun`/`deno` targets. The platform dispatch worker decides phase
|
|
1616
|
+
* purely from which array the rule lives in, so shipping `force` is dead weight.
|
|
1617
|
+
*
|
|
1618
|
+
* Keep in sync with `stripOriginForProd` in `router/compile.ts`, which strips
|
|
1619
|
+
* only `origin` because the Node/Bun/Deno-target generated code still reads
|
|
1620
|
+
* `force` at runtime to label the dev trace header.
|
|
1621
|
+
*/
|
|
1622
|
+
function stripDevOnlyRuleFields(rules) {
|
|
1623
|
+
if (!rules) return rules;
|
|
1624
|
+
return rules.map(({ origin: _origin, force: _force, ...rest }) => rest);
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Layer user `routing.fallbacks` over the SPA default. The platform SPA switch
|
|
1628
|
+
* is global, so a SPA with user fallbacks turns it off and appends a synthetic
|
|
1629
|
+
* `/* -> /index.html` rule after them (first match wins). An explicit
|
|
1630
|
+
* `routing.notFound` is resolved first, so overriding away from SPA also skips
|
|
1631
|
+
* the synthetic rule. `detectPreset` classes a plain Vite MPA as `spa`, so this
|
|
1632
|
+
* override is its only escape hatch.
|
|
1633
|
+
*/
|
|
1634
|
+
function resolveSpaFallbackChain(appType, fallbackRules, notFound) {
|
|
1635
|
+
const userRules = fallbackRules && fallbackRules.length > 0 ? fallbackRules : void 0;
|
|
1636
|
+
const handling = notFound ?? (appType === "spa" ? "single-page-application" : appType === "static" ? "404-page" : "none");
|
|
1637
|
+
if (handling !== "single-page-application") return {
|
|
1638
|
+
assetConfig: { not_found_handling: handling },
|
|
1639
|
+
fallbackRules: userRules
|
|
1640
|
+
};
|
|
1641
|
+
if (!userRules) return {
|
|
1642
|
+
assetConfig: { not_found_handling: "single-page-application" },
|
|
1643
|
+
fallbackRules: void 0
|
|
1644
|
+
};
|
|
1645
|
+
if (userRules.some((r) => r.host === void 0 && r.source === "/*" && r.destination === "/index.html")) return {
|
|
1646
|
+
assetConfig: { not_found_handling: "none" },
|
|
1647
|
+
fallbackRules: userRules
|
|
1648
|
+
};
|
|
1649
|
+
const syntheticSpaFallback = {
|
|
1650
|
+
source: "/*",
|
|
1651
|
+
destination: "/index.html",
|
|
1652
|
+
status: 200,
|
|
1653
|
+
force: false,
|
|
1654
|
+
origin: "spa-default"
|
|
1655
|
+
};
|
|
1656
|
+
return {
|
|
1657
|
+
assetConfig: { not_found_handling: "none" },
|
|
1658
|
+
fallbackRules: [...userRules, syntheticSpaFallback]
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
function isNotFoundError(err) {
|
|
1662
|
+
return err instanceof Error && err.code === "ENOENT";
|
|
1663
|
+
}
|
|
1664
|
+
const PROGRESS_CHUNK = 50;
|
|
1665
|
+
/**
|
|
1666
|
+
* Yield to the event loop every PROGRESS_CHUNK iterations so spinner ticks
|
|
1667
|
+
* can repaint, and emit a progress message via onProgress. Otherwise the
|
|
1668
|
+
* tight sync loop blocks the spinner interval and the user sees no updates.
|
|
1669
|
+
*/
|
|
1670
|
+
async function tickProgress(i, total, onProgress, label) {
|
|
1671
|
+
if (!onProgress) return;
|
|
1672
|
+
if (i % PROGRESS_CHUNK !== 0 && i !== total) return;
|
|
1673
|
+
onProgress(`${label} (${i}/${total})`);
|
|
1674
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* Compute a CF-compatible asset hash: BLAKE3 of base64(content) + extension,
|
|
1678
|
+
* first 32 hex chars. Matches Wrangler's hashFile algorithm exactly.
|
|
1679
|
+
* Must match the API's computeAssetHash algorithm exactly.
|
|
1680
|
+
*/
|
|
1681
|
+
function computeAssetHash(content, filePath) {
|
|
1682
|
+
const base64 = content.toString("base64");
|
|
1683
|
+
const ext = extname(filePath).substring(1);
|
|
1684
|
+
const input = textEncoder.encode(base64 + ext);
|
|
1685
|
+
return Buffer.from(hash(input)).toString("hex").slice(0, 32);
|
|
1686
|
+
}
|
|
1687
|
+
/**
|
|
1688
|
+
* Compute MD5 of raw content as a 32-char lowercase hex string.
|
|
1689
|
+
*
|
|
1690
|
+
* Used as the wire-level upload-integrity primitive on direct-to-R2 PUTs:
|
|
1691
|
+
* R2's S3 compatibility layer enforces `Content-MD5` (RFC 1864) on PutObject,
|
|
1692
|
+
* so the upload itself fails if uploaded bytes don't match the declared MD5.
|
|
1693
|
+
* MD5 collision resistance is acceptable here because (a) this hash is purely
|
|
1694
|
+
* an upload-integrity primitive (BLAKE3 remains the canonical content key),
|
|
1695
|
+
* and (b) project-scoped storage (`assets/${projectId}/${blake3}`) means any
|
|
1696
|
+
* collision attack is exploitable only against the attacker's own project.
|
|
1697
|
+
*/
|
|
1698
|
+
function computeMd5(content) {
|
|
1699
|
+
return createHash("md5").update(content).digest("hex");
|
|
1700
|
+
}
|
|
1701
|
+
/**
|
|
1702
|
+
* Create a filter function that excludes .assetsignore, _headers, _redirects,
|
|
1703
|
+
* OS metadata files, dotenv files, deploy-internal patterns, and any patterns
|
|
1704
|
+
* listed in a .assetsignore file.
|
|
1705
|
+
* Matches Wrangler's createAssetsIgnoreFunction from workers-shared/utils/helpers.ts.
|
|
1706
|
+
*/
|
|
1707
|
+
function createAssetsIgnoreFunction(dir, extraPatterns = []) {
|
|
1708
|
+
const patterns = [
|
|
1709
|
+
"/.assetsignore",
|
|
1710
|
+
"/_redirects",
|
|
1711
|
+
"/_headers",
|
|
1712
|
+
".DS_Store",
|
|
1713
|
+
".env",
|
|
1714
|
+
".env.*",
|
|
1715
|
+
...extraPatterns
|
|
1716
|
+
];
|
|
1717
|
+
const assetsIgnorePath = join(dir, ".assetsignore");
|
|
1718
|
+
let assetsIgnoreFilePresent = false;
|
|
1719
|
+
if (existsSync(assetsIgnorePath)) {
|
|
1720
|
+
assetsIgnoreFilePresent = true;
|
|
1721
|
+
const content = readFileSync(assetsIgnorePath, "utf-8");
|
|
1722
|
+
patterns.push(...content.split("\n"));
|
|
1723
|
+
}
|
|
1724
|
+
const ig = ignore().add(patterns);
|
|
1725
|
+
const isIgnored = (relativePath) => {
|
|
1726
|
+
const normalized = relativePath.startsWith("/") ? relativePath.slice(1) : relativePath;
|
|
1727
|
+
return ig.test(normalized).ignored;
|
|
1728
|
+
};
|
|
1729
|
+
return {
|
|
1730
|
+
isIgnored,
|
|
1731
|
+
assetsIgnoreFilePresent
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* Guard against accidentally uploading _worker.js as a static asset.
|
|
1736
|
+
* Matches Wrangler's errorOnLegacyPagesWorkerJSAsset behavior:
|
|
1737
|
+
* throws only when no .assetsignore file exists (if .assetsignore is present
|
|
1738
|
+
* but doesn't exclude _worker.js, the user intentionally wants to upload it).
|
|
1739
|
+
*/
|
|
1740
|
+
function checkWorkerJsAsset(relativePath, assetsIgnoreFilePresent) {
|
|
1741
|
+
if (assetsIgnoreFilePresent) return;
|
|
1742
|
+
const name = relativePath.startsWith("/") ? relativePath.slice(1) : relativePath;
|
|
1743
|
+
if (name === "_worker.js" || name.startsWith("_worker.js/")) throw new Error(`deploy: Refusing to upload _worker.js ${name === "_worker.js" ? "file" : "directory"} as an asset. This could expose private server-side code to the public Internet. If this is unintended, remove it or add a .assetsignore file containing "_worker.js". If intentional, add an empty .assetsignore file to suppress this error.`);
|
|
1744
|
+
}
|
|
1745
|
+
/**
|
|
1746
|
+
* Collect all files in a directory, compute hashes, and return both the
|
|
1747
|
+
* manifest (for preflight) and the raw file map (for selective packaging).
|
|
1748
|
+
*
|
|
1749
|
+
* Each file is read from disk exactly once. Both BLAKE3 (storage key) and
|
|
1750
|
+
* MD5 (R2 upload-integrity) digests are derived from that single buffer.
|
|
1751
|
+
*/
|
|
1752
|
+
async function collectAndHashAssets(dir, onProgress, opts = {}) {
|
|
1753
|
+
const assetManifest = {};
|
|
1754
|
+
const assetFiles = /* @__PURE__ */ new Map();
|
|
1755
|
+
try {
|
|
1756
|
+
const { isIgnored, assetsIgnoreFilePresent } = createAssetsIgnoreFunction(dir, opts.ignorePatterns);
|
|
1757
|
+
const files = collectAssetFiles(dir, isIgnored);
|
|
1758
|
+
let processed = 0;
|
|
1759
|
+
for (const filePath of files) {
|
|
1760
|
+
processed++;
|
|
1761
|
+
await tickProgress(processed, files.length, onProgress, "Hashing assets");
|
|
1762
|
+
const relativePath = "/" + relative(dir, filePath);
|
|
1763
|
+
checkWorkerJsAsset(relativePath, assetsIgnoreFilePresent);
|
|
1764
|
+
const content = readFileSync(filePath);
|
|
1765
|
+
const blake3 = computeAssetHash(content, filePath);
|
|
1766
|
+
assetManifest[relativePath] = {
|
|
1767
|
+
blake3,
|
|
1768
|
+
md5: computeMd5(content),
|
|
1769
|
+
size: content.length,
|
|
1770
|
+
hash: blake3
|
|
1771
|
+
};
|
|
1772
|
+
assetFiles.set(relativePath, content);
|
|
1773
|
+
}
|
|
1774
|
+
} catch (err) {
|
|
1775
|
+
if (!isNotFoundError(err)) throw err;
|
|
1776
|
+
}
|
|
1777
|
+
return {
|
|
1778
|
+
assetManifest,
|
|
1779
|
+
assetFiles
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
function toWebSocketManifest(routes) {
|
|
1783
|
+
if (!routes || routes.length === 0) return;
|
|
1784
|
+
return {
|
|
1785
|
+
routes: routes.map((route) => ({
|
|
1786
|
+
pattern: route.pattern,
|
|
1787
|
+
filePath: route.filePath,
|
|
1788
|
+
className: route.className,
|
|
1789
|
+
mode: route.mode,
|
|
1790
|
+
hasAuthGate: route.hasAuthGate
|
|
1791
|
+
})),
|
|
1792
|
+
classes: routes.map((route) => route.className)
|
|
1793
|
+
};
|
|
1794
|
+
}
|
|
1795
|
+
function toSandboxManifest(sandbox) {
|
|
1796
|
+
return {
|
|
1797
|
+
controllerProtocol: 2,
|
|
1798
|
+
bindingName: sandbox.binding,
|
|
1799
|
+
className: sandbox.className,
|
|
1800
|
+
containerName: sandbox.containerName,
|
|
1801
|
+
image: sandbox.platformImage,
|
|
1802
|
+
...sandbox.instanceType && { instanceType: sandbox.instanceType }
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
function toLiveManifest(live) {
|
|
1806
|
+
const bindingName = live?.bindingName ?? "VOID_LIVE";
|
|
1807
|
+
const className = live?.className ?? "VoidLiveStreamDurableObject";
|
|
1808
|
+
return {
|
|
1809
|
+
bindingName,
|
|
1810
|
+
className,
|
|
1811
|
+
storage: "sqlite",
|
|
1812
|
+
classes: [className],
|
|
1813
|
+
migrations: [{
|
|
1814
|
+
tag: LIVE_MIGRATION_TAG,
|
|
1815
|
+
newSqliteClasses: [className]
|
|
1816
|
+
}]
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
/**
|
|
1820
|
+
* If the user configured `routing.fallbacks` on an app whose resolved
|
|
1821
|
+
* `not_found_handling` is not `single-page-application`, the platform still
|
|
1822
|
+
* applies the rules but no `/* -> /index.html` SPA shell is synthesised —
|
|
1823
|
+
* unmatched paths fall through to the platform 404. Returns the warning
|
|
1824
|
+
* message string when that mismatch is detected, or `null` otherwise. Pure
|
|
1825
|
+
* function; callers decide whether/how to surface the message (CLI progress,
|
|
1826
|
+
* `console.warn`, etc.).
|
|
1827
|
+
*
|
|
1828
|
+
* The message names `not_found_handling` rather than the app type because an
|
|
1829
|
+
* explicit `routing.notFound` can land here too — the app may well be a SPA.
|
|
1830
|
+
*/
|
|
1831
|
+
function detectNonSpaFallbackWarning(assetConfig, fallbackRules) {
|
|
1832
|
+
if (assetConfig?.not_found_handling === "single-page-application") return null;
|
|
1833
|
+
if (!fallbackRules || fallbackRules.length === 0) return null;
|
|
1834
|
+
return "routing.fallbacks is set but not_found_handling is not \"single-page-application\"; fallbacks will be applied, but no \"/* -> /index.html\" SPA shell fallback is synthesised — unmatched paths fall through to the platform 404.";
|
|
1835
|
+
}
|
|
1836
|
+
/** Add the database, KV, R2, and AI bindings shared by Void and framework manifests. */
|
|
1837
|
+
function applyResourceBindings(manifest, bindings, migrationCount, dialect, bindingNames) {
|
|
1838
|
+
if (needsApplicationDatabase(bindings, migrationCount)) {
|
|
1839
|
+
if (dialect !== "sqlite") manifest.bindings.hyperdrive = ["HYPERDRIVE"];
|
|
1840
|
+
else manifest.bindings.d1 = [bindingNames?.d1 ?? "DB"];
|
|
1841
|
+
}
|
|
1842
|
+
if (bindings.needsKV) manifest.bindings.kv = [bindingNames?.kv ?? "KV"];
|
|
1843
|
+
if (bindings.needsR2) manifest.bindings.r2 = [bindingNames?.r2 ?? "STORAGE"];
|
|
1844
|
+
if (bindings.needsAI) manifest.bindings.ai = true;
|
|
1845
|
+
}
|
|
1846
|
+
/** Describe bundled migrations; SQLite migrations also carry their plan bundle. */
|
|
1847
|
+
function toMigrationsManifest(migrations, dialect) {
|
|
1848
|
+
const planBundle = dialect === "sqlite" ? buildMigrationPlanBundle(migrations) : void 0;
|
|
1849
|
+
return {
|
|
1850
|
+
dialect,
|
|
1851
|
+
...planBundle ? {
|
|
1852
|
+
classifierVersion: planBundle.classifierVersion,
|
|
1853
|
+
migrationListHash: planBundle.migrationListHash,
|
|
1854
|
+
protocolVersion: planBundle.protocolVersion,
|
|
1855
|
+
stability: planBundle.plans.some((plan) => plan.stability === "legacy") ? "legacy" : "stable"
|
|
1856
|
+
} : {},
|
|
1857
|
+
pending: migrations.length
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
/**
|
|
1861
|
+
* Build a Void-app deploy manifest from the inferred bindings + per-feature
|
|
1862
|
+
* inputs. Pure shape transformation: reads no files, has no I/O.
|
|
1863
|
+
*
|
|
1864
|
+
* Note: callers are responsible for surfacing the non-SPA-fallbacks warning
|
|
1865
|
+
* (see `detectNonSpaFallbackWarning`) before invoking this builder. The
|
|
1866
|
+
* builder itself stays I/O-free.
|
|
1867
|
+
*/
|
|
1868
|
+
function buildVoidManifest(opts) {
|
|
1869
|
+
const { bindings, migrations, schedules, ssr, framework, revalidate, pages, pathRevalidate, queues, prerender, assetConfig, options, hashedAssetsPrefix, headerRules, redirectRules, fallbackRules, dialect, webSockets } = opts;
|
|
1870
|
+
const resolvedDialect = dialect ?? "sqlite";
|
|
1871
|
+
const manifest = {
|
|
1872
|
+
version: 3,
|
|
1873
|
+
assetManifestVersion: 2,
|
|
1874
|
+
bindings: {}
|
|
1875
|
+
};
|
|
1876
|
+
if (opts.hasEmailHandlers) manifest.emailProtocolVersion = 1;
|
|
1877
|
+
if (bindings.needsAuth) manifest.auth = true;
|
|
1878
|
+
applyResourceBindings(manifest, bindings, migrations?.length ?? 0, resolvedDialect, options?.bindingNames);
|
|
1879
|
+
if (bindings.needsSandbox) {
|
|
1880
|
+
if (!options?.sandbox) throw new Error("deploy: Sandbox bindings require sandbox package options.");
|
|
1881
|
+
manifest.sandbox = toSandboxManifest({
|
|
1882
|
+
...options.sandbox,
|
|
1883
|
+
binding: options.bindingNames?.sandbox ?? options.sandbox.binding
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1886
|
+
if (bindings.needsLive) manifest.live = toLiveManifest(options?.live);
|
|
1887
|
+
if (migrations && migrations.length > 0) manifest.migrations = toMigrationsManifest(migrations, resolvedDialect);
|
|
1888
|
+
if (schedules && schedules.length > 0) manifest.schedules = schedules;
|
|
1889
|
+
if (queues && queues.length > 0) manifest.queues = queues;
|
|
1890
|
+
if (ssr) manifest.ssr = true;
|
|
1891
|
+
if (framework) manifest.framework = framework;
|
|
1892
|
+
manifest.type = framework ? "framework" : "void";
|
|
1893
|
+
if (revalidate != null && revalidate !== 0) manifest.revalidate = revalidate;
|
|
1894
|
+
if (options?.revalidateQueryAllowlist && Object.keys(options.revalidateQueryAllowlist).length > 0) manifest.revalidateQueryAllowlist = options.revalidateQueryAllowlist;
|
|
1895
|
+
if (pages && pages.length > 0) manifest.pages = pages;
|
|
1896
|
+
if (pathRevalidate && Object.keys(pathRevalidate).length > 0) manifest.pathRevalidate = pathRevalidate;
|
|
1897
|
+
if (prerender && prerender.length > 0) manifest.prerender = prerender;
|
|
1898
|
+
let resolvedAssetConfig = assetConfig;
|
|
1899
|
+
let resolvedFallbackRules = fallbackRules;
|
|
1900
|
+
if (assetConfig?.not_found_handling === "single-page-application") {
|
|
1901
|
+
const chain = resolveSpaFallbackChain("spa", fallbackRules);
|
|
1902
|
+
resolvedAssetConfig = {
|
|
1903
|
+
...assetConfig,
|
|
1904
|
+
...chain.assetConfig
|
|
1905
|
+
};
|
|
1906
|
+
resolvedFallbackRules = chain.fallbackRules;
|
|
1907
|
+
}
|
|
1908
|
+
if (resolvedAssetConfig) manifest.assetConfig = resolvedAssetConfig;
|
|
1909
|
+
if (options?.workerMain) manifest.workerMain = options.workerMain;
|
|
1910
|
+
if (options?.compatibilityDate) manifest.compatibilityDate = options.compatibilityDate;
|
|
1911
|
+
if (options?.compatibilityFlags && options.compatibilityFlags.length > 0) manifest.compatibilityFlags = options.compatibilityFlags;
|
|
1912
|
+
if (options?.cpuMs) manifest.limits = { cpuMs: options.cpuMs };
|
|
1913
|
+
if (hashedAssetsPrefix) manifest.hashedAssetsPrefix = hashedAssetsPrefix;
|
|
1914
|
+
if (headerRules && headerRules.length > 0) manifest.headerRules = headerRules;
|
|
1915
|
+
if (redirectRules && redirectRules.length > 0) manifest.redirectRules = stripDevOnlyRuleFields(redirectRules);
|
|
1916
|
+
if (resolvedFallbackRules && resolvedFallbackRules.length > 0) manifest.fallbackRules = stripDevOnlyRuleFields(resolvedFallbackRules);
|
|
1917
|
+
const websocketManifest = toWebSocketManifest(webSockets);
|
|
1918
|
+
if (websocketManifest) manifest.websocket = websocketManifest;
|
|
1919
|
+
return manifest;
|
|
1920
|
+
}
|
|
1921
|
+
/**
|
|
1922
|
+
* Read every worker module under `workerDir` once and return them as a map
|
|
1923
|
+
* keyed by relative path. Skipped files (build metadata, wrangler config,
|
|
1924
|
+
* non-module extensions) are filtered out by `isWorkerModule`. Reads each
|
|
1925
|
+
* file from disk exactly once — buffer threading downstream is the caller's
|
|
1926
|
+
* responsibility.
|
|
1927
|
+
*/
|
|
1928
|
+
function collectWorkerFiles(workerDir) {
|
|
1929
|
+
const out = /* @__PURE__ */ new Map();
|
|
1930
|
+
let workerPaths;
|
|
1931
|
+
try {
|
|
1932
|
+
workerPaths = collectFiles(workerDir);
|
|
1933
|
+
} catch (err) {
|
|
1934
|
+
if (isNotFoundError(err)) return out;
|
|
1935
|
+
throw err;
|
|
1936
|
+
}
|
|
1937
|
+
for (const filePath of workerPaths) {
|
|
1938
|
+
const relativePath = relative(workerDir, filePath);
|
|
1939
|
+
if (!isWorkerModule(relativePath)) continue;
|
|
1940
|
+
out.set(relativePath, readFileSync(filePath));
|
|
1941
|
+
}
|
|
1942
|
+
return out;
|
|
1943
|
+
}
|
|
1944
|
+
/**
|
|
1945
|
+
* Build a static-deploy manifest. Pure shape transformation — no I/O.
|
|
1946
|
+
*/
|
|
1947
|
+
function buildStaticManifest(opts) {
|
|
1948
|
+
const { appType, hashedAssetsPrefix, headerRules, redirectRules, fallbackRules, notFound } = opts;
|
|
1949
|
+
const { assetConfig, fallbackRules: resolvedFallbackRules } = resolveSpaFallbackChain(appType, fallbackRules, notFound);
|
|
1950
|
+
const manifest = {
|
|
1951
|
+
version: 3,
|
|
1952
|
+
assetManifestVersion: 2,
|
|
1953
|
+
bindings: {},
|
|
1954
|
+
type: appType,
|
|
1955
|
+
assetConfig
|
|
1956
|
+
};
|
|
1957
|
+
if (hashedAssetsPrefix) manifest.hashedAssetsPrefix = hashedAssetsPrefix;
|
|
1958
|
+
if (headerRules && headerRules.length > 0) manifest.headerRules = headerRules;
|
|
1959
|
+
if (redirectRules && redirectRules.length > 0) manifest.redirectRules = stripDevOnlyRuleFields(redirectRules);
|
|
1960
|
+
if (resolvedFallbackRules && resolvedFallbackRules.length > 0) manifest.fallbackRules = stripDevOnlyRuleFields(resolvedFallbackRules);
|
|
1961
|
+
return manifest;
|
|
1962
|
+
}
|
|
1963
|
+
/**
|
|
1964
|
+
* Build a framework-deploy manifest. Pure shape transformation — no I/O.
|
|
1965
|
+
*/
|
|
1966
|
+
function buildFrameworkManifest(opts) {
|
|
1967
|
+
const po = opts.packageOptions;
|
|
1968
|
+
const dialect = opts.dialect ?? "sqlite";
|
|
1969
|
+
const manifest = {
|
|
1970
|
+
version: 3,
|
|
1971
|
+
assetManifestVersion: 2,
|
|
1972
|
+
bindings: {}
|
|
1973
|
+
};
|
|
1974
|
+
applyResourceBindings(manifest, opts.bindings, opts.migrations?.length ?? 0, dialect, po?.bindingNames);
|
|
1975
|
+
if (opts.migrations && opts.migrations.length > 0) manifest.migrations = toMigrationsManifest(opts.migrations, dialect);
|
|
1976
|
+
if (opts.schedules && opts.schedules.length > 0) manifest.schedules = opts.schedules;
|
|
1977
|
+
if (opts.queues && opts.queues.length > 0) manifest.queues = opts.queues;
|
|
1978
|
+
if (opts.revalidate != null && opts.revalidate !== 0) manifest.revalidate = opts.revalidate;
|
|
1979
|
+
if (opts.revalidateQueryAllowlist && Object.keys(opts.revalidateQueryAllowlist).length > 0) manifest.revalidateQueryAllowlist = opts.revalidateQueryAllowlist;
|
|
1980
|
+
if (opts.prerender && opts.prerender.length > 0) manifest.prerender = opts.prerender;
|
|
1981
|
+
if (opts.assetConfig) manifest.assetConfig = opts.assetConfig;
|
|
1982
|
+
manifest.ssr = true;
|
|
1983
|
+
manifest.framework = opts.frameworkName;
|
|
1984
|
+
manifest.type = "framework";
|
|
1985
|
+
manifest.workerMain = opts.workerMain;
|
|
1986
|
+
if (po?.compatibilityDate) manifest.compatibilityDate = po.compatibilityDate;
|
|
1987
|
+
if (po?.compatibilityFlags && po.compatibilityFlags.length > 0) manifest.compatibilityFlags = po.compatibilityFlags;
|
|
1988
|
+
if (po?.cpuMs) manifest.limits = { cpuMs: po.cpuMs };
|
|
1989
|
+
if (opts.hashedAssetsPrefix) manifest.hashedAssetsPrefix = opts.hashedAssetsPrefix;
|
|
1990
|
+
if (opts.headerRules && opts.headerRules.length > 0) manifest.headerRules = opts.headerRules;
|
|
1991
|
+
if (opts.redirectRules && opts.redirectRules.length > 0) manifest.redirectRules = stripDevOnlyRuleFields(opts.redirectRules);
|
|
1992
|
+
if (opts.fallbackRules && opts.fallbackRules.length > 0) manifest.fallbackRules = stripDevOnlyRuleFields(opts.fallbackRules);
|
|
1993
|
+
return manifest;
|
|
1994
|
+
}
|
|
1995
|
+
/**
|
|
1996
|
+
* Check if a file (by relative path) should be included as a worker module.
|
|
1997
|
+
* Skips build metadata and files with extensions CF Workers can't handle as modules.
|
|
1998
|
+
*/
|
|
1999
|
+
function isWorkerModule(relativePath) {
|
|
2000
|
+
const norm = relativePath.replace(/\\/g, "/");
|
|
2001
|
+
if (norm.includes("/.vite/") || norm.startsWith(".vite/")) return false;
|
|
2002
|
+
if (norm === "wrangler.json" || norm === "wrangler.jsonc") return false;
|
|
2003
|
+
return /\.(js|mjs|cjs|wasm|map|txt)$/.test(norm);
|
|
2004
|
+
}
|
|
2005
|
+
/**
|
|
2006
|
+
* Recursively collect static asset file paths, refusing symlinks so deploys
|
|
2007
|
+
* cannot publish bytes from outside the intended asset root.
|
|
2008
|
+
*/
|
|
2009
|
+
function collectAssetFiles(root, isIgnored, dir = root) {
|
|
2010
|
+
const files = [];
|
|
2011
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
2012
|
+
for (const entry of entries) {
|
|
2013
|
+
const fullPath = join(dir, entry.name);
|
|
2014
|
+
const relativePath = "/" + relative(root, fullPath);
|
|
2015
|
+
if (isIgnored(relativePath)) continue;
|
|
2016
|
+
if (entry.isSymbolicLink()) throw new Error(`deploy: Refusing to upload symlink as an asset: ${relativePath}`);
|
|
2017
|
+
if (entry.isDirectory()) files.push(...collectAssetFiles(root, isIgnored, fullPath));
|
|
2018
|
+
else files.push(fullPath);
|
|
2019
|
+
}
|
|
2020
|
+
return files;
|
|
2021
|
+
}
|
|
2022
|
+
//#endregion
|
|
2023
|
+
//#region src/cli/deploy-managed-framework.ts
|
|
2024
|
+
/**
|
|
2025
|
+
* Class B/C framework deploy: build with framework CLI, package with preset output paths.
|
|
2026
|
+
*/
|
|
2027
|
+
async function runFrameworkDeploy(root, config, client, detected, fwPreset, voidConfig, skipBuild, gitMetadata, cliLog) {
|
|
2028
|
+
log.info(`${detected.name} framework deploy`);
|
|
2029
|
+
cliLog?.info("deploy_mode", {
|
|
2030
|
+
mode: "framework",
|
|
2031
|
+
framework: detected.name
|
|
2032
|
+
});
|
|
2033
|
+
const buildCmd = voidConfig.inference?.build ?? formatProjectCommand(root, fwPreset.buildCommand);
|
|
2034
|
+
if (!skipBuild) {
|
|
2035
|
+
log.step(`Building (${buildCmd})...`);
|
|
2036
|
+
cliLog?.info("build_start", { command: buildCmd });
|
|
2037
|
+
const buildStartedAt = Date.now();
|
|
2038
|
+
execSync(voidConfig.inference?.build ?? resolveProjectCommand(root, fwPreset.buildCommand), {
|
|
2039
|
+
cwd: root,
|
|
2040
|
+
stdio: "inherit",
|
|
2041
|
+
env: buildEnv({ ...MANAGED_DEPLOY_BUILD_ENV }, { localBinRoot: root })
|
|
2042
|
+
});
|
|
2043
|
+
cliLog?.info("build_end", { durationMs: Date.now() - buildStartedAt });
|
|
2044
|
+
} else cliLog?.info("build_skipped", { reason: "skipBuild" });
|
|
2045
|
+
const workerDir = join(root, fwPreset.workerDir);
|
|
2046
|
+
const assetsDir = join(root, fwPreset.assetsDir);
|
|
2047
|
+
const frameworkAssetIgnorePatterns = fwPreset.workerDir === fwPreset.assetsDir && fwPreset.workerMain === "_worker.js" ? [`/${fwPreset.workerMain}`, "/_routes.json"] : [];
|
|
2048
|
+
const { headerRules, redirectRules, fallbackRules } = mergeAndLintRoutingRules(assetsDir, voidConfig.routing);
|
|
2049
|
+
warnIgnoredNotFoundForFramework(voidConfig.routing?.notFound, detected.name);
|
|
2050
|
+
if (!existsSync(workerDir)) {
|
|
2051
|
+
log.error(`deploy: Worker output '${fwPreset.workerDir}' not found. Run '${buildCmd}' first.`);
|
|
2052
|
+
process.exit(1);
|
|
2053
|
+
}
|
|
2054
|
+
const analysis = analyzeApplicationSync(root, {
|
|
2055
|
+
config: voidConfig,
|
|
2056
|
+
environment: "prod"
|
|
2057
|
+
});
|
|
2058
|
+
const { paths, bindings, authEnabled, dialect } = analysis;
|
|
2059
|
+
if (authEnabled) {
|
|
2060
|
+
log.error("deploy: Void-managed Better Auth is only supported in Void apps. Use Better Auth's official integration for this framework instead.");
|
|
2061
|
+
process.exit(1);
|
|
2062
|
+
}
|
|
2063
|
+
if (bindings.needsLive) {
|
|
2064
|
+
log.error("deploy: void/live is currently supported in Void apps only.");
|
|
2065
|
+
process.exit(1);
|
|
2066
|
+
}
|
|
2067
|
+
assertDeploySchemaDriftFree(root, dialect);
|
|
2068
|
+
const validatedMigrations = validateDeployMigrations(paths.migrationsDir, analysis.migrations);
|
|
2069
|
+
if (dialect !== "sqlite" && analysis.needsDatabase) await ensureManagedHyperdrive(client, config.projectId, dialect);
|
|
2070
|
+
const rawJobs = analysis.jobs;
|
|
2071
|
+
const rawQueues = analysis.queues;
|
|
2072
|
+
const originalEntry = join(workerDir, fwPreset.workerMain);
|
|
2073
|
+
const wrapperFeatures = {
|
|
2074
|
+
crons: rawJobs,
|
|
2075
|
+
queues: rawQueues,
|
|
2076
|
+
hasMigrations: validatedMigrations != null && validatedMigrations.length > 0,
|
|
2077
|
+
migrationDialect: dialect
|
|
2078
|
+
};
|
|
2079
|
+
let actualWorkerDir = workerDir;
|
|
2080
|
+
let actualWorkerMain = fwPreset.workerMain;
|
|
2081
|
+
if (fwPreset.bundle) {
|
|
2082
|
+
log.step("Bundling worker...");
|
|
2083
|
+
const bundleResult = await bundleEntry(root, originalEntry);
|
|
2084
|
+
actualWorkerDir = bundleResult.outputDir;
|
|
2085
|
+
actualWorkerMain = bundleResult.entryFilename;
|
|
2086
|
+
log.info("Worker bundled");
|
|
2087
|
+
}
|
|
2088
|
+
if (needsWrapper(wrapperFeatures)) {
|
|
2089
|
+
log.step("Generating wrapper...");
|
|
2090
|
+
await generateDeployWrapperIfNeeded(root, actualWorkerDir, actualWorkerMain, wrapperFeatures);
|
|
2091
|
+
log.info("Wrapper generated");
|
|
2092
|
+
}
|
|
2093
|
+
const wranglerCompat = resolveDeployWranglerCompat(root, voidConfig, workerDir, { dialect });
|
|
2094
|
+
const revalidate = collectRevalidate(voidConfig, null);
|
|
2095
|
+
const prerender = voidConfig.routing?.isr === false ? void 0 : voidConfig.routing?.prerender;
|
|
2096
|
+
const { schedules, queues } = toManifestSchedulesAndQueues(rawJobs, rawQueues);
|
|
2097
|
+
await assertEnvSchemaValid(root, client, config.projectId);
|
|
2098
|
+
const s = createSpinner();
|
|
2099
|
+
const onProgress = (msg) => s.message(msg);
|
|
2100
|
+
s.start("Checking for changes...");
|
|
2101
|
+
cliLog?.info("preflight_start", { dir: assetsDir });
|
|
2102
|
+
const { assetManifest, assetFiles } = await collectAndHashAssets(assetsDir, onProgress, { ignorePatterns: frameworkAssetIgnorePatterns });
|
|
2103
|
+
const { needed, skipped } = await client.preflight(config.projectId, assetManifest, true);
|
|
2104
|
+
s.stop("Checked for changes");
|
|
2105
|
+
cliLog?.info("preflight_end", {
|
|
2106
|
+
total: Object.keys(assetManifest).length,
|
|
2107
|
+
needed: needed.length,
|
|
2108
|
+
skipped
|
|
2109
|
+
});
|
|
2110
|
+
if (skipped > 0) log.info(`Skipping ${formatCount(skipped, "unchanged asset")}`);
|
|
2111
|
+
s.start("Packaging...");
|
|
2112
|
+
cliLog?.info("package_start", {
|
|
2113
|
+
mode: "framework",
|
|
2114
|
+
framework: detected.name
|
|
2115
|
+
});
|
|
2116
|
+
const packageStartedAt = Date.now();
|
|
2117
|
+
const packageOptions = {
|
|
2118
|
+
bindingNames: analysis.bindingNames,
|
|
2119
|
+
compatibilityDate: wranglerCompat?.compatibilityDate,
|
|
2120
|
+
compatibilityFlags: wranglerCompat?.compatibilityFlags,
|
|
2121
|
+
cpuMs: voidConfig.worker?.limits?.cpu_ms
|
|
2122
|
+
};
|
|
2123
|
+
const manifest = buildFrameworkManifest({
|
|
2124
|
+
frameworkName: detected.name,
|
|
2125
|
+
workerMain: actualWorkerMain,
|
|
2126
|
+
bindings,
|
|
2127
|
+
migrations: validatedMigrations,
|
|
2128
|
+
schedules,
|
|
2129
|
+
queues,
|
|
2130
|
+
revalidate,
|
|
2131
|
+
revalidateQueryAllowlist: voidConfig.routing?.revalidateQueryAllowlist,
|
|
2132
|
+
prerender,
|
|
2133
|
+
assetConfig: { not_found_handling: "none" },
|
|
2134
|
+
packageOptions,
|
|
2135
|
+
hashedAssetsPrefix: fwPreset.hashedAssetsPrefix ?? "assets",
|
|
2136
|
+
headerRules,
|
|
2137
|
+
redirectRules,
|
|
2138
|
+
fallbackRules,
|
|
2139
|
+
dialect
|
|
2140
|
+
});
|
|
2141
|
+
const workerFiles = collectWorkerFiles(actualWorkerDir);
|
|
2142
|
+
cliLog?.info("package_end", {
|
|
2143
|
+
mode: "framework",
|
|
2144
|
+
durationMs: Date.now() - packageStartedAt
|
|
2145
|
+
});
|
|
2146
|
+
const result = await streamDirectR2Deploy({
|
|
2147
|
+
client,
|
|
2148
|
+
projectId: config.projectId,
|
|
2149
|
+
manifest,
|
|
2150
|
+
assetManifest,
|
|
2151
|
+
assetFiles,
|
|
2152
|
+
needed,
|
|
2153
|
+
gitMetadata,
|
|
2154
|
+
preflightUsed: skipped > 0,
|
|
2155
|
+
workerFiles,
|
|
2156
|
+
spinner: s,
|
|
2157
|
+
cliLog
|
|
2158
|
+
});
|
|
2159
|
+
note(formatDeploySummary({
|
|
2160
|
+
workers: result.workers,
|
|
2161
|
+
assets: result.assets,
|
|
2162
|
+
unchangedAssets: skipped,
|
|
2163
|
+
migrations: result.migrations,
|
|
2164
|
+
cronJobs: schedules.length,
|
|
2165
|
+
queues: queues.length,
|
|
2166
|
+
prerenderedPages: prerender?.length,
|
|
2167
|
+
revalidate,
|
|
2168
|
+
framework: detected.name
|
|
2169
|
+
}), result.url);
|
|
2170
|
+
cleanupWrapper(root);
|
|
2171
|
+
warnUnpublishedRouting(result);
|
|
2172
|
+
printDeploySuccess(result.published !== false);
|
|
2173
|
+
}
|
|
2174
|
+
function assertManagedDurableStateSupported(root) {
|
|
2175
|
+
if (scanDurableObjectModules(root).length > 0) throw new Error("deploy: typed Durable State modules in durable-objects/ are not yet supported by the Void managed platform. Deploy to your own Cloudflare account with `void deploy --platform cloudflare`.");
|
|
2176
|
+
}
|
|
2177
|
+
//#endregion
|
|
2178
|
+
//#region src/cli/deploy-managed-static.ts
|
|
2179
|
+
/**
|
|
2180
|
+
* Assemble the Void deploy manifest, deriving the route-aware ISR `pages` table
|
|
2181
|
+
* from the page scan. This is the single production seam that forwards
|
|
2182
|
+
* `pageScan` → `manifest.pages`; `runFullDeploy` calls it so the wiring is
|
|
2183
|
+
* exercised (and regression-guarded) by a unit test.
|
|
2184
|
+
*
|
|
2185
|
+
* `pages` is a Void pages-mode concept. In framework mode (Class-A frameworks
|
|
2186
|
+
* routed through `runFullDeploy`) we must NOT emit it — the framework owns its
|
|
2187
|
+
* own routing/ISR — so `isFrameworkMode` suppresses the table.
|
|
2188
|
+
*/
|
|
2189
|
+
function assembleVoidManifest(pageScan, isFrameworkMode, opts, revalidateConfig, isrEnabled = true) {
|
|
2190
|
+
return buildVoidManifest({
|
|
2191
|
+
...opts,
|
|
2192
|
+
...isrEnabled ? {} : {
|
|
2193
|
+
revalidate: void 0,
|
|
2194
|
+
prerender: void 0
|
|
2195
|
+
},
|
|
2196
|
+
pages: isFrameworkMode ? void 0 : collectPages(pageScan, revalidateConfig, isrEnabled),
|
|
2197
|
+
pathRevalidate: !isrEnabled || isFrameworkMode || revalidateConfig == null || typeof revalidateConfig !== "object" ? void 0 : revalidateConfig
|
|
2198
|
+
});
|
|
2199
|
+
}
|
|
2200
|
+
async function runStaticDeploy(root, config, client, preset, skipBuild, gitMetadata, hashedAssetsPrefix, headerRules, redirectRules, fallbackRules, routing, cliLog, notFound) {
|
|
2201
|
+
const typeLabel = preset.appType === "spa" ? "Static SPA" : "Static Site";
|
|
2202
|
+
log.info(`${typeLabel} deploy`);
|
|
2203
|
+
cliLog?.info("deploy_mode", {
|
|
2204
|
+
mode: "static",
|
|
2205
|
+
appType: preset.appType
|
|
2206
|
+
});
|
|
2207
|
+
if (preset.buildCommand && !skipBuild) {
|
|
2208
|
+
log.step("Building...");
|
|
2209
|
+
cliLog?.info("build_start", { command: preset.buildCommand });
|
|
2210
|
+
const buildStartedAt = Date.now();
|
|
2211
|
+
execSync(preset.buildCommand, {
|
|
2212
|
+
cwd: root,
|
|
2213
|
+
stdio: "inherit",
|
|
2214
|
+
env: buildEnv({ ...MANAGED_DEPLOY_BUILD_ENV }, { localBinRoot: root })
|
|
2215
|
+
});
|
|
2216
|
+
cliLog?.info("build_end", { durationMs: Date.now() - buildStartedAt });
|
|
2217
|
+
} else if (skipBuild) cliLog?.info("build_skipped", { reason: "skipBuild" });
|
|
2218
|
+
if (!existsSync(preset.outputDir)) {
|
|
2219
|
+
log.error(`deploy: Output directory '${preset.outputDir}' not found. Run the build first.`);
|
|
2220
|
+
process.exit(1);
|
|
2221
|
+
}
|
|
2222
|
+
if (routing || !headerRules && !redirectRules && !fallbackRules) {
|
|
2223
|
+
const merged = mergeAndLintRoutingRules(preset.outputDir, routing);
|
|
2224
|
+
headerRules = merged.headerRules;
|
|
2225
|
+
redirectRules = merged.redirectRules;
|
|
2226
|
+
fallbackRules = merged.fallbackRules;
|
|
2227
|
+
}
|
|
2228
|
+
const s = createSpinner();
|
|
2229
|
+
const onProgress = (msg) => s.message(msg);
|
|
2230
|
+
s.start("Checking for changes...");
|
|
2231
|
+
cliLog?.info("preflight_start", { dir: preset.outputDir });
|
|
2232
|
+
const { assetManifest, assetFiles } = await collectAndHashAssets(preset.outputDir, onProgress);
|
|
2233
|
+
const { needed, skipped } = await client.preflight(config.projectId, assetManifest);
|
|
2234
|
+
s.stop("Checked for changes");
|
|
2235
|
+
cliLog?.info("preflight_end", {
|
|
2236
|
+
total: Object.keys(assetManifest).length,
|
|
2237
|
+
needed: needed.length,
|
|
2238
|
+
skipped
|
|
2239
|
+
});
|
|
2240
|
+
if (skipped > 0) log.info(`Skipping ${formatCount(skipped, "unchanged asset")}`);
|
|
2241
|
+
s.start("Packaging...");
|
|
2242
|
+
cliLog?.info("package_start", { mode: "static" });
|
|
2243
|
+
const packageStartedAt = Date.now();
|
|
2244
|
+
const manifest = buildStaticManifest({
|
|
2245
|
+
appType: preset.appType,
|
|
2246
|
+
hashedAssetsPrefix: hashedAssetsPrefix ?? "assets",
|
|
2247
|
+
headerRules,
|
|
2248
|
+
redirectRules,
|
|
2249
|
+
fallbackRules,
|
|
2250
|
+
notFound: notFound ?? routing?.notFound
|
|
2251
|
+
});
|
|
2252
|
+
cliLog?.info("package_end", {
|
|
2253
|
+
mode: "static",
|
|
2254
|
+
durationMs: Date.now() - packageStartedAt
|
|
2255
|
+
});
|
|
2256
|
+
const result = await streamDirectR2Deploy({
|
|
2257
|
+
client,
|
|
2258
|
+
projectId: config.projectId,
|
|
2259
|
+
manifest,
|
|
2260
|
+
assetManifest,
|
|
2261
|
+
assetFiles,
|
|
2262
|
+
needed,
|
|
2263
|
+
gitMetadata,
|
|
2264
|
+
preflightUsed: skipped > 0,
|
|
2265
|
+
spinner: s,
|
|
2266
|
+
cliLog
|
|
2267
|
+
});
|
|
2268
|
+
note(formatDeploySummary({
|
|
2269
|
+
typeLabel,
|
|
2270
|
+
assets: result.assets,
|
|
2271
|
+
unchangedAssets: skipped
|
|
2272
|
+
}), result.url);
|
|
2273
|
+
warnUnpublishedRouting(result);
|
|
2274
|
+
printDeploySuccess(result.published !== false);
|
|
2275
|
+
}
|
|
2276
|
+
//#endregion
|
|
2277
|
+
export { resolveDeployLiveBindingConfig as A, generateDeployWrapperIfNeeded as C, hasPrerenderSources as D, hasNonPageBackend as E, resolveStaticDeployPreset as F, toManifestSchedulesAndQueues as I, validateDeployMigrations as L, resolveDeployWranglerCompat as M, resolveFullDeployBuildCommand as N, log as O, resolveStaticBuildCommand as P, needsWrapper as R, ensureManagedHyperdrive as S, getGitCommit as T, hasDeploySchemaDrift as _, buildStaticManifest as a, assertWorkerMainUploaded as b, detectNonSpaFallbackWarning as c, printDeploySuccess as d, mergeAndLintRoutingRules as f, getDeploySchemaDriftMessage as g, assertDeploySchemaDriftFree as h, runFrameworkDeploy as i, resolveDeployWorkerMain as j, promptDeployProjectConfig as k, formatDeploySummary as l, warnUnpublishedRouting as m, runStaticDeploy as n, collectAndHashAssets as o, streamDirectR2Deploy as p, assertManagedDurableStateSupported as r, collectWorkerFiles as s, assembleVoidManifest as t, printDeployContext as u, assertEnvSchemaValid as v, getExitCode as w, createProjectFromSlug as x, assertProjectDeployAccess as y };
|