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
|
@@ -1,36 +1,21 @@
|
|
|
1
|
-
import { a as join, n as dirname, o as normalize, t as basename
|
|
1
|
+
import { a as join, c as resolve$2, n as dirname, o as normalize, t as basename } from "./dist-ipQi0Q8y.mjs";
|
|
2
2
|
import { i as voidWarn, r as voidLog } from "./log-vqY3pqwI.mjs";
|
|
3
|
-
import { _ as persistMaterializedWranglerConfig, j as writeProvisionedWranglerConfig, s as assertNoDuplicateMembers, y as readProvisionedBindingEntry } from "./wrangler-
|
|
4
|
-
import { n as scanWranglerDotenvKeys } from "./dotenv-
|
|
5
|
-
import { l as resolveCloudflareOperationArgv, n as decodeCloudflareOperationResult, r as encodeCloudflareOperation } from "./cloudflare-process-
|
|
3
|
+
import { _ as persistMaterializedWranglerConfig, j as writeProvisionedWranglerConfig, s as assertNoDuplicateMembers, y as readProvisionedBindingEntry } from "./wrangler-BYs07Rxy.mjs";
|
|
4
|
+
import { n as scanWranglerDotenvKeys } from "./dotenv-CNazY3-w.mjs";
|
|
5
|
+
import { l as resolveCloudflareOperationArgv, n as decodeCloudflareOperationResult, r as encodeCloudflareOperation } from "./cloudflare-process-CeDWJRhj.mjs";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { closeSync, existsSync, fsyncSync, lstatSync, openSync, readFileSync, readlinkSync, realpathSync, renameSync, unlinkSync, writeSync } from "node:fs";
|
|
8
8
|
import { isRecord } from "@void/deploy-core";
|
|
9
9
|
import { randomUUID } from "node:crypto";
|
|
10
10
|
import { parse } from "jsonc-parser";
|
|
11
|
-
import { types } from "node:util";
|
|
12
11
|
import { spawn } from "node:child_process";
|
|
13
12
|
import { resolve } from "node:path/posix";
|
|
14
13
|
import { resolve as resolve$1 } from "node:path/win32";
|
|
15
14
|
import lockfile from "proper-lockfile";
|
|
16
15
|
//#region src/cli/provision.ts
|
|
17
16
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* provisioned ONLY when its binding entry exists with a REAL (non-placeholder)
|
|
21
|
-
* identifier — the SAME predicate the provisioner's read-first uses:
|
|
22
|
-
* - D1 `database_id` is a real UUID ({@link isRealD1Id}, not `'local'`);
|
|
23
|
-
* - KV `id` is a real 32-hex ({@link isRealKvId}, not `'local'`);
|
|
24
|
-
* - R2 `bucket_name` names a real bucket ({@link isRealStoredR2BucketName});
|
|
25
|
-
* - Hyperdrive `id` is a real 32-hex ({@link isRealHyperdriveId}).
|
|
26
|
-
*
|
|
27
|
-
* Queues are NAME-bearing (no id in the config), so they are NOT part of this
|
|
28
|
-
* id-drift check. A missing config file yields every desired resource as missing.
|
|
29
|
-
*
|
|
30
|
-
* Used by the deploy orchestrator to decide whether automatic provisioning is needed
|
|
31
|
-
* and to verify that every id-bearing resource was satisfied afterward.
|
|
32
|
-
* A malformed/duplicate-binding config makes {@link readProvisionedBindingEntry}
|
|
33
|
-
* throw — fail closed, exactly as the provisioner would.
|
|
17
|
+
* Desired id-bearing resources whose binding has no real id in the root config, using the
|
|
18
|
+
* same rule as read-first. Reads only the local config; queues have no id and are skipped.
|
|
34
19
|
*/
|
|
35
20
|
function checkCloudflareProvisionDrift(configPath, desired) {
|
|
36
21
|
const config = readRootConfig(configPath);
|
|
@@ -76,22 +61,13 @@ const KV_ID_PLACEHOLDER = "local";
|
|
|
76
61
|
const R2_BUCKET_NAME_PLACEHOLDER = "default";
|
|
77
62
|
const D1_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
78
63
|
const HEX32_ID_RE = /^[0-9a-f]{32}$/i;
|
|
79
|
-
/** R2 bucket
|
|
64
|
+
/** R2 bucket-name grammar (wrangler's `isValidR2BucketName`). */
|
|
80
65
|
const R2_BUCKET_NAME_RE = /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/;
|
|
81
|
-
/**
|
|
82
|
-
* A D1 `database_id` that is a real (non-placeholder) hyphenated UUID. Exported so
|
|
83
|
-
* the deploy orchestrator applies the SAME placeholder rule when deciding whether a
|
|
84
|
-
* stored `database_name` names a real DB (a synced `database_id: "local"` must not
|
|
85
|
-
* let its paired name become the production database identity).
|
|
86
|
-
*/
|
|
66
|
+
/** A real (non-placeholder) D1 `database_id`. */
|
|
87
67
|
function isRealD1Id(value) {
|
|
88
68
|
return typeof value === "string" && value !== D1_ID_PLACEHOLDER && D1_ID_RE.test(value);
|
|
89
69
|
}
|
|
90
|
-
/**
|
|
91
|
-
* A KV namespace `id` that is a real (non-placeholder) 32-hex string. Exported so the
|
|
92
|
-
* deploy orchestrator applies the SAME placeholder rule when checking whether two KV
|
|
93
|
-
* bindings already share one provisioned namespace.
|
|
94
|
-
*/
|
|
70
|
+
/** A real (non-placeholder) KV namespace `id`. */
|
|
95
71
|
function isRealKvId(value) {
|
|
96
72
|
return typeof value === "string" && value !== KV_ID_PLACEHOLDER && HEX32_ID_RE.test(value);
|
|
97
73
|
}
|
|
@@ -104,11 +80,8 @@ function isValidR2BucketNameFormat(value) {
|
|
|
104
80
|
return typeof value === "string" && R2_BUCKET_NAME_RE.test(value);
|
|
105
81
|
}
|
|
106
82
|
/**
|
|
107
|
-
* A
|
|
108
|
-
*
|
|
109
|
-
* `local` IS a real name here. Grammar is
|
|
110
|
-
* deliberately not required: a grammar-invalid stored name that contradicts the desired
|
|
111
|
-
* bucket must still fail closed as a contradiction, never be silently rewritten.
|
|
83
|
+
* A stored R2 `bucket_name` naming a real bucket: any non-empty string except the `default`
|
|
84
|
+
* placeholder (unless desired). Grammar is not checked, so a contradicting name still fails.
|
|
112
85
|
*/
|
|
113
86
|
function isRealStoredR2BucketName(value, desiredName) {
|
|
114
87
|
return typeof value === "string" && value.length > 0 && (value !== R2_BUCKET_NAME_PLACEHOLDER || value === desiredName);
|
|
@@ -118,16 +91,8 @@ const defaultLogger = {
|
|
|
118
91
|
warn: (msg) => voidWarn(msg)
|
|
119
92
|
};
|
|
120
93
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* different env-block/account) than the ONE this provision pinned:
|
|
124
|
-
* - `CLOUDFLARE_API_BASE_URL` / `CF_API_BASE_URL` — override the API base URL directly
|
|
125
|
-
* (`getCloudflareApiBaseUrlFromEnv`).
|
|
126
|
-
* - `WRANGLER_API_ENVIRONMENT=staging` — routes to `api.staging.cloudflare.com`.
|
|
127
|
-
* - `CLOUDFLARE_COMPLIANCE_REGION=fedramp_high` — routes to `api.fed.cloudflare.com`.
|
|
128
|
-
* - `CLOUDFLARE_ENV` — selects a named `env.*` config block (different account/bindings).
|
|
129
|
-
* We pin ONE resolved base explicitly (see {@link provisionEnv}) and fail closed when a
|
|
130
|
-
* project dotenv would reselect one (see {@link assertNoTransportRedirectInProjectDotenv}).
|
|
94
|
+
* Env vars that move wrangler's API transport to another endpoint or select another config
|
|
95
|
+
* env. Provisioning pins one base ({@link provisionEnv}) and rejects them in project dotenv.
|
|
131
96
|
*/
|
|
132
97
|
const TRANSPORT_REDIRECT_ENV_VARS = [
|
|
133
98
|
"CLOUDFLARE_API_BASE_URL",
|
|
@@ -137,53 +102,17 @@ const TRANSPORT_REDIRECT_ENV_VARS = [
|
|
|
137
102
|
"CLOUDFLARE_ENV"
|
|
138
103
|
];
|
|
139
104
|
/**
|
|
140
|
-
* Wrangler
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* (`getOutputFilePathFromEnv`/`getOutputFileDirectoryFromEnv`), wrangler's startup
|
|
144
|
-
* middleware appends a `wrangler-session` entry with the VERBATIM
|
|
145
|
-
* `command_line_args: argv` to that file (`writeOutput` in wrangler-dist/cli.js) — so
|
|
146
|
-
* `hyperdrive create --connection-string=postgres://user:PASSWORD@…` lands on disk.
|
|
147
|
-
* They are scrubbed from every provisioner child env ({@link provisionEnv}) and fenced in
|
|
148
|
-
* the project dotenv ({@link assertNoTransportRedirectInProjectDotenv}) — wrangler reloads
|
|
149
|
-
* dotenv into `process.env` BEFORE writing the session entry, so a dotenv-declared sink
|
|
150
|
-
* would resurrect the leak. `WRANGLER_LOG_SANITIZE` (`getSanitizeLogs`, default true) is
|
|
151
|
-
* PINNED to `true` instead: a truthy ambient value survives wrangler's dotenv reload
|
|
152
|
-
* (dotenv-expand keeps an already-set truthy `process.env` value), so the pin cannot be
|
|
153
|
-
* overridden into unsanitized logging of the secret-bearing API request.
|
|
105
|
+
* Wrangler session-output sinks. When set, wrangler writes the raw argv to disk, which can
|
|
106
|
+
* include a Hyperdrive password. They are scrubbed from the child env and rejected in project
|
|
107
|
+
* dotenv, which wrangler reloads before writing.
|
|
154
108
|
*/
|
|
155
109
|
const OUTPUT_FILE_ENV_VARS = ["WRANGLER_OUTPUT_FILE_PATH", "WRANGLER_OUTPUT_FILE_DIRECTORY"];
|
|
156
110
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
* getCloudflareApiBaseUrlFromEnv() ??
|
|
163
|
-
* `https://api${getComplianceRegionSubdomain(complianceConfig)}${getStagingSubdomain()}.cloudflare.com/client/v4`
|
|
164
|
-
*
|
|
165
|
-
* Order + semantics mirrored exactly:
|
|
166
|
-
* 1. The DIRECT base override is evaluated FIRST, by PRESENCE (wrangler's env factory
|
|
167
|
-
* checks `variableName in process.env`), with `CLOUDFLARE_API_BASE_URL` taking
|
|
168
|
-
* precedence over the deprecated `CF_API_BASE_URL`. A present-but-EMPTY override is
|
|
169
|
-
* returned AS-IS: `"" ?? derived` keeps the empty string (NULLISH, not falsy,
|
|
170
|
-
* coalescing), so wrangler pins the empty base (its API calls then fail on the
|
|
171
|
-
* unparsable URL) — we mirror that instead of silently substituting an endpoint.
|
|
172
|
-
* 2. Only on the DERIVED branch are the staging/compliance selectors read — wrangler's
|
|
173
|
-
* `??` short-circuits past `getComplianceRegionSubdomain`/`getStagingSubdomain` when a
|
|
174
|
-
* direct base is present, so a direct base + conflicting compliance settings resolves
|
|
175
|
-
* to the direct base in wrangler (NO conflict error) and must not be rejected here.
|
|
176
|
-
* 3. On the derived branch both env selectors are choice-VALIDATED (wrangler's
|
|
177
|
-
* `getProcessEnv` → `assertOneOf` throws on any present value outside the choices,
|
|
178
|
-
* e.g. `WRANGLER_API_ENVIRONMENT=stagng` or an empty string), so an invalid selector
|
|
179
|
-
* FAILS CLOSED instead of silently pinning public production.
|
|
180
|
-
* 4. The config's own `compliance_region` VALUE is validated UNCONDITIONALLY — wrangler's
|
|
181
|
-
* config validation (`isOneOf("public", "fedramp_high")` on the `compliance_region`
|
|
182
|
-
* field) rejects an invalid config value for every command regardless of which branch
|
|
183
|
-
* resolves the base.
|
|
184
|
-
* Read ONCE from the REAL ambient `process.env` (which does NOT include the project `.env`
|
|
185
|
-
* — Node never auto-loads it), so an ambient/CI pin is honored while a project dotenv
|
|
186
|
-
* redirect is rejected up front by {@link assertNoTransportRedirectInProjectDotenv}.
|
|
111
|
+
* The one API base URL this provision uses, resolved like wrangler's `getCloudflareApiBaseUrl`
|
|
112
|
+
* so the Hyperdrive REST lister and wrangler agree:
|
|
113
|
+
* 1. a present `CLOUDFLARE_API_BASE_URL` (then `CF_API_BASE_URL`) is used as-is, even empty;
|
|
114
|
+
* 2. otherwise the staging and compliance selectors derive it, and invalid values fail;
|
|
115
|
+
* 3. the config's `compliance_region` is always validated.
|
|
187
116
|
*/
|
|
188
117
|
function resolvePinnedApiBaseUrl(env = process.env, configComplianceRegion) {
|
|
189
118
|
const configRegion = configComplianceRegion !== void 0 ? assertComplianceChoice(configComplianceRegion, "the wrangler config \"compliance_region\"") : void 0;
|
|
@@ -191,51 +120,27 @@ function resolvePinnedApiBaseUrl(env = process.env, configComplianceRegion) {
|
|
|
191
120
|
if (direct !== void 0) return direct;
|
|
192
121
|
return `https://api${resolveComplianceRegion(env, configRegion) === "fedramp_high" ? ".fed" : ""}${resolveApiEnvironment(env) === "staging" ? ".staging" : ""}.cloudflare.com/client/v4`;
|
|
193
122
|
}
|
|
194
|
-
/**
|
|
195
|
-
* Mirror wrangler 4.131's `getCloudflareApiEnvironmentFromEnv` (env factory with
|
|
196
|
-
* `choices: ["production", "staging"]`, default `"production"`): an ABSENT variable
|
|
197
|
-
* defaults to production; a PRESENT value outside the choices — `"stagng"`, an empty
|
|
198
|
-
* string — makes wrangler's `assertOneOf` throw, so we fail closed identically instead of
|
|
199
|
-
* silently pinning public production where wrangler stops.
|
|
200
|
-
*/
|
|
123
|
+
/** `WRANGLER_API_ENVIRONMENT` like wrangler: absent means production; other values fail. */
|
|
201
124
|
function resolveApiEnvironment(env) {
|
|
202
125
|
const raw = env.WRANGLER_API_ENVIRONMENT;
|
|
203
126
|
if (raw === void 0) return "production";
|
|
204
127
|
if (raw !== "production" && raw !== "staging") throw new Error(`provision: the WRANGLER_API_ENVIRONMENT environment variable must be one of "production" or "staging", got ${JSON.stringify(raw)}. wrangler rejects this value, so provisioning refuses to guess an endpoint; fix or unset it and retry.`);
|
|
205
128
|
return raw;
|
|
206
129
|
}
|
|
207
|
-
/**
|
|
130
|
+
/** Compliance-region values wrangler accepts. */
|
|
208
131
|
const COMPLIANCE_REGIONS = ["public", "fedramp_high"];
|
|
209
132
|
function assertComplianceChoice(value, source) {
|
|
210
133
|
if (value !== "public" && value !== "fedramp_high") throw new Error(`provision: ${source} must be one of ${COMPLIANCE_REGIONS.map((c) => JSON.stringify(c)).join(" or ")}, got ${JSON.stringify(value)}.`);
|
|
211
134
|
return value;
|
|
212
135
|
}
|
|
213
|
-
/**
|
|
214
|
-
* Reconcile the effective Cloudflare compliance region, mirroring wrangler 4.131's
|
|
215
|
-
* `getCloudflareComplianceRegion(complianceConfig)` EXACTLY (evaluated only on the
|
|
216
|
-
* DERIVED-base branch — with a direct base override wrangler never calls it):
|
|
217
|
-
* - reads `CLOUDFLARE_COMPLIANCE_REGION` by PRESENCE and validates it against the
|
|
218
|
-
* `public`/`fedramp_high` choices (wrangler's `getProcessEnv` → `assertOneOf`, which
|
|
219
|
-
* throws on ANY present out-of-choices value — an empty string included);
|
|
220
|
-
* - when BOTH the env var AND the (already-validated) config region are set to
|
|
221
|
-
* DIFFERENT values, THROWS (fail closed) — wrangler raises a `UserError` for exactly
|
|
222
|
-
* this conflict, so we must not paper over it by pinning one endpoint while the config
|
|
223
|
-
* declares the other region;
|
|
224
|
-
* - otherwise returns `env || config || "public"` (both values are validated choices by
|
|
225
|
-
* now, so wrangler's falsy-coalescing is equivalent to nullish here).
|
|
226
|
-
*/
|
|
136
|
+
/** Effective compliance region like wrangler: values are validated and a conflict fails. */
|
|
227
137
|
function resolveComplianceRegion(env, configRegion) {
|
|
228
138
|
const rawEnv = env.CLOUDFLARE_COMPLIANCE_REGION;
|
|
229
139
|
const envRegion = rawEnv !== void 0 ? assertComplianceChoice(rawEnv, "the CLOUDFLARE_COMPLIANCE_REGION environment variable") : void 0;
|
|
230
140
|
if (envRegion !== void 0 && configRegion !== void 0 && envRegion !== configRegion) throw new Error(`provision: the Cloudflare compliance region is set to conflicting values — the CLOUDFLARE_COMPLIANCE_REGION environment variable is ${JSON.stringify(envRegion)} but the wrangler config "compliance_region" is ${JSON.stringify(configRegion)}. wrangler refuses to run with a mismatched compliance region; reconcile them (remove one, or set them equal) and retry.`);
|
|
231
141
|
return envRegion ?? configRegion ?? "public";
|
|
232
142
|
}
|
|
233
|
-
/**
|
|
234
|
-
* Mirror wrangler 4.131's `getDefaultEnvFiles(env)`: base `.env`/`.env.local`, then —
|
|
235
|
-
* when the environment argument is DEFINED (we always pass `--env=`, so wrangler sees
|
|
236
|
-
* `args.env === ""`, which is `!== undefined`) — the per-env pair `.env.${env}`/`.env.${env}.local`.
|
|
237
|
-
* With the empty environment those are the empty-env filenames `.env.` and `.env..local`.
|
|
238
|
-
*/
|
|
143
|
+
/** Dotenv files wrangler loads for `--env=<env>`; provisioning always passes an empty env. */
|
|
239
144
|
function defaultWranglerEnvFiles(env) {
|
|
240
145
|
return [
|
|
241
146
|
".env",
|
|
@@ -245,20 +150,9 @@ function defaultWranglerEnvFiles(env) {
|
|
|
245
150
|
];
|
|
246
151
|
}
|
|
247
152
|
/**
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
* setting `CLOUDFLARE_API_BASE_URL` / `WRANGLER_API_ENVIRONMENT` /
|
|
252
|
-
* `CLOUDFLARE_COMPLIANCE_REGION` / `CLOUDFLARE_ENV` would redirect wrangler's authenticated
|
|
253
|
-
* transport (and credentials) to a staging / FedRAMP / arbitrary endpoint AFTER we resolved
|
|
254
|
-
* the pinned base — while the Hyperdrive existence check runs against the pinned base.
|
|
255
|
-
* Single-account/single-endpoint provisioning cannot reason about a transport redirected
|
|
256
|
-
* out from under it, so we refuse. The fix is to export the variable in the shell/CI
|
|
257
|
-
* environment (which we DO honor, pinning both wrangler and the lister to it) instead of
|
|
258
|
-
* the project dotenv.
|
|
259
|
-
*
|
|
260
|
-
* Exported for direct unit testing of the file-set and the Windows case rule; production
|
|
261
|
-
* always calls it with the real `process.platform`.
|
|
153
|
+
* Fail when a project dotenv file sets a transport-redirect or session-output variable.
|
|
154
|
+
* Wrangler reloads these files on every command, which would move its authenticated transport
|
|
155
|
+
* away from the pinned base. Values exported in the shell are honored instead.
|
|
262
156
|
*/
|
|
263
157
|
function assertNoTransportRedirectInProjectDotenv(root, platform = process.platform) {
|
|
264
158
|
const dotenvCaseInsensitive = platform === "win32";
|
|
@@ -281,14 +175,8 @@ function assertNoTransportRedirectInProjectDotenv(root, platform = process.platf
|
|
|
281
175
|
}
|
|
282
176
|
}
|
|
283
177
|
/**
|
|
284
|
-
* Delete `name` from `env`
|
|
285
|
-
*
|
|
286
|
-
* resolves `WRANGLER_OUTPUT_FILE_PATH` from an ambient `Wrangler_Output_File_Path`
|
|
287
|
-
* too, so an exact-case `delete` leaves a mixed-case spelling live. On win32 every
|
|
288
|
-
* case-insensitive spelling is removed; elsewhere POSIX env vars are case-sensitive,
|
|
289
|
-
* so only the exact key is a match. Exported so the deploy orchestrator scrubs its
|
|
290
|
-
* OWN extra deploy-time keys (worker-name override, build-config-path) through the
|
|
291
|
-
* SAME rule the provisioner uses, instead of re-deriving it.
|
|
178
|
+
* Delete `name` from `env` in place. Windows env keys are case-insensitive, so every spelling
|
|
179
|
+
* is removed there; elsewhere only the exact key.
|
|
292
180
|
*/
|
|
293
181
|
function deleteEnvVarCaseAware(env, name, platform = process.platform) {
|
|
294
182
|
if (platform !== "win32") {
|
|
@@ -299,19 +187,8 @@ function deleteEnvVarCaseAware(env, name, platform = process.platform) {
|
|
|
299
187
|
for (const key of Object.keys(env)) if (key.toLowerCase() === lower) delete env[key];
|
|
300
188
|
}
|
|
301
189
|
/**
|
|
302
|
-
* Child
|
|
303
|
-
*
|
|
304
|
-
* HTTP requests and must never leak into the spawned wrangler process (which may
|
|
305
|
-
* in turn run user hooks). This includes the short-lived cloudflared
|
|
306
|
-
* `CF_ACCESS_TOKEN` JWT used by local readiness probes.
|
|
307
|
-
*
|
|
308
|
-
* Pins ONE transport context: scrubs every {@link TRANSPORT_REDIRECT_ENV_VARS}
|
|
309
|
-
* from the child env, then re-sets `CLOUDFLARE_API_BASE_URL` to the SINGLE resolved
|
|
310
|
-
* `apiBaseUrl` so the spawned wrangler and the Hyperdrive REST lister hit the exact same
|
|
311
|
-
* endpoint. Scrubbing the compliance/staging/env selectors makes the explicit base
|
|
312
|
-
* authoritative (they can no longer re-derive a different base). Combined with
|
|
313
|
-
* {@link assertNoTransportRedirectInProjectDotenv} (which blocks a project dotenv from
|
|
314
|
-
* repopulating any of them via wrangler's per-command reload), the pin cannot be moved.
|
|
190
|
+
* Child env for a Cloudflare operation. CF Access credentials, transport selectors and output
|
|
191
|
+
* sinks are scrubbed; the account, API base and sanitized logging are pinned.
|
|
315
192
|
*/
|
|
316
193
|
function provisionEnv(ctx) {
|
|
317
194
|
const env = { ...process.env };
|
|
@@ -344,53 +221,24 @@ function provisionEnv(ctx) {
|
|
|
344
221
|
}
|
|
345
222
|
return env;
|
|
346
223
|
}
|
|
347
|
-
/**
|
|
348
|
-
* Hard per-command timeout for a single wrangler invocation. A hung wrangler must
|
|
349
|
-
* NEVER hang the whole provision, so the default runner SIGKILLs a child that runs
|
|
350
|
-
* past this and reports a HARD failure (a non-null signal — see
|
|
351
|
-
* {@link normalizeRunWranglerResult}). With the ASYNC runner this bound is
|
|
352
|
-
* INDEPENDENT of {@link LOCK_STALE_MS}: awaiting the child keeps the event loop free,
|
|
353
|
-
* so `proper-lockfile`'s heartbeat refreshes the mutex throughout — the timeout only
|
|
354
|
-
* bounds a hang, it is not load-bearing for lock safety.
|
|
355
|
-
*/
|
|
224
|
+
/** Per-command timeout; the default runner SIGKILLs a child that runs longer. */
|
|
356
225
|
const COMMAND_TIMEOUT_MS = 12e4;
|
|
357
226
|
/**
|
|
358
|
-
* After a
|
|
359
|
-
*
|
|
360
|
-
* giving up. SIGKILL is uncatchable, so a healthy kernel reaps the group within
|
|
361
|
-
* milliseconds; this generous bound only guards a pathological uninterruptible-sleep child.
|
|
362
|
-
* The runner NEVER settles a timeout as "done" until both facts are confirmed; an expiry
|
|
363
|
-
* (or a failed kill) REJECTS with a {@link WranglerQuarantineError} instead, so the
|
|
364
|
-
* whole-provision lock — released only after a CONFIRMED settle — is intentionally
|
|
365
|
-
* retained while the real wrangler CLI may still be creating resources.
|
|
227
|
+
* After a SIGKILL, how long to wait for both `close` and the whole process group to be gone.
|
|
228
|
+
* If that cannot be confirmed, the runner rejects with {@link WranglerQuarantineError}.
|
|
366
229
|
*/
|
|
367
230
|
const CONFIRM_KILL_GRACE_MS = 5e3;
|
|
368
231
|
/** The pid-poll interval while confirming post-SIGKILL termination. */
|
|
369
232
|
const CONFIRM_KILL_POLL_MS = 20;
|
|
370
|
-
/**
|
|
371
|
-
* The margin the coordinator-WIDE teardown deadline adds on top of a
|
|
372
|
-
* runner's own confirmation grace window: a signal teardown is bounded at
|
|
373
|
-
* `confirmGraceMs + this` even if the runner's bounded confirmation never calls back.
|
|
374
|
-
*/
|
|
233
|
+
/** Extra time a signal teardown allows beyond a runner's confirmation window. */
|
|
375
234
|
const SIGNAL_TEARDOWN_DEADLINE_MARGIN_MS = 5e3;
|
|
376
|
-
/**
|
|
377
|
-
* Classify a process-group kill error. `ESRCH` means the group ALREADY exited (there was
|
|
378
|
-
* nothing left to kill) → success. ANY other code (`EPERM`, …) is a REAL kill failure: the
|
|
379
|
-
* child may still be running, so it must be surfaced, never swallowed. Exported for direct
|
|
380
|
-
* unit testing of the classification the timeout/cancel paths rely on.
|
|
381
|
-
*/
|
|
235
|
+
/** ESRCH means the group already exited; any other kill error is a real failure. */
|
|
382
236
|
function isAlreadyGoneKillError(error) {
|
|
383
237
|
return error?.code === "ESRCH";
|
|
384
238
|
}
|
|
385
239
|
/**
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
* non-ESRCH reason (EPERM, …), or the post-SIGKILL confirmation window expired with the
|
|
389
|
-
* group still (possibly) alive. The child may STILL be creating resources / writing
|
|
390
|
-
* config, so the whole-provision mutex must be INTENTIONALLY RETAINED — never released —
|
|
391
|
-
* and this error surfaces with manual-recovery guidance naming the surviving pid.
|
|
392
|
-
* {@link provisionCloudflareResources} detects it via {@link isQuarantineError} and calls
|
|
393
|
-
* `lock.quarantine()` instead of `lock.release()`.
|
|
240
|
+
* The runner could not confirm that a spawned child and its process group are dead. The child
|
|
241
|
+
* may still be creating resources, so the provision lock is kept instead of released.
|
|
394
242
|
*/
|
|
395
243
|
var WranglerQuarantineError = class extends Error {
|
|
396
244
|
quarantine = true;
|
|
@@ -413,17 +261,8 @@ function acquireProcessProvisionSlot() {
|
|
|
413
261
|
};
|
|
414
262
|
}
|
|
415
263
|
/**
|
|
416
|
-
* The
|
|
417
|
-
*
|
|
418
|
-
* SIGINT/SIGTERM while a spawned wrangler child's teardown could NOT be confirmed,
|
|
419
|
-
* proper-lockfile's signal-exit sweep would remove the still-TRACKED provision mutex —
|
|
420
|
-
* releasing the lock under a possibly-live child. {@link provisionCloudflareResources}
|
|
421
|
-
* sets this for the duration of the provision; a registering wrangler child CAPTURES it
|
|
422
|
-
* at registration (see {@link registerRunner}) so an unresolved teardown quarantines
|
|
423
|
-
* exactly ITS OWNING provision lock with ITS OWN pid metadata — never a broadcast
|
|
424
|
-
* through every lock (the previous Set this replaces stamped the FIRST unresolved
|
|
425
|
-
* registrant's pid into every lock's sidecar, so a foreign group's death could clear a
|
|
426
|
-
* quarantine whose real child was alive).
|
|
264
|
+
* The active provision's lock-quarantine hook. A child captures it at registration, so a
|
|
265
|
+
* failed teardown quarantines only its own provision lock with its own pid.
|
|
427
266
|
*/
|
|
428
267
|
let activeProvisionQuarantineHook;
|
|
429
268
|
/** True when `error` is a quarantine failure whose provision lock must stay retained. */
|
|
@@ -493,8 +332,7 @@ function coordinatorHandleSignal(signal) {
|
|
|
493
332
|
teardown.dispatching = false;
|
|
494
333
|
maybeFinishCoordinatorTeardown(teardown);
|
|
495
334
|
}
|
|
496
|
-
/** Finish
|
|
497
|
-
* snapshot registrant resolved (confirmed or quarantined). */
|
|
335
|
+
/** Finish after dispatch completes and every registrant is resolved. */
|
|
498
336
|
function maybeFinishCoordinatorTeardown(teardown) {
|
|
499
337
|
if (!teardown.dispatching && teardown.pending === 0) finishCoordinatorTeardown(teardown);
|
|
500
338
|
}
|
|
@@ -554,12 +392,7 @@ function finishCoordinatorTeardown(teardown) {
|
|
|
554
392
|
const RETENTION_RETRY_MIN_MS = 250;
|
|
555
393
|
const RETENTION_RETRY_MAX_MS = 5e3;
|
|
556
394
|
let retentionWatchdog;
|
|
557
|
-
/**
|
|
558
|
-
* Quarantine `lock`, and on a PERSISTENCE failure arm the retention
|
|
559
|
-
* watchdog BEFORE rethrowing. THE single quarantine entry point for provision-level
|
|
560
|
-
* code (the body-error path and the signal coordinator's hook both route through it),
|
|
561
|
-
* so no persistence failure can leave the process free to drain unprotected.
|
|
562
|
-
*/
|
|
395
|
+
/** Quarantine `lock`; if persisting fails, arm the retention watchdog before rethrowing. */
|
|
563
396
|
function retainQuarantinedLock(lock, info) {
|
|
564
397
|
try {
|
|
565
398
|
lock.quarantine(info);
|
|
@@ -610,20 +443,11 @@ function armRetentionWatchdog(lock, info) {
|
|
|
610
443
|
};
|
|
611
444
|
}
|
|
612
445
|
/**
|
|
613
|
-
* Default
|
|
614
|
-
*
|
|
615
|
-
* travel as literal JSON argv; secrets travel through stdin. Captured stdout carries
|
|
616
|
-
* the structured result envelope, while stderr carries provider diagnostics.
|
|
617
|
-
*
|
|
618
|
-
* ASYNC (returns a Promise). `spawn` + `await` (vs. the old blocking
|
|
619
|
-
* `spawnSync`) keeps the Node event loop free while wrangler runs, so
|
|
620
|
-
* `proper-lockfile`'s update timer keeps refreshing the mutex mtime for the WHOLE
|
|
621
|
-
* provision — no wrangler call, however slow, can freeze the loop long enough for
|
|
622
|
-
* another process to see the lock stale and steal it while we still hold it. A
|
|
623
|
-
* {@link COMMAND_TIMEOUT_MS} SIGKILL guards against a hung child.
|
|
446
|
+
* Default runner: spawns the typed operation entry ({@link resolveCloudflareOperationArgv})
|
|
447
|
+
* without a shell; secrets go through stdin. It is async so the lock heartbeat keeps running.
|
|
624
448
|
*/
|
|
625
449
|
function createDefaultRunWrangler(root, options) {
|
|
626
|
-
const launcher = options?.launcher ?? resolveCloudflareOperationArgv(
|
|
450
|
+
const launcher = options?.launcher ?? resolveCloudflareOperationArgv();
|
|
627
451
|
const timeoutMs = options?.timeoutMs ?? COMMAND_TIMEOUT_MS;
|
|
628
452
|
const confirmGraceMs = options?.confirmKillGraceMs ?? CONFIRM_KILL_GRACE_MS;
|
|
629
453
|
const killProcessGroup = options?.killProcessGroup ?? ((target, signal) => process.kill(target, signal));
|
|
@@ -867,14 +691,8 @@ function createDefaultRunWrangler(root, options) {
|
|
|
867
691
|
});
|
|
868
692
|
}
|
|
869
693
|
/**
|
|
870
|
-
* Normalize a
|
|
871
|
-
*
|
|
872
|
-
*
|
|
873
|
-
* The naive `status ?? (error ? 1 : 0)` collapses a SIGTERM'd child (which
|
|
874
|
-
* returns `status: null`, a non-null `signal`, and NO `error`) to `0`, so a
|
|
875
|
-
* killed `create` would be recorded as `created` though nothing happened. So we
|
|
876
|
-
* fail on ANY non-numeric `status`, a non-null `signal`, or a present `error`,
|
|
877
|
-
* and surface the signal/spawn-error text in `stderr` for diagnostics.
|
|
694
|
+
* Normalize a finished child into a {@link RunWranglerResult}. A signal, a spawn error or a
|
|
695
|
+
* non-numeric status is a non-zero failure, never success.
|
|
878
696
|
*/
|
|
879
697
|
function normalizeRunWranglerResult(result) {
|
|
880
698
|
const stdout = result.stdout ?? "";
|
|
@@ -891,12 +709,7 @@ function normalizeRunWranglerResult(result) {
|
|
|
891
709
|
error: result.error != null ? result.error.message : null
|
|
892
710
|
};
|
|
893
711
|
}
|
|
894
|
-
/**
|
|
895
|
-
* THE single provenance rule. A result is a HARD failure when the child was
|
|
896
|
-
* signal-killed or could not be spawned: it did not run to completion, so its
|
|
897
|
-
* buffered status/stdout/stderr is unreliable and must NEVER be read as "absent"
|
|
898
|
-
* or "success". Every success/absence/adopt classifier below defers to this.
|
|
899
|
-
*/
|
|
712
|
+
/** A signal-killed or unspawned child: its output is never read as success or absence. */
|
|
900
713
|
function isHardFailure(result) {
|
|
901
714
|
return result.signal != null || result.error != null;
|
|
902
715
|
}
|
|
@@ -904,27 +717,13 @@ function isHardFailure(result) {
|
|
|
904
717
|
function isCleanSuccess(result) {
|
|
905
718
|
return !isHardFailure(result) && result.status === 0;
|
|
906
719
|
}
|
|
907
|
-
/**
|
|
908
|
-
* The child ran to completion and reported a FAILURE status (a numeric non-zero
|
|
909
|
-
* exit, no signal, no spawn error) — the ONLY case whose stdout/stderr may be
|
|
910
|
-
* classified for a resource-specific not-found signal.
|
|
911
|
-
*/
|
|
720
|
+
/** A completed child with a non-zero exit: the only result that may mean "not found". */
|
|
912
721
|
function isCleanFailure(result) {
|
|
913
722
|
return !isHardFailure(result) && Number.isSafeInteger(result.status) && result.status > 0;
|
|
914
723
|
}
|
|
915
|
-
/**
|
|
916
|
-
* The OVERALL deadline for the Hyperdrive existence lister (across ALL its paginated
|
|
917
|
-
* requests). A stalled body or a never-settling injected seam must never hold the
|
|
918
|
-
* whole-provision lock (whose heartbeat keeps refreshing while the loop is free) forever, so
|
|
919
|
-
* the caller fails closed at this bound.
|
|
920
|
-
*/
|
|
724
|
+
/** Overall deadline for the Hyperdrive lister, so a stalled list cannot hold the lock forever. */
|
|
921
725
|
const HYPERDRIVE_LIST_TIMEOUT_MS = 6e4;
|
|
922
|
-
/**
|
|
923
|
-
* Run a lister call under an OVERALL deadline. Builds one `AbortController`, hands its
|
|
924
|
-
* signal to `start` (threaded into every paginated `fetch`), and races the returned promise
|
|
925
|
-
* against a timer that ABORTS the signal AND rejects — so even a seam that ignores the signal
|
|
926
|
-
* (never settling) can NOT hold the caller (and thus the provision lock) past `ms`.
|
|
927
|
-
*/
|
|
726
|
+
/** Run `start` under an overall deadline: abort and reject at `ms`, even if it ignores the signal. */
|
|
928
727
|
function withListerDeadline(start, ms) {
|
|
929
728
|
return new Promise((resolve, reject) => {
|
|
930
729
|
const controller = new AbortController();
|
|
@@ -942,21 +741,16 @@ function withListerDeadline(start, ms) {
|
|
|
942
741
|
});
|
|
943
742
|
}
|
|
944
743
|
/**
|
|
945
|
-
*
|
|
946
|
-
* the
|
|
947
|
-
* treated as absent. This is the ONE resolver — the up-front Hyperdrive fence and the
|
|
948
|
-
* default lister below both use it, so the fence can never promise a check the lister
|
|
949
|
-
* then declines to make.
|
|
744
|
+
* API token for the Hyperdrive REST check: the caller's, else `CLOUDFLARE_API_TOKEN`. Shared by
|
|
745
|
+
* the up-front fence and the default lister so they always agree.
|
|
950
746
|
*/
|
|
951
747
|
function resolveHyperdriveApiToken(apiToken) {
|
|
952
748
|
const token = apiToken ?? process.env.CLOUDFLARE_API_TOKEN;
|
|
953
749
|
return typeof token === "string" && token.length > 0 ? token : void 0;
|
|
954
750
|
}
|
|
955
751
|
/**
|
|
956
|
-
* Default Hyperdrive
|
|
957
|
-
*
|
|
958
|
-
* it cannot be built (no account id / no token) — the caller then fails closed
|
|
959
|
-
* before any blind create. Never called in unit tests — those inject a stub.
|
|
752
|
+
* Default Hyperdrive lister over the REST API. Returns `undefined` without a token, so the
|
|
753
|
+
* caller fails closed before a blind create.
|
|
960
754
|
*/
|
|
961
755
|
function createDefaultListHyperdriveConfigs(apiToken) {
|
|
962
756
|
const token = resolveHyperdriveApiToken(apiToken);
|
|
@@ -995,16 +789,8 @@ function createDefaultListHyperdriveConfigs(apiToken) {
|
|
|
995
789
|
};
|
|
996
790
|
}
|
|
997
791
|
/**
|
|
998
|
-
*
|
|
999
|
-
*
|
|
1000
|
-
* only inside the default fetch. Validates EVERY member up front — BEFORE any name
|
|
1001
|
-
* filtering: rejects any non-object member, empty/non-string `name`, and duplicate `id`,
|
|
1002
|
-
* AND requires every member's `id` to be a REAL 32-hex Hyperdrive id (not merely
|
|
1003
|
-
* non-empty). A malformed id on ANY member means the list is untrustworthy, so we fail
|
|
1004
|
-
* closed rather than skip that member and mistake the target for absent. Then requires
|
|
1005
|
-
* EXACTLY 0 or 1 exact-name match (>1 is ambiguous — Hyperdrive names are NOT
|
|
1006
|
-
* account-unique — and fails closed) BEFORE any match/write. Returns the single
|
|
1007
|
-
* exact-name match, or `undefined` when none matches.
|
|
792
|
+
* Validate a Hyperdrive list from any lister and return the config named `name`, if any. A
|
|
793
|
+
* malformed member, a bad or duplicate id, or more than one match fails closed.
|
|
1008
794
|
*/
|
|
1009
795
|
function selectHyperdriveMatch(configs, name, configPath) {
|
|
1010
796
|
const seenIds = /* @__PURE__ */ new Set();
|
|
@@ -1028,18 +814,8 @@ function quarantineSidecarPath(lockTarget) {
|
|
|
1028
814
|
return `${lockTarget}.quarantine.json`;
|
|
1029
815
|
}
|
|
1030
816
|
/**
|
|
1031
|
-
*
|
|
1032
|
-
*
|
|
1033
|
-
* partial JSON. On POSIX the spawned wrangler child is `detached` (its own process-group
|
|
1034
|
-
* leader), so the recorded `pgid` IS its pid; Windows has no process groups (the gate
|
|
1035
|
-
* probes the pid directly there).
|
|
1036
|
-
*
|
|
1037
|
-
* DURABLE: the temp file is fsync'd before the rename, the directory is
|
|
1038
|
-
* fsync'd after it (see the classification below), and the final sidecar is READ
|
|
1039
|
-
* BACK and verified. Any failure THROWS — callers treat persistence as a PRECONDITION
|
|
1040
|
-
* for detaching the lock from proper-lockfile's tracking (see
|
|
1041
|
-
* {@link ProvisionLock.quarantine}); a sidecar that merely "probably" landed is not a
|
|
1042
|
-
* durable marker the acquisition gate can rely on.
|
|
817
|
+
* Persist quarantine metadata durably (temp file, fsync, rename, directory fsync), then read it
|
|
818
|
+
* back. Throws on any failure.
|
|
1043
819
|
*/
|
|
1044
820
|
function writeQuarantineSidecar(sidecarPath, info, dirDurability) {
|
|
1045
821
|
const payload = JSON.stringify({
|
|
@@ -1098,33 +874,11 @@ function readPositiveInt(value) {
|
|
|
1098
874
|
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
|
|
1099
875
|
}
|
|
1100
876
|
/**
|
|
1101
|
-
*
|
|
1102
|
-
*
|
|
1103
|
-
*
|
|
1104
|
-
*
|
|
1105
|
-
*
|
|
1106
|
-
* - no sidecar → nothing quarantined → proceed to normal acquisition;
|
|
1107
|
-
* - sidecar present → probe the recorded group (`kill(-pgid, 0)`; the plain pid where
|
|
1108
|
-
* no group exists): ESRCH (confirmed dead) → clear the sidecar and proceed; still
|
|
1109
|
-
* alive OR the probe fails (EPERM, no recorded pid, unreadable sidecar) → FAIL
|
|
1110
|
-
* CLOSED with the recorded metadata + manual-recovery guidance. PID reuse can make
|
|
1111
|
-
* a dead group probe "alive" — that keeps failing closed (the safe direction; the
|
|
1112
|
-
* guidance covers manual clearance).
|
|
1113
|
-
* Clearing touches ONLY the sidecar (unlink, ENOENT-tolerant — concurrent clearers are
|
|
1114
|
-
* fine). The retained mutex DIRECTORY is deliberately NOT removed by hand: its
|
|
1115
|
-
* heartbeat stopped at quarantine, so proper-lockfile's own stale lifecycle reclaims it
|
|
1116
|
-
* during the acquisition below — a manual rmdir here could race a concurrent acquirer's
|
|
1117
|
-
* fresh mutex (remove-then-acquire is not atomic across processes) and let two
|
|
1118
|
-
* provisions hold the lock at once.
|
|
1119
|
-
*
|
|
1120
|
-
* This gate now ALWAYS runs under the RECOVERY mutex (see
|
|
1121
|
-
* {@link acquireProvisionLock}), so its read IS the re-read-under-the-mutex every
|
|
1122
|
-
* recoverer must base its decision on, and a clear may only remove the exact sidecar
|
|
1123
|
-
* GENERATION it just inspected: the sidecar is re-read immediately before the unlink
|
|
1124
|
-
* and the decision fails closed if the generation changed (a newer quarantine was
|
|
1125
|
-
* persisted by the current main-mutex holder — whose quarantine writes are the one
|
|
1126
|
-
* interleaving the recovery mutex cannot serialize — while this decision was pending).
|
|
1127
|
-
* `onBeforeClear` is a test seam injecting exactly that interleaving deterministically.
|
|
877
|
+
* Quarantine gate, run under the recovery mutex before each lock attempt. With a sidecar
|
|
878
|
+
* present, the recorded group is probed: confirmed dead clears the sidecar, alive or unknown
|
|
879
|
+
* fails closed. Only the sidecar generation that was inspected is cleared; the mutex directory
|
|
880
|
+
* is left for proper-lockfile's stale handling, since removing it by hand could race another
|
|
881
|
+
* acquirer.
|
|
1128
882
|
*/
|
|
1129
883
|
function assertQuarantineClearedOrClear(lockTarget, onBeforeClear) {
|
|
1130
884
|
const sidecarPath = quarantineSidecarPath(lockTarget);
|
|
@@ -1176,28 +930,13 @@ function loadProperLockfileInternals() {
|
|
|
1176
930
|
return;
|
|
1177
931
|
}
|
|
1178
932
|
}
|
|
1179
|
-
/**
|
|
1180
|
-
* `realpathSync` is `node:fs`, so it returns a NATIVE path — backslashes on Windows —
|
|
1181
|
-
* while every other path in this module comes from `pathslash` and is POSIX-normalized,
|
|
1182
|
-
* including the not-yet-created branch below (whose `join()` already normalizes). Without
|
|
1183
|
-
* this, `canonicalizeConfigPath` hands back TWO different spellings of the same file
|
|
1184
|
-
* depending only on whether it happens to exist yet, defeating the "ONE identity" contract
|
|
1185
|
-
* the function exists to provide — and splitting the lock identity, the wrangler `-c` argv,
|
|
1186
|
-
* and the paths printed in error messages. No-op off Windows, where `\` is a legal filename
|
|
1187
|
-
* character that must survive byte-for-byte.
|
|
1188
|
-
*/
|
|
933
|
+
/** `realpathSync` returns backslashes on Windows; normalize so one file has one spelling. */
|
|
1189
934
|
function toPosixConfigPath(nativePath) {
|
|
1190
935
|
return process.platform === "win32" ? normalize(nativePath) : nativePath;
|
|
1191
936
|
}
|
|
1192
937
|
/**
|
|
1193
|
-
*
|
|
1194
|
-
*
|
|
1195
|
-
* config file EXISTS, its own realpath is returned (a symlink resolves to its target).
|
|
1196
|
-
* When it does NOT yet exist, `realpath` would throw (ENOENT), so we canonicalize the
|
|
1197
|
-
* PARENT directory (which normally exists — the project root) and rejoin the basename, so
|
|
1198
|
-
* two aliases to the same not-yet-created config still resolve to one identity. If neither
|
|
1199
|
-
* the file nor its parent can be canonicalized, the lexical path is returned unchanged (no
|
|
1200
|
-
* worse than before, and the common case — an existing project dir — is always canonical).
|
|
938
|
+
* Canonical config path, so symlink aliases share one lock identity. A dangling symlink is
|
|
939
|
+
* followed; a missing file canonicalizes its parent directory instead.
|
|
1201
940
|
*/
|
|
1202
941
|
function canonicalizeConfigPath(absoluteConfigPath, depth = 0) {
|
|
1203
942
|
try {
|
|
@@ -1225,49 +964,22 @@ function canonicalizeConfigPath(absoluteConfigPath, depth = 0) {
|
|
|
1225
964
|
}
|
|
1226
965
|
}
|
|
1227
966
|
/**
|
|
1228
|
-
* A lock
|
|
1229
|
-
*
|
|
1230
|
-
* future one. A LIVE owner refreshes the mutex mtime every {@link LOCK_UPDATE_MS}
|
|
1231
|
-
* (strictly less than this), so it never expires by age. Generous vs. a real
|
|
1232
|
-
* provision's duration.
|
|
1233
|
-
*
|
|
1234
|
-
* CORRECTNESS — this heartbeat is a `setTimeout` on the Node event loop, so it only
|
|
1235
|
-
* fires while the loop is FREE. The default runner is ASYNC (`spawn`, awaited), so no
|
|
1236
|
-
* wrangler call blocks the loop; between/`during` every `await` the pending update timer
|
|
1237
|
-
* runs and refreshes the mtime. Fast synchronous bookkeeping (JSON parse, small config
|
|
1238
|
-
* reads) is « `LOCK_UPDATE_MS`, so the mtime is continuously refreshed and NO other
|
|
1239
|
-
* process ever sees this lock stale → it can never be stolen while we hold it. (The old
|
|
1240
|
-
* blocking `spawnSync` could freeze the loop past `LOCK_STALE_MS`, starving the
|
|
1241
|
-
* heartbeat AND `onCompromised`, which is exactly the race this closes.)
|
|
967
|
+
* A lock not refreshed for this long counts as abandoned. A live owner refreshes it every
|
|
968
|
+
* {@link LOCK_UPDATE_MS}; this needs a free event loop, which is why the runner is async.
|
|
1242
969
|
*/
|
|
1243
970
|
const LOCK_STALE_MS = 12e4;
|
|
1244
971
|
/** Refresh interval keeping a live owner's lock fresh (must be < {@link LOCK_STALE_MS}). */
|
|
1245
972
|
const LOCK_UPDATE_MS = 2e4;
|
|
1246
|
-
/**
|
|
1247
|
-
* Backoff budget while waiting for a held (non-stale) lock. Polls with jittered backoff
|
|
1248
|
-
* for roughly a minute before giving up, so a concurrent provision blocks (not spins)
|
|
1249
|
-
* until the holder releases, then proceeds.
|
|
1250
|
-
*/
|
|
973
|
+
/** Backoff while waiting for a held lock (about a minute). */
|
|
1251
974
|
const LOCK_RETRIES = {
|
|
1252
975
|
retries: 100,
|
|
1253
976
|
factor: 1.5,
|
|
1254
977
|
minTimeout: 200,
|
|
1255
978
|
maxTimeout: 2e3
|
|
1256
979
|
};
|
|
1257
|
-
/**
|
|
1258
|
-
* The stale window of the RECOVERY mutex (the small proper-lockfile
|
|
1259
|
-
* lock serializing every gate→probe→clear→acquire→revalidate decision — see
|
|
1260
|
-
* {@link acquireProvisionLock}). Deliberately SHORT: its critical section is a few
|
|
1261
|
-
* synchronous fs reads plus one mkdir, so a crashed recoverer frees it within seconds
|
|
1262
|
-
* (proper-lockfile's own heartbeat keeps a live recoverer's window fresh).
|
|
1263
|
-
*/
|
|
980
|
+
/** Stale window of the short-lived recovery mutex. */
|
|
1264
981
|
const RECOVERY_LOCK_STALE_MS = 6e3;
|
|
1265
|
-
/**
|
|
1266
|
-
* Normalize the `retries` knob into the ONE backoff schedule OUR
|
|
1267
|
-
* acquisition loop runs (proper-lockfile's internal retrying is disabled with
|
|
1268
|
-
* `retries: 0` so the quarantine gate runs on EVERY attempt — see
|
|
1269
|
-
* {@link acquireProvisionLock}). A bare number keeps {@link LOCK_RETRIES}' backoff shape.
|
|
1270
|
-
*/
|
|
982
|
+
/** Backoff schedule for our own retry loop (proper-lockfile runs with `retries: 0`). */
|
|
1271
983
|
function normalizeLockRetries(retries) {
|
|
1272
984
|
if (typeof retries === "number") return {
|
|
1273
985
|
...LOCK_RETRIES,
|
|
@@ -1280,52 +992,18 @@ function normalizeLockRetries(retries) {
|
|
|
1280
992
|
maxTimeout: retries?.maxTimeout ?? LOCK_RETRIES.maxTimeout
|
|
1281
993
|
};
|
|
1282
994
|
}
|
|
1283
|
-
/**
|
|
1284
|
-
* Match the key spelling `proper-lockfile` stores when `realpath: false`.
|
|
1285
|
-
*
|
|
1286
|
-
* The library uses the host's native `node:path.resolve`, including backslashes on
|
|
1287
|
-
* Windows. Most Void paths intentionally use `pathslash`, but its forward-slash output
|
|
1288
|
-
* cannot address `proper-lockfile`'s internal registry on Windows.
|
|
1289
|
-
*/
|
|
995
|
+
/** The registry key proper-lockfile uses with `realpath: false` (native separators). */
|
|
1290
996
|
function resolveProperLockfileKey(file, platform = process.platform) {
|
|
1291
997
|
return platform === "win32" ? resolve$1(file) : resolve(file);
|
|
1292
998
|
}
|
|
1293
999
|
/**
|
|
1294
|
-
* Acquire the
|
|
1295
|
-
* `
|
|
1296
|
-
*
|
|
1297
|
-
* Both lock identities here are deliberately SYNTHETIC adjacent paths, so acquisition
|
|
1298
|
-
* passes `realpath: false`: proper-lockfile resolves the identity lexically and creates
|
|
1299
|
-
* only its temporary mutex directory — never an empty marker file in the user's project.
|
|
1300
|
-
* This is safe because the config path was already canonicalized (including missing-file
|
|
1301
|
-
* and symlink cases) before reaching this function. It also preserves the writer's
|
|
1302
|
-
* missing-vs-empty config semantics. Returns a handle whose `release()` drops the mutex;
|
|
1303
|
-
* ALWAYS call it in a `finally` so an interruption never leaks the lock.
|
|
1304
|
-
*
|
|
1305
|
-
* `onCompromised` FAILS CLOSED (re-throws): if the lock is ever detected stolen (its
|
|
1306
|
-
* mtime changed, or the mutex vanished under a live owner), provisioning must NEVER
|
|
1307
|
-
* continue — a compromised lock means another process may be racing the same config.
|
|
1000
|
+
* Acquire the provision lock (`<configPath>.void-provision.lock.lock`). The lock targets are
|
|
1001
|
+
* synthetic paths, so `realpath: false` avoids creating marker files; the config path is
|
|
1002
|
+
* already canonical. A compromised lock throws.
|
|
1308
1003
|
*
|
|
1309
|
-
*
|
|
1310
|
-
*
|
|
1311
|
-
*
|
|
1312
|
-
* function (proper-lockfile runs with `retries: 0`), so a waiter that entered while the
|
|
1313
|
-
* holder was healthy re-runs the gate on every backoff attempt and can never stale-take
|
|
1314
|
-
* a mutex whose holder QUARANTINED mid-wait; the post-acquire revalidation catches a
|
|
1315
|
-
* sidecar that appeared between the last gate and the acquisition itself (the fresh
|
|
1316
|
-
* lock is released and the acquisition fails closed).
|
|
1317
|
-
*
|
|
1318
|
-
* The ENTIRE per-attempt protocol runs under a second small
|
|
1319
|
-
* proper-lockfile RECOVERY mutex (`<lockTarget>.recovery`, `retries: 0`, short stale):
|
|
1320
|
-
* without it, two recoverers could interleave ACROSS processes — B reads a dead sidecar
|
|
1321
|
-
* S0, gets descheduled; A clears S0, acquires the main mutex, quarantines again writing
|
|
1322
|
-
* a LIVE sidecar S1; B resumes and unlinks the PATHNAME (now S1) on its stale decision,
|
|
1323
|
-
* then stale-reclaims A's retained mutex (the revalidation sees no sidecar). Under the
|
|
1324
|
-
* recovery mutex every decision re-reads the sidecar after acquiring it, and the clear
|
|
1325
|
-
* is additionally GENERATION-verified (see {@link assertQuarantineClearedOrClear}) for
|
|
1326
|
-
* the one interleaving the recovery mutex cannot serialize (the current MAIN-mutex
|
|
1327
|
-
* holder quarantining mid-decision). The recovery mutex deliberately does NOT take the
|
|
1328
|
-
* caller's `fs` seam — that seam simulates main-mutex failures.
|
|
1004
|
+
* Each attempt runs under a short recovery mutex: quarantine gate, one non-retrying
|
|
1005
|
+
* acquisition, then the gate again under ownership. This loop owns retries, so the gate runs
|
|
1006
|
+
* on every attempt and a quarantined lock is never taken over by age.
|
|
1329
1007
|
*/
|
|
1330
1008
|
async function acquireProvisionLock(configPath, lockOptions) {
|
|
1331
1009
|
const lockTarget = `${configPath}.void-provision.lock`;
|
|
@@ -1427,39 +1105,13 @@ async function acquireProvisionLock(configPath, lockOptions) {
|
|
|
1427
1105
|
}
|
|
1428
1106
|
};
|
|
1429
1107
|
}
|
|
1430
|
-
/**
|
|
1431
|
-
* Parse a wrangler `--json` stdout into an array, distinguishing a VALID JSON
|
|
1432
|
-
* array (returns it, possibly empty) from unparseable/non-array output (returns
|
|
1433
|
-
* `undefined`). Members are returned UNFILTERED — a malformed member (e.g. a
|
|
1434
|
-
* `null`) is preserved, NOT silently dropped, so {@link validateResourceList}
|
|
1435
|
-
* can fail closed on it (a dropped malformed member could BE the target and look
|
|
1436
|
-
* like "absent", minting a duplicate). Used everywhere an empty list must mean
|
|
1437
|
-
* "authoritatively absent" and garbage must NOT be coerced to "absent".
|
|
1438
|
-
*/
|
|
1439
|
-
function parseJsonArrayStrict(value) {
|
|
1440
|
-
return Array.isArray(value) ? value : void 0;
|
|
1441
|
-
}
|
|
1442
|
-
function parseJsonObject(value) {
|
|
1443
|
-
return isRecord(value) ? value : void 0;
|
|
1444
|
-
}
|
|
1445
|
-
/**
|
|
1446
|
-
* Read the single provisioned binding entry for `field`/`binding` from the resolved
|
|
1447
|
-
* config, delegating to the STRICT canonical reader in `wrangler.ts`
|
|
1448
|
-
* ({@link readProvisionedBindingEntry}). Unlike the old loose first-match `.find()`, this
|
|
1449
|
-
* validates the target field's outer shape, fails closed on a DUPLICATE binding, and runs
|
|
1450
|
-
* the worker-wide CROSS-TYPE collision check — so a
|
|
1451
|
-
* duplicate/collision config can never be reported `reused` or pass a create postcondition.
|
|
1452
|
-
*/
|
|
1108
|
+
/** One binding entry, via the strict reader that fails on duplicate or colliding bindings. */
|
|
1453
1109
|
function readBindingEntry(configPath, field, binding) {
|
|
1454
1110
|
return readProvisionedBindingEntry(readRootConfig(configPath), field, binding, configPath);
|
|
1455
1111
|
}
|
|
1456
1112
|
/**
|
|
1457
|
-
* Read
|
|
1458
|
-
*
|
|
1459
|
-
* parsing, so we must too, else a BOM-prefixed config would look unparseable). FAILS CLOSED
|
|
1460
|
-
* on a genuine parse error or a non-object root: never silently treat a real
|
|
1461
|
-
* config as `{}`, which would miss existing ids (re-provision/orphan) and mask a
|
|
1462
|
-
* corrupt file. A missing file is fine (nothing to read-first → `{}`).
|
|
1113
|
+
* Read the root config fresh (wrangler edits it), without a BOM. A parse error or a non-object
|
|
1114
|
+
* fails; a missing file reads as `{}`.
|
|
1463
1115
|
*/
|
|
1464
1116
|
function readRootConfig(configPath) {
|
|
1465
1117
|
if (!existsSync(configPath)) return {};
|
|
@@ -1471,27 +1123,13 @@ function readRootConfig(configPath) {
|
|
|
1471
1123
|
if (!isRecord(parsed)) throw new Error(`provision: "${configPath}" must be a JSON object.`);
|
|
1472
1124
|
return parsed;
|
|
1473
1125
|
}
|
|
1474
|
-
/**
|
|
1475
|
-
* FAIL-CLOSED gate over a discovery/list/info result. Throws (no create) unless
|
|
1476
|
-
* the child exited cleanly — a NUMERIC `status === 0`. Because
|
|
1477
|
-
* {@link normalizeRunWranglerResult} already folds a signal-killed child or a
|
|
1478
|
-
* spawn error into a non-zero status, a single numeric check here rejects ALL of
|
|
1479
|
-
* nonzero-exit / SIGTERM / spawn-error / non-numeric status. MUST be called
|
|
1480
|
-
* BEFORE reading provider data, so failed discovery never implies absence.
|
|
1481
|
-
*/
|
|
1126
|
+
/** Throw unless a discovery call exited cleanly, so a failed list never means "absent". */
|
|
1482
1127
|
function assertDiscoverySucceeded(result, label) {
|
|
1483
1128
|
if (!isCleanSuccess(result)) throw new Error(`provision: "${label}" failed (exit ${String(result.status)}): ${result.stderr || result.stdout || "no output"}. Cannot verify existing resources; refusing to create a possible duplicate.`);
|
|
1484
1129
|
}
|
|
1485
1130
|
/**
|
|
1486
|
-
*
|
|
1487
|
-
*
|
|
1488
|
-
* FORMAT-VALID id (per {@link ResourceListSpec.hasValidId}). A member is NEVER
|
|
1489
|
-
* silently filtered, and a member with a malformed id is NEVER accepted, because
|
|
1490
|
-
* either could BE the requested target (or signal a corrupt list) and treating it as
|
|
1491
|
-
* "absent" would mint a duplicate. Validates EVERY member up front — BEFORE any
|
|
1492
|
-
* name-filtering by the caller — so a nonmatching member with a bogus id can no longer
|
|
1493
|
-
* pass the gate as authoritative absence. Returns the validated members (every one
|
|
1494
|
-
* guaranteed to have a real id + name) on success.
|
|
1131
|
+
* Reject the whole list if any member lacks a non-empty name or a valid id. A malformed member
|
|
1132
|
+
* could be the target, so it is never filtered out as "absent".
|
|
1495
1133
|
*/
|
|
1496
1134
|
function validateResourceList(list, spec) {
|
|
1497
1135
|
const validated = [];
|
|
@@ -1521,25 +1159,9 @@ function reconcileD1IdAliases(entry) {
|
|
|
1521
1159
|
};
|
|
1522
1160
|
}
|
|
1523
1161
|
/**
|
|
1524
|
-
*
|
|
1525
|
-
* `
|
|
1526
|
-
*
|
|
1527
|
-
* treated as ABSENT for each — and returns a usable id ONLY when the entry
|
|
1528
|
-
* unambiguously IS the requested database:
|
|
1529
|
-
* - `{ kind: 'other' }` — no non-empty name alias equals `requestedName`, so this
|
|
1530
|
-
* entry is a different database (the caller skips it, never mistakes it for the
|
|
1531
|
-
* target, and never adopts it under a normalization of a DIFFERENT name);
|
|
1532
|
-
* - `{ kind: 'claimed-but-invalid' }` — a name alias DOES equal `requestedName`
|
|
1533
|
-
* but the entry is ambiguous: CONFLICTING non-empty name aliases (e.g.
|
|
1534
|
-
* `name:"wanted"` + `database_name:"other"`), OR not EXACTLY ONE distinct
|
|
1535
|
-
* non-empty id (0 → no id; >1 → conflicting/blank uuid vs database_id). The
|
|
1536
|
-
* caller fails closed (never adopt, never create a duplicate);
|
|
1537
|
-
* - `{ kind: 'match', id }` — exactly one requested-name alias and exactly one
|
|
1538
|
-
* unambiguous non-empty id.
|
|
1539
|
-
* The old two-helper split (`normalizeD1Identity` preferring `database_name`,
|
|
1540
|
-
* `d1EntryName` preferring `name`) let a `{name,database_name}` mismatch be
|
|
1541
|
-
* SELECTED under one alias yet KEYED under the other. This collapses both into one
|
|
1542
|
-
* exact-match rule so no sibling gap remains.
|
|
1162
|
+
* Strict D1 identity check shared by `d1 list` and `d1 info`. `other` when no name alias
|
|
1163
|
+
* (`name`, `database_name`) equals `requestedName`; `claimed-but-invalid` when one does but the
|
|
1164
|
+
* name or id aliases conflict; otherwise `match`.
|
|
1543
1165
|
*/
|
|
1544
1166
|
function normalizeD1(entry, requestedName) {
|
|
1545
1167
|
if (entry === void 0) return { kind: "other" };
|
|
@@ -1553,73 +1175,32 @@ function normalizeD1(entry, requestedName) {
|
|
|
1553
1175
|
id: reconciled.id
|
|
1554
1176
|
};
|
|
1555
1177
|
}
|
|
1556
|
-
/**
|
|
1557
|
-
* `wrangler kv namespace list` → the `{ id }` of every namespace whose title
|
|
1558
|
-
* matches. The list has ALREADY been through {@link validateResourceList} (every
|
|
1559
|
-
* member has a non-empty string `id` + `title`), so this only filters + collects.
|
|
1560
|
-
*
|
|
1561
|
-
* A title-matching namespace whose `id` is NOT a real KV id (32-hex) is ambiguous:
|
|
1562
|
-
* we can neither adopt it nor treat it as absent (which would mint a duplicate title), so
|
|
1563
|
-
* we FAIL CLOSED. Real wrangler always returns 32-hex ids; this guards a malformed seam.
|
|
1564
|
-
*/
|
|
1565
|
-
function extractKvMatches(list, title) {
|
|
1566
|
-
const matches = [];
|
|
1567
|
-
for (const ns of list) {
|
|
1568
|
-
if (ns.title !== title) continue;
|
|
1569
|
-
if (!isRealKvId(ns.id)) throw new Error(`provision: "wrangler kv namespace list" returned a namespace titled ${JSON.stringify(title)} with a malformed id ${JSON.stringify(ns.id)} (expected a 32-character hex namespace id). Cannot verify whether it already exists; refusing to create a possible duplicate.`);
|
|
1570
|
-
matches.push({ id: ns.id });
|
|
1571
|
-
}
|
|
1572
|
-
return matches;
|
|
1573
|
-
}
|
|
1574
1178
|
/** True when a `d1 create` failure means the name is already taken (code 7502). */
|
|
1575
1179
|
function isAlreadyExistsError(result) {
|
|
1576
1180
|
return result.errorCode === 7502;
|
|
1577
1181
|
}
|
|
1578
1182
|
/**
|
|
1579
|
-
*
|
|
1580
|
-
*
|
|
1581
|
-
* with the provider's typed not-found result (R2 error code 10006; the queue
|
|
1582
|
-
* operation's `not-found` failure). Everything else returns `false` so the caller
|
|
1583
|
-
* fails closed — a signal/spawn failure, an auth/network/metrics error, or a
|
|
1584
|
-
* GENERIC "not found" (e.g. `Account not found`) is NEVER treated as absence,
|
|
1585
|
-
* which would mint a duplicate resource.
|
|
1183
|
+
* True only for a completed non-zero probe with the provider's typed not-found result (R2
|
|
1184
|
+
* code 10006, or the queue operation's `not-found`). Nothing else counts as absence.
|
|
1586
1185
|
*/
|
|
1587
1186
|
function isConfirmedAbsent(result, kind) {
|
|
1588
1187
|
return isCleanFailure(result) && (kind === "r2" ? result.errorCode === 10006 : result.failure === "not-found");
|
|
1589
1188
|
}
|
|
1590
1189
|
/** Only a completed, typed not-found response confirms queue absence. */
|
|
1591
|
-
function isQueueConfirmedAbsent(result
|
|
1190
|
+
function isQueueConfirmedAbsent(result) {
|
|
1592
1191
|
return isConfirmedAbsent(result, "queue");
|
|
1593
1192
|
}
|
|
1594
1193
|
/** Reuse and post-create verification require clean success and the exact returned name. */
|
|
1595
1194
|
function queueInfoConfirmsExists(result, name) {
|
|
1596
1195
|
return isCleanSuccess(result) && isRecord(result.data) && result.data.name === name;
|
|
1597
1196
|
}
|
|
1598
|
-
/**
|
|
1599
|
-
* Throw when the writer could not attach `migrations_dir` because no matching
|
|
1600
|
-
* `d1_databases[]` entry exists. The provisioner only writes `migrations_dir`
|
|
1601
|
-
* AFTER establishing the D1 entry (create/adopt/reuse), so a skip here means the
|
|
1602
|
-
* D1 resource is unexpectedly absent from the config — never silently succeed
|
|
1603
|
-
* with the gap unfilled.
|
|
1604
|
-
*/
|
|
1197
|
+
/** Throw when the writer found no D1 entry to attach `migrations_dir` to. */
|
|
1605
1198
|
function assertD1MigrationsDirApplied(result, binding, configPath) {
|
|
1606
1199
|
if (result.d1MigrationsDirSkipped) throw new Error(`provision: could not set migrations_dir for D1 binding ${JSON.stringify(binding)} — no matching d1_databases entry exists in "${configPath}" after provisioning it. The D1 resource was not written to the config as expected; inspect the file and retry.`);
|
|
1607
1200
|
}
|
|
1608
1201
|
/**
|
|
1609
|
-
*
|
|
1610
|
-
*
|
|
1611
|
-
* `wrangler-dist/cli.js` and maps a signal-killed child (`code === null`) to
|
|
1612
|
-
* `process.exit(0)`, so `spawnSync` can see a `create` report "success" though the
|
|
1613
|
-
* real CLI was killed and `--update-config` wrote NO id. After every config-writing
|
|
1614
|
-
* create (d1/kv/r2/hyperdrive) reports success we RE-READ the resolved config and
|
|
1615
|
-
* CONFIRM the target binding now holds a REAL id AND — where the resource's name is
|
|
1616
|
-
* its identity (R2 `bucket_name`, D1 `database_name`) — that the recorded name EXACTLY
|
|
1617
|
-
* matches the requested one; otherwise we FAIL CLOSED. A bare "some non-placeholder
|
|
1618
|
-
* value" check is NOT enough: for R2, `bucket_name` IS the identity, so
|
|
1619
|
-
* `{ bucket_name: 'other' }` would otherwise pass and bind the WRONG bucket. This also
|
|
1620
|
-
* catches any partial/silent create failure, not only kills. (Queue creates write NO
|
|
1621
|
-
* config, so they cannot be verified this way — a killed queue-create self-heals on
|
|
1622
|
-
* the next read-first/probe run, which re-detects absence and re-creates.)
|
|
1202
|
+
* After a create reports success, re-read the config and require a real id, plus the exact
|
|
1203
|
+
* name where the name is the identity. A killed child can still report exit 0.
|
|
1623
1204
|
*/
|
|
1624
1205
|
function assertCreateWroteRealId(configPath, spec, binding, name) {
|
|
1625
1206
|
const entry = readBindingEntry(configPath, spec.field, binding);
|
|
@@ -1639,19 +1220,7 @@ function hasControlChar(value) {
|
|
|
1639
1220
|
function assertNoOptionInjection(kind, name) {
|
|
1640
1221
|
if (typeof name !== "string" || name.length === 0 || name.startsWith("-") || hasControlChar(name)) throw new Error(`provision: refusing to pass an unsafe ${kind} name ${JSON.stringify(name)} to wrangler. A name may not be empty, begin with "-" (it would be parsed as a CLI flag), or contain control characters. Rename the resource and retry.`);
|
|
1641
1222
|
}
|
|
1642
|
-
/**
|
|
1643
|
-
* Per-resource Cloudflare naming GRAMMAR, verified 2026-07 against wrangler 4.107
|
|
1644
|
-
* and live account resources:
|
|
1645
|
-
* - `r2` — wrangler's own `isValidR2BucketName`: 3–63 chars, lowercase letters /
|
|
1646
|
-
* digits / `-`, begin+end alphanumeric (DNS-like). This IS the identity.
|
|
1647
|
-
* - `kvTitle` — the KV namespace positional is forwarded verbatim as the account TITLE;
|
|
1648
|
-
* Cloudflare allows a broad charset INCLUDING SPACES (≤512 chars). We only
|
|
1649
|
-
* forbid control chars (already covered by the injection guard) — spaces
|
|
1650
|
-
* and mixed case are legal, so "My Own Namespace" is accepted.
|
|
1651
|
-
* - `d1` / `hyperdrive` / `queue` — 1–63 chars, begin with a letter/digit, then
|
|
1652
|
-
* letters / digits / `-` / `_` (matches every live D1 `name`, Hyperdrive
|
|
1653
|
-
* config `name`, and Void's `QUEUE_<NAME>`-derived queue names).
|
|
1654
|
-
*/
|
|
1223
|
+
/** Per-resource Cloudflare naming grammar. KV titles may contain spaces and mixed case. */
|
|
1655
1224
|
const RESOURCE_NAME_GRAMMARS = {
|
|
1656
1225
|
r2: {
|
|
1657
1226
|
re: R2_BUCKET_NAME_RE,
|
|
@@ -1679,34 +1248,13 @@ const RESOURCE_NAME_GRAMMARS = {
|
|
|
1679
1248
|
rule: "be 1–63 characters, contain only letters, digits and \"-\", and begin and end with a letter or digit"
|
|
1680
1249
|
}
|
|
1681
1250
|
};
|
|
1682
|
-
/**
|
|
1683
|
-
* Validate a name destined for a wrangler ARGV positional: FIRST the universal
|
|
1684
|
-
* option-injection guard, THEN the resource's own Cloudflare grammar. Fail closed on any
|
|
1685
|
-
* violation, before the account gate or any wrangler/REST call.
|
|
1686
|
-
*
|
|
1687
|
-
* Exported — like {@link isQueueConfirmedAbsent} — so the deploy orchestrator's
|
|
1688
|
-
* pre-provision queue probe (`cli/deploy.ts`) gates names on the SAME rules this
|
|
1689
|
-
* provisioner enforces. Queue names
|
|
1690
|
-
* are taken verbatim from filenames with no sanitization, so without this gate a name
|
|
1691
|
-
* beginning with `-` reaches wrangler as a FLAG rather than a positional.
|
|
1692
|
-
*/
|
|
1251
|
+
/** Validate an argv positional: the option-injection guard, then the resource grammar. */
|
|
1693
1252
|
function assertValidResourceName(kind, name) {
|
|
1694
1253
|
assertNoOptionInjection(RESOURCE_NAME_GRAMMARS[kind].label, name);
|
|
1695
1254
|
const grammar = RESOURCE_NAME_GRAMMARS[kind];
|
|
1696
1255
|
if (!grammar.re.test(name)) throw new Error(`provision: refusing to pass an invalid ${grammar.label} name ${JSON.stringify(name)} to wrangler. A ${grammar.label} name must ${grammar.rule}. Rename the resource and retry.`);
|
|
1697
1256
|
}
|
|
1698
|
-
/**
|
|
1699
|
-
* Build a redactor that strips connection-string secrets from every string the
|
|
1700
|
-
* provisioner PROPAGATES (buffered wrangler stdout/stderr, error strings, logged
|
|
1701
|
-
* warnings). Two layers:
|
|
1702
|
-
* 1. the EXACT desired connection string (when this provision carries one) is replaced
|
|
1703
|
-
* whole, so no derived message can echo it verbatim;
|
|
1704
|
-
* 2. any URL userinfo password (`scheme://user:secret@…`) is masked generically, so a
|
|
1705
|
-
* wrangler error that re-renders a connection string in ANY spelling still never
|
|
1706
|
-
* leaks the password (the scheme + user survive for diagnostics).
|
|
1707
|
-
* Applied at the single runWrangler choke point (every call site reads pre-redacted
|
|
1708
|
-
* results) and to the Hyperdrive lister's propagated failure message.
|
|
1709
|
-
*/
|
|
1257
|
+
/** Redactor that replaces the exact connection string and masks any URL userinfo password. */
|
|
1710
1258
|
function createSecretRedactor(connectionString) {
|
|
1711
1259
|
return (text) => {
|
|
1712
1260
|
let out = text;
|
|
@@ -1715,304 +1263,16 @@ function createSecretRedactor(connectionString) {
|
|
|
1715
1263
|
return out;
|
|
1716
1264
|
};
|
|
1717
1265
|
}
|
|
1718
|
-
/**
|
|
1719
|
-
* The engine's own lazy `Error#stack` accessor pair, captured ONCE at
|
|
1720
|
-
* module init. V8 defines `stack` as an own get/set accessor on every Error instance,
|
|
1721
|
-
* sharing ONE getter/setter function across all of them — so FUNCTION IDENTITY proves
|
|
1722
|
-
* "this is the engine's deterministic lazy-stack slot" (user code cannot forge these
|
|
1723
|
-
* function identities). It is the ONLY accessor the in-place sanitizer may handle;
|
|
1724
|
-
* every other accessor forces the inert fallback carrier.
|
|
1725
|
-
*/
|
|
1726
|
-
const engineStackDescriptor = Object.getOwnPropertyDescriptor(/* @__PURE__ */ new Error(), "stack");
|
|
1727
|
-
const ENGINE_STACK_GET = engineStackDescriptor?.get;
|
|
1728
|
-
const ENGINE_STACK_SET = engineStackDescriptor?.set;
|
|
1729
|
-
/**
|
|
1730
|
-
* The ONLY prototypes a rebuilt carrier may have restored. Restoring an
|
|
1731
|
-
* ARBITRARY prototype would reintroduce hostile behavior on the otherwise-inert carrier
|
|
1732
|
-
* (a prototype getter or `toString` the carrier's readers would still hit), so an
|
|
1733
|
-
* unknown prototype degrades to the base Error/Object/Array prototype the fresh carrier
|
|
1734
|
-
* was built with. `WranglerQuarantineError` is included so a rebuilt quarantine carrier
|
|
1735
|
-
* keeps its `instanceof` classification.
|
|
1736
|
-
*/
|
|
1737
|
-
const KNOWN_SAFE_PROTOTYPES = /* @__PURE__ */ new Set([
|
|
1738
|
-
Error.prototype,
|
|
1739
|
-
TypeError.prototype,
|
|
1740
|
-
RangeError.prototype,
|
|
1741
|
-
ReferenceError.prototype,
|
|
1742
|
-
SyntaxError.prototype,
|
|
1743
|
-
EvalError.prototype,
|
|
1744
|
-
URIError.prototype,
|
|
1745
|
-
AggregateError.prototype,
|
|
1746
|
-
WranglerQuarantineError.prototype,
|
|
1747
|
-
Object.prototype,
|
|
1748
|
-
Array.prototype
|
|
1749
|
-
]);
|
|
1750
|
-
function tryRedactValue(value, redactText) {
|
|
1751
|
-
try {
|
|
1752
|
-
if (typeof value === "string") return {
|
|
1753
|
-
ok: true,
|
|
1754
|
-
value: redactText(value)
|
|
1755
|
-
};
|
|
1756
|
-
if (typeof value !== "object" || value === null) return {
|
|
1757
|
-
ok: true,
|
|
1758
|
-
value
|
|
1759
|
-
};
|
|
1760
|
-
if (!types.isProxy(value) && sanitizeInPlace(value, redactText)) return {
|
|
1761
|
-
ok: true,
|
|
1762
|
-
value
|
|
1763
|
-
};
|
|
1764
|
-
return {
|
|
1765
|
-
ok: true,
|
|
1766
|
-
value: deepSanitizedCopy(value, redactText)
|
|
1767
|
-
};
|
|
1768
|
-
} catch {
|
|
1769
|
-
return { ok: false };
|
|
1770
|
-
}
|
|
1771
|
-
}
|
|
1772
|
-
/**
|
|
1773
|
-
* Attempt to redact `root` fully IN PLACE. Returns `true` only when EVERY string
|
|
1774
|
-
* that needed redaction was verifiably rewritten (assignment read back); any immutable
|
|
1775
|
-
* or unverifiable slot holding a secret returns `false` so the caller switches to the
|
|
1776
|
-
* fallback-carrier copy. Partial rewrites applied before a `false` are harmless — they
|
|
1777
|
-
* only ever REMOVE secret text, and the caller then discards the original anyway.
|
|
1778
|
-
*/
|
|
1779
|
-
function sanitizeInPlace(root, redactText) {
|
|
1780
|
-
const seen = /* @__PURE__ */ new WeakSet();
|
|
1781
|
-
let ok = true;
|
|
1782
|
-
const visitChild = (child) => {
|
|
1783
|
-
if (typeof child === "object" && child !== null) visit(child);
|
|
1784
|
-
};
|
|
1785
|
-
const visit = (v) => {
|
|
1786
|
-
if (!ok || seen.has(v)) return;
|
|
1787
|
-
seen.add(v);
|
|
1788
|
-
if (types.isProxy(v)) {
|
|
1789
|
-
ok = false;
|
|
1790
|
-
return;
|
|
1791
|
-
}
|
|
1792
|
-
if (v instanceof Map) {
|
|
1793
|
-
for (const [key, val] of Array.from(v.entries())) {
|
|
1794
|
-
const newKey = typeof key === "string" ? redactText(key) : key;
|
|
1795
|
-
const newVal = typeof val === "string" ? redactText(val) : val;
|
|
1796
|
-
visitChild(key);
|
|
1797
|
-
visitChild(val);
|
|
1798
|
-
if (newKey !== key) v.delete(key);
|
|
1799
|
-
if (newKey !== key || newVal !== val) v.set(newKey, newVal);
|
|
1800
|
-
if (!ok) return;
|
|
1801
|
-
}
|
|
1802
|
-
return;
|
|
1803
|
-
}
|
|
1804
|
-
if (v instanceof Set) {
|
|
1805
|
-
for (const member of Array.from(v.values())) {
|
|
1806
|
-
if (typeof member === "string") {
|
|
1807
|
-
const red = redactText(member);
|
|
1808
|
-
if (red !== member) {
|
|
1809
|
-
v.delete(member);
|
|
1810
|
-
v.add(red);
|
|
1811
|
-
}
|
|
1812
|
-
} else visitChild(member);
|
|
1813
|
-
if (!ok) return;
|
|
1814
|
-
}
|
|
1815
|
-
return;
|
|
1816
|
-
}
|
|
1817
|
-
for (const key of Reflect.ownKeys(v)) {
|
|
1818
|
-
const desc = Object.getOwnPropertyDescriptor(v, key);
|
|
1819
|
-
if (!desc) continue;
|
|
1820
|
-
if (desc.get !== void 0 || desc.set !== void 0) {
|
|
1821
|
-
if (!(key === "stack" && ENGINE_STACK_GET !== void 0 && desc.get === ENGINE_STACK_GET && desc.set === ENGINE_STACK_SET)) {
|
|
1822
|
-
ok = false;
|
|
1823
|
-
return;
|
|
1824
|
-
}
|
|
1825
|
-
let current;
|
|
1826
|
-
try {
|
|
1827
|
-
current = desc.get !== void 0 ? desc.get.call(v) : void 0;
|
|
1828
|
-
} catch {
|
|
1829
|
-
ok = false;
|
|
1830
|
-
return;
|
|
1831
|
-
}
|
|
1832
|
-
if (typeof current === "string") {
|
|
1833
|
-
const red = redactText(current);
|
|
1834
|
-
if (red !== current) {
|
|
1835
|
-
if (desc.set === void 0) {
|
|
1836
|
-
ok = false;
|
|
1837
|
-
return;
|
|
1838
|
-
}
|
|
1839
|
-
try {
|
|
1840
|
-
desc.set.call(v, red);
|
|
1841
|
-
} catch {
|
|
1842
|
-
ok = false;
|
|
1843
|
-
return;
|
|
1844
|
-
}
|
|
1845
|
-
let after;
|
|
1846
|
-
try {
|
|
1847
|
-
const afterDesc = Object.getOwnPropertyDescriptor(v, key);
|
|
1848
|
-
after = afterDesc?.get !== void 0 ? afterDesc.get.call(v) : afterDesc?.value;
|
|
1849
|
-
} catch {
|
|
1850
|
-
ok = false;
|
|
1851
|
-
return;
|
|
1852
|
-
}
|
|
1853
|
-
if (after !== red) {
|
|
1854
|
-
ok = false;
|
|
1855
|
-
return;
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
|
-
} else visitChild(current);
|
|
1859
|
-
if (!ok) return;
|
|
1860
|
-
continue;
|
|
1861
|
-
}
|
|
1862
|
-
const val = desc.value;
|
|
1863
|
-
if (typeof val === "string") {
|
|
1864
|
-
const red = redactText(val);
|
|
1865
|
-
if (red === val) continue;
|
|
1866
|
-
if (desc.writable !== true) {
|
|
1867
|
-
ok = false;
|
|
1868
|
-
return;
|
|
1869
|
-
}
|
|
1870
|
-
try {
|
|
1871
|
-
v[key] = red;
|
|
1872
|
-
} catch {
|
|
1873
|
-
ok = false;
|
|
1874
|
-
return;
|
|
1875
|
-
}
|
|
1876
|
-
if (Object.getOwnPropertyDescriptor(v, key)?.value !== red) {
|
|
1877
|
-
ok = false;
|
|
1878
|
-
return;
|
|
1879
|
-
}
|
|
1880
|
-
} else {
|
|
1881
|
-
visitChild(val);
|
|
1882
|
-
if (!ok) return;
|
|
1883
|
-
}
|
|
1884
|
-
}
|
|
1885
|
-
};
|
|
1886
|
-
visit(root);
|
|
1887
|
-
return ok;
|
|
1888
|
-
}
|
|
1889
|
-
/**
|
|
1890
|
-
* The fallback CARRIER: a fully sanitized deep copy of `root`, built when in-place
|
|
1891
|
-
* redaction is impossible. Fresh objects/arrays/Errors/Maps/Sets (all mutable), every
|
|
1892
|
-
* string redacted, every own property — enumerable or not, string or symbol key —
|
|
1893
|
-
* carried over as a plain data property, accessors snapshotted (an unreadable getter
|
|
1894
|
-
* becomes `'[unredactable]'`), the original PROTOTYPE restored so `instanceof`
|
|
1895
|
-
* classification (e.g. {@link WranglerQuarantineError}) survives the rebuild. Cycle-safe
|
|
1896
|
-
* (a cycle in the original maps to the same cycle among the copies).
|
|
1897
|
-
*/
|
|
1898
|
-
function deepSanitizedCopy(root, redactText) {
|
|
1899
|
-
const copies = /* @__PURE__ */ new WeakMap();
|
|
1900
|
-
const copy = (v) => {
|
|
1901
|
-
if (typeof v === "string") return redactText(v);
|
|
1902
|
-
if (typeof v !== "object" || v === null) return v;
|
|
1903
|
-
if (copies.has(v)) return copies.get(v);
|
|
1904
|
-
if (v instanceof Map) {
|
|
1905
|
-
const out = /* @__PURE__ */ new Map();
|
|
1906
|
-
copies.set(v, out);
|
|
1907
|
-
for (const [key, val] of v.entries()) out.set(copy(key), copy(val));
|
|
1908
|
-
return out;
|
|
1909
|
-
}
|
|
1910
|
-
if (v instanceof Set) {
|
|
1911
|
-
const out = /* @__PURE__ */ new Set();
|
|
1912
|
-
copies.set(v, out);
|
|
1913
|
-
for (const member of v.values()) out.add(copy(member));
|
|
1914
|
-
return out;
|
|
1915
|
-
}
|
|
1916
|
-
const out = Array.isArray(v) ? [] : v instanceof Error ? /* @__PURE__ */ new Error() : {};
|
|
1917
|
-
const proto = Object.getPrototypeOf(v);
|
|
1918
|
-
if (typeof proto === "object" && proto !== null && KNOWN_SAFE_PROTOTYPES.has(proto)) try {
|
|
1919
|
-
Object.setPrototypeOf(out, proto);
|
|
1920
|
-
} catch {}
|
|
1921
|
-
copies.set(v, out);
|
|
1922
|
-
for (const key of Reflect.ownKeys(v)) {
|
|
1923
|
-
const desc = Object.getOwnPropertyDescriptor(v, key);
|
|
1924
|
-
if (!desc) continue;
|
|
1925
|
-
let val;
|
|
1926
|
-
if (desc.get !== void 0 || desc.set !== void 0) try {
|
|
1927
|
-
val = desc.get !== void 0 ? desc.get.call(v) : void 0;
|
|
1928
|
-
} catch {
|
|
1929
|
-
val = "[unredactable]";
|
|
1930
|
-
}
|
|
1931
|
-
else val = desc.value;
|
|
1932
|
-
try {
|
|
1933
|
-
Object.defineProperty(out, key, {
|
|
1934
|
-
value: copy(val),
|
|
1935
|
-
writable: true,
|
|
1936
|
-
enumerable: desc.enumerable === true,
|
|
1937
|
-
configurable: true
|
|
1938
|
-
});
|
|
1939
|
-
} catch {}
|
|
1940
|
-
}
|
|
1941
|
-
return out;
|
|
1942
|
-
};
|
|
1943
|
-
return copy(root);
|
|
1944
|
-
}
|
|
1945
|
-
/**
|
|
1946
|
-
* Extract a SAFELY-READ, redacted one-line message from an unknown
|
|
1947
|
-
* value, for logging. Never coerces an object (`String()`/template literals invoke an
|
|
1948
|
-
* arbitrary — possibly hostile — `toString`): only primitives are stringified, and for
|
|
1949
|
-
* objects only a plain string `message` field is read (inside the no-throw boundary; a
|
|
1950
|
-
* throwing trap/getter degrades to the generic placeholder). THE one helper every
|
|
1951
|
-
* logging path uses for unknown rejection values (grep-proven: the Hyperdrive lister
|
|
1952
|
-
* warning and the rebuilt-carrier message below).
|
|
1953
|
-
*/
|
|
1954
|
-
function safeRedactedErrorMessage(value, redactText) {
|
|
1955
|
-
try {
|
|
1956
|
-
if (typeof value === "string") return redactText(value);
|
|
1957
|
-
if (value === null || value === void 0 || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
|
|
1958
|
-
const message = value.message;
|
|
1959
|
-
if (typeof message === "string") return redactText(message);
|
|
1960
|
-
} catch {}
|
|
1961
|
-
return "the rejection could not be rendered safely; its content was withheld";
|
|
1962
|
-
}
|
|
1963
|
-
/** The conservative carrier for a rejection that resists inspection. */
|
|
1964
|
-
function unsafeInspectionQuarantineCarrier() {
|
|
1965
|
-
const carrier = /* @__PURE__ */ new Error("provision: a propagated rejection could not be safely inspected (a property or prototype trap threw during redaction); its content was withheld. Treating it conservatively as an unconfirmed-wrangler-child (quarantine) failure: the whole-provision lock is retained, not released.");
|
|
1966
|
-
Object.assign(carrier, {
|
|
1967
|
-
quarantine: true,
|
|
1968
|
-
pid: void 0
|
|
1969
|
-
});
|
|
1970
|
-
return carrier;
|
|
1971
|
-
}
|
|
1972
|
-
/**
|
|
1973
|
-
* Redact a seam REJECTION with EVERYTHING — the quarantine
|
|
1974
|
-
* classification included — inside the ONE no-throw boundary, GUARANTEEING:
|
|
1975
|
-
* 1. this function NEVER throws (pre-fix, `isQuarantineError`'s `instanceof` ran on
|
|
1976
|
-
* the ORIGINAL value before any boundary — a Proxy with throwing
|
|
1977
|
-
* `getPrototypeOf`/property traps made the redactor itself throw a
|
|
1978
|
-
* secret-bearing, classification-free replacement error, and the outer path then
|
|
1979
|
-
* RELEASED the mutex under a possibly-live child);
|
|
1980
|
-
* 2. if inspection is unsafe ANYWHERE (classification, redaction, message reads), it
|
|
1981
|
-
* FAILS CLOSED AS QUARANTINE: a fresh INERT sanitized carrier marked
|
|
1982
|
-
* `{ quarantine: true }` propagates, so the mutex is retained (conservative — the
|
|
1983
|
-
* hostile value could have been hiding a real quarantine) and no trap error or
|
|
1984
|
-
* secret ever escapes;
|
|
1985
|
-
* 3. a quarantined rejection can never degrade into an ordinary failure: the
|
|
1986
|
-
* classification + pid are captured inside the boundary and restored structurally
|
|
1987
|
-
* on the carrier when a rebuild lost them.
|
|
1988
|
-
*/
|
|
1266
|
+
/** Redact a runner rejection, keeping the same Error object so quarantine fields survive. */
|
|
1989
1267
|
function redactRejection(error, redactText) {
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
if (!attempt.ok) return unsafeInspectionQuarantineCarrier();
|
|
1995
|
-
const redacted = attempt.value;
|
|
1996
|
-
if (!wasQuarantine || isQuarantineError(redacted)) return redacted;
|
|
1997
|
-
const carrier = redacted instanceof Error ? redacted : new Error(safeRedactedErrorMessage(redacted, redactText));
|
|
1998
|
-
Object.assign(carrier, {
|
|
1999
|
-
quarantine: true,
|
|
2000
|
-
pid: quarantinePid
|
|
2001
|
-
});
|
|
2002
|
-
return carrier;
|
|
2003
|
-
} catch {
|
|
2004
|
-
return unsafeInspectionQuarantineCarrier();
|
|
2005
|
-
}
|
|
1268
|
+
if (!(error instanceof Error)) return new Error(redactText(String(error)));
|
|
1269
|
+
error.message = redactText(error.message);
|
|
1270
|
+
if (typeof error.stack === "string") error.stack = redactText(error.stack);
|
|
1271
|
+
return error;
|
|
2006
1272
|
}
|
|
2007
1273
|
/**
|
|
2008
|
-
* Ensure every resource
|
|
2009
|
-
* creating
|
|
2010
|
-
* `wrangler <res> create --update-config` write real ids into the root config.
|
|
2011
|
-
* For resources that already exist (adopt path) — where `create` cannot run —
|
|
2012
|
-
* the real id is captured from `wrangler <res> list/info` (or, for Hyperdrive,
|
|
2013
|
-
* the account-list existence seam) and written through `writeProvisionedWranglerConfig`'s
|
|
2014
|
-
* adopt fields. Pure provisioning: NO build, deploy, or drift-check. Idempotent +
|
|
2015
|
-
* read-first: a binding already pointing at a real id is left untouched.
|
|
1274
|
+
* Ensure every desired resource exists in the user's Cloudflare account and record its id in
|
|
1275
|
+
* the root config, creating or adopting as needed. A binding with a real id is left as is.
|
|
2016
1276
|
*/
|
|
2017
1277
|
async function provisionCloudflareResources(root, rootWranglerPath, desired, opts) {
|
|
2018
1278
|
const releaseProvisionSlot = acquireProcessProvisionSlot();
|
|
@@ -2024,7 +1284,6 @@ async function provisionCloudflareResources(root, rootWranglerPath, desired, opt
|
|
|
2024
1284
|
}
|
|
2025
1285
|
/** The provision body, run while holding this process's single provision slot. */
|
|
2026
1286
|
async function provisionWithProcessSlot(root, rootWranglerPath, desired, opts) {
|
|
2027
|
-
await Promise.resolve();
|
|
2028
1287
|
const configPath = canonicalizeConfigPath(resolve$2(root, rootWranglerPath));
|
|
2029
1288
|
const lock = await acquireProvisionLock(configPath, opts?.lockOptions);
|
|
2030
1289
|
const quarantineHook = (info) => retainQuarantinedLock(lock, info);
|
|
@@ -2101,8 +1360,8 @@ async function reconcileD1(desired, configPath, runWrangler, write, persistCreat
|
|
|
2101
1360
|
configPath
|
|
2102
1361
|
}, {});
|
|
2103
1362
|
assertDiscoverySucceeded(listResult, "wrangler d1 list");
|
|
2104
|
-
const rawList =
|
|
2105
|
-
if (!rawList) throw new Error(`provision: "wrangler d1 list --json" did not return a JSON array; cannot verify whether D1 database ${JSON.stringify(name)} exists. Refusing to create a possible duplicate.`);
|
|
1363
|
+
const rawList = listResult.data;
|
|
1364
|
+
if (!Array.isArray(rawList)) throw new Error(`provision: "wrangler d1 list --json" did not return a JSON array; cannot verify whether D1 database ${JSON.stringify(name)} exists. Refusing to create a possible duplicate.`);
|
|
2106
1365
|
const claimants = validateResourceList(rawList, {
|
|
2107
1366
|
resource: "D1 database",
|
|
2108
1367
|
command: "wrangler d1 list",
|
|
@@ -2168,7 +1427,7 @@ async function reconcileD1(desired, configPath, runWrangler, write, persistCreat
|
|
|
2168
1427
|
configPath
|
|
2169
1428
|
}, {});
|
|
2170
1429
|
assertDiscoverySucceeded(infoResult, `wrangler d1 info ${name}`);
|
|
2171
|
-
const info = normalizeD1(
|
|
1430
|
+
const info = normalizeD1(isRecord(infoResult.data) ? infoResult.data : void 0, name);
|
|
2172
1431
|
if (info.kind !== "match") throw new Error(`provision: "wrangler d1 create ${name}" reported the database already exists, but "wrangler d1 info ${name} --json" did not return a database with a matching name and an unambiguous id. Refusing to adopt an unverified database.`);
|
|
2173
1432
|
log.info(`adopted existing D1 database ${name} (${info.id})`);
|
|
2174
1433
|
assertD1MigrationsDirApplied(write({
|
|
@@ -2233,16 +1492,16 @@ async function reconcileKv(desired, configPath, runWrangler, write, persistCreat
|
|
|
2233
1492
|
configPath
|
|
2234
1493
|
}, {});
|
|
2235
1494
|
assertDiscoverySucceeded(listResult, "wrangler kv namespace list");
|
|
2236
|
-
const rawList =
|
|
2237
|
-
if (!rawList) throw new Error(`provision: "wrangler kv namespace list" did not return a JSON array; cannot verify whether KV namespace ${JSON.stringify(title)} exists. Refusing to create a possible duplicate.`);
|
|
2238
|
-
const matches =
|
|
1495
|
+
const rawList = listResult.data;
|
|
1496
|
+
if (!Array.isArray(rawList)) throw new Error(`provision: "wrangler kv namespace list" did not return a JSON array; cannot verify whether KV namespace ${JSON.stringify(title)} exists. Refusing to create a possible duplicate.`);
|
|
1497
|
+
const matches = validateResourceList(rawList, {
|
|
2239
1498
|
resource: "KV namespace",
|
|
2240
1499
|
command: "wrangler kv namespace list",
|
|
2241
1500
|
hasValidId: (member) => isRealKvId(member.id),
|
|
2242
1501
|
idRule: "its id is missing or not a well-formed 32-hex namespace id",
|
|
2243
1502
|
nameField: "title",
|
|
2244
1503
|
nameLabel: "titled"
|
|
2245
|
-
})
|
|
1504
|
+
}).filter((ns) => ns.title === title).map((ns) => ({ id: ns.id }));
|
|
2246
1505
|
if (matches.length > 0) {
|
|
2247
1506
|
const chosen = matches.reduce((min, ns) => ns.id < min.id ? ns : min);
|
|
2248
1507
|
if (matches.length > 1) log.warn(`multiple KV namespaces titled "${title}" (${matches.map((m) => m.id).join(", ")}); adopting the lexicographically-smallest id ${chosen.id}`);
|
|
@@ -2303,8 +1562,8 @@ async function reconcileR2(desired, configPath, runWrangler, write, persistCreat
|
|
|
2303
1562
|
configPath
|
|
2304
1563
|
}, {});
|
|
2305
1564
|
if (isCleanSuccess(info)) {
|
|
2306
|
-
const payload =
|
|
2307
|
-
if (!payload || payload.name !== bucketName) throw new Error(`provision: "wrangler r2 bucket info ${bucketName} --json" exited 0 but did not identify the requested bucket (expected name ${JSON.stringify(bucketName)}). Refusing to adopt an unverified bucket. Output: ${info.stdout || info.stderr}`);
|
|
1565
|
+
const payload = info.data;
|
|
1566
|
+
if (!isRecord(payload) || payload.name !== bucketName) throw new Error(`provision: "wrangler r2 bucket info ${bucketName} --json" exited 0 but did not identify the requested bucket (expected name ${JSON.stringify(bucketName)}). Refusing to adopt an unverified bucket. Output: ${info.stdout || info.stderr}`);
|
|
2308
1567
|
log.info(`adopted existing R2 bucket ${bucketName}`);
|
|
2309
1568
|
write({ r2: {
|
|
2310
1569
|
binding,
|
|
@@ -2338,8 +1597,7 @@ async function reconcileR2(desired, configPath, runWrangler, write, persistCreat
|
|
|
2338
1597
|
name: bucketName,
|
|
2339
1598
|
configPath
|
|
2340
1599
|
}, {});
|
|
2341
|
-
|
|
2342
|
-
if (!payload || payload.name !== bucketName) throw new Error(`provision: "wrangler r2 bucket create ${bucketName}" reported success but a follow-up "wrangler r2 bucket info ${bucketName}" did not confirm the bucket exists. The create may have been interrupted — wrangler's launcher can mask a signal-killed child as exit 0, and the config entry is indistinguishable from the local-dev placeholder for this name. Refusing to record an unprovisioned bucket; inspect the account and retry.`);
|
|
1600
|
+
if (!isCleanSuccess(verify) || !isRecord(verify.data) || verify.data.name !== bucketName) throw new Error(`provision: "wrangler r2 bucket create ${bucketName}" reported success but a follow-up "wrangler r2 bucket info ${bucketName}" did not confirm the bucket exists. The create may have been interrupted — wrangler's launcher can mask a signal-killed child as exit 0, and the config entry is indistinguishable from the local-dev placeholder for this name. Refusing to record an unprovisioned bucket; inspect the account and retry.`);
|
|
2343
1601
|
}
|
|
2344
1602
|
persistCreatedConfig();
|
|
2345
1603
|
resources.push({
|
|
@@ -2377,7 +1635,7 @@ async function reconcileHyperdrive(desired, configPath, runWrangler, write, pers
|
|
|
2377
1635
|
validatedConfigs = result;
|
|
2378
1636
|
existenceUnknown = false;
|
|
2379
1637
|
} catch (error) {
|
|
2380
|
-
log.warn(`Hyperdrive existence check for ${name} failed: ` +
|
|
1638
|
+
log.warn(`Hyperdrive existence check for ${name} failed: ` + redactSecrets(error instanceof Error ? error.message : String(error)));
|
|
2381
1639
|
existenceUnknown = true;
|
|
2382
1640
|
validatedConfigs = void 0;
|
|
2383
1641
|
}
|
|
@@ -2466,12 +1724,7 @@ async function reconcileQueues(desired, configPath, runWrangler, resources) {
|
|
|
2466
1724
|
});
|
|
2467
1725
|
}
|
|
2468
1726
|
}
|
|
2469
|
-
/**
|
|
2470
|
-
* The provision body, run entirely UNDER the whole-provision lock (see
|
|
2471
|
-
* {@link provisionCloudflareResources}). `configPath` is the ONE resolved absolute
|
|
2472
|
-
* config path the lock is keyed on — threaded through read-first, wrangler `-c`, and
|
|
2473
|
-
* the writer so reads/writes can never hit different files.
|
|
2474
|
-
*/
|
|
1727
|
+
/** The provision body, run under the lock with the canonical config path. */
|
|
2475
1728
|
async function provisionCloudflareResourcesLocked(root, configPath, desired, opts) {
|
|
2476
1729
|
if (existsSync(configPath)) {
|
|
2477
1730
|
const raw = readFileSync(configPath, "utf-8");
|
|
@@ -2541,4 +1794,4 @@ async function provisionCloudflareResourcesLocked(root, configPath, desired, opt
|
|
|
2541
1794
|
};
|
|
2542
1795
|
}
|
|
2543
1796
|
//#endregion
|
|
2544
|
-
export {
|
|
1797
|
+
export { deleteEnvVarCaseAware as a, isRealKvId as c, resolvePinnedApiBaseUrl as d, createDefaultRunWrangler as i, provisionCloudflareResources as l, assertValidResourceName as n, isQueueConfirmedAbsent as o, checkCloudflareProvisionDrift as r, isRealD1Id as s, assertNoTransportRedirectInProjectDotenv as t, provisionEnv as u };
|