void 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/{account-cmd-CjyxcGkM.mjs → account-cmd-C84Ee8cO.mjs} +4 -4
- package/dist/{scan-ClYmX3sa.mjs → application-analysis-BVsVqc11.mjs} +135 -18
- package/dist/application-routing-B7QEjqSR.mjs +27 -0
- package/dist/{auth-CZuiVsFh.mjs → auth-CCjt0hcq.mjs} +1 -1
- package/dist/{auth-DLNN0D3Z.mjs → auth-CSkdO2Bb.mjs} +4 -47
- package/dist/{auth-link-BLO4ptzo.mjs → auth-link-CioEg6uY.mjs} +4 -4
- package/dist/{auth-router-ZUg9MF_U.mjs → auth-router-BsR981d4.mjs} +4 -4
- package/dist/{better-auth-shared-rsBGBvWJ.mjs → better-auth-shared-hy6RPh9W.mjs} +13 -2
- package/dist/{build-cmd-BxR5FROK.mjs → build-cmd-LzvNJORH.mjs} +17 -5
- package/dist/{cache-D0sWhgKI.mjs → cache-C4MvnrMH.mjs} +2 -2
- package/dist/{cancel-deploy-D4NUqFbi.mjs → cancel-deploy-abUxpP2n.mjs} +2 -2
- package/dist/{cf-build-output-BJ6yGEIS.mjs → cf-build-output-BPqOT964.mjs} +2 -1
- package/dist/cf-build-output-_0HNWysu.mjs +2 -0
- package/dist/cli/cli.mjs +80 -455
- package/dist/cli/{cf-compat.mjs → cloudflare-operation-process.mjs} +442 -421
- package/dist/cli/env-schema-probe.d.mts +2 -1
- package/dist/cli/env-schema-probe.mjs +3 -3
- package/dist/client-Cu7jWiF1.mjs +2 -0
- package/dist/{client-BTZ3XkrB.mjs → client-RV8NVeB8.mjs} +140 -21
- package/dist/{cloudflare-auth-DQkqoMYa.mjs → cloudflare-auth-Qdc7tw8F.mjs} +41 -38
- package/dist/{cloudflare-cmd-BKlfGHAy.mjs → cloudflare-cmd-BcrVyTmJ.mjs} +5 -5
- package/dist/cloudflare-config-Bktvwtpf.mjs +182 -0
- package/dist/{cloudflare-connect-Ctmrw579.mjs → cloudflare-connect-B8uPZ4nx.mjs} +3 -3
- package/dist/{cloudflare-operations-BT6OWFBk.mjs → cloudflare-operations-AiWashgg.mjs} +1 -1
- package/dist/{cloudflare-operations-B9tzgjmf.mjs → cloudflare-operations-fxHb-byx.mjs} +116 -172
- package/dist/{preset-UHj9ARyP.mjs → cloudflare-process-B-wekeR6.mjs} +93 -6
- package/dist/{config-VavjpDnp.d.mts → config-BMHb8RCj.d.mts} +1 -0
- package/dist/config-C_XRIPx2.mjs +89 -0
- package/dist/config-entry.d.mts +1 -1
- package/dist/{connect-Dg-WkW-C.mjs → connect-Bd4kJd9U.mjs} +5 -5
- package/dist/{create-project-DdoqwFLF.mjs → create-project-Boczwj5r.mjs} +1 -1
- package/dist/{create-project-l9J7pbtm.mjs → create-project-bMf6ffLZ.mjs} +2 -2
- package/dist/{db-gp2sCXyN.mjs → db-8uG64XLl.mjs} +21 -21
- package/dist/{delete-TTedbH6B.mjs → delete-NvbiyeJf.mjs} +2 -2
- package/dist/{deploy-CH-o2CaZ.mjs → deploy-DzAIwqNU.mjs} +908 -582
- package/dist/{deploy-B19L2Bra.mjs → deploy-bjXdFCtn.mjs} +1 -1
- package/dist/{domain-JRF59P_r.mjs → domain-y5Tvydvo.mjs} +3 -3
- package/dist/{email-Bo6G9LOZ.mjs → email-B3umsW75.mjs} +13 -28
- package/dist/{env-BYOrWQnv.mjs → env-BS6qYHDb.mjs} +4 -4
- package/dist/{env-public-BxU_0yTL.d.mts → env-public-BX_r8HR6.d.mts} +1 -1
- package/dist/{env-validation-BB4GkLxn.mjs → env-validation-BPn7vk-V.mjs} +18 -71
- package/dist/{env-validation-BXge7uyK.mjs → env-validation-DbTg7-ar.mjs} +1 -1
- package/dist/{fetch-CXDChK7B.mjs → fetch-BIZJh7vR.mjs} +2 -2
- package/dist/{fetch-stream-AOByI7Ki.mjs → fetch-stream-IvCYKyQL.mjs} +24 -4
- package/dist/{gen-sCtlCOdA.mjs → gen-B580--vC.mjs} +2 -2
- package/dist/gen-BVaUUumi.mjs +2 -0
- package/dist/{github-cmd-0-7PexDq.mjs → github-cmd-v45BdfKX.mjs} +2 -2
- package/dist/{handler-HEcZsaij.d.mts → handler-CZ4nAylQ.d.mts} +1 -1
- package/dist/help-DofyZuY7.mjs +2 -0
- package/dist/{help-GKtwl07I.mjs → help-daGKjXGk.mjs} +229 -747
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +288 -1502
- package/dist/info-B97bTX9N.mjs +113 -0
- package/dist/{init-FZx3Elvz.mjs → init-Bvy7zrBo.mjs} +46 -15
- package/dist/limits-Bq5LG8Id.d.mts +27 -0
- package/dist/limits-Cjuk2VPm.mjs +68 -0
- package/dist/{link-BrQfb_CU.mjs → link-CDqqCjFl.mjs} +3 -3
- package/dist/{list-D44jmIAM.mjs → list-CZj0dzKY.mjs} +3 -3
- package/dist/{live-CKJlvlNp.d.mts → live-Chw1eIMv.d.mts} +1 -1
- package/dist/{local-d1-Bg9OzEEO.mjs → local-d1-2CMnpuW_.mjs} +2 -2
- package/dist/login-DYt_An22.mjs +2 -0
- package/dist/{login-CAsAsQ-_.mjs → login-UZKFM_u7.mjs} +3 -3
- package/dist/{logs-BjKvFnVM.mjs → logs-CUZ6t9t3.mjs} +3 -3
- package/dist/migrate-8_2u55MD.mjs +2 -0
- package/dist/{migrate-BPITvDJN.mjs → migrate-DHul7PRV.mjs} +2 -1
- package/dist/{node-Dt7z256D.mjs → node-BkyRWRx8.mjs} +1 -1
- package/dist/operator-args-CLgKGlwU.mjs +690 -0
- package/dist/{operator-cmd-BK5CCiT9.mjs → operator-cmd-DBA6dl0m.mjs} +68 -22
- package/dist/pages/client.d.mts +34 -2
- package/dist/pages/client.mjs +59 -3
- package/dist/pages/index.d.mts +1 -1
- package/dist/pages/index.mjs +1 -1
- package/dist/pages/islands-plugin.mjs +1 -1
- package/dist/pages/protocol.d.mts +2 -2
- package/dist/pages/protocol.mjs +2 -308
- package/dist/{parse-filename-DioPHiR9.mjs → parse-filename-CUbj-1MP.mjs} +35 -1
- package/dist/plan-D1Q5rf-r.mjs +2 -0
- package/dist/plan-NPpwZ_kc.mjs +58 -0
- package/dist/platform-args-BJdRtlLq.mjs +506 -0
- package/dist/platform-args-D3RXyR6h.mjs +2 -0
- package/dist/{platform-auth-config-BlN8xTdD.mjs → platform-auth-config-B56E9YP1.mjs} +3 -3
- package/dist/{platform-auth-protection-7d5aV2Jg.mjs → platform-auth-protection-BDWO_aER.mjs} +2 -2
- package/dist/{platform-auth-recovery-Dxij8ZbR.mjs → platform-auth-recovery-1gg4CSRe.mjs} +3 -3
- package/dist/{platform-cmd-E0FuL212.mjs → platform-cmd-Bt-1w6pY.mjs} +1 -1
- package/dist/{platform-cmd-B3hwKrFK.mjs → platform-cmd-DMcQStSc.mjs} +24 -3
- package/dist/{platform-domain-D6Xcy9ZX.mjs → platform-domain-BNkcz0OB.mjs} +2 -2
- package/dist/{platform-lifecycle-k0E0xoxx.mjs → platform-lifecycle-9OyBALhH.mjs} +327 -215
- package/dist/{platform-lifecycle-DMh_qrry.mjs → platform-lifecycle-BE6C_jxh.mjs} +1 -1
- package/dist/{platform-management-Brz_BDiT.mjs → platform-management-CjwLVQwN.mjs} +6 -3
- package/dist/{platform-management-DOtss0BN.mjs → platform-management-CnyTdcWX.mjs} +1 -1
- package/dist/platform-plans-config-BNGKGr4P.mjs +359 -0
- package/dist/{platform-recovery-BvtcXON5.mjs → platform-recovery-D6KSpuFm.mjs} +2 -2
- package/dist/{plugin-inference-BMfKRSqE.mjs → plugin-inference-CXWnn79A.mjs} +174 -64
- package/dist/{prepare-C-6YZyyg.mjs → prepare-B5Mkic5u.mjs} +1 -1
- package/dist/{prepare-CRhJbVrG.mjs → prepare-DOsL0CC9.mjs} +4 -20
- package/dist/prepare-cgvDMtSb.mjs +2 -0
- package/dist/{project-cmd-BZLCqOqw.mjs → project-cmd-CNzrqvBv.mjs} +16 -16
- package/dist/{project-team-BvgM0WoX.mjs → project-team-DQOWPfQT.mjs} +2 -2
- package/dist/{project-token-l5DqK6Az.mjs → project-token-CTDYU0Cc.mjs} +2 -2
- package/dist/{project-zero-trust-v6Mvpp8y.mjs → project-zero-trust-BNAkW-_0.mjs} +2 -2
- package/dist/{protocol-BBa6cstI.d.mts → protocol-CjF_iI9X.d.mts} +2 -2
- package/dist/protocol-U7bfjHmA.mjs +331 -0
- package/dist/{provision-Fr9pRqce.mjs → provision-C4ORE7G7.mjs} +1 -1
- package/dist/{provision-C4IGqkBf.mjs → provision-CJFgTZY8.mjs} +89 -198
- package/dist/{requests-DN-BbNiM.mjs → requests-MhxYnau8.mjs} +2 -2
- package/dist/resource-name-C7LVpcRm.mjs +11 -0
- package/dist/{rollback-D7rzSaZM.mjs → rollback-CJ6iSDoU.mjs} +3 -3
- package/dist/route-url-CG7U-cRN.mjs +15 -0
- package/dist/{runner-B8wXwWlo.mjs → runner-CXA9Fh8h.mjs} +1 -1
- package/dist/{runner-p-dMs2UN.mjs → runner-Ol0TNjk6.mjs} +2 -2
- package/dist/runtime/ai.d.mts +12 -8
- package/dist/runtime/ai.mjs +84 -17
- package/dist/runtime/better-auth-mysql.mjs +1 -1
- package/dist/runtime/better-auth-pg.mjs +1 -1
- package/dist/runtime/better-auth.mjs +1 -1
- package/dist/runtime/client-react.mjs +2 -2
- package/dist/runtime/client-solid.mjs +2 -2
- package/dist/runtime/client-svelte.mjs +2 -2
- package/dist/runtime/client-vue.mjs +2 -2
- package/dist/runtime/client.mjs +2 -2
- package/dist/runtime/durable.d.mts +3 -1
- package/dist/runtime/durable.mjs +4 -1
- package/dist/runtime/email/testing.mjs +1 -1
- package/dist/runtime/env-public.d.mts +1 -1
- package/dist/runtime/fetch-stream.mjs +1 -1
- package/dist/runtime/fetch.mjs +1 -1
- package/dist/runtime/handler.d.mts +1 -1
- package/dist/runtime/kv.mjs +0 -1
- package/dist/runtime/limits.d.mts +2 -0
- package/dist/runtime/limits.mjs +2 -0
- package/dist/runtime/live-client.d.mts +1 -1
- package/dist/runtime/live-server.mjs +25 -16
- package/dist/runtime/live.d.mts +1 -1
- package/dist/runtime/migration-handler.mjs +62 -42
- package/dist/runtime/route-url.d.mts +4 -0
- package/dist/runtime/route-url.mjs +2 -0
- package/dist/runtime/routing.d.mts +180 -0
- package/dist/runtime/routing.mjs +1082 -0
- package/dist/runtime/sandbox-container.d.mts +1 -1
- package/dist/runtime/sandbox-container.mjs +1 -1
- package/dist/runtime/sandbox.d.mts +3 -3
- package/dist/runtime/sandbox.mjs +75 -45
- package/dist/runtime/sse.mjs +1 -1
- package/dist/runtime/validator.d.mts +1 -1
- package/dist/runtime/ws-server.d.mts +4 -2
- package/dist/runtime/ws-server.mjs +27 -2
- package/dist/runtime/ws.d.mts +2 -2
- package/dist/runtime/ws.mjs +8 -6
- package/dist/{sandbox-qpNBT8a3.d.mts → sandbox-XZAqzFlG.d.mts} +11 -8
- package/dist/{sandbox-container-DdNEBfCc.d.mts → sandbox-container-Bo0eiFKz.d.mts} +2 -1
- package/dist/{sandbox-container-4fdqLnyb.mjs → sandbox-container-C6ItmVuN.mjs} +4 -2
- package/dist/{scan-4tfN-PSn.mjs → scan-C7okrLyM.mjs} +4 -35
- package/dist/{secret-BOtOl_cb.mjs → secret-Dli5fP0B.mjs} +4 -4
- package/dist/{sse-BaC1jXko.mjs → sse-CQNaDFFV.mjs} +6 -3
- package/dist/validate-Dq_L3s0S.mjs +2 -0
- package/dist/{validate-CIUwFpjB.mjs → validate-ctOrgiS3.mjs} +2 -1
- package/dist/{wrangler-DQF1vKyf.mjs → wrangler-7K-bW_DL.mjs} +8 -239
- package/dist/{ws-BoY7vQML.d.mts → ws-CL1w7GXU.d.mts} +13 -2
- package/package.json +15 -8
- package/skills/migrate-vite-cloudflare-to-void/SKILL.md +34 -157
- package/skills/void/SKILL.md +50 -135
- package/skills/void/docs/guide/ai.md +94 -84
- package/skills/void/docs/guide/app-types.md +3 -32
- package/skills/void/docs/guide/auth.md +12 -116
- package/skills/void/docs/guide/database/d1.md +9 -54
- package/skills/void/docs/guide/database/mysql.md +1 -1
- package/skills/void/docs/guide/database/postgresql.md +5 -26
- package/skills/void/docs/guide/database.md +23 -75
- package/skills/void/docs/guide/deployment.md +27 -113
- package/skills/void/docs/guide/durable-state.md +43 -18
- package/skills/void/docs/guide/edge/headers.md +3 -47
- package/skills/void/docs/guide/edge/prerendering.md +5 -20
- package/skills/void/docs/guide/edge/redirects.md +11 -64
- package/skills/void/docs/guide/edge/revalidation.md +5 -18
- package/skills/void/docs/guide/edge/rewrites.md +56 -284
- package/skills/void/docs/guide/edge/static-assets.md +22 -72
- package/skills/void/docs/guide/email/domains.md +112 -0
- package/skills/void/docs/guide/email/receiving.md +139 -0
- package/skills/void/docs/guide/email/sending.md +231 -0
- package/skills/void/docs/guide/email.md +13 -619
- package/skills/void/docs/guide/env-migration.md +11 -11
- package/skills/void/docs/guide/env-vars.md +9 -29
- package/skills/void/docs/guide/index.md +0 -15
- package/skills/void/docs/guide/jobs.md +3 -18
- package/skills/void/docs/guide/kv.md +5 -11
- package/skills/void/docs/guide/live.md +5 -56
- package/skills/void/docs/guide/pages-routing/actions-and-forms.md +78 -125
- package/skills/void/docs/guide/pages-routing/head.md +10 -10
- package/skills/void/docs/guide/pages-routing/islands.md +6 -36
- package/skills/void/docs/guide/pages-routing/layouts.md +6 -128
- package/skills/void/docs/guide/pages-routing/loaders.md +3 -19
- package/skills/void/docs/guide/pages-routing/markdown.md +13 -171
- package/skills/void/docs/guide/pages-routing/overview.md +7 -17
- package/skills/void/docs/guide/pages-routing/view-transitions.md +1 -1
- package/skills/void/docs/guide/platform/administration/access.md +1 -4
- package/skills/void/docs/guide/platform/administration/email.md +35 -8
- package/skills/void/docs/guide/platform/administration/operations.md +26 -4
- package/skills/void/docs/guide/platform/administration/plans.md +126 -0
- package/skills/void/docs/guide/platform/administration/projects.md +5 -2
- package/skills/void/docs/guide/platform/administration/zero-trust.md +23 -152
- package/skills/void/docs/guide/platform/development/runtime.md +3 -13
- package/skills/void/docs/guide/platform/development/schema-ci.md +0 -58
- package/skills/void/docs/guide/platform/installation/credentials.md +6 -4
- package/skills/void/docs/guide/platform/installation/domains.md +30 -2
- package/skills/void/docs/guide/platform/installation/first-deployment.md +2 -0
- package/skills/void/docs/guide/platform/installation/maintenance.md +3 -1
- package/skills/void/docs/guide/platform/installation/prerequisites.md +20 -15
- package/skills/void/docs/guide/platform/installation/setup.md +9 -5
- package/skills/void/docs/guide/platform-administration.md +1 -0
- package/skills/void/docs/guide/queues.md +7 -9
- package/skills/void/docs/guide/quickstart.md +38 -37
- package/skills/void/docs/guide/remote-dev.md +4 -9
- package/skills/void/docs/guide/sandboxes.md +29 -21
- package/skills/void/docs/guide/server-routing.md +9 -72
- package/skills/void/docs/guide/sse.md +4 -18
- package/skills/void/docs/guide/ssg.md +3 -15
- package/skills/void/docs/guide/ssr.md +14 -62
- package/skills/void/docs/guide/storage.md +9 -4
- package/skills/void/docs/guide/type-safety.md +3 -14
- package/skills/void/docs/guide/typed-fetch.md +3 -7
- package/skills/void/docs/guide/websockets.md +68 -40
- package/skills/void/docs/integrations/agents.md +3 -3
- package/skills/void/docs/integrations/cloudflare.md +85 -316
- package/skills/void/docs/integrations/frameworks/analog.md +5 -64
- package/skills/void/docs/integrations/frameworks/astro.md +4 -73
- package/skills/void/docs/integrations/frameworks/nuxt.md +5 -62
- package/skills/void/docs/integrations/frameworks/overview.md +11 -54
- package/skills/void/docs/integrations/frameworks/react-router.md +5 -60
- package/skills/void/docs/integrations/frameworks/sveltekit.md +6 -65
- package/skills/void/docs/integrations/frameworks/tanstack-start.md +4 -62
- package/skills/void/docs/integrations/nodejs-bun-deno.md +5 -69
- package/skills/void/docs/reference/api/auth.md +156 -0
- package/skills/void/docs/reference/api/client.md +87 -0
- package/skills/void/docs/reference/api/database.md +95 -0
- package/skills/void/docs/reference/api/durable.md +46 -0
- package/skills/void/docs/reference/api/env.md +50 -0
- package/skills/void/docs/reference/api/handlers.md +254 -0
- package/skills/void/docs/reference/api/pages.md +241 -0
- package/skills/void/docs/reference/api/plugin.md +39 -0
- package/skills/void/docs/reference/api/resources.md +109 -0
- package/skills/void/docs/reference/api/rewrites.md +76 -0
- package/skills/void/docs/reference/api/types.md +92 -0
- package/skills/void/docs/reference/api.md +56 -1218
- package/skills/void/docs/reference/cli/auth.md +88 -0
- package/skills/void/docs/reference/cli/database.md +128 -0
- package/skills/void/docs/reference/cli/deploy.md +85 -0
- package/skills/void/docs/reference/cli/domains.md +41 -0
- package/skills/void/docs/reference/cli/email.md +129 -0
- package/skills/void/docs/reference/cli/generate.md +116 -0
- package/skills/void/docs/reference/cli/github.md +189 -0
- package/skills/void/docs/reference/cli/platform-config.md +92 -0
- package/skills/void/docs/reference/cli/platform-email.md +81 -0
- package/skills/void/docs/reference/cli/platform-installation.md +127 -0
- package/skills/void/docs/reference/cli/platform-operations.md +90 -0
- package/skills/void/docs/reference/cli/platform-users.md +89 -0
- package/skills/void/docs/reference/cli/platform-zero-trust.md +45 -0
- package/skills/void/docs/reference/cli/platform.md +70 -0
- package/skills/void/docs/reference/cli/project.md +214 -0
- package/skills/void/docs/reference/cli/secrets.md +76 -0
- package/skills/void/docs/reference/cli/setup.md +70 -0
- package/skills/void/docs/reference/cli.md +32 -1682
- package/skills/void/docs/reference/config.md +12 -18
- package/skills/void/docs/reference/resource-inference.md +3 -58
- package/skills/void/docs/reference/structure.md +14 -41
- package/dist/canonical-json-DuDiiUsQ.mjs +0 -13
- package/dist/client-Czz8o5jP.mjs +0 -2
- package/dist/gen-CNJ62MM7.mjs +0 -2
- package/dist/help-CmZzxUba.mjs +0 -2
- package/dist/login-CGcRKEoi.mjs +0 -2
- package/dist/migrate-CYfbKkXh.mjs +0 -2
- package/dist/plan-BEZ8VJW0.mjs +0 -256
- package/dist/plan-DpuOr14e.mjs +0 -2
- package/dist/prepare-Bm3iq-u4.mjs +0 -2
- package/dist/validate-EKmJWxmy.mjs +0 -2
- /package/dist/cli/{cf-compat.d.mts → cloudflare-operation-process.d.mts} +0 -0
|
@@ -2,20 +2,20 @@ import { n as __toESM } from "./rolldown-runtime-DXIUcv95.mjs";
|
|
|
2
2
|
import { a as join, n as dirname, u as resolve } from "./dist-BVqXeR_h.mjs";
|
|
3
3
|
import { t as require_picocolors } from "./picocolors-BTps1_gs.mjs";
|
|
4
4
|
import { t as cfAccessHeaders } from "./cf-access-DRsQRe6k.mjs";
|
|
5
|
-
import { d as openBrowser } from "./auth-
|
|
5
|
+
import { d as openBrowser } from "./auth-CSkdO2Bb.mjs";
|
|
6
6
|
import { a as listInstallationManifests, d as readInstallationRecoverySecrets, f as readPendingInstallationSecrets, g as savePlatformConnection, h as resolveInstallationManifest, i as latestPlatformWorkerCutoverGeneration, l as platformWorkerSecretUpdateIntentState, n as assertPlatformInstallationSlugAvailable, s as normalizePlatformApiUrl, t as acquirePlatformLifecycleLock, u as promoteInstallationSecrets, v as writeInstallationManifest, y as writePendingInstallationSecrets } from "./platform-registry-QBa4DK32.mjs";
|
|
7
7
|
import { n as ensurePlatformAccessToken } from "./cf-access-P0iZnu0q.mjs";
|
|
8
8
|
import { B as text$1, E as confirm, F as password, G as settings, I as select, K as u$1, M as multiselect, N as note, O as intro, P as outro, R as symbol, U as CANCEL_SYMBOL, W as isCancel, g as S_PASSWORD_MASK, i as S_BAR_END, j as log, r as S_BAR } from "./dist-CTBk70IR.mjs";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { _ as resolvePinnedApiBaseUrl, o as createDefaultRunWrangler } from "./provision-
|
|
9
|
+
import { c as resolveCloudflareOperationArgv } from "./cloudflare-process-B-wekeR6.mjs";
|
|
10
|
+
import { o as runCloudflareAuthCommand } from "./cloudflare-auth-Qdc7tw8F.mjs";
|
|
11
|
+
import { _ as resolvePinnedApiBaseUrl, o as createDefaultRunWrangler } from "./provision-CJFgTZY8.mjs";
|
|
12
12
|
import { c as import_picocolors$1, i as createSpinner, n as cliCommandName, r as cliTitle } from "./output-Dm_A4Tbv.mjs";
|
|
13
13
|
import { copyFileSync, cpSync, existsSync, lstatSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
import { createHash, createHmac, randomBytes, randomUUID } from "node:crypto";
|
|
16
16
|
import { tmpdir } from "node:os";
|
|
17
17
|
import { execFile } from "node:child_process";
|
|
18
|
-
import { PLATFORM_AUTH_SETUP_PROOF_PREFIX, PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE, PLATFORM_HEALTH_HOSTNAME_LABEL, PLATFORM_LOGIN_KINDS, PLATFORM_R2_OWNERSHIP_MARKER_KEY, PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY,
|
|
18
|
+
import { PLATFORM_AUTH_SETUP_PROOF_PREFIX, PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE, PLATFORM_HEALTH_HOSTNAME_LABEL, PLATFORM_LOGIN_KINDS, PLATFORM_R2_OWNERSHIP_MARKER_KEY, PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY, createApplicationRoutes, createPlatformDeploymentManifest, createPlatformInstallationManifest, createPlatformResourceNames, createPlatformResourceOwnershipMarker, createPlatformWorkerConfigs, normalizeApplicationDomain, normalizeInstallationSlug, normalizePlatformDashboardUrl, parsePlatformAccessProtection, parsePlatformAdmissionPolicy, parsePlatformAuthenticationSetup, parsePlatformInstallationManifest, parsePlatformLoginConfiguration, parsePlatformResourceOwnershipMarker, platformDashboardUrl, recordInstallationResource, recordInstallationStep, setInstallationDesiredState, setInstallationEndpoints, transitionInstallation } from "@void/platform";
|
|
19
19
|
import { stripVTControlCharacters } from "node:util";
|
|
20
20
|
import { encodeCloudflareVersionMessage, parseCloudflareVersionMessage, readCloudflareTriggerConfig, readCloudflareVersionMigrationTag, stabilizeCloudflareVersionTriggers } from "@void/deploy-cloudflare";
|
|
21
21
|
import { readFile } from "node:fs/promises";
|
|
@@ -717,6 +717,14 @@ function text(value, name) {
|
|
|
717
717
|
if (typeof value !== "string" || !value || value.length > 8192) throw new Error(`platform: Cloudflare Access did not return ${name}.`);
|
|
718
718
|
return value;
|
|
719
719
|
}
|
|
720
|
+
function platformAccessIdentityProvider(provider) {
|
|
721
|
+
const id = text(provider.id, "identity provider ID");
|
|
722
|
+
const name = provider.name;
|
|
723
|
+
return {
|
|
724
|
+
id,
|
|
725
|
+
name: name == null || typeof name === "string" && !name.trim() ? id : text(name, "identity provider name")
|
|
726
|
+
};
|
|
727
|
+
}
|
|
720
728
|
function ids(value) {
|
|
721
729
|
if (!Array.isArray(value)) return [];
|
|
722
730
|
return value.map((entry) => typeof entry === "string" ? entry : text(object(entry, "policy").id, "policy ID"));
|
|
@@ -846,10 +854,7 @@ async function inspectPlatformAccess(client, input) {
|
|
|
846
854
|
audience: "validation"
|
|
847
855
|
}]
|
|
848
856
|
});
|
|
849
|
-
const identityProviders = (await client.list("identity_providers")).map(
|
|
850
|
-
id: text(provider.id, "identity provider ID"),
|
|
851
|
-
name: text(provider.name ?? provider.id, "identity provider name")
|
|
852
|
-
}));
|
|
857
|
+
const identityProviders = (await client.list("identity_providers")).map(platformAccessIdentityProvider);
|
|
853
858
|
if (!input.identityProviderIds.length || !input.policyIds.length || input.identityProviderIds.length > 16 || input.policyIds.length > 16 || new Set(input.identityProviderIds).size !== input.identityProviderIds.length || new Set(input.policyIds).size !== input.policyIds.length) throw new Error("platform: Select explicit identity providers and company policies for Access.");
|
|
854
859
|
if (input.identityProviderIds.some((id) => !identityProviders.some((provider) => provider.id === id))) throw new Error("platform: A selected Access identity provider is unavailable in this account.");
|
|
855
860
|
const policies = await Promise.all(input.policyIds.map(async (id) => object(await client.get(`policies/${encodeURIComponent(id)}`), "Access policy")));
|
|
@@ -1277,9 +1282,9 @@ async function preparePlatformAccessSetup(input) {
|
|
|
1277
1282
|
const identityProviderIds = spec.identityProviderIds ?? (process.stdin.isTTY ? answer$1(await multiselect({
|
|
1278
1283
|
message: "Company identity providers:",
|
|
1279
1284
|
required: true,
|
|
1280
|
-
options: providers.filter((provider) => typeof provider.id === "string").map((provider) => ({
|
|
1281
|
-
value:
|
|
1282
|
-
label: stripVTControlCharacters(
|
|
1285
|
+
options: providers.filter((provider) => typeof provider.id === "string").map(platformAccessIdentityProvider).map((provider) => ({
|
|
1286
|
+
value: provider.id,
|
|
1287
|
+
label: stripVTControlCharacters(provider.name)
|
|
1283
1288
|
}))
|
|
1284
1289
|
})) : []);
|
|
1285
1290
|
const policyIds = spec.policyIds ?? (process.stdin.isTTY ? answer$1(await multiselect({
|
|
@@ -1417,9 +1422,9 @@ async function resolvePlatformCloudflareCredentialWithInput(readInput, dependenc
|
|
|
1417
1422
|
input,
|
|
1418
1423
|
credential: result.credential
|
|
1419
1424
|
};
|
|
1420
|
-
const login = await runWrangler(
|
|
1425
|
+
const login = await runWrangler({ type: "auth.login" }, false);
|
|
1421
1426
|
assertNotCancelled();
|
|
1422
|
-
if (login.status !== 0) throw new Error("platform: Cloudflare browser login did not complete.");
|
|
1427
|
+
if (login.status !== 0 || login.signal || login.error) throw new Error("platform: Cloudflare browser login did not complete.");
|
|
1423
1428
|
const credential = await readWranglerOAuthCredential(runWrangler);
|
|
1424
1429
|
assertNotCancelled();
|
|
1425
1430
|
if (!credential) throw new Error("platform: Cloudflare sign-in completed without a usable OAuth credential.");
|
|
@@ -1442,15 +1447,11 @@ async function readExistingCredential(dependencies, runWrangler) {
|
|
|
1442
1447
|
return await readWranglerOAuthCredential(runWrangler);
|
|
1443
1448
|
}
|
|
1444
1449
|
async function readWranglerOAuthCredential(runWrangler = (args) => runCloudflareAuthCommand(process.cwd(), args)) {
|
|
1445
|
-
const result = await runWrangler(
|
|
1446
|
-
|
|
1447
|
-
"token",
|
|
1448
|
-
"--json"
|
|
1449
|
-
], true);
|
|
1450
|
-
if (result.status !== 0) return null;
|
|
1450
|
+
const result = await runWrangler({ type: "auth.token" }, true);
|
|
1451
|
+
if (result.status !== 0 || result.signal || result.error) return null;
|
|
1451
1452
|
try {
|
|
1452
|
-
const parsed =
|
|
1453
|
-
if (parsed.type !== "oauth" || typeof parsed.token !== "string" || !parsed.token.trim()) return null;
|
|
1453
|
+
const parsed = result.data;
|
|
1454
|
+
if (!parsed || parsed.type !== "oauth" || typeof parsed.token !== "string" || !parsed.token.trim()) return null;
|
|
1454
1455
|
return {
|
|
1455
1456
|
source: "oauth",
|
|
1456
1457
|
token: parsed.token.trim()
|
|
@@ -1490,6 +1491,7 @@ async function verifyInstalledPlatformAccessProtection(input) {
|
|
|
1490
1491
|
if ([
|
|
1491
1492
|
input.plan.apiUrl,
|
|
1492
1493
|
input.plan.proxyUrl,
|
|
1494
|
+
...input.dashboardUrl ? [input.dashboardUrl] : [],
|
|
1493
1495
|
...input.manifest.endpoints.dashboardUrl ? [input.manifest.endpoints.dashboardUrl] : []
|
|
1494
1496
|
].map((value) => new URL(value).origin).some((origin) => !origins.includes(origin)) || protection.applications.some((entry) => entry.audience !== app.aud || !platformAccessPublicDestinations(app).includes(new URL(entry.origin).hostname))) throw new Error("platform: Access audiences and complete origin coverage must match the active platform protection. Restore the current Access checkpoint or repair its applications.");
|
|
1495
1497
|
for (const other of await access.list("apps")) {
|
|
@@ -1505,7 +1507,7 @@ async function verifyInstalledPlatformAccessProtection(input) {
|
|
|
1505
1507
|
}
|
|
1506
1508
|
const existingWorkers = input.plan.actions.filter((action) => action.kind === "worker" && action.action === "update").map((action) => action.name);
|
|
1507
1509
|
if (existingWorkers.length) {
|
|
1508
|
-
const { verifyProtectionWorkerOrigins } = await import("./platform-auth-protection-
|
|
1510
|
+
const { verifyProtectionWorkerOrigins } = await import("./platform-auth-protection-BDWO_aER.mjs");
|
|
1509
1511
|
await verifyProtectionWorkerOrigins({
|
|
1510
1512
|
request: input.client.request.bind(input.client),
|
|
1511
1513
|
listWorkerDomains: input.client.listWorkerDomains.bind(input.client),
|
|
@@ -1598,6 +1600,7 @@ async function verifyPlatformAuthenticationDatabase(client, accountId, resource,
|
|
|
1598
1600
|
//#endregion
|
|
1599
1601
|
//#region src/cli/platform-plan.ts
|
|
1600
1602
|
async function createPlatformInstallPlan(client, input, existing, options) {
|
|
1603
|
+
const dashboardUrl = input.dashboardUrl !== void 0 ? normalizePlatformDashboardUrl(input.dashboardUrl) : existing ? existing.deployment.controlPlane.dashboardUrl ?? (existing.endpoints.dashboardUrl ? normalizePlatformDashboardUrl(existing.endpoints.dashboardUrl) : null) ?? platformDashboardUrl(existing.deployment) : null;
|
|
1601
1604
|
const account = resolveAccount(await client.listAccounts(), input.accountId);
|
|
1602
1605
|
if (existing && existing.account.id !== account.id) throw new Error(`platform: Refusing to resume installation ${existing.id} in a different Cloudflare account.`);
|
|
1603
1606
|
const providedEmailSharedZoneId = input.emailSharedZoneId?.trim();
|
|
@@ -1646,7 +1649,8 @@ async function createPlatformInstallPlan(client, input, existing, options) {
|
|
|
1646
1649
|
},
|
|
1647
1650
|
controlPlane: {
|
|
1648
1651
|
apiHostname: input.controlPlaneDomain || null,
|
|
1649
|
-
dashboardHostname: null,
|
|
1652
|
+
dashboardHostname: dashboardUrl ? new URL(dashboardUrl).hostname : null,
|
|
1653
|
+
...dashboardUrl ? { dashboardUrl } : {},
|
|
1650
1654
|
proxyHostname: null
|
|
1651
1655
|
},
|
|
1652
1656
|
displayName: input.displayName,
|
|
@@ -1664,13 +1668,21 @@ async function createPlatformInstallPlan(client, input, existing, options) {
|
|
|
1664
1668
|
sandbox: true
|
|
1665
1669
|
}
|
|
1666
1670
|
} : existingWithoutEmailZone;
|
|
1667
|
-
const
|
|
1671
|
+
const existingWithDashboard = {
|
|
1668
1672
|
...existingWithSandbox,
|
|
1673
|
+
controlPlane: {
|
|
1674
|
+
...existingWithSandbox.controlPlane,
|
|
1675
|
+
dashboardHostname: deployment.controlPlane.dashboardHostname,
|
|
1676
|
+
...deployment.controlPlane.dashboardUrl ? { dashboardUrl: deployment.controlPlane.dashboardUrl } : {}
|
|
1677
|
+
}
|
|
1678
|
+
};
|
|
1679
|
+
const comparableExistingWithoutEmailZone = existing && deployment.capabilities.email && !existing.deployment.capabilities.email ? {
|
|
1680
|
+
...existingWithDashboard,
|
|
1669
1681
|
capabilities: {
|
|
1670
|
-
...
|
|
1682
|
+
...existingWithDashboard.capabilities,
|
|
1671
1683
|
email: true
|
|
1672
1684
|
}
|
|
1673
|
-
} :
|
|
1685
|
+
} : existingWithDashboard;
|
|
1674
1686
|
if (existing && (JSON.stringify(comparableExistingWithoutEmailZone) !== JSON.stringify(deploymentWithoutEmailZone) || recordedEmailZone !== void 0 && recordedEmailZone !== configuredEmailZone || recordedSenderDomain !== void 0 && recordedSenderDomain !== configuredSenderDomain)) throw new Error(`platform: --resume inputs do not match installation ${existing.id}. Use its recorded configuration.`);
|
|
1675
1687
|
const names = deployment.resources;
|
|
1676
1688
|
const wildcardHostname = `*.${deployment.application.baseDomain}`;
|
|
@@ -1694,7 +1706,7 @@ async function createPlatformInstallPlan(client, input, existing, options) {
|
|
|
1694
1706
|
}
|
|
1695
1707
|
throw error;
|
|
1696
1708
|
});
|
|
1697
|
-
const [workersSubdomain, aiGateways, routingKv, isrKv, legacyKv, r2Buckets, usageQueues, cronQueues, dispatchNamespaces, hyperdrives, workers] = await Promise.all([
|
|
1709
|
+
const [workersSubdomain, aiGateways, routingKv, isrKv, legacyKv, r2Buckets, usageQueues, cronQueues, deploymentQueues, dispatchNamespaces, hyperdrives, workers] = await Promise.all([
|
|
1698
1710
|
Promise.resolve(accountSubdomain),
|
|
1699
1711
|
gatewayInventory,
|
|
1700
1712
|
client.listKvNamespaces(account.id, names.routingKv),
|
|
@@ -1703,6 +1715,7 @@ async function createPlatformInstallPlan(client, input, existing, options) {
|
|
|
1703
1715
|
client.listR2Buckets(account.id),
|
|
1704
1716
|
client.listQueues(account.id, names.usageQueue),
|
|
1705
1717
|
client.listQueues(account.id, names.cronQueue),
|
|
1718
|
+
options?.deploymentExecution ?? existing?.resources.some((resource) => resource.key === "deployment-queue") ? client.listQueues(account.id, names.deploymentQueue) : Promise.resolve([]),
|
|
1706
1719
|
client.listDispatchNamespaces(account.id),
|
|
1707
1720
|
client.listHyperdriveConfigs(account.id),
|
|
1708
1721
|
client.listWorkerNames(account.id)
|
|
@@ -1737,11 +1750,13 @@ async function createPlatformInstallPlan(client, input, existing, options) {
|
|
|
1737
1750
|
const expectedPatterns = new Set(createApplicationRoutes(deployment.application).map((route) => route.pattern));
|
|
1738
1751
|
const expectedWorker = deployment.resources.dispatchWorker;
|
|
1739
1752
|
const ownedWorker = intentProofs.has("dispatch-worker") || existing?.resources.some((resource) => resource.key === "dispatch-worker" && resource.kind === "worker" && resource.name === expectedWorker && resource.state === "ready");
|
|
1740
|
-
const
|
|
1753
|
+
const [workerRoutes, workerDomains] = await Promise.all([client.listWorkerRoutes(zone.id), client.listWorkerDomains(account.id)]);
|
|
1754
|
+
const conflicts = workerRoutes.filter((route) => expectedPatterns.has(route.pattern) && (route.script !== expectedWorker || !ownedWorker));
|
|
1741
1755
|
if (conflicts.length > 0) {
|
|
1742
1756
|
const conflict = conflicts[0];
|
|
1743
1757
|
throw new Error(`platform: Application route ${JSON.stringify(conflict.pattern)} is already assigned to ${JSON.stringify(conflict.script ?? "no Worker")}. Choose another application domain.`);
|
|
1744
1758
|
}
|
|
1759
|
+
assertApplicationRoutesPreserveWorkerDomains([...expectedPatterns], expectedWorker, ownedWorker === true, zone.name, workerRoutes, workerDomains);
|
|
1745
1760
|
}
|
|
1746
1761
|
if (deployment.controlPlane.apiHostname) {
|
|
1747
1762
|
const apiHostname = deployment.controlPlane.apiHostname;
|
|
@@ -1784,6 +1799,7 @@ async function createPlatformInstallPlan(client, input, existing, options) {
|
|
|
1784
1799
|
addNamedPlan(actions, existing, "assets-bucket", "r2-bucket", names.assetsBucket, r2Buckets.filter((resource) => resource.name === names.assetsBucket), "reuse", options, remoteOwnership, intentProofs, ambiguousResources);
|
|
1785
1800
|
addNamedPlan(actions, existing, "usage-queue", "queue", names.usageQueue, usageQueues, "reuse", options, remoteOwnership);
|
|
1786
1801
|
addNamedPlan(actions, existing, "cron-queue", "queue", names.cronQueue, cronQueues, "reuse", options, remoteOwnership);
|
|
1802
|
+
if (options?.deploymentExecution ?? existing?.resources.some((resource) => resource.key === "deployment-queue")) addNamedPlan(actions, existing, "deployment-queue", "queue", names.deploymentQueue, deploymentQueues, "reuse", options, remoteOwnership);
|
|
1787
1803
|
addNamedPlan(actions, existing, "dispatch-namespace", "dispatch-namespace", names.dispatchNamespace, dispatchNamespaces.filter((resource) => resource.name === names.dispatchNamespace), "reuse", options, remoteOwnership);
|
|
1788
1804
|
const recordedHyperdrive = existing?.resources.find((resource) => resource.key === "hyperdrive");
|
|
1789
1805
|
const externalHyperdriveId = input.externalHyperdrive?.id || (recordedHyperdrive?.ownership === "adopted" ? recordedHyperdrive.id : "");
|
|
@@ -1861,7 +1877,8 @@ async function revalidatePlatformInstallAction(client, original, manifest, actio
|
|
|
1861
1877
|
}, manifest, {
|
|
1862
1878
|
...options,
|
|
1863
1879
|
includeEmailGateway: original.actions.some((entry) => entry.key === "email-gateway-worker"),
|
|
1864
|
-
enableSandbox: original.deployment.capabilities.sandbox
|
|
1880
|
+
enableSandbox: original.deployment.capabilities.sandbox,
|
|
1881
|
+
deploymentExecution: original.actions.some((entry) => entry.key === "deployment-queue")
|
|
1865
1882
|
})).actions.find((entry) => entry.key === action.key);
|
|
1866
1883
|
if (!current || current.kind !== action.kind || current.name !== action.name || current.action !== action.action) throw new Error(`platform: Cloudflare changed after the installation plan was approved for ${action.kind} ${JSON.stringify(action.name)}. Review a new plan before retrying.`);
|
|
1867
1884
|
if (action.kind === "worker" && action.action === "update") {
|
|
@@ -2055,6 +2072,7 @@ const RESOURCE_NAME_KEY_BY_ACTION = {
|
|
|
2055
2072
|
"api-worker": "apiWorker",
|
|
2056
2073
|
"assets-bucket": "assetsBucket",
|
|
2057
2074
|
"cron-queue": "cronQueue",
|
|
2075
|
+
"deployment-queue": "deploymentQueue",
|
|
2058
2076
|
"dispatch-namespace": "dispatchNamespace",
|
|
2059
2077
|
"dispatch-worker": "dispatchWorker",
|
|
2060
2078
|
hyperdrive: "hyperdrive",
|
|
@@ -2066,6 +2084,33 @@ const RESOURCE_NAME_KEY_BY_ACTION = {
|
|
|
2066
2084
|
"tail-worker": "tailWorker",
|
|
2067
2085
|
"usage-queue": "usageQueue"
|
|
2068
2086
|
};
|
|
2087
|
+
function assertApplicationRoutesPreserveWorkerDomains(patterns, dispatchWorker, ownedDispatchWorker, zoneName, workerRoutes, workerDomains) {
|
|
2088
|
+
const plannedRoutes = patterns.map(parseWorkerRoutePattern);
|
|
2089
|
+
const existingRoutes = workerRoutes.map((route) => ({
|
|
2090
|
+
...parseWorkerRoutePattern(route.pattern),
|
|
2091
|
+
script: route.script
|
|
2092
|
+
}));
|
|
2093
|
+
for (const domain of workerDomains) {
|
|
2094
|
+
const hostname = domain.hostname.toLowerCase();
|
|
2095
|
+
if (!(domain.zoneName ? domain.zoneName.toLowerCase() === zoneName.toLowerCase() : hostname === zoneName.toLowerCase() || hostname.endsWith(`.${zoneName.toLowerCase()}`)) || domain.service === dispatchWorker && ownedDispatchWorker) continue;
|
|
2096
|
+
for (const planned of plannedRoutes) {
|
|
2097
|
+
if (!workerRouteHostMatches(planned.host, hostname)) continue;
|
|
2098
|
+
if (!["http", "https"].every((scheme) => existingRoutes.some((route) => route.script !== dispatchWorker && route.path === "/*" && (!route.scheme || route.scheme === scheme) && workerRouteHostMatches(route.host, hostname) && route.host.replace(/^\*/, "").length > planned.host.replace(/^\*/, "").length))) throw new Error(`platform: Application route ${JSON.stringify(planned.pattern)} would intercept Worker custom domain ${JSON.stringify(domain.hostname)}, currently served by ${JSON.stringify(domain.service)}. Choose another application domain, or use \`void platform install --workers-dev\` for a new installation and add a suitable domain later.`);
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
function parseWorkerRoutePattern(pattern) {
|
|
2103
|
+
const match = /^(?:(https?):\/\/)?([^/]+)(\/.*)?$/.exec(pattern);
|
|
2104
|
+
return {
|
|
2105
|
+
host: match?.[2]?.toLowerCase() ?? "",
|
|
2106
|
+
path: match?.[3] ?? "/",
|
|
2107
|
+
pattern,
|
|
2108
|
+
scheme: match?.[1] ?? null
|
|
2109
|
+
};
|
|
2110
|
+
}
|
|
2111
|
+
function workerRouteHostMatches(host, hostname) {
|
|
2112
|
+
return host.startsWith("*") ? hostname.endsWith(host.slice(1)) : host === hostname;
|
|
2113
|
+
}
|
|
2069
2114
|
function platformProvisionIntentHash(manifest, key, kind, name, previousResourceId = null) {
|
|
2070
2115
|
return createHash("sha256").update(JSON.stringify({
|
|
2071
2116
|
key,
|
|
@@ -2101,10 +2146,12 @@ async function readRemoteOwnership(client, accountId, routingNamespaces, existin
|
|
|
2101
2146
|
}
|
|
2102
2147
|
function formatPlatformInstallPlan(plan) {
|
|
2103
2148
|
const lines = [
|
|
2104
|
-
`${import_picocolors$1.default.bold(`${plan.deployment.displayName} in ${plan.account.name}`)}
|
|
2105
|
-
|
|
2149
|
+
`${import_picocolors$1.default.bold(`${plan.deployment.displayName} in ${plan.account.name}`)}`,
|
|
2150
|
+
`${import_picocolors$1.default.bold("Account:")} ${plan.account.id}`,
|
|
2151
|
+
plan.deployment.application.zoneName ? `${import_picocolors$1.default.bold("Applications:")} *.${plan.deployment.application.baseDomain}` : `${import_picocolors$1.default.bold("Applications:")} workers.dev for testing. Add a domain with void platform domain set <domain>`,
|
|
2106
2152
|
`${import_picocolors$1.default.bold("Control plane:")} ${plan.apiUrl}`,
|
|
2107
2153
|
`${import_picocolors$1.default.bold("Resource proxy:")} ${plan.proxyUrl}`,
|
|
2154
|
+
...platformDashboardUrl(plan.deployment) ? [`${import_picocolors$1.default.bold("Dashboard:")} ${platformDashboardUrl(plan.deployment)} (deploy separately)`] : [],
|
|
2108
2155
|
`${import_picocolors$1.default.bold("Login methods:")} ${plan.deployment.authentication.providers.join(", ")}`,
|
|
2109
2156
|
`${import_picocolors$1.default.bold("Access protection:")} ${plan.deployment.authentication.accessProtection ? "Enabled" : "Not enabled"}`,
|
|
2110
2157
|
`${import_picocolors$1.default.bold("Login callback:")} ${plan.apiUrl}/auth/callback`,
|
|
@@ -2933,8 +2980,8 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
2933
2980
|
if (plan.actions.some((action) => action.action === "inspect")) throw new Error("platform: AI Gateway access must be verified before provisioning. Provide AI Gateway: Edit on the runtime token, then retry.");
|
|
2934
2981
|
const now = dependencies.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
2935
2982
|
const cloudflareApiBaseUrl = dependencies.client.apiBaseUrl ?? resolvePinnedApiBaseUrl();
|
|
2936
|
-
const runPinnedPlatformWrangler = (
|
|
2937
|
-
const runParallelPinnedPlatformWrangler = (
|
|
2983
|
+
const runPinnedPlatformWrangler = (operation, accountId, cwd, capture) => runPlatformWranglerAtApiBase(operation, accountId, cwd, capture, cloudflareApiBaseUrl);
|
|
2984
|
+
const runParallelPinnedPlatformWrangler = (operation, accountId, cwd, capture) => runPlatformWranglerAtApiBase(operation, accountId, cwd, capture, cloudflareApiBaseUrl, true);
|
|
2938
2985
|
const resolvedRuntime = dependencies.runtime ?? resolvePlatformRuntime();
|
|
2939
2986
|
const runtimeDirectory = resolvedRuntime.directory;
|
|
2940
2987
|
const runtime = resolvedRuntime.manifest;
|
|
@@ -3152,6 +3199,12 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
3152
3199
|
manifest = transitionInstallation(manifest, "provisioning", now());
|
|
3153
3200
|
writeInstallationManifest(manifest);
|
|
3154
3201
|
}
|
|
3202
|
+
if (runtime.deploymentExecution !== 1 && (manifest.deploymentExecution === 1 || dependencies.databaseRollbackSource?.manifest.deploymentExecution === 1)) {
|
|
3203
|
+
if (!releaseDatabaseLock.checkpoint) throw new Error("platform: The database lifecycle lock cannot fence deployment admission.");
|
|
3204
|
+
await releaseDatabaseLock.checkpoint(manifest);
|
|
3205
|
+
dependencies.onProgress?.("Verifying durable deployment operations have drained");
|
|
3206
|
+
await (dependencies.verifyDeploymentExecutionDrain ?? verifyPlatformDeploymentExecutionDrain)(plan.apiUrl, createPlatformHealthProbeToken(manifest.ownershipNonce), manifest.id);
|
|
3207
|
+
}
|
|
3155
3208
|
if (dependencies.prepareAuthentication) {
|
|
3156
3209
|
dependencies.onProgress?.("Preparing platform authentication");
|
|
3157
3210
|
try {
|
|
@@ -3203,6 +3256,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
3203
3256
|
client: dependencies.client,
|
|
3204
3257
|
manifest,
|
|
3205
3258
|
plan,
|
|
3259
|
+
dashboardUrl: platformDashboardUrl(plan.deployment),
|
|
3206
3260
|
protection,
|
|
3207
3261
|
checkpoint: secretCheckpoint(secrets, workerSecretUpdateIntent)
|
|
3208
3262
|
});
|
|
@@ -3270,11 +3324,6 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
3270
3324
|
if (targetSandboxDrainProtocol >= 1 && !prepareSandboxDrain) throw new Error("platform: The PostgreSQL lifecycle lock cannot establish the sandbox-drain safety checkpoint.");
|
|
3271
3325
|
const sandboxDrainPreparation = targetSandboxDrainProtocol >= 1 ? await prepareSandboxDrain(targetSandboxDrainProtocol >= 3 ? 3 : targetSandboxDrainProtocol >= 2 ? 2 : 0) : null;
|
|
3272
3326
|
if (sandboxDrainPreparation && sandboxDrainPreparation.admissionProtocol < targetSandboxDrainProtocol) throw new Error("platform: The database did not arm the Sandbox admission protocol required by this runtime.");
|
|
3273
|
-
const sandboxDrainRoutingKvId = requireId(Object.fromEntries(manifest.resources.map((resource) => [resource.key, resource.id])), "routing-kv");
|
|
3274
|
-
const writeSandboxDrainMirror = async (preparation) => {
|
|
3275
|
-
await dependencies.client.putKvValue(plan.account.id, sandboxDrainRoutingKvId, PLATFORM_SANDBOX_DRAIN_KEY, preparation.mirrorValue);
|
|
3276
|
-
};
|
|
3277
|
-
if (sandboxDrainPreparation) await writeSandboxDrainMirror(sandboxDrainPreparation);
|
|
3278
3327
|
const sandboxDrainState = sandboxDrainPreparation?.state ?? "complete";
|
|
3279
3328
|
if (targetSandboxDrainProtocol >= 1 && (manifest.safetyProtocols?.sandboxDrain ?? 0) < targetSandboxDrainProtocol) {
|
|
3280
3329
|
manifest = {
|
|
@@ -3333,6 +3382,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
3333
3382
|
accountId: plan.account.id,
|
|
3334
3383
|
apiUrl: plan.apiUrl,
|
|
3335
3384
|
deployment: plan.deployment,
|
|
3385
|
+
deploymentExecution: runtime.deploymentExecution,
|
|
3336
3386
|
healthProbeToken: createPlatformHealthProbeToken(manifest.ownershipNonce),
|
|
3337
3387
|
installationId: manifest.id,
|
|
3338
3388
|
ownershipNonce: manifest.ownershipNonce,
|
|
@@ -3544,7 +3594,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
3544
3594
|
} else for (const key of workerKeys) await deployOneWorker(key);
|
|
3545
3595
|
manifest = setInstallationEndpoints(manifest, {
|
|
3546
3596
|
apiUrl: plan.apiUrl,
|
|
3547
|
-
dashboardUrl:
|
|
3597
|
+
dashboardUrl: platformDashboardUrl(plan.deployment),
|
|
3548
3598
|
proxyUrl: plan.proxyUrl
|
|
3549
3599
|
}, now());
|
|
3550
3600
|
if (preserveDisabled) {
|
|
@@ -3555,6 +3605,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
3555
3605
|
manifest = {
|
|
3556
3606
|
...manifest,
|
|
3557
3607
|
deployment: plan.deployment,
|
|
3608
|
+
deploymentExecution: runtime.deploymentExecution,
|
|
3558
3609
|
platformVersion: version,
|
|
3559
3610
|
runtime: runtimeProvenance,
|
|
3560
3611
|
updatedAt: now(),
|
|
@@ -3653,9 +3704,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
3653
3704
|
zoneNameServers: zone?.nameServers ?? []
|
|
3654
3705
|
};
|
|
3655
3706
|
}
|
|
3656
|
-
|
|
3657
|
-
if (completedDrain.state !== "complete") throw new Error("platform: Sandbox drain completion changed while it was being verified. Traffic remains disabled.");
|
|
3658
|
-
await writeSandboxDrainMirror(completedDrain);
|
|
3707
|
+
if ((await prepareSandboxDrain()).state !== "complete") throw new Error("platform: Sandbox drain completion changed while it was being verified. Traffic remains disabled.");
|
|
3659
3708
|
}
|
|
3660
3709
|
if (requiresStagedCommit) {
|
|
3661
3710
|
if (!authenticationCutoverCommitted) {
|
|
@@ -3766,6 +3815,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
|
|
|
3766
3815
|
manifest = {
|
|
3767
3816
|
...manifest,
|
|
3768
3817
|
deployment: plan.deployment,
|
|
3818
|
+
deploymentExecution: runtime.deploymentExecution,
|
|
3769
3819
|
platformVersion: version,
|
|
3770
3820
|
runtime: runtimeProvenance,
|
|
3771
3821
|
safetyProtocols: runtime.safetyProtocols ?? { sandboxDrain: 0 },
|
|
@@ -4404,14 +4454,6 @@ async function preparePlatformSandboxDrainConnection(client, requiredAdmissionPr
|
|
|
4404
4454
|
return {
|
|
4405
4455
|
admissionProtocol: row.admission_protocol,
|
|
4406
4456
|
generation: row.generation,
|
|
4407
|
-
mirrorValue: JSON.stringify({
|
|
4408
|
-
version: 1,
|
|
4409
|
-
generation: row.generation,
|
|
4410
|
-
state: row.state,
|
|
4411
|
-
startedAt: row.started_at,
|
|
4412
|
-
updatedAt: row.updated_at,
|
|
4413
|
-
...row.completed_at ? { completedAt: row.completed_at } : {}
|
|
4414
|
-
}),
|
|
4415
4457
|
state: row.state === "complete" ? "complete" : "pending"
|
|
4416
4458
|
};
|
|
4417
4459
|
};
|
|
@@ -4578,11 +4620,12 @@ let cachedPlatformUploaderHash;
|
|
|
4578
4620
|
function platformWorkerUploaderHash() {
|
|
4579
4621
|
if (cachedPlatformUploaderHash !== void 0) return cachedPlatformUploaderHash;
|
|
4580
4622
|
try {
|
|
4581
|
-
const launcher =
|
|
4623
|
+
const launcher = resolveCloudflareOperationArgv(process.cwd())[1];
|
|
4582
4624
|
if (!launcher) return cachedPlatformUploaderHash = null;
|
|
4583
4625
|
const digest = createHash("sha256");
|
|
4584
4626
|
digest.update("platform-uploader:v1\0");
|
|
4585
4627
|
digest.update(process.version);
|
|
4628
|
+
digest.update(readFileSync(launcher));
|
|
4586
4629
|
digest.update(readFileSync(resolve(dirname(launcher), "..", "..", "package.json")));
|
|
4587
4630
|
const cfDirectory = dirname(fileURLToPath(import.meta.resolve("cf/package.json")));
|
|
4588
4631
|
digest.update(readFileSync(join(cfDirectory, "package.json")));
|
|
@@ -4692,28 +4735,27 @@ function snapshotPlatformWorkerUploadInputs(config, directory) {
|
|
|
4692
4735
|
}
|
|
4693
4736
|
function platformWorkerApiReadRunner(accountId, workerName, readCloudflare, fallback) {
|
|
4694
4737
|
const base = `/accounts/${encodeURIComponent(accountId)}/workers/scripts/${encodeURIComponent(workerName)}`;
|
|
4695
|
-
return async (
|
|
4738
|
+
return async (operation, account, cwd, capture) => {
|
|
4696
4739
|
try {
|
|
4697
|
-
if (
|
|
4740
|
+
if (operation.type === "deployments.status" || operation.type === "deployments.list") {
|
|
4698
4741
|
const response = await readCloudflare(`${base}/deployments`);
|
|
4699
4742
|
const deployments = response && typeof response === "object" && !Array.isArray(response) ? response.deployments : null;
|
|
4700
4743
|
if (!Array.isArray(deployments)) throw new Error(`platform: Cloudflare returned malformed deployment history for ${workerName}.`);
|
|
4701
4744
|
return {
|
|
4702
|
-
status:
|
|
4745
|
+
status: operation.type === "deployments.status" && deployments.length === 0 ? 1 : 0,
|
|
4703
4746
|
stderr: "",
|
|
4704
|
-
stdout:
|
|
4705
|
-
|
|
4706
|
-
}
|
|
4707
|
-
if (args[0] === "versions" && args[1] === "view" && args[2]) {
|
|
4708
|
-
const version = await readCloudflare(`${base}/versions/${encodeURIComponent(args[2])}`);
|
|
4709
|
-
return {
|
|
4710
|
-
status: 0,
|
|
4711
|
-
stderr: "",
|
|
4712
|
-
stdout: JSON.stringify(version)
|
|
4747
|
+
stdout: "",
|
|
4748
|
+
data: operation.type === "deployments.status" ? deployments[0] : deployments
|
|
4713
4749
|
};
|
|
4714
4750
|
}
|
|
4751
|
+
if (operation.type === "versions.view") return {
|
|
4752
|
+
status: 0,
|
|
4753
|
+
stderr: "",
|
|
4754
|
+
stdout: "",
|
|
4755
|
+
data: await readCloudflare(`${base}/versions/${encodeURIComponent(operation.versionId)}`)
|
|
4756
|
+
};
|
|
4715
4757
|
} catch {}
|
|
4716
|
-
return fallback(
|
|
4758
|
+
return fallback(operation, account, cwd, capture);
|
|
4717
4759
|
};
|
|
4718
4760
|
}
|
|
4719
4761
|
async function deployPlatformWorker(config, secrets, options) {
|
|
@@ -4831,27 +4873,21 @@ async function deployPlatformWorker(config, secrets, options) {
|
|
|
4831
4873
|
};
|
|
4832
4874
|
writeFileSync(configPath, `${JSON.stringify(uploadConfig, null, 2)}\n`, { mode: 384 });
|
|
4833
4875
|
}
|
|
4834
|
-
const
|
|
4835
|
-
|
|
4836
|
-
"--config",
|
|
4876
|
+
const operation = {
|
|
4877
|
+
type: options.workerExisted ? "versions.upload" : "worker.deploy",
|
|
4837
4878
|
configPath,
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
"--experimental-auto-create=false",
|
|
4841
|
-
...!options.workerExisted ? ["--autoconfig=false"] : [],
|
|
4842
|
-
"--message",
|
|
4843
|
-
encodeCloudflareVersionMessage([], nextTriggers, uploadedContentHash ?? void 0)
|
|
4844
|
-
];
|
|
4879
|
+
message: encodeCloudflareVersionMessage([], nextTriggers, uploadedContentHash ?? void 0)
|
|
4880
|
+
};
|
|
4845
4881
|
if (secrets) {
|
|
4846
4882
|
const secretsPath = join(directory, "secrets.json");
|
|
4847
4883
|
writeFileSync(secretsPath, JSON.stringify(secrets), { mode: 384 });
|
|
4848
|
-
|
|
4884
|
+
operation.secretsFilePath = secretsPath;
|
|
4849
4885
|
}
|
|
4850
4886
|
const releaseUploadSlot = await options.acquireUploadSlot?.();
|
|
4851
4887
|
let uploaded;
|
|
4852
4888
|
let uploadError;
|
|
4853
4889
|
try {
|
|
4854
|
-
uploaded = options.workerExisted ? await options.runner(
|
|
4890
|
+
uploaded = options.workerExisted ? await options.runner(operation, accountId, directory, true) : await runPlatformWranglerForConditionalCreate(operation, accountId, directory, true, workerName, options.apiBaseUrl);
|
|
4855
4891
|
assertCleanPlatformWrangler(uploaded, `${options.workerExisted ? "upload" : "bootstrap"} ${workerName}`);
|
|
4856
4892
|
} catch (error) {
|
|
4857
4893
|
uploadError = error;
|
|
@@ -4859,7 +4895,8 @@ async function deployPlatformWorker(config, secrets, options) {
|
|
|
4859
4895
|
} finally {
|
|
4860
4896
|
releaseUploadSlot?.(uploadError);
|
|
4861
4897
|
}
|
|
4862
|
-
|
|
4898
|
+
const uploadData = uploaded.data;
|
|
4899
|
+
uploadedVersionId = typeof uploadData?.versionId === "string" ? uploadData.versionId : null;
|
|
4863
4900
|
if (!options.workerExisted) activatedVersionId = uploadedVersionId;
|
|
4864
4901
|
if (!uploadedVersionId) throw new Error(`platform: Wrangler uploaded ${workerName} without reporting its immutable version id.`);
|
|
4865
4902
|
await options.onStaged(previousVersionId, uploadedVersionId, previousTriggers);
|
|
@@ -4869,16 +4906,15 @@ async function deployPlatformWorker(config, secrets, options) {
|
|
|
4869
4906
|
const deploymentsBeforeActivation = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
|
|
4870
4907
|
if (deploymentsBeforeActivation[0]?.id !== previousDeploymentId || !deploymentRunsOnlyVersion(deploymentsBeforeActivation[0], previousVersionId)) throw new Error(`platform: Worker ${workerName} changed concurrently before activation; refusing to overwrite it.`);
|
|
4871
4908
|
activatedVersionId = uploadedVersionId;
|
|
4872
|
-
assertCleanPlatformWrangler(await options.runner(
|
|
4873
|
-
"versions",
|
|
4874
|
-
"deploy",
|
|
4875
|
-
`${uploadedVersionId}@100`,
|
|
4876
|
-
"--config",
|
|
4909
|
+
assertCleanPlatformWrangler(await options.runner({
|
|
4910
|
+
type: "versions.deploy",
|
|
4877
4911
|
configPath,
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4912
|
+
versions: [{
|
|
4913
|
+
versionId: uploadedVersionId,
|
|
4914
|
+
percentage: 100
|
|
4915
|
+
}],
|
|
4916
|
+
message: `Void platform activation ${uploadedVersionId}`
|
|
4917
|
+
}, accountId, directory, true), `activate ${workerName}`);
|
|
4882
4918
|
}
|
|
4883
4919
|
const deploymentsAfterActivation = await readPlatformWorkerDeployments(configPath, accountId, workerName, readRunner);
|
|
4884
4920
|
const lineage = classifyPlatformWorkerDeploymentLineage(deploymentsAfterActivation, uploadedVersionId, previousDeploymentId);
|
|
@@ -5072,28 +5108,24 @@ async function rollbackPlatformWorker(input) {
|
|
|
5072
5108
|
}
|
|
5073
5109
|
}
|
|
5074
5110
|
function platformWorkerCommandRunner(accountId, cwd, runner) {
|
|
5075
|
-
return (
|
|
5111
|
+
return (operation, options) => runner(operation, accountId, cwd, options.capture);
|
|
5076
5112
|
}
|
|
5077
|
-
async function runPlatformWrangler(
|
|
5078
|
-
return runPlatformWranglerAtApiBase(
|
|
5113
|
+
async function runPlatformWrangler(operation, accountId, cwd, capture) {
|
|
5114
|
+
return runPlatformWranglerAtApiBase(operation, accountId, cwd, capture);
|
|
5079
5115
|
}
|
|
5080
5116
|
const PLATFORM_WRANGLER_TIMEOUT_MS = 6e5;
|
|
5081
|
-
async function runPlatformWranglerAtApiBase(
|
|
5117
|
+
async function runPlatformWranglerAtApiBase(operation, accountId, cwd, capture, apiBaseUrl = resolvePinnedApiBaseUrl(), parallelWorkerUpload = false) {
|
|
5082
5118
|
const startedAt = performance.now();
|
|
5083
5119
|
const result = await createDefaultRunWrangler(process.cwd(), {
|
|
5084
|
-
launcher:
|
|
5120
|
+
launcher: resolveCloudflareOperationArgv(process.cwd()),
|
|
5085
5121
|
parallelWorkerUpload,
|
|
5086
5122
|
timeoutMs: PLATFORM_WRANGLER_TIMEOUT_MS
|
|
5087
|
-
})(
|
|
5123
|
+
})(operation, {
|
|
5088
5124
|
accountId,
|
|
5089
5125
|
apiBaseUrl,
|
|
5090
|
-
captureJson: capture,
|
|
5091
5126
|
cwd
|
|
5092
5127
|
});
|
|
5093
|
-
if (process.env.VOID_PLATFORM_TIMINGS === "1") {
|
|
5094
|
-
const operation = args[0] === "versions" || args[0] === "deployments" ? `${args[0]} ${args[1] ?? ""}` : args[0] ?? "unknown";
|
|
5095
|
-
process.stderr.write(`[platform timing] wrangler ${operation} ${(performance.now() - startedAt).toFixed(0)} ms\n`);
|
|
5096
|
-
}
|
|
5128
|
+
if (process.env.VOID_PLATFORM_TIMINGS === "1") process.stderr.write(`[platform timing] Cloudflare ${operation.type} ${(performance.now() - startedAt).toFixed(0)} ms\n`);
|
|
5097
5129
|
const stdout = result.stdout || "";
|
|
5098
5130
|
const stderr = result.stderr || "";
|
|
5099
5131
|
if (!capture && result.status !== 0) {
|
|
@@ -5101,7 +5133,7 @@ async function runPlatformWranglerAtApiBase(args, accountId, cwd, capture, apiBa
|
|
|
5101
5133
|
if (stderr) process.stderr.write(stderr);
|
|
5102
5134
|
}
|
|
5103
5135
|
return {
|
|
5104
|
-
|
|
5136
|
+
...result,
|
|
5105
5137
|
stderr,
|
|
5106
5138
|
stdout
|
|
5107
5139
|
};
|
|
@@ -5222,48 +5254,30 @@ async function withConditionalPlatformWorkerCreate(accountId, workerName, operat
|
|
|
5222
5254
|
if (proxy.state.conflictStatus !== null) throw new Error(`platform: Worker ${workerName} was created concurrently (HTTP ${proxy.state.conflictStatus}); no existing Worker was changed. Re-run to inspect the collision.`);
|
|
5223
5255
|
return result;
|
|
5224
5256
|
}
|
|
5225
|
-
async function runPlatformWranglerForConditionalCreate(
|
|
5226
|
-
return withConditionalPlatformWorkerCreate(accountId, workerName, (apiBaseUrl) => runPlatformWranglerAtApiBase(
|
|
5257
|
+
async function runPlatformWranglerForConditionalCreate(operation, accountId, cwd, capture, workerName, upstreamApiBaseUrl) {
|
|
5258
|
+
return withConditionalPlatformWorkerCreate(accountId, workerName, (apiBaseUrl) => runPlatformWranglerAtApiBase(operation, accountId, cwd, capture, apiBaseUrl), upstreamApiBaseUrl);
|
|
5227
5259
|
}
|
|
5228
5260
|
async function readActivePlatformWorkerVersion(configPath, accountId, workerName, runner = runPlatformWrangler) {
|
|
5229
|
-
const result = await runner(
|
|
5230
|
-
"deployments",
|
|
5231
|
-
"status",
|
|
5232
|
-
"--config",
|
|
5261
|
+
const result = await runner({
|
|
5262
|
+
type: "deployments.status",
|
|
5233
5263
|
configPath,
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
"--json"
|
|
5237
|
-
], accountId, process.cwd(), true);
|
|
5264
|
+
workerName
|
|
5265
|
+
}, accountId, process.cwd(), true);
|
|
5238
5266
|
if (result.status !== 0) assertCleanPlatformWrangler(result, `read active version for ${workerName}`);
|
|
5239
|
-
|
|
5240
|
-
try {
|
|
5241
|
-
value = JSON.parse(result.stdout);
|
|
5242
|
-
} catch {
|
|
5243
|
-
throw new Error(`platform: Wrangler returned malformed deployment status for ${workerName}.`);
|
|
5244
|
-
}
|
|
5267
|
+
const value = result.data;
|
|
5245
5268
|
if (!value || typeof value !== "object" || !Array.isArray(value.versions)) throw new Error(`platform: Wrangler returned malformed deployment status for ${workerName}.`);
|
|
5246
5269
|
const active = value.versions.filter((entry) => entry && typeof entry === "object" && Number(entry.percentage) === 100);
|
|
5247
5270
|
if (active.length !== 1 || typeof active[0].version_id !== "string") return null;
|
|
5248
5271
|
return active[0].version_id;
|
|
5249
5272
|
}
|
|
5250
5273
|
async function readPlatformWorkerDeployments(configPath, accountId, workerName, runner = runPlatformWrangler) {
|
|
5251
|
-
const result = await runner(
|
|
5252
|
-
"deployments",
|
|
5253
|
-
"list",
|
|
5254
|
-
"--config",
|
|
5274
|
+
const result = await runner({
|
|
5275
|
+
type: "deployments.list",
|
|
5255
5276
|
configPath,
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
"--json"
|
|
5259
|
-
], accountId, process.cwd(), true);
|
|
5277
|
+
workerName
|
|
5278
|
+
}, accountId, process.cwd(), true);
|
|
5260
5279
|
if (result.status !== 0) assertCleanPlatformWrangler(result, `read deployment lineage for ${workerName}`);
|
|
5261
|
-
|
|
5262
|
-
try {
|
|
5263
|
-
value = JSON.parse(result.stdout);
|
|
5264
|
-
} catch {
|
|
5265
|
-
throw new Error(`platform: Wrangler returned malformed deployment history for ${workerName}.`);
|
|
5266
|
-
}
|
|
5280
|
+
const value = result.data;
|
|
5267
5281
|
if (!Array.isArray(value)) throw new Error(`platform: Wrangler returned malformed deployment history for ${workerName}.`);
|
|
5268
5282
|
return value.map((entry, index) => {
|
|
5269
5283
|
if (!entry || typeof entry !== "object" || typeof entry.id !== "string" || typeof entry.created_on !== "string" || !Array.isArray(entry.versions)) throw new Error(`platform: Wrangler returned malformed deployment history for ${workerName}.`);
|
|
@@ -5298,16 +5312,15 @@ async function restorePlatformWorkerDeployment(input) {
|
|
|
5298
5312
|
if (restorationVersions.length === 0 || restorationVersions.length > 2 || restorationVersions.some((version, index) => version.percentage > 100 || restorationVersions.findIndex((entry) => entry.versionId === version.versionId) !== index) || Math.abs(totalPercentage - 100) > .001) throw new Error(`Worker ${input.workerName} deployment ${input.restoration.id} has an unsupported traffic distribution.`);
|
|
5299
5313
|
const before = await readPlatformWorkerDeployments(input.configPath, input.accountId, input.workerName, input.runner);
|
|
5300
5314
|
if (before[0]?.id !== input.expectedActiveDeploymentId || !deploymentRunsOnlyVersion(before[0], input.expectedActiveVersionId) || before[1]?.id !== input.restoration.id || !samePlatformWorkerTraffic(before[1], input.restoration)) throw new Error(`Worker ${input.workerName} changed concurrently before deployment restoration; refusing to overwrite the newer deployment.`);
|
|
5301
|
-
assertCleanPlatformWrangler(await input.runner(
|
|
5302
|
-
"versions",
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
`Void platform restoration ${input.restoration.id}
|
|
5309
|
-
|
|
5310
|
-
], input.accountId, input.directory, true), `restore deployment ${input.restoration.id}`);
|
|
5315
|
+
assertCleanPlatformWrangler(await input.runner({
|
|
5316
|
+
type: "versions.deploy",
|
|
5317
|
+
configPath: input.configPath,
|
|
5318
|
+
versions: restorationVersions.map((version) => ({
|
|
5319
|
+
versionId: version.versionId,
|
|
5320
|
+
percentage: version.percentage
|
|
5321
|
+
})),
|
|
5322
|
+
message: `Void platform restoration ${input.restoration.id}`
|
|
5323
|
+
}, input.accountId, input.directory, true), `restore deployment ${input.restoration.id}`);
|
|
5311
5324
|
const after = await readPlatformWorkerDeployments(input.configPath, input.accountId, input.workerName, input.runner);
|
|
5312
5325
|
if (!samePlatformWorkerTraffic(after[0], input.restoration) || after[1]?.id !== input.expectedActiveDeploymentId) throw new Error(`Worker ${input.workerName} deployment restoration was superseded or did not restore deployment ${input.restoration.id}.`);
|
|
5313
5326
|
}
|
|
@@ -5363,23 +5376,14 @@ async function readActivePlatformWorkerSnapshot(configPath, accountId, workerNam
|
|
|
5363
5376
|
});
|
|
5364
5377
|
}
|
|
5365
5378
|
async function readPlatformWorkerVersionSnapshot(configPath, accountId, workerName, expectedOwnershipMarker, versionId, runner, validateCurrentVersion) {
|
|
5366
|
-
const result = await runner(
|
|
5367
|
-
"versions",
|
|
5368
|
-
"view",
|
|
5379
|
+
const result = await runner({
|
|
5380
|
+
type: "versions.view",
|
|
5369
5381
|
versionId,
|
|
5370
|
-
"--config",
|
|
5371
5382
|
configPath,
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
"--json"
|
|
5375
|
-
], accountId, process.cwd(), true);
|
|
5383
|
+
workerName
|
|
5384
|
+
}, accountId, process.cwd(), true);
|
|
5376
5385
|
assertCleanPlatformWrangler(result, `read rollback metadata for ${workerName}`);
|
|
5377
|
-
|
|
5378
|
-
try {
|
|
5379
|
-
value = JSON.parse(result.stdout);
|
|
5380
|
-
} catch {
|
|
5381
|
-
throw new Error(`platform: Wrangler returned malformed version metadata for ${workerName}.`);
|
|
5382
|
-
}
|
|
5386
|
+
const value = result.data;
|
|
5383
5387
|
if (!value || typeof value !== "object") throw new Error(`platform: Wrangler returned malformed version metadata for ${workerName}.`);
|
|
5384
5388
|
const resources = value.resources;
|
|
5385
5389
|
const bindings = resources && typeof resources === "object" && !Array.isArray(resources) ? resources.bindings : null;
|
|
@@ -5405,11 +5409,8 @@ function withoutCompletedPlatformWorkerMigrations(config, activeMigrationTag) {
|
|
|
5405
5409
|
delete result.migrations;
|
|
5406
5410
|
return result;
|
|
5407
5411
|
}
|
|
5408
|
-
function parseUploadedPlatformWorkerVersion(output) {
|
|
5409
|
-
return /(?:Worker|Current) Version ID:\s*([0-9a-f]{8}-[0-9a-f-]{27})/i.exec(output)?.[1] ?? null;
|
|
5410
|
-
}
|
|
5411
5412
|
function assertCleanPlatformWrangler(result, operation) {
|
|
5412
|
-
if (result.status !== 0) throw new Error(`platform: Wrangler failed to ${operation} (${result.stderr.trim() || `exit ${result.status}`}).`);
|
|
5413
|
+
if (result.status !== 0 || result.signal || result.error) throw new Error(`platform: Wrangler failed to ${operation} (${result.stderr.trim() || `exit ${result.status}`}).`);
|
|
5413
5414
|
}
|
|
5414
5415
|
function cleanPlatformCommandResult() {
|
|
5415
5416
|
return {
|
|
@@ -5530,18 +5531,18 @@ function parseWorkerCutoverSnapshot(value) {
|
|
|
5530
5531
|
return null;
|
|
5531
5532
|
}
|
|
5532
5533
|
}
|
|
5533
|
-
function
|
|
5534
|
+
function workerCutoverGenerations(manifest) {
|
|
5534
5535
|
const generations = /* @__PURE__ */ new Map();
|
|
5535
5536
|
for (const step of manifest.steps) {
|
|
5536
5537
|
const resolution = /^worker-cutover:(\d+):(committed|restored)$/.exec(step.key);
|
|
5537
5538
|
if (resolution) {
|
|
5538
5539
|
const generation = Number(resolution[1]);
|
|
5539
5540
|
const entry = generations.get(generation) ?? {
|
|
5540
|
-
|
|
5541
|
+
resolution: null,
|
|
5541
5542
|
staged: /* @__PURE__ */ new Map(),
|
|
5542
5543
|
unsafeTraffic: /* @__PURE__ */ new Set()
|
|
5543
5544
|
};
|
|
5544
|
-
entry.
|
|
5545
|
+
entry.resolution = resolution[2];
|
|
5545
5546
|
generations.set(generation, entry);
|
|
5546
5547
|
continue;
|
|
5547
5548
|
}
|
|
@@ -5549,7 +5550,7 @@ function interruptedWorkerCutover(manifest) {
|
|
|
5549
5550
|
if (unsafeTraffic) {
|
|
5550
5551
|
const generation = Number(unsafeTraffic[1]);
|
|
5551
5552
|
const entry = generations.get(generation) ?? {
|
|
5552
|
-
|
|
5553
|
+
resolution: null,
|
|
5553
5554
|
staged: /* @__PURE__ */ new Map(),
|
|
5554
5555
|
unsafeTraffic: /* @__PURE__ */ new Set()
|
|
5555
5556
|
};
|
|
@@ -5563,14 +5564,17 @@ function interruptedWorkerCutover(manifest) {
|
|
|
5563
5564
|
const snapshot = parseWorkerCutoverSnapshot(step.resultHash);
|
|
5564
5565
|
if (!snapshot) throw new Error(`platform: Invalid Worker cutover checkpoint ${step.key}.`);
|
|
5565
5566
|
const entry = generations.get(generation) ?? {
|
|
5566
|
-
|
|
5567
|
+
resolution: null,
|
|
5567
5568
|
staged: /* @__PURE__ */ new Map(),
|
|
5568
5569
|
unsafeTraffic: /* @__PURE__ */ new Set()
|
|
5569
5570
|
};
|
|
5570
5571
|
entry.staged.set(staged[2], snapshot);
|
|
5571
5572
|
generations.set(generation, entry);
|
|
5572
5573
|
}
|
|
5573
|
-
|
|
5574
|
+
return generations;
|
|
5575
|
+
}
|
|
5576
|
+
function interruptedWorkerCutover(manifest) {
|
|
5577
|
+
const unresolved = [...workerCutoverGenerations(manifest).entries()].filter(([, entry]) => entry.resolution === null && entry.staged.size > 0).sort(([left], [right]) => right - left)[0];
|
|
5574
5578
|
return unresolved ? {
|
|
5575
5579
|
generation: unresolved[0],
|
|
5576
5580
|
staged: unresolved[1].staged,
|
|
@@ -5584,8 +5588,8 @@ async function verifyPlatformWorkerCutoverVersions(manifest, generation, configs
|
|
|
5584
5588
|
"tail",
|
|
5585
5589
|
"dispatch"
|
|
5586
5590
|
]) {
|
|
5587
|
-
const cutover =
|
|
5588
|
-
if (cutover
|
|
5591
|
+
const cutover = workerCutoverGenerations(manifest).get(generation);
|
|
5592
|
+
if (!cutover || cutover.resolution === "restored" || cutover.staged.size !== workerKeys.length) throw new Error(`platform: Worker cutover generation ${generation} is missing a complete immutable-version checkpoint.`);
|
|
5589
5593
|
const directory = mkdtempSync(join(tmpdir(), "void-platform-cutover-verify-"));
|
|
5590
5594
|
try {
|
|
5591
5595
|
for (const key of workerKeys) {
|
|
@@ -5721,9 +5725,8 @@ async function verifyPlatformSandboxDrain(apiUrl, healthProbeToken, fetcher = fe
|
|
|
5721
5725
|
if (status.version !== 1 || !validState || typeof status.complete !== "boolean" || !validPhase || status.generation !== null && (typeof status.generation !== "string" || status.generation.length === 0) || status.updatedAt !== null && (typeof status.updatedAt !== "string" || !Number.isFinite(Date.parse(status.updatedAt))) || status.state === "required" && (status.complete || status.generation !== null || status.updatedAt !== null || status.phase !== null) || status.state === "draining" && (status.complete || typeof status.generation !== "string" || typeof status.updatedAt !== "string" || status.phase === null) || status.state === "complete" && (!status.complete || typeof status.generation !== "string" || typeof status.updatedAt !== "string" || status.phase === null)) throw new Error("platform: Sandbox drain probe returned an invalid DB-backed protocol v1 document. Traffic remains disabled.");
|
|
5722
5726
|
return status.state === "complete";
|
|
5723
5727
|
}
|
|
5724
|
-
async function
|
|
5725
|
-
const response = await fetcher(`${apiUrl}/
|
|
5726
|
-
method: "POST",
|
|
5728
|
+
async function verifyPlatformDeploymentExecutionDrain(apiUrl, healthProbeToken, installationId, fetcher = fetch) {
|
|
5729
|
+
const response = await fetcher(`${apiUrl}/__void/platform/deployment-execution`, {
|
|
5727
5730
|
headers: {
|
|
5728
5731
|
"X-Void-Platform-Probe": healthProbeToken,
|
|
5729
5732
|
...cfAccessHeaders(process.env, apiUrl)
|
|
@@ -5731,18 +5734,104 @@ async function bootstrapPlatformSharedEmail(apiUrl, healthProbeToken, fetcher =
|
|
|
5731
5734
|
signal: AbortSignal.timeout(3e4),
|
|
5732
5735
|
redirect: "manual"
|
|
5733
5736
|
});
|
|
5737
|
+
if (!response.ok) throw new Error(`platform: Deployment execution drain probe failed (HTTP ${response.status}). Keep the current runtime until its durable deployment operations have drained.`);
|
|
5734
5738
|
let value;
|
|
5735
5739
|
try {
|
|
5736
5740
|
value = await response.json();
|
|
5737
5741
|
} catch {
|
|
5738
|
-
throw new Error(
|
|
5742
|
+
throw new Error("platform: Deployment execution drain probe returned an invalid document.");
|
|
5739
5743
|
}
|
|
5740
|
-
const status = value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
5741
|
-
if (
|
|
5742
|
-
|
|
5744
|
+
const status = value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
5745
|
+
if (status?.protocolVersion !== 1 || status.installationId !== installationId || typeof status.admissionClosed !== "boolean" || !Number.isSafeInteger(status.pendingDeployments) || Number(status.pendingDeployments) < 0) throw new Error("platform: Deployment execution drain probe returned an invalid document.");
|
|
5746
|
+
if (!status.admissionClosed || status.pendingDeployments !== 0) throw new Error("platform: Durable deployment operations are still admitted or unfinished. Keep the current runtime and retry this command after they finish.");
|
|
5747
|
+
}
|
|
5748
|
+
async function bootstrapPlatformSharedEmail(apiUrl, healthProbeToken, fetcher = fetch) {
|
|
5749
|
+
const deadline = Date.now() + 3e5;
|
|
5750
|
+
let operationId = null;
|
|
5751
|
+
const timedOut = () => /* @__PURE__ */ new Error(`platform: Shared email bootstrap did not finish within five minutes at ${apiUrl}.${operationId ? ` Operation ${operationId} remains incomplete.` : ""} Traffic remains disabled. Retry the installation's repair or resume command to reconcile the existing email operation.`);
|
|
5752
|
+
const waitForProgress = async () => {
|
|
5753
|
+
const remaining = deadline - Date.now();
|
|
5754
|
+
if (remaining <= 0) throw timedOut();
|
|
5755
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(1e3, remaining)));
|
|
5756
|
+
};
|
|
5757
|
+
let response;
|
|
5758
|
+
let value;
|
|
5759
|
+
let status;
|
|
5760
|
+
for (;;) {
|
|
5761
|
+
const remaining = deadline - Date.now();
|
|
5762
|
+
if (remaining <= 0) throw timedOut();
|
|
5763
|
+
const signal = AbortSignal.timeout(Math.min(12e4, remaining));
|
|
5764
|
+
try {
|
|
5765
|
+
response = await fetcher(`${apiUrl}/internal/platform/email-bootstrap`, {
|
|
5766
|
+
method: "POST",
|
|
5767
|
+
headers: {
|
|
5768
|
+
"X-Void-Platform-Probe": healthProbeToken,
|
|
5769
|
+
...cfAccessHeaders(process.env, apiUrl)
|
|
5770
|
+
},
|
|
5771
|
+
signal,
|
|
5772
|
+
redirect: "manual"
|
|
5773
|
+
});
|
|
5774
|
+
} catch (error) {
|
|
5775
|
+
if (signal.aborted || error instanceof Error && error.name === "TimeoutError") {
|
|
5776
|
+
await waitForProgress();
|
|
5777
|
+
continue;
|
|
5778
|
+
}
|
|
5779
|
+
throw new Error(`platform: Shared email bootstrap request failed at ${apiUrl}. Traffic remains disabled. Retry the installation's repair or resume command to reconcile the existing email operation.`);
|
|
5780
|
+
}
|
|
5781
|
+
try {
|
|
5782
|
+
value = await response.json();
|
|
5783
|
+
} catch (error) {
|
|
5784
|
+
if (signal.aborted || error instanceof Error && error.name === "TimeoutError") {
|
|
5785
|
+
await waitForProgress();
|
|
5786
|
+
continue;
|
|
5787
|
+
}
|
|
5788
|
+
throw new Error(`platform: Shared email bootstrap failed at ${apiUrl} (HTTP ${response.status}). Traffic remains disabled.`);
|
|
5789
|
+
}
|
|
5790
|
+
status = value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
5791
|
+
const observedOperationId = typeof status?.operationId === "string" && /^[A-Za-z0-9_-]{1,100}$/.test(status.operationId) ? status.operationId : null;
|
|
5792
|
+
if (status?.version === 1 && observedOperationId) {
|
|
5793
|
+
if (operationId && operationId !== observedOperationId) throw new Error("platform: Shared email bootstrap operation identity changed while waiting. Traffic remains disabled. Inspect the installation before retrying.");
|
|
5794
|
+
operationId = observedOperationId;
|
|
5795
|
+
}
|
|
5796
|
+
if ([
|
|
5797
|
+
200,
|
|
5798
|
+
202,
|
|
5799
|
+
503
|
|
5800
|
+
].includes(response.status) && status?.version === 1 && status.state === "incomplete" && observedOperationId && (status.operationStatus === "pending" || status.operationStatus === "running") && status.failureCode == null) {
|
|
5801
|
+
await waitForProgress();
|
|
5802
|
+
continue;
|
|
5803
|
+
}
|
|
5804
|
+
break;
|
|
5805
|
+
}
|
|
5806
|
+
if (!response.ok || status?.version !== 1 || status.state !== "ready" || operationId === null || status.operationId !== operationId || status.operationStatus != null && status.operationStatus !== "completed" || status.failureCode != null) {
|
|
5807
|
+
const code = (value) => typeof value === "string" && /^[a-z][a-z0-9_-]{0,63}$/.test(value) ? value : null;
|
|
5808
|
+
const operationStatus = code(status?.operationStatus);
|
|
5809
|
+
const operationId = typeof status?.operationId === "string" && /^[A-Za-z0-9_-]{1,100}$/.test(status.operationId) ? status.operationId : null;
|
|
5810
|
+
const details = operationId ? [`Operation ${operationId}: ${operationStatus ?? "incomplete"}.`] : operationStatus ? [`Operation status: ${operationStatus}.`] : [];
|
|
5811
|
+
const currentStep = code(status?.currentStep);
|
|
5812
|
+
const failureCode = code(status?.failureCode);
|
|
5813
|
+
if (currentStep) details.push(`Step: ${currentStep}.`);
|
|
5814
|
+
if (failureCode) details.push(`Failure: ${failureCode}.`);
|
|
5815
|
+
let domain = null;
|
|
5816
|
+
if (typeof status?.domain === "string") try {
|
|
5817
|
+
domain = normalizeApplicationDomain(status.domain);
|
|
5818
|
+
} catch {}
|
|
5819
|
+
const inboundStatus = code(status?.inboundStatus);
|
|
5820
|
+
if (domain) details.push(`Domain: ${domain}${inboundStatus ? ` (inbound: ${inboundStatus})` : ""}.`);
|
|
5821
|
+
const mxDetail = domain ? `MX records for ${domain} do not route mail to Cloudflare Email Routing` : null;
|
|
5822
|
+
const safeDetails = [
|
|
5823
|
+
...domain ? [mxDetail, `enabled explicit Cloudflare email rules conflict with ${domain}`] : [],
|
|
5824
|
+
"zone catch-all does not point to this ingress",
|
|
5825
|
+
"another enabled catch-all owns this zone"
|
|
5826
|
+
];
|
|
5827
|
+
if (typeof status?.failureDetail === "string" && safeDetails.includes(status.failureDetail)) {
|
|
5828
|
+
details.push(`${status.failureDetail}.`);
|
|
5829
|
+
if (currentStep === "inbound_probe" && status.failureDetail === mxDetail) details.push(`Enable Email Routing for ${domain} in the Cloudflare dashboard, then resume this installation.`);
|
|
5830
|
+
}
|
|
5831
|
+
const detail = details.length ? ` ${details.join(" ")}` : "";
|
|
5743
5832
|
throw new Error(`platform: Shared email bootstrap did not reach inbound readiness at ${apiUrl} (HTTP ${response.status}).${detail} Traffic remains disabled.`);
|
|
5744
5833
|
}
|
|
5745
|
-
return
|
|
5834
|
+
return operationId;
|
|
5746
5835
|
}
|
|
5747
5836
|
function createPlatformHealthProbeToken(ownershipNonce) {
|
|
5748
5837
|
return createHash("sha256").update("void-platform-health-probe:v1\0").update(ownershipNonce).digest("base64url");
|
|
@@ -5753,7 +5842,7 @@ function answer(value) {
|
|
|
5753
5842
|
if (isCancel(value) || typeof value === "symbol") throw new Error("platform: Cancelled.");
|
|
5754
5843
|
return value;
|
|
5755
5844
|
}
|
|
5756
|
-
async function selectInstallationAuthentication(file, recorded) {
|
|
5845
|
+
async function selectInstallationAuthentication(file, recorded, choices = {}) {
|
|
5757
5846
|
if (file) {
|
|
5758
5847
|
const input = JSON.parse(await readFile(file, "utf8"));
|
|
5759
5848
|
if (!input || typeof input !== "object" || Array.isArray(input) || !("connections" in input) || !Array.isArray(input.connections) || !input.connections.length || input.connections.length > 32) throw new Error("platform: An authentication file must define between 1 and 32 connections.");
|
|
@@ -5772,9 +5861,12 @@ async function selectInstallationAuthentication(file, recorded) {
|
|
|
5772
5861
|
file: selected
|
|
5773
5862
|
};
|
|
5774
5863
|
}
|
|
5775
|
-
if (recorded)
|
|
5776
|
-
|
|
5777
|
-
|
|
5864
|
+
if (recorded) {
|
|
5865
|
+
if (choices.loginMethods && JSON.stringify(choices.loginMethods) !== JSON.stringify(recorded.providers) || choices.accessProtection !== void 0 && choices.accessProtection !== !!recorded.accessProtection) throw new Error("platform: Resume with the recorded login methods and protection settings.");
|
|
5866
|
+
return { authentication: recorded };
|
|
5867
|
+
}
|
|
5868
|
+
if (!process.stdin.isTTY && !choices.loginMethods && choices.accessProtection === void 0) return { authentication: { providers: ["github"] } };
|
|
5869
|
+
const providers = choices.loginMethods ?? (!process.stdin.isTTY ? ["github"] : answer(await multiselect({
|
|
5778
5870
|
message: "Platform login methods:",
|
|
5779
5871
|
initialValues: ["github"],
|
|
5780
5872
|
required: true,
|
|
@@ -5796,11 +5888,11 @@ async function selectInstallationAuthentication(file, recorded) {
|
|
|
5796
5888
|
label: "Cloudflare Access login"
|
|
5797
5889
|
}
|
|
5798
5890
|
]
|
|
5799
|
-
}));
|
|
5800
|
-
const protection = answer(await confirm({
|
|
5891
|
+
})));
|
|
5892
|
+
const protection = choices.accessProtection ?? (!process.stdin.isTTY ? false : answer(await confirm({
|
|
5801
5893
|
message: "Also protect the platform UI and APIs with Cloudflare Access?",
|
|
5802
5894
|
initialValue: false
|
|
5803
|
-
}));
|
|
5895
|
+
})));
|
|
5804
5896
|
return { authentication: {
|
|
5805
5897
|
providers,
|
|
5806
5898
|
...protection || providers.length !== 1 || providers[0] !== "github" ? { configurable: true } : {},
|
|
@@ -5837,6 +5929,7 @@ async function collectInstallationAuthentication(selection, plan, checkpoint, sa
|
|
|
5837
5929
|
const accessEntry = entries.find((entry) => entry.configuration.kind === "cloudflare-access");
|
|
5838
5930
|
const prepared = access && (selection.authentication.accessProtection || accessEntry && (!selection.file || selection.file.cloudflareAccess)) ? await preparePlatformAccessSetup({
|
|
5839
5931
|
plan,
|
|
5932
|
+
...platformDashboardUrl(plan.deployment) ? { additionalOrigins: [platformDashboardUrl(plan.deployment)] } : {},
|
|
5840
5933
|
ownershipNonce: access.ownershipNonce,
|
|
5841
5934
|
protection: !!selection.authentication.accessProtection,
|
|
5842
5935
|
...accessEntry ? { login: {
|
|
@@ -6044,13 +6137,13 @@ function platformSetupFieldHelp(key) {
|
|
|
6044
6137
|
switch (key) {
|
|
6045
6138
|
case "databaseUrl": return {
|
|
6046
6139
|
placeholder: "postgresql://user:pw@…/database",
|
|
6047
|
-
description: "Stores platform users, projects, and deployments
|
|
6140
|
+
description: "Stores platform users, projects, and deployments. Use an empty hosted database, e.g. " + platformSetupLink({
|
|
6048
6141
|
title: "Neon",
|
|
6049
6142
|
url: "https://neon.com/"
|
|
6050
6143
|
}) + ". " + docs("prerequisites", "platform-database", "Database setup")
|
|
6051
6144
|
};
|
|
6052
|
-
case "jwtSecret": return { description: "Signs platform login tokens. Run openssl rand -base64 32
|
|
6053
|
-
case "projectSecretKey": return { description: "Encrypts app secrets. Run openssl rand -base64 32 separately
|
|
6145
|
+
case "jwtSecret": return { description: "Signs platform login tokens. Run openssl rand -base64 32. Save the key in your password manager. " + docs("credentials", "signing-and-encryption-keys", "Key setup") };
|
|
6146
|
+
case "projectSecretKey": return { description: "Encrypts app secrets. Run openssl rand -base64 32 separately. Save this key for recovery. " + docs("credentials", "signing-and-encryption-keys", "Key setup") };
|
|
6054
6147
|
default: return;
|
|
6055
6148
|
}
|
|
6056
6149
|
}
|
|
@@ -6168,8 +6261,8 @@ function platformSetupPages(plan) {
|
|
|
6168
6261
|
type: "account"
|
|
6169
6262
|
}),
|
|
6170
6263
|
instructions: [
|
|
6171
|
-
"Select Object Read & Write
|
|
6172
|
-
"Keep Apply to all buckets in this account
|
|
6264
|
+
"Select Object Read & Write.",
|
|
6265
|
+
"Keep Apply to all buckets in this account selected.",
|
|
6173
6266
|
"Create the token, then copy the Access Key ID and Secret Access Key into the CLI."
|
|
6174
6267
|
].join("\n")
|
|
6175
6268
|
}
|
|
@@ -6178,16 +6271,20 @@ function platformSetupPages(plan) {
|
|
|
6178
6271
|
function formatPlatformSetupPlan(plan) {
|
|
6179
6272
|
const pages = platformSetupPages(plan);
|
|
6180
6273
|
return [
|
|
6181
|
-
"Credentials needed
|
|
6274
|
+
"Credentials needed during installation:",
|
|
6275
|
+
"PostgreSQL database URL",
|
|
6182
6276
|
platformSetupLink(pages.runtime),
|
|
6183
|
-
pages.runtime.instructions,
|
|
6184
|
-
"",
|
|
6185
6277
|
platformSetupLink(pages.r2),
|
|
6186
|
-
pages.
|
|
6278
|
+
...plan.deployment.authentication.providers.includes("github") ? [platformSetupLink(pages.github)] : [],
|
|
6279
|
+
...plan.deployment.authentication.providers.filter((provider) => provider !== "github").map((provider) => (provider === "google" ? "Google OAuth" : provider === "oidc" ? "OIDC" : "Cloudflare Access OIDC") + " client ID and secret"),
|
|
6280
|
+
...plan.deployment.authentication.accessProtection ? ["Cloudflare Access application and installer credentials"] : [],
|
|
6281
|
+
platformSetupLink({
|
|
6282
|
+
title: "JWT signing key and project encryption key",
|
|
6283
|
+
url: "https://void.cloud/guide/platform/installation/credentials#signing-and-encryption-keys"
|
|
6284
|
+
}),
|
|
6187
6285
|
"",
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
...plan.deployment.authentication.accessProtection ? ["Cloudflare Access protection: configure API and proxy origins and admitted installer/CI credentials."] : []
|
|
6286
|
+
"Login callback: " + plan.apiUrl + "/auth/callback",
|
|
6287
|
+
"Installation collects and validates missing credentials after you confirm the plan."
|
|
6191
6288
|
].join("\n");
|
|
6192
6289
|
}
|
|
6193
6290
|
function formatPlatformInstallCommand(options) {
|
|
@@ -6215,10 +6312,18 @@ function formatPlatformInstallCommand(options) {
|
|
|
6215
6312
|
flag("--application-domain", options.applicationDomain);
|
|
6216
6313
|
flag("--zone", options.zoneName);
|
|
6217
6314
|
if (options.dedicatedZone) parts.push("--dedicated-zone");
|
|
6315
|
+
else parts.push("--no-dedicated-zone");
|
|
6218
6316
|
}
|
|
6219
6317
|
flag("--control-plane-domain", options.controlPlaneDomain);
|
|
6318
|
+
if (!options.controlPlaneDomain) parts.push("--no-control-plane-domain");
|
|
6220
6319
|
}
|
|
6320
|
+
flag("--hyperdrive", options.hyperdrive ?? "");
|
|
6221
6321
|
flag("--auth-config", options.authConfig ?? "");
|
|
6322
|
+
if (!options.authConfig && !options.resumeId && options.authentication) {
|
|
6323
|
+
flag("--login-methods", options.authentication.providers.join(","));
|
|
6324
|
+
parts.push(options.authentication.accessProtection ? "--access-protection" : "--no-access-protection");
|
|
6325
|
+
}
|
|
6326
|
+
flag("--dashboard-url", options.dashboardUrl ?? "");
|
|
6222
6327
|
flag("--runtime", options.runtime);
|
|
6223
6328
|
if (options.yes) parts.push("--yes");
|
|
6224
6329
|
return parts.join(" ");
|
|
@@ -6278,14 +6383,14 @@ function resolvePlatformInstallDatabaseUrl(checkpoint, manifest = null) {
|
|
|
6278
6383
|
}
|
|
6279
6384
|
async function runPlatformLifecycleCommand(args) {
|
|
6280
6385
|
if (args.subcommand === "domain") {
|
|
6281
|
-
const { runPlatformDomainCommand } = await import("./platform-domain-
|
|
6386
|
+
const { runPlatformDomainCommand } = await import("./platform-domain-BNkcz0OB.mjs");
|
|
6282
6387
|
await runPlatformDomainCommand(args);
|
|
6283
6388
|
return;
|
|
6284
6389
|
}
|
|
6285
6390
|
if (args.subcommand === "discover") {
|
|
6286
6391
|
const client = new PlatformCloudflareClient((await resolvePlatformCloudflareCredential()).token);
|
|
6287
6392
|
const accountId = await selectAccount(await readAccounts(client), args.account);
|
|
6288
|
-
const { recoverPlatformInstallations } = await import("./platform-recovery-
|
|
6393
|
+
const { recoverPlatformInstallations } = await import("./platform-recovery-D6KSpuFm.mjs");
|
|
6289
6394
|
const recovered = await recoverPlatformInstallations(client, accountId, void 0, args.selector);
|
|
6290
6395
|
if (recovered.length === 0) {
|
|
6291
6396
|
log.info(`No recoverable Void platform installations were found in ${accountId}.`);
|
|
@@ -6295,7 +6400,7 @@ async function runPlatformLifecycleCommand(args) {
|
|
|
6295
6400
|
return;
|
|
6296
6401
|
}
|
|
6297
6402
|
if (args.subcommand !== "install") {
|
|
6298
|
-
const { runInstalledPlatformCommand } = await import("./platform-management-
|
|
6403
|
+
const { runInstalledPlatformCommand } = await import("./platform-management-CnyTdcWX.mjs");
|
|
6299
6404
|
await runInstalledPlatformCommand(args);
|
|
6300
6405
|
return;
|
|
6301
6406
|
}
|
|
@@ -6410,9 +6515,9 @@ async function runPlatformLifecycleCommand(args) {
|
|
|
6410
6515
|
const applicationDomain = recorded?.application.baseDomain ?? (workersDev ? "" : normalizeApplicationDomain(args.applicationDomain || await promptText("Application base domain:", "example.app")));
|
|
6411
6516
|
const zoneName = recorded?.application.zoneName ?? (workersDev ? "" : normalizeApplicationDomain(args.zone || await promptText("Cloudflare zone:", applicationDomain)));
|
|
6412
6517
|
const displayName = recorded?.displayName ?? (args.displayName || await promptText("Platform display name:", `${installationSlug} Void`));
|
|
6413
|
-
const controlPlaneDomain = recorded ? recorded.controlPlane.apiHostname ?? "" : args.controlPlaneDomain || (process.stdin.isTTY ? await promptText("
|
|
6518
|
+
const controlPlaneDomain = recorded ? recorded.controlPlane.apiHostname ?? "" : args.controlPlaneDomain || (process.stdin.isTTY && !args.noControlPlaneDomain ? await promptText("Custom API hostname. Leave empty for workers.dev:", "", true) : "");
|
|
6414
6519
|
let dedicatedZone = recorded?.application.dedicatedZone ?? args.dedicatedZone;
|
|
6415
|
-
if (!workersDev && !recorded && !args.dedicatedZone && applicationDomain === zoneName && process.stdin.isTTY) {
|
|
6520
|
+
if (!workersDev && !recorded && !args.dedicatedZone && !args.dedicatedZoneSpecified && applicationDomain === zoneName && process.stdin.isTTY) {
|
|
6416
6521
|
const catchAll = await confirm({
|
|
6417
6522
|
message: `Dedicate all unmatched ${zoneName} traffic to Void apps?`,
|
|
6418
6523
|
initialValue: false
|
|
@@ -6454,11 +6559,12 @@ async function runPlatformLifecycleCommand(args) {
|
|
|
6454
6559
|
host: externalHyperdriveHost,
|
|
6455
6560
|
user: externalHyperdriveUser
|
|
6456
6561
|
} : void 0;
|
|
6457
|
-
if (
|
|
6562
|
+
if (args.hyperdrive && externalHyperdrive && args.hyperdrive !== externalHyperdrive.id) throw new Error("platform: --hyperdrive conflicts with VOID_PLATFORM_HYPERDRIVE_ID.");
|
|
6563
|
+
if (!externalHyperdrive && args.hyperdrive !== "create" && (process.stdin.isTTY || args.hyperdrive) && (!existing || isPlatformSetupDraft(existing)) && !existing?.resources.some((resource) => resource.key === "hyperdrive")) {
|
|
6458
6564
|
const suggestedName = existing?.deployment.resources.hyperdrive ?? createPlatformResourceNames(installationSlug).hyperdrive;
|
|
6459
6565
|
const configurations = await client.listHyperdriveConfigs(accountId);
|
|
6460
6566
|
const suggestedNameAvailable = !configurations.some((resource) => resource.name === suggestedName);
|
|
6461
|
-
const choice = await select({
|
|
6567
|
+
const choice = args.hyperdrive ? "existing" : await select({
|
|
6462
6568
|
message: "How should Void set up Hyperdrive?",
|
|
6463
6569
|
options: [
|
|
6464
6570
|
...configurations.length > 0 ? [{
|
|
@@ -6478,12 +6584,12 @@ async function runPlatformLifecycleCommand(args) {
|
|
|
6478
6584
|
});
|
|
6479
6585
|
if (isCancel(choice)) throw new Error("platform: Cancelled.");
|
|
6480
6586
|
if (choice === "setup") {
|
|
6481
|
-
note(`Create a Hyperdrive in account ${accountId} with any unused name${suggestedNameAvailable ? `
|
|
6587
|
+
note(`Create a Hyperdrive in account ${accountId} with any unused name${suggestedNameAvailable ? ` such as ${suggestedName}` : ""}. Point it at the same dedicated PostgreSQL database you will give Void, use a runtime database user, and disable SQL result caching. If your organization manages Hyperdrive, ask its administrator to create it. Then rerun this command and select it from the list.\nSetup: https://developers.cloudflare.com/hyperdrive/get-started/`, "Set up Hyperdrive first");
|
|
6482
6588
|
outro("No Cloudflare or local installation state was changed.");
|
|
6483
6589
|
return;
|
|
6484
6590
|
}
|
|
6485
6591
|
if (choice === "existing") {
|
|
6486
|
-
const selectedId = await select({
|
|
6592
|
+
const selectedId = args.hyperdrive ?? await select({
|
|
6487
6593
|
message: "Which Hyperdrive should Void use?",
|
|
6488
6594
|
options: [...configurations].sort((a, b) => Number(b.name === suggestedName) - Number(a.name === suggestedName) || a.name.localeCompare(b.name) || a.id.localeCompare(b.id)).map((resource) => ({
|
|
6489
6595
|
value: resource.id,
|
|
@@ -6498,7 +6604,7 @@ async function runPlatformLifecycleCommand(args) {
|
|
|
6498
6604
|
if (live.id !== selected.id || live.name !== selected.name || !live.origin.user) throw new Error(`platform: Could not verify the database user for existing Hyperdrive ${selected.name}.`);
|
|
6499
6605
|
note(`Database: ${live.origin.database}\nHost: ${live.origin.host}\nPort: ${live.origin.port}\nUser: ${live.origin.user}\nSQL result caching: ${live.cachingDisabled ? "disabled" : "enabled"}`, `Existing Hyperdrive ${live.name}`);
|
|
6500
6606
|
if (!live.cachingDisabled) throw new Error(`platform: Disable SQL result caching on existing Hyperdrive ${live.name} before installing.`);
|
|
6501
|
-
const confirmed = await confirm({
|
|
6607
|
+
const confirmed = args.hyperdrive ? true : await confirm({
|
|
6502
6608
|
message: `Use ${live.name} for this platform?`,
|
|
6503
6609
|
initialValue: true
|
|
6504
6610
|
});
|
|
@@ -6519,7 +6625,7 @@ async function runPlatformLifecycleCommand(args) {
|
|
|
6519
6625
|
progress?.stop("Read the saved installation.");
|
|
6520
6626
|
progress = void 0;
|
|
6521
6627
|
if (authoritative) {
|
|
6522
|
-
const { selectAuthoritativePlatformManifest } = await import("./platform-management-
|
|
6628
|
+
const { selectAuthoritativePlatformManifest } = await import("./platform-management-CnyTdcWX.mjs");
|
|
6523
6629
|
const selected = selectAuthoritativePlatformManifest(existing, authoritative);
|
|
6524
6630
|
authoritativeProvisionIntents = true;
|
|
6525
6631
|
if (!args.plan && JSON.stringify(selected) !== JSON.stringify(existing)) writeInstallationManifest(selected);
|
|
@@ -6527,12 +6633,13 @@ async function runPlatformLifecycleCommand(args) {
|
|
|
6527
6633
|
if (selectedInstall && !isPlatformInstallInProgress(existing)) throw new Error(`platform: Installation ${existing.id} is no longer unfinished. Run \`void platform status ${existing.id}\` to inspect it.`);
|
|
6528
6634
|
}
|
|
6529
6635
|
}
|
|
6530
|
-
const authenticationSelection = await selectInstallationAuthentication(args.authConfig, existing?.deployment.authentication);
|
|
6636
|
+
const authenticationSelection = await selectInstallationAuthentication(args.authConfig, existing?.deployment.authentication, args);
|
|
6531
6637
|
const planInput = {
|
|
6532
6638
|
authentication: authenticationSelection.authentication,
|
|
6533
6639
|
accountId,
|
|
6534
6640
|
applicationDomain,
|
|
6535
6641
|
controlPlaneDomain,
|
|
6642
|
+
...args.dashboardUrl !== void 0 ? { dashboardUrl: args.dashboardUrl } : {},
|
|
6536
6643
|
dedicatedZone,
|
|
6537
6644
|
displayName,
|
|
6538
6645
|
...externalHyperdrive ? { externalHyperdrive } : {},
|
|
@@ -6546,13 +6653,15 @@ async function runPlatformLifecycleCommand(args) {
|
|
|
6546
6653
|
authoritativeProvisionIntents,
|
|
6547
6654
|
allowUnverifiedAiGateway: true,
|
|
6548
6655
|
includeEmailGateway: runtime.manifest.artifacts.emailGateway !== void 0,
|
|
6549
|
-
enableSandbox: runtime.manifest.safetyProtocols.sandboxDrain >= 3
|
|
6656
|
+
enableSandbox: runtime.manifest.safetyProtocols.sandboxDrain >= 3,
|
|
6657
|
+
deploymentExecution: runtime.manifest.deploymentExecution === 1
|
|
6550
6658
|
});
|
|
6551
6659
|
note(formatPlatformInstallPlan(plan), args.plan ? "Read-only installation plan" : "Installation plan");
|
|
6552
6660
|
note(formatPlatformRuntime(runtime), "Platform runtime");
|
|
6553
6661
|
if (args.plan) {
|
|
6554
6662
|
console.log(formatPlatformSetupPlan(plan));
|
|
6555
|
-
|
|
6663
|
+
outro("Plan complete. No installation changes were made.");
|
|
6664
|
+
console.log(`\n${import_picocolors$1.default.bold(import_picocolors$1.default.cyan(existing ? "Next step: run this command to resume" : "Next step: run this command to install"))}\n\n${import_picocolors$1.default.cyan(formatPlatformInstallCommand({
|
|
6556
6665
|
commandName: cliCommandName(),
|
|
6557
6666
|
accountId,
|
|
6558
6667
|
installationSlug,
|
|
@@ -6560,15 +6669,16 @@ async function runPlatformLifecycleCommand(args) {
|
|
|
6560
6669
|
applicationDomain,
|
|
6561
6670
|
zoneName,
|
|
6562
6671
|
controlPlaneDomain,
|
|
6672
|
+
...args.dashboardUrl !== void 0 ? { dashboardUrl: args.dashboardUrl } : {},
|
|
6563
6673
|
dedicatedZone,
|
|
6564
6674
|
workersDev,
|
|
6565
6675
|
authConfig: args.authConfig,
|
|
6676
|
+
authentication: authenticationSelection.authentication,
|
|
6677
|
+
hyperdrive: !existing || isPlatformSetupDraft(existing) ? externalHyperdrive?.id ?? "create" : void 0,
|
|
6566
6678
|
runtime: args.runtime,
|
|
6567
6679
|
...existing ? { resumeId: existing.id } : {},
|
|
6568
6680
|
yes: args.yes || process.stdin.isTTY !== true
|
|
6569
|
-
})}\n`);
|
|
6570
|
-
if (!existing && !args.authConfig && process.stdin.isTTY) console.log("Choose the same login methods when prompted.\n");
|
|
6571
|
-
outro("No Cloudflare or local installation state was changed.");
|
|
6681
|
+
}))}\n`);
|
|
6572
6682
|
return;
|
|
6573
6683
|
}
|
|
6574
6684
|
assertPlatformOperatorCredentialCanApplyPlan(operatorCredential, plan);
|
|
@@ -6744,11 +6854,12 @@ async function runPlatformLifecycleCommand(args) {
|
|
|
6744
6854
|
authoritativeProvisionIntents: true,
|
|
6745
6855
|
repairMissing: true,
|
|
6746
6856
|
includeEmailGateway: runtime.manifest.artifacts.emailGateway !== void 0,
|
|
6747
|
-
enableSandbox: runtime.manifest.safetyProtocols.sandboxDrain >= 3
|
|
6857
|
+
enableSandbox: runtime.manifest.safetyProtocols.sandboxDrain >= 3,
|
|
6858
|
+
deploymentExecution: runtime.manifest.deploymentExecution === 1
|
|
6748
6859
|
})).actions.filter((action) => action.kind === "worker"),
|
|
6749
6860
|
revalidateBeforeWorkerDeploy: (manifest, action) => revalidatePlatformInstallAction(client, plan, manifest, action),
|
|
6750
6861
|
verifyExistingOwnership: async (manifest, databaseCheckpointExists) => {
|
|
6751
|
-
const { verifyPlatformInstallationOwnership } = await import("./platform-management-
|
|
6862
|
+
const { verifyPlatformInstallationOwnership } = await import("./platform-management-CnyTdcWX.mjs");
|
|
6752
6863
|
const retainedReplacementKeys = new Set(plan.actions.filter((action) => {
|
|
6753
6864
|
const previous = manifest.resources.find((resource) => resource.key === action.key);
|
|
6754
6865
|
return action.action === "create" && previous && previous.name !== action.name;
|
|
@@ -6760,7 +6871,7 @@ async function runPlatformLifecycleCommand(args) {
|
|
|
6760
6871
|
});
|
|
6761
6872
|
}
|
|
6762
6873
|
});
|
|
6763
|
-
progress?.stop(result.waitingForDns || result.waitingForSandboxDrain ? "Installation paused
|
|
6874
|
+
progress?.stop(result.waitingForDns || result.waitingForSandboxDrain ? "Installation paused. Progress is saved." : "Platform installed.");
|
|
6764
6875
|
progress = void 0;
|
|
6765
6876
|
if (result.waitingForDns) {
|
|
6766
6877
|
note(result.zoneNameServers.length > 0 ? `Update the domain at your registrar to use:\n${result.zoneNameServers.join("\n")}\n\nThen rerun: void platform install --resume --name ${result.manifest.id}` : `Wait for ${zoneName} to become active, then rerun:\nvoid platform install --resume --name ${result.manifest.id}`, "Waiting for nameservers");
|
|
@@ -6768,13 +6879,13 @@ async function runPlatformLifecycleCommand(args) {
|
|
|
6768
6879
|
return;
|
|
6769
6880
|
}
|
|
6770
6881
|
if (result.waitingForSandboxDrain) {
|
|
6771
|
-
note("The compatible candidate runtime is live in sandbox-drain maintenance mode and application traffic remains disabled. Preview bounded work with `void platform system sandbox-drain --plan
|
|
6882
|
+
note("The compatible candidate runtime is live in sandbox-drain maintenance mode and application traffic remains disabled. Preview bounded work with `void platform system sandbox-drain --plan`. Follow any returned `nextCursor` with `--cursor '<value>'`. Then apply with `void platform system sandbox-drain --yes` and rerun until it reports DB-backed `complete: true`. Rerun this install command afterward to enable traffic.", "Waiting for managed Sandbox drain");
|
|
6772
6883
|
outro(`Installation ${result.manifest.id} is safely checkpointed.`);
|
|
6773
6884
|
return;
|
|
6774
6885
|
}
|
|
6775
6886
|
log.success(`${result.manifest.deployment.displayName} is installed at ${new URL("/admin", plan.apiUrl).href}.`);
|
|
6776
6887
|
log.info(`API URL: ${plan.apiUrl}`);
|
|
6777
|
-
if (result.emailOperationId) log.info(`Shared email routing ready
|
|
6888
|
+
if (result.emailOperationId) log.info(`Shared email routing ready. Operation: ${result.emailOperationId}.`);
|
|
6778
6889
|
const completedAuthCode = readInstallationRecoverySecrets(result.manifest)?.authBootstrapCode;
|
|
6779
6890
|
if (result.setupPending && completedAuthCode) {
|
|
6780
6891
|
log.info(`First administrator setup: ${plan.apiUrl}/setup`);
|
|
@@ -6814,7 +6925,8 @@ async function verifyPlatformAiGatewayPlan(client, input, existing, preview, aut
|
|
|
6814
6925
|
verified = await createPlatformInstallPlan(client, input, existing, {
|
|
6815
6926
|
authoritativeProvisionIntents,
|
|
6816
6927
|
includeEmailGateway: preview.actions.some((entry) => entry.key === "email-gateway-worker"),
|
|
6817
|
-
enableSandbox: preview.deployment.capabilities.sandbox
|
|
6928
|
+
enableSandbox: preview.deployment.capabilities.sandbox,
|
|
6929
|
+
deploymentExecution: preview.actions.some((entry) => entry.key === "deployment-queue")
|
|
6818
6930
|
});
|
|
6819
6931
|
} catch (error) {
|
|
6820
6932
|
if (error instanceof PlatformCloudflareApiError && (error.status === 401 || error.status === 403) && error.requestPath?.includes("/ai-gateway/")) throw new Error("platform: The runtime token could not verify AI Gateway access. Include Account → AI Gateway → Edit for this account, then retry. No platform resources were created.", { cause: error });
|