void 0.28.0 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/dist/{account-cmd-Ci00cncT.mjs → account-cmd-BTcUzv9L.mjs} +4 -4
  2. package/dist/{application-analysis-D72ZQfMZ.mjs → application-analysis-AB_YYWgH.mjs} +1 -1
  3. package/dist/{application-routing-Dy589Vgn.mjs → application-routing-L7V-TFhZ.mjs} +1 -1
  4. package/dist/auth-Cbqi1iVX.mjs +1 -0
  5. package/dist/{auth-link-DWr6Afao.mjs → auth-link-DUJuvRx5.mjs} +4 -4
  6. package/dist/{auth-router-DP14wVkZ.mjs → auth-router-CIxiMG7i.mjs} +3 -3
  7. package/dist/{build-cmd-Cr3FJxv0.mjs → build-cmd-BkIv25PA.mjs} +3 -3
  8. package/dist/{cache-D0Tt28cn.mjs → cache-Ctzfig-W.mjs} +3 -3
  9. package/dist/{cancel-deploy-CJKvk7AN.mjs → cancel-deploy-CD9o-SsC.mjs} +2 -2
  10. package/dist/cf-build-output-BU3hqdHu.mjs +1 -0
  11. package/dist/cli/cli.mjs +31 -31
  12. package/dist/cli/cloudflare-operation-process.mjs +2 -2
  13. package/dist/cli/env-schema-probe.d.mts +2 -1
  14. package/dist/cli/env-schema-probe.mjs +2 -2
  15. package/dist/client-B60PwCC4.mjs +1 -0
  16. package/dist/{cloudflare-cmd-CTrn-lXJ.mjs → cloudflare-cmd-BIAF_BIY.mjs} +2 -2
  17. package/dist/{cloudflare-connect-BosmCNlq.mjs → cloudflare-connect-WjwnVOJA.mjs} +1 -1
  18. package/dist/{cloudflare-operations-BKdSfNBN.mjs → cloudflare-operations-B42EZeJy.mjs} +3 -3
  19. package/dist/cloudflare-operations-CrSUFRzJ.mjs +1 -0
  20. package/dist/collect-Cae8A7Zr.mjs +1 -0
  21. package/dist/config-QrJWu0IX.mjs +1 -0
  22. package/dist/{connect-nukQSoEO.mjs → connect-CKtk-EZe.mjs} +5 -5
  23. package/dist/create-project-B-ewF_Af.mjs +1 -0
  24. package/dist/{create-project-D4APeBwX.mjs → create-project-yInGo72M.mjs} +3 -3
  25. package/dist/database-provider.mjs +1 -2
  26. package/dist/{db-27RztQLy.mjs → db-7I_gfdVq.mjs} +37 -38
  27. package/dist/db-types-Bb4WBWIs.mjs +49 -0
  28. package/dist/{delete-BbsZFZIw.mjs → delete-Cy-M1u-r.mjs} +3 -3
  29. package/dist/deploy-9lgfhrXS.mjs +2 -0
  30. package/dist/{deploy-B06EQq4t.mjs → deploy-BYYlnsiy.mjs} +176 -21
  31. package/dist/{deploy-managed-static-CGswbcay.mjs → deploy-managed-static-Ca4TFdmx.mjs} +24 -706
  32. package/dist/{deploy-transport-BiicfoQW.mjs → deploy-transport-DWlWBe6M.mjs} +1 -1
  33. package/dist/deploy.mjs +4 -4
  34. package/dist/discover-ClzFSwns.mjs +1 -0
  35. package/dist/dispatch-DrTTk8Xg.mjs +431 -0
  36. package/dist/{dist-D3m88sjf.mjs → dist-BVdsO7Py.mjs} +5 -6
  37. package/dist/dist-CfgIkieS.mjs +1 -0
  38. package/dist/{domain-Bfq9n03H.mjs → domain-CZGxPv9u.mjs} +4 -4
  39. package/dist/edge.mjs +1 -2
  40. package/dist/{email-DIwaQU4P.mjs → email-DzyNVuoU.mjs} +6 -6
  41. package/dist/{email-cloudflare-CH053RxK.mjs → email-cloudflare-DfG6UEMm.mjs} +1 -1
  42. package/dist/{env-BhdWMrNy.mjs → env-0jrMQJI_.mjs} +5 -2
  43. package/dist/{env-C6ipBLp3.mjs → env-Nx3yf50r.mjs} +4 -4
  44. package/dist/{env-raw-Cklzcde3.mjs → env-raw-D74P4ebM.mjs} +12 -1
  45. package/dist/env-validation-BT7z-qpQ.mjs +1 -0
  46. package/dist/{env-validation-cE0zcmzO.mjs → env-validation-DYxjz_Wo.mjs} +2 -0
  47. package/dist/{gen-CnN1Xidx.mjs → gen-CufHEf42.mjs} +2 -2
  48. package/dist/gen-sXyp5DFO.mjs +1 -0
  49. package/dist/generate-ClI89brV.mjs +1 -0
  50. package/dist/{github-cmd-BdkU4Nhe.mjs → github-cmd-FU8EjFyU.mjs} +3 -3
  51. package/dist/{help-Bug7pyK_.mjs → help-BT2hovNI.mjs} +9 -5
  52. package/dist/help-i2YGGnTH.mjs +1 -0
  53. package/dist/{inbound-DmkTQeWp.mjs → inbound-BuTfm11U.mjs} +1 -1
  54. package/dist/index.mjs +551 -303
  55. package/dist/{info-CujPwtMe.mjs → info-l249eUdH.mjs} +1 -1
  56. package/dist/{init-ZWOJvc-Z.mjs → init-D0A6M64Q.mjs} +60 -14
  57. package/dist/{isr-config-DPjXM8J2.mjs → isr-config-fZaJJ8t7.mjs} +1 -30
  58. package/dist/{link-UEc7bnF4.mjs → link-WMoJFf4i.mjs} +4 -4
  59. package/dist/{list-CuWeOnzk.mjs → list-CQ2EQEHL.mjs} +4 -4
  60. package/dist/{live-BxLX-JqJ.mjs → live-BcHoTDFe.mjs} +1 -1
  61. package/dist/{local-d1-BM3p74mm.mjs → local-d1-BxvQYEFi.mjs} +1 -1
  62. package/dist/{login-CZ2Cqa-D.mjs → login-C2T1oVG-.mjs} +3 -3
  63. package/dist/login-XD844Xs-.mjs +1 -0
  64. package/dist/{logs-TihRcLTk.mjs → logs-DnC1d7xB.mjs} +3 -3
  65. package/dist/{migrate-uFwojusJ.mjs → migrate-BDIprOYd.mjs} +2 -2
  66. package/dist/migrate-BRwU-blY.mjs +1 -0
  67. package/dist/mysql-migration-sql-BIS5vbNk.mjs +241 -0
  68. package/dist/nitro.d.mts +30 -0
  69. package/dist/nitro.mjs +88 -0
  70. package/dist/{node-DoKbo1SZ.mjs → node-cPadMgUO.mjs} +3 -3
  71. package/dist/operator-cmd-D6gnZsnK.mjs +1 -0
  72. package/dist/{operator-cmd-C5lCctnc.mjs → operator-cmd-DHieWtFp.mjs} +5 -5
  73. package/dist/{output-FNHKw5Ac.mjs → output-Ba6ZbrzY.mjs} +1 -1
  74. package/dist/pages/head.mjs +1 -2
  75. package/dist/pages/prefetch.mjs +1 -2
  76. package/dist/pages/serialize.mjs +1 -2
  77. package/dist/plan-CKX_jWnD.mjs +1 -0
  78. package/dist/platform-args-B0HeTh0C.mjs +1 -0
  79. package/dist/{platform-args-C6Mb_I3a.mjs → platform-args-CaCL_1Ch.mjs} +9 -2
  80. package/dist/{platform-auth-config-CJmNkFT2.mjs → platform-auth-config-Z16-I8nT.mjs} +6 -6
  81. package/dist/{platform-auth-protection-D5wvsI-Y.mjs → platform-auth-protection-BXwZiJSF.mjs} +4 -4
  82. package/dist/{platform-auth-recovery-D_0I3n-1.mjs → platform-auth-recovery-CXjfqyOv.mjs} +4 -4
  83. package/dist/{platform-cmd-DxxZO6e5.mjs → platform-cmd-DaAOiWTl.mjs} +7 -7
  84. package/dist/platform-cmd-PT4YwMLM.mjs +1 -0
  85. package/dist/{platform-domain-zLXnW1G_.mjs → platform-domain-CxpvUeo0.mjs} +4 -4
  86. package/dist/{platform-lifecycle-DOW-wLfx.mjs → platform-lifecycle-DBiS0cJd.mjs} +317 -99
  87. package/dist/platform-lifecycle-u9pMOvxC.mjs +1 -0
  88. package/dist/{platform-management-t7HKy4-U.mjs → platform-management-DhEFdQAf.mjs} +67 -9
  89. package/dist/platform-management-DvBS83rC.mjs +1 -0
  90. package/dist/{platform-plans-config-CNH6zbPX.mjs → platform-plans-config-BWn1S2Rv.mjs} +2 -2
  91. package/dist/{platform-recovery-DeucI7A9.mjs → platform-recovery-BUGOB4RC.mjs} +2 -2
  92. package/dist/{platform-setup-CTf6MMT9.mjs → platform-setup-YvN9VQXV.mjs} +3 -3
  93. package/dist/prepare-CYT6tAzD.mjs +1 -0
  94. package/dist/{prepare-CSH9dKTI.mjs → prepare-DEB5SeVJ.mjs} +3 -2
  95. package/dist/{prepare-C4zfwks4.mjs → prepare-PQCgsny0.mjs} +3 -3
  96. package/dist/prerender-render.mjs +1 -2
  97. package/dist/{project-cmd-Dq75AXzy.mjs → project-cmd-DVatD8Ev.mjs} +16 -16
  98. package/dist/project-slug-6cyz8E7k.mjs +1 -0
  99. package/dist/{project-team-B3_QcStG.mjs → project-team-DJrWEAG1.mjs} +3 -3
  100. package/dist/{project-token-CvsRvv2I.mjs → project-token-CBgLBuL6.mjs} +3 -3
  101. package/dist/project-zdB3fs9G.mjs +1 -0
  102. package/dist/{project-zero-trust-D1ICiwN_.mjs → project-zero-trust-92gLtZ25.mjs} +3 -3
  103. package/dist/{provision-DDh7ITVS.mjs → provision-BnSSkiED.mjs} +1 -1
  104. package/dist/{requests-3CL3BuZh.mjs → requests-7W1Pygt2.mjs} +2 -2
  105. package/dist/resolve-project-PnhzMtY6.mjs +1 -0
  106. package/dist/{resolve-project-C_Cu7E8a.mjs → resolve-project-WpwkCB_c.mjs} +1 -1
  107. package/dist/{rollback-Ce1d_0J9.mjs → rollback-BcASVnwP.mjs} +3 -3
  108. package/dist/{route-types-IrBuwJ7u.mjs → route-types-BZi26DVS.mjs} +2 -49
  109. package/dist/{runner-DcVDrzuR.mjs → runner-CabsQ-iM.mjs} +15 -6
  110. package/dist/runner-So9mYfcm.mjs +1 -0
  111. package/dist/runner-mysql-BhRaONkO.mjs +146 -0
  112. package/dist/runner-mysql-DWwE8jKi.mjs +1 -0
  113. package/dist/{runner-pg-CPd-CB_F.mjs → runner-pg-BRemq_GX.mjs} +2 -2
  114. package/dist/runner-pg-_Xncr3H5.mjs +1 -0
  115. package/dist/runtime/ai.mjs +15 -4
  116. package/dist/runtime/auth-client-react.mjs +1 -2
  117. package/dist/runtime/auth-client-solid.mjs +1 -2
  118. package/dist/runtime/auth-client-svelte.mjs +1 -2
  119. package/dist/runtime/auth-client-vue.mjs +1 -2
  120. package/dist/runtime/auth-client.mjs +1 -2
  121. package/dist/runtime/auth.mjs +1 -2
  122. package/dist/runtime/better-auth-mysql.mjs +4 -1
  123. package/dist/runtime/db.mjs +6 -2
  124. package/dist/runtime/durable.d.mts +2 -1
  125. package/dist/runtime/durable.mjs +1 -1
  126. package/dist/runtime/email/testing.mjs +1 -1
  127. package/dist/runtime/email-protocol.mjs +1 -2
  128. package/dist/runtime/email.mjs +1 -1
  129. package/dist/runtime/env-public.mjs +2 -2
  130. package/dist/runtime/env.mjs +1 -2
  131. package/dist/runtime/isr.mjs +1 -1
  132. package/dist/runtime/kv.mjs +1 -1
  133. package/dist/runtime/limits.mjs +1 -2
  134. package/dist/runtime/live-client.d.mts +2 -1
  135. package/dist/runtime/live-server.d.mts +2 -1
  136. package/dist/runtime/live-server.mjs +1 -1
  137. package/dist/runtime/live.mjs +1 -2
  138. package/dist/runtime/log.d.mts +2 -1
  139. package/dist/runtime/migration-handler-mysql.mjs +38 -8
  140. package/dist/runtime/migration-handler.mjs +1 -2
  141. package/dist/runtime/queues.mjs +1 -1
  142. package/dist/runtime/response.mjs +1 -2
  143. package/dist/runtime/route-url.mjs +1 -2
  144. package/dist/runtime/routing.d.mts +2 -1
  145. package/dist/runtime/sandbox-container.mjs +1 -2
  146. package/dist/runtime/sandbox.mjs +3 -3
  147. package/dist/runtime/seed.d.mts +2 -1
  148. package/dist/runtime/sse.mjs +1 -2
  149. package/dist/runtime/storage.mjs +1 -1
  150. package/dist/runtime/validator.mjs +1 -2
  151. package/dist/runtime/ws-server.d.mts +2 -1
  152. package/dist/runtime/ws-server.mjs +1 -1
  153. package/dist/{secret-B5J8bBsI.mjs → secret-bg73f1Ge.mjs} +5 -5
  154. package/dist/{skills-BJsan98C.mjs → skills-C_yRTg0g.mjs} +1 -1
  155. package/dist/{subcommand-prompt-gQ-NDA-u.mjs → subcommand-prompt-Bm3ZlYgA.mjs} +2 -2
  156. package/dist/sveltekit.d.mts +2 -1
  157. package/dist/validate-BQFdf0Pi.mjs +1 -0
  158. package/dist/wrapper-CwhpNWtH.mjs +663 -0
  159. package/docs/guide/ai.md +8 -2
  160. package/docs/guide/database/mysql.md +14 -3
  161. package/docs/guide/database.md +1 -5
  162. package/docs/guide/durable-state.md +9 -5
  163. package/docs/guide/platform/installation/credentials.md +18 -2
  164. package/docs/guide/platform/installation/domains.md +1 -1
  165. package/docs/guide/platform/installation/maintenance.md +38 -1
  166. package/docs/guide/platform/installation/prerequisites.md +6 -2
  167. package/docs/guide/platform/installation/setup.md +23 -1
  168. package/docs/guide/platform/installation/uninstall.md +6 -1
  169. package/docs/guide/sandboxes.md +1 -1
  170. package/docs/guide/self-hosted-platform.md +1 -1
  171. package/docs/integrations/cloudflare.md +2 -2
  172. package/docs/integrations/frameworks/analog.md +3 -11
  173. package/docs/integrations/frameworks/nuxt.md +7 -10
  174. package/docs/integrations/nodejs-bun-deno.md +2 -0
  175. package/docs/reference/api.md +1 -0
  176. package/docs/reference/cli/platform-installation.md +18 -6
  177. package/docs/reference/config.md +2 -0
  178. package/package.json +34 -28
  179. package/sandbox.Dockerfile +1 -1
  180. package/skills/void/SKILL.md +1 -1
  181. package/dist/auth-wn6F8dB8.mjs +0 -2
  182. package/dist/cf-build-output-2u583yJK.mjs +0 -2
  183. package/dist/client-C1BjRn3g.mjs +0 -2
  184. package/dist/cloudflare-operations-Ds-lX3Ts.mjs +0 -2
  185. package/dist/collect-NUlJMpLY.mjs +0 -2
  186. package/dist/config-CNnrSMYm.mjs +0 -2
  187. package/dist/create-project-DL7YXpjj.mjs +0 -2
  188. package/dist/deploy-Db2ALnQP.mjs +0 -3
  189. package/dist/discover-CTUiSsfG.mjs +0 -2
  190. package/dist/dist-Bx1z6Eva.mjs +0 -2
  191. package/dist/env-validation-CTn-HhG1.mjs +0 -2
  192. package/dist/gen-F8pSZnhc.mjs +0 -2
  193. package/dist/generate-Rii9klHB.mjs +0 -2
  194. package/dist/help-s9q7LTcu.mjs +0 -2
  195. package/dist/login-DhK7o1oJ.mjs +0 -2
  196. package/dist/migrate-BgT-hmu8.mjs +0 -2
  197. package/dist/operator-cmd-Cjk5DiRx.mjs +0 -2
  198. package/dist/plan-CH2G51je.mjs +0 -2
  199. package/dist/platform-args-Cm7x-fot.mjs +0 -2
  200. package/dist/platform-cmd-Cuj4oUVo.mjs +0 -2
  201. package/dist/platform-lifecycle-_gfnjUBr.mjs +0 -2
  202. package/dist/platform-management-CYrLvTGH.mjs +0 -2
  203. package/dist/prepare-dr9uxQSV.mjs +0 -2
  204. package/dist/project-DiiOBZPF.mjs +0 -2
  205. package/dist/project-slug-DofjTFd-.mjs +0 -2
  206. package/dist/resolve-project-B9Hr0D3n.mjs +0 -2
  207. package/dist/runner-B5huK2eC.mjs +0 -2
  208. package/dist/runner-mysql-CCtyI0p6.mjs +0 -55
  209. package/dist/runner-mysql-CaJq-hd6.mjs +0 -2
  210. package/dist/runner-pg-BJhFZZG4.mjs +0 -2
  211. package/dist/validate-C9xtFOYR.mjs +0 -2
  212. package/dist/{sandbox-container-DBKIqDEK.mjs → sandbox-container-a9LRUtii.mjs} +1 -1
@@ -3,17 +3,17 @@ import { t as writeFileAtomic } from "./atomic-write-Dq6R0BPN.mjs";
3
3
  import { S as writePendingInstallationSecrets, a as latestPlatformWorkerCutoverGeneration, c as normalizePlatformApiUrl, d as platformRegistryDirectory, f as platformWorkerSecretUpdateIntentState, h as readPendingInstallationSecrets, m as readInstallationRecoverySecrets, n as assertPlatformInstallationSlugAvailable, o as listInstallationManifests, p as promoteInstallationSecrets, t as acquirePlatformLifecycleLock, v as resolveInstallationManifest, x as writeInstallationManifest, y as savePlatformConnection } from "./platform-registry-B9mWX9LO.mjs";
4
4
  import { t as cfAccessHeaders } from "./cf-access-BM9G0lhO.mjs";
5
5
  import { t as ensurePlatformAccessToken } from "./cf-access-CWP4avfP.mjs";
6
- import { A as confirm, H as outro, K as select, L as log, U as password, X as text$1, tt as isCancel, z as multiselect } from "./dist-D3m88sjf.mjs";
7
- import { f as import_picocolors, l as formatUrl, m as printSuccess, n as cliCommandName, p as note, r as createSpinner, u as getErrorMessage } from "./output-FNHKw5Ac.mjs";
6
+ import { A as confirm, H as outro, K as select, L as log, U as password, X as text$1, tt as isCancel, z as multiselect } from "./dist-BVdsO7Py.mjs";
7
+ import { f as import_picocolors, l as formatUrl, m as printSuccess, n as cliCommandName, p as note, r as createSpinner, u as getErrorMessage } from "./output-Ba6ZbrzY.mjs";
8
8
  import { l as resolveCloudflareOperationArgv } from "./cloudflare-process-CeDWJRhj.mjs";
9
- import { d as resolvePinnedApiBaseUrl, i as createDefaultRunWrangler } from "./provision-DDh7ITVS.mjs";
10
- import { t as parsePlatformArgs } from "./platform-args-C6Mb_I3a.mjs";
9
+ import { d as resolvePinnedApiBaseUrl, i as createDefaultRunWrangler } from "./provision-BnSSkiED.mjs";
10
+ import { t as parsePlatformArgs } from "./platform-args-CaCL_1Ch.mjs";
11
11
  import { o as runCloudflareAuthCommand } from "./cloudflare-auth-CqFBDo52.mjs";
12
- import { a as platformManagementSetupPage, c as platformSetupLink, i as platformInstallCommandArguments, l as platformSetupPages, n as formatPlatformSetupPlan, o as platformPromptAnswer, r as openPlatformSetupPage, s as platformSetupFieldHelp, t as formatPlatformInstallCommand } from "./platform-setup-CTf6MMT9.mjs";
12
+ import { a as platformManagementSetupPage, c as platformSetupLink, i as platformInstallCommandArguments, l as platformSetupPages, n as formatPlatformSetupPlan, o as platformPromptAnswer, r as openPlatformSetupPage, s as platformSetupFieldHelp, t as formatPlatformInstallCommand } from "./platform-setup-YvN9VQXV.mjs";
13
13
  import { copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
14
14
  import { fileURLToPath } from "node:url";
15
15
  import { isRecord } from "@void/deploy-core";
16
- import { PLATFORM_APP_ACCESS_SETUP_PROOF_PREFIX, 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, PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY as REMOTE_INSTALLATION_MANIFEST_KEY, createApplicationRoutes, createPlatformDeploymentManifest, createPlatformInstallationManifest, createPlatformResourceNames, createPlatformResourceOwnershipMarker, createPlatformWorkerConfigs, normalizeApplicationDomain, normalizeInstallationSlug, normalizePlatformDashboardUrl, parsePlatformAccessProtection, parsePlatformAdmissionPolicy, parsePlatformAppAccessSetup, parsePlatformAuthenticationSetup, parsePlatformInstallationManifest, parsePlatformLoginConfiguration, parsePlatformResourceOwnershipMarker, platformDashboardUrl, recordInstallationResource, recordInstallationStep, setInstallationEndpoints, transitionInstallation } from "@void/platform";
16
+ import { PLATFORM_APP_ACCESS_SETUP_PROOF_PREFIX, PLATFORM_APP_ACCESS_TURN_OFF_PROOF_PREFIX, 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, PLATFORM_REMOTE_INSTALLATION_MANIFEST_KEY as REMOTE_INSTALLATION_MANIFEST_KEY, createApplicationRoutes, createPlatformDeploymentManifest, createPlatformInstallationManifest, createPlatformResourceNames, createPlatformResourceOwnershipMarker, createPlatformWorkerConfigs, isPlatformAccessCompanyIdentityProvider, isPlatformAccessEmailDomain, isPlatformAccessIdentityRule, normalizeApplicationDomain, normalizeInstallationSlug, normalizePlatformDashboardUrl, parsePlatformAccessProtection, parsePlatformAdmissionPolicy, parsePlatformAppAccessSetup, parsePlatformAuthenticationSetup, parsePlatformInstallationManifest, parsePlatformLoginConfiguration, parsePlatformResourceOwnershipMarker, platformDashboardUrl, recordInstallationResource, recordInstallationStep, setInstallationEndpoints, transitionInstallation } from "@void/platform";
17
17
  import { createHash, createHmac, randomBytes, randomUUID } from "node:crypto";
18
18
  import { tmpdir } from "node:os";
19
19
  import { stripVTControlCharacters } from "node:util";
@@ -910,52 +910,41 @@ var PlatformAccessClient = class {
910
910
  throw new Error("platform: Access inventory exceeded the supported pagination limit.");
911
911
  }
912
912
  };
913
- /**
914
- * Project Zero Trust keeps its own, broader copy of this list in
915
- * platform/packages/api/src/zero-trust/cloudflare.ts; review both when adding
916
- * a rule type.
917
- */
918
- function identityRule(rule) {
919
- if (rule.email_domain) {
920
- const domain = object(rule.email_domain, "email domain").domain;
921
- return typeof domain === "string" && /^(?:[a-z0-9-]+\.)+[a-z]{2,63}$/i.test(domain);
922
- }
923
- if (rule.email) {
924
- const email = object(rule.email, "email rule").email;
925
- return typeof email === "string" && /^[^\s*@]+@[^\s*@]+\.[^\s*@]+$/.test(email);
926
- }
927
- return [
928
- "email",
929
- "email_domain",
930
- "email_list",
931
- "gsuite",
932
- "github-organization",
933
- "saml",
934
- "okta",
935
- "group"
936
- ].some((key) => Object.hasOwn(rule, key) && !!rule[key] && typeof rule[key] === "object" && !Array.isArray(rule[key]) && Object.keys(rule[key]).length > 0);
937
- }
913
+ /** The selected providers or policies cannot protect Void; other errors are not about the selection. */
914
+ var PlatformAccessPolicyRejectedError = class extends Error {};
938
915
  /** Preserve corporate MFA/device requirements; reject broad policies rather than rewriting them. */
939
- async function verifyCompanyAccessPolicies(client, policies) {
916
+ async function verifyCompanyAccessPolicies(client, policies, identityProviderIds, identityProviders) {
917
+ const companyProviders = new Set((identityProviders ?? await client.list("identity_providers")).filter(isPlatformAccessCompanyIdentityProvider).map((provider) => provider.id));
918
+ const selectedCompanyProviders = new Set(identityProviderIds.filter((id) => companyProviders.has(id)));
919
+ const identityRule = (entry, name, providers = selectedCompanyProviders) => {
920
+ const rule = object(entry, name);
921
+ const entra = rule.azureAD || rule.auth_context;
922
+ if (entra) {
923
+ const id = typeof entra === "object" ? entra.identity_provider_id : null;
924
+ return typeof id === "string" && providers.has(id);
925
+ }
926
+ return isPlatformAccessIdentityRule(rule, (id) => providers.has(id));
927
+ };
928
+ const restricted = (target, providers) => Array.isArray(target.include) && target.include.length > 0 && (Array.isArray(target.require) && target.require.some((entry) => identityRule(entry, "policy rule", providers)) || target.include.every((entry) => identityRule(entry, "policy rule", providers)));
940
929
  const groups = /* @__PURE__ */ new Set();
941
930
  const checkedGroups = /* @__PURE__ */ new Set();
942
931
  const checkRules = async (value, depth = 0) => {
943
932
  if (value === void 0) return;
944
- if (!Array.isArray(value) || value.length > 100 || depth > 8) throw new Error("platform: Unsupported Access policy rule structure.");
933
+ if (!Array.isArray(value) || value.length > 100 || depth > 8) throw new PlatformAccessPolicyRejectedError("platform: Unsupported Access policy rule structure.");
945
934
  for (const entry of value) {
946
935
  const rule = object(entry, "policy rule");
947
- if (Object.hasOwn(rule, "everyone") || Object.hasOwn(rule, "any_valid_service_token")) throw new Error("platform: Company Access policies must not allow Everyone or any valid service token.");
936
+ if (Object.hasOwn(rule, "everyone") || Object.hasOwn(rule, "any_valid_service_token")) throw new PlatformAccessPolicyRejectedError("platform: Company Access policies must not allow Everyone or any valid service token.");
948
937
  if (rule.group) {
949
938
  const id = text(object(rule.group, "Access group").id, "Access group ID");
950
- if (groups.has(id)) throw new Error("platform: Access groups contain a recursive membership rule.");
939
+ if (groups.has(id)) throw new PlatformAccessPolicyRejectedError("platform: Access groups contain a recursive membership rule.");
951
940
  if (checkedGroups.has(id)) continue;
952
- if (checkedGroups.size >= 100) throw new Error("platform: Access group policy exceeds the inspection limit.");
941
+ if (checkedGroups.size >= 100) throw new PlatformAccessPolicyRejectedError("platform: Access group policy exceeds the inspection limit.");
953
942
  groups.add(id);
954
943
  const group = object(await client.get(`groups/${encodeURIComponent(id)}`), "Access group");
955
944
  await checkRules(group.include, depth + 1);
956
945
  await checkRules(group.require, depth + 1);
957
- if (!Array.isArray(group.include) || !group.include.length) throw new Error("platform: Access groups must contain explicit identity rules.");
958
- if (!(Array.isArray(group.require) && group.require.some((entry) => identityRule(object(entry, "group rule")))) && !group.include.every((entry) => identityRule(object(entry, "group rule")))) throw new Error("platform: Access groups must restrict company identities.");
946
+ if (!Array.isArray(group.include) || !group.include.length) throw new PlatformAccessPolicyRejectedError("platform: Access groups must contain explicit identity rules.");
947
+ if (!(Array.isArray(group.require) && group.require.some((entry) => identityRule(entry, "group rule"))) && !group.include.every((entry) => identityRule(entry, "group rule"))) throw new PlatformAccessPolicyRejectedError("platform: Access groups must restrict company identities.");
959
948
  groups.delete(id);
960
949
  checkedGroups.add(id);
961
950
  }
@@ -968,15 +957,15 @@ async function verifyCompanyAccessPolicies(client, policies) {
968
957
  "deny",
969
958
  "non_identity",
970
959
  "bypass"
971
- ].includes(String(policy.decision))) throw new Error("platform: Unsupported Access policy decision.");
972
- if (policy.decision === "bypass") throw new Error("platform: A bypass policy cannot protect a Void platform.");
960
+ ].includes(String(policy.decision))) throw new PlatformAccessPolicyRejectedError("platform: Unsupported Access policy decision.");
961
+ if (policy.decision === "bypass") throw new PlatformAccessPolicyRejectedError("platform: A bypass policy cannot protect a Void platform.");
973
962
  if (policy.decision !== "allow") continue;
974
963
  allows++;
975
964
  await checkRules(policy.include);
976
965
  await checkRules(policy.require);
977
- if (!Array.isArray(policy.include) || !policy.include.length || !(Array.isArray(policy.require) && policy.require.some((entry) => identityRule(object(entry, "policy rule")))) && !policy.include.every((entry) => identityRule(object(entry, "policy rule")))) throw new Error("platform: Select Access allow policies restricted to company identities.");
966
+ if (!restricted(policy)) throw new PlatformAccessPolicyRejectedError(restricted(policy, companyProviders) ? "platform: An Access allow policy uses an identity provider that is not one of the selected identity providers." : "platform: Select Access allow policies restricted to company identities.");
978
967
  }
979
- if (!allows) throw new Error("platform: Select at least one company Access allow policy.");
968
+ if (!allows) throw new PlatformAccessPolicyRejectedError("platform: Select at least one company Access allow policy.");
980
969
  }
981
970
  function platformAccessPublicDestinations(application) {
982
971
  const destinations = Array.isArray(application.destinations) ? application.destinations : [];
@@ -1001,6 +990,19 @@ function platformAccessCreateRejected(error) {
1001
990
  429
1002
991
  ].includes(error.status);
1003
992
  }
993
+ /** Read reusable policies by ID and check that together they admit only company identities. */
994
+ async function verifySelectedAccessPolicies(client, policyIds, identityProviderIds, identityProviders) {
995
+ const policies = await Promise.all(policyIds.map(async (id) => {
996
+ try {
997
+ return object(await client.get(`policies/${encodeURIComponent(id)}`), "Access policy");
998
+ } catch (error) {
999
+ if (error instanceof PlatformCloudflareApiError && error.status === 404) throw new PlatformAccessPolicyRejectedError(`platform: The selected Access policy ${encodeURIComponent(id)} is unavailable in this account.`, { cause: error });
1000
+ throw error;
1001
+ }
1002
+ }));
1003
+ await verifyCompanyAccessPolicies(client, policies, identityProviderIds, identityProviders);
1004
+ return policies;
1005
+ }
1004
1006
  async function inspectPlatformAccess(client, input) {
1005
1007
  const authDomain = text(object(await client.get("organizations"), "Access organization").auth_domain, "Access team domain");
1006
1008
  const teamDomain = authDomain.startsWith("https://") ? authDomain : `https://${authDomain}`;
@@ -1011,15 +1013,14 @@ async function inspectPlatformAccess(client, input) {
1011
1013
  audience: "validation"
1012
1014
  }]
1013
1015
  });
1014
- const identityProviders = (await client.list("identity_providers")).map(platformAccessIdentityProvider);
1015
- 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.");
1016
- 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.");
1017
- const policies = await Promise.all(input.policyIds.map(async (id) => object(await client.get(`policies/${encodeURIComponent(id)}`), "Access policy")));
1018
- await verifyCompanyAccessPolicies(client, policies);
1016
+ const providers = await client.list("identity_providers");
1017
+ const identityProviders = providers.map(platformAccessIdentityProvider);
1018
+ 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 PlatformAccessPolicyRejectedError("platform: Select explicit identity providers and company policies for Access.");
1019
+ if (input.identityProviderIds.some((id) => !identityProviders.some((provider) => provider.id === id))) throw new PlatformAccessPolicyRejectedError("platform: A selected Access identity provider is unavailable in this account.");
1019
1020
  return {
1020
1021
  teamDomain,
1021
1022
  identityProviders,
1022
- policies
1023
+ policies: await verifySelectedAccessPolicies(client, input.policyIds, input.identityProviderIds, providers)
1023
1024
  };
1024
1025
  }
1025
1026
  async function provisionPlatformAccess(client, input, previous, save) {
@@ -1259,7 +1260,7 @@ async function provisionPlatformAccess(client, input, previous, save) {
1259
1260
  async function verifyPlatformAccessApplication(client, input, app, type, machineId) {
1260
1261
  if (app.type !== type || !sameSet(ids(app.allowed_idps), input.identityProviderIds)) throw new Error("platform: Access application identity-provider configuration does not match the reviewed plan.");
1261
1262
  const currentPolicies = await client.list(`apps/${encodeURIComponent(text(app.id, "application ID"))}/policies`);
1262
- await verifyCompanyAccessPolicies(client, currentPolicies);
1263
+ await verifyCompanyAccessPolicies(client, currentPolicies, input.identityProviderIds);
1263
1264
  if (!sameSet(currentPolicies.filter((policy) => policy.decision === "allow" || policy.decision === "deny").map((policy) => text(policy.id, "policy ID")), input.policyIds)) throw new Error("platform: Access application policies do not match the reviewed company policies.");
1264
1265
  for (const policy of currentPolicies.filter((policy) => policy.decision === "non_identity")) {
1265
1266
  const includes = Array.isArray(policy.include) ? policy.include : [];
@@ -1275,7 +1276,13 @@ function accessSetupGuide() {
1275
1276
  url: "https://void.cloud/guide/platform/installation/setup#cloudflare-access"
1276
1277
  });
1277
1278
  }
1278
- async function selectCompanyAccessPolicies(client, initialPolicies, accountId, mode) {
1279
+ /** Parse up to ten exact company email domains, or return undefined when any entry is invalid. */
1280
+ function companyEmailDomains(value) {
1281
+ const domains = [...new Set(value.split(/[\s,]+/).map((entry) => entry.trim().replace(/^@/, "").toLowerCase()).filter(Boolean))];
1282
+ return domains.length && domains.length <= 10 && domains.every(isPlatformAccessEmailDomain) ? domains : void 0;
1283
+ }
1284
+ async function selectCompanyAccessPolicies(client, initialPolicies, accountId, mode, identityProviderIds) {
1285
+ const emailDomain = "__email_domain__";
1279
1286
  const create = "__create__";
1280
1287
  const refresh = "__refresh__";
1281
1288
  let policies = initialPolicies;
@@ -1287,14 +1294,22 @@ async function selectCompanyAccessPolicies(client, initialPolicies, accountId, m
1287
1294
  }));
1288
1295
  const available = new Set(options.map((option) => option.value));
1289
1296
  selectedIds = selectedIds.filter((id) => available.has(id));
1290
- const actions = [{
1291
- value: create,
1292
- label: "Create new",
1293
- hint: "Set up a policy in Cloudflare, then return here"
1294
- }, {
1295
- value: refresh,
1296
- label: "Refresh list"
1297
- }];
1297
+ const actions = [
1298
+ ...mode === "create" ? [{
1299
+ value: emailDomain,
1300
+ label: "Allow my company’s email domain",
1301
+ hint: "Void creates this Allow policy for you"
1302
+ }] : [],
1303
+ {
1304
+ value: create,
1305
+ label: "Create in Cloudflare",
1306
+ hint: "Set up a policy in Cloudflare, then return here"
1307
+ },
1308
+ {
1309
+ value: refresh,
1310
+ label: "Refresh list"
1311
+ }
1312
+ ];
1298
1313
  let choices;
1299
1314
  if (options.length === 0) {
1300
1315
  log.info("No reusable Allow or Deny policies were found in this account. Create an Allow policy for your company, or refresh after saving one.");
@@ -1309,7 +1324,26 @@ async function selectCompanyAccessPolicies(client, initialPolicies, accountId, m
1309
1324
  required: true
1310
1325
  }));
1311
1326
  selectedIds = choices.filter((id) => available.has(id));
1312
- if (choices.includes(create)) {
1327
+ if (choices.includes(emailDomain)) {
1328
+ const domains = companyEmailDomains(platformPromptAnswer(await text$1({
1329
+ message: "Company email domains (comma-separated):",
1330
+ placeholder: "example.com",
1331
+ validate: (value) => companyEmailDomains(value ?? "") ? void 0 : "Enter up to 10 email domains, such as example.com."
1332
+ })));
1333
+ const name = `Emails ending in @${domains[0]}${domains.length > 1 ? ` and ${domains.length - 1} more` : ""}`;
1334
+ try {
1335
+ const created = await client.create("policies", {
1336
+ name,
1337
+ decision: "allow",
1338
+ include: domains.map((domain) => ({ email_domain: { domain } }))
1339
+ });
1340
+ if (typeof created?.id === "string" && created.id) selectedIds.push(created.id);
1341
+ log.info(`Created the Access policy “${name}” in Cloudflare. It stays there for reuse; you can add MFA or device rules to it later.`);
1342
+ } catch (error) {
1343
+ if (!(error instanceof PlatformCloudflareApiError)) throw error;
1344
+ log.warn(`${error.message} Choose “Create in Cloudflare” instead.`);
1345
+ }
1346
+ } else if (choices.includes(create)) {
1313
1347
  note([
1314
1348
  "1. Open Zero Trust → Access controls → Policies → Add a policy in this account.",
1315
1349
  "2. Name the policy and choose Allow.",
@@ -1325,7 +1359,13 @@ async function selectCompanyAccessPolicies(client, initialPolicies, accountId, m
1325
1359
  message: "Have you saved the new policy?",
1326
1360
  initialValue: true
1327
1361
  }))) continue;
1328
- } else if (!choices.includes(refresh)) return selectedIds;
1362
+ } else if (!choices.includes(refresh)) try {
1363
+ await verifySelectedAccessPolicies(client, selectedIds, identityProviderIds);
1364
+ return selectedIds;
1365
+ } catch (error) {
1366
+ if (!(error instanceof PlatformAccessPolicyRejectedError) && !(error instanceof PlatformCloudflareApiError && error.status === 404)) throw error;
1367
+ log.warn(`${error.message.replace(/^platform: /, "")} Choose other policies, or fix the policy in Cloudflare and refresh the list.`);
1368
+ }
1329
1369
  policies = await client.list("policies");
1330
1370
  }
1331
1371
  }
@@ -1518,7 +1558,7 @@ async function preparePlatformAccessSetup(input) {
1518
1558
  input.save({ accessSetupSpecJson: JSON.stringify(spec) });
1519
1559
  }
1520
1560
  if (!spec.policyIds && process.stdin.isTTY) log.info("Choose which existing access rules apply to this platform, including MFA and device requirements.");
1521
- const policyIds = spec.policyIds ?? (process.stdin.isTTY ? await selectCompanyAccessPolicies(client, policies, accountId, spec.mode) : []);
1561
+ const policyIds = spec.policyIds ?? (process.stdin.isTTY ? await selectCompanyAccessPolicies(client, policies, accountId, spec.mode, identityProviderIds) : []);
1522
1562
  if (!identityProviderIds.length || !policyIds.length) throw new Error("platform: Access setup requires explicit identityProviderIds and policyIds.");
1523
1563
  spec = {
1524
1564
  ...spec,
@@ -1578,6 +1618,7 @@ async function preparePlatformAccessSetup(input) {
1578
1618
  loginClientSecret = platformPromptAnswer(await password({ message: "Existing Access OIDC client secret:" }));
1579
1619
  input.save({ accessExistingLoginSecret: loginClientSecret });
1580
1620
  }
1621
+ let provisioning = previous;
1581
1622
  const result = await provisionPlatformAccess(client, {
1582
1623
  accountId,
1583
1624
  installationId: input.plan.installationId,
@@ -1593,7 +1634,18 @@ async function preparePlatformAccessSetup(input) {
1593
1634
  } } : {},
1594
1635
  ...input.protection ? { protection: { applicationId: spec.protectionApplicationId } } : {},
1595
1636
  serviceToken
1596
- }, previous, (state) => input.save({ accessProvisioningJson: JSON.stringify(state) }));
1637
+ }, previous, (state) => {
1638
+ provisioning = state;
1639
+ input.save({ accessProvisioningJson: JSON.stringify(state) });
1640
+ }).catch((error) => {
1641
+ if (!(error instanceof PlatformAccessPolicyRejectedError)) throw error;
1642
+ if (!input.spec && process.stdin.isTTY && !Object.keys(provisioning.resources).length) {
1643
+ const { identityProviderIds: _providers, policyIds: _policies, ...unpicked } = spec;
1644
+ input.save({ accessSetupSpecJson: JSON.stringify(unpicked) });
1645
+ throw new PlatformAccessPolicyRejectedError(`${error.message} Choose other policies when you run the command again, or fix the policy in Cloudflare.`, { cause: error });
1646
+ }
1647
+ throw new PlatformAccessPolicyRejectedError(`${error.message} Fix the selected policy in Cloudflare, then resume.`, { cause: error });
1648
+ });
1597
1649
  if (result.credentials) {
1598
1650
  const accessCredentialsJson = JSON.stringify(result.credentials);
1599
1651
  input.save({ accessCredentialsJson });
@@ -1894,7 +1946,7 @@ async function prepareInstallationAppAccess(input) {
1894
1946
  }
1895
1947
  if (!policyIds.length) {
1896
1948
  if (!process.stdin.isTTY) throw new Error("platform: Set appAccess.policyIds in --auth-config for the hosting account.");
1897
- policyIds = await selectCompanyAccessPolicies(access, await access.list("policies"), input.plan.account.id, "create");
1949
+ policyIds = await selectCompanyAccessPolicies(access, await access.list("policies"), input.plan.account.id, "create", identityProviderIds);
1898
1950
  input.save({ appAccessPolicyIds: JSON.stringify(policyIds) });
1899
1951
  }
1900
1952
  } else log.info("This account requires Access. Void will create public rules scoped to app hostnames.");
@@ -1961,6 +2013,37 @@ async function initializeInstalledAppAccess(input, fetcher = fetch) {
1961
2013
  }
1962
2014
  throw new Error("platform: App access setup is safely checkpointed. Resume installation to finish before app traffic opens.");
1963
2015
  }
2016
+ /**
2017
+ * Turn off the project Zero Trust the installer enabled, for an uninstall
2018
+ * before any platform user exists. Signed with the app access setup authority
2019
+ * that turned it on.
2020
+ */
2021
+ async function turnOffInstalledProjectZeroTrust(input, fetcher = fetch) {
2022
+ const body = JSON.stringify({ version: 1 });
2023
+ const signature = createHmac("sha256", input.setupToken).update(`${PLATFORM_APP_ACCESS_TURN_OFF_PROOF_PREFIX}\n${input.installationId}\n${body}`).digest("base64url");
2024
+ const response = await fetcher(new URL("/internal/platform/app-access/turn-off", input.apiUrl), {
2025
+ method: "POST",
2026
+ headers: {
2027
+ "Content-Type": "application/json",
2028
+ "X-Void-Platform-Probe": input.probeToken,
2029
+ "X-Void-App-Access-Setup": signature,
2030
+ ...cfAccessHeaders(process.env, input.apiUrl)
2031
+ },
2032
+ body,
2033
+ redirect: "manual",
2034
+ signal: AbortSignal.timeout(6e4)
2035
+ });
2036
+ let result = {};
2037
+ try {
2038
+ result = await response.json();
2039
+ } catch {}
2040
+ if (response.status === 404 || response.status === 503 && result.error === "Platform disabled" || response.status === 403 && result.error === "access_human_identity_required") throw new Error(`platform: This platform runtime cannot turn off project Zero Trust before an administrator exists. Run \`${input.updateCommand}\`, then retry the uninstall.`);
2041
+ if (result.code === "users_exist") throw new Error("platform: A platform user exists, so an administrator must turn off project Zero Trust. Run `void platform zero-trust disable`, then retry the uninstall.");
2042
+ if (result.code === "app_access_credentials") throw new Error("platform: Cloudflare rejected the saved app access token, so Void cannot turn off project Zero Trust.");
2043
+ if (!response.ok || result.installationId !== input.installationId) throw new Error(`platform: ${result.error || `Turning off project Zero Trust failed (HTTP ${response.status}).`}`);
2044
+ if (!result.turnedOff) throw new Error("platform: Project Zero Trust is still turning off. Rerun the uninstall.");
2045
+ if (result.usersExist) throw new Error("platform: A platform user signed in while project Zero Trust was turning off, so the uninstall stopped. Project Zero Trust is now off and the platform still runs. To keep the platform, an administrator can turn project Zero Trust back on in Admin → Zero Trust or with `void platform zero-trust configure`. To remove it, run the uninstall again.");
2046
+ }
1964
2047
  //#endregion
1965
2048
  //#region src/cli/platform-auth-bootstrap.ts
1966
2049
  /** Checkpoint a replacement before submission so an interrupted response is safely replayable. */
@@ -2670,7 +2753,7 @@ function addNamedPlan(context, target) {
2670
2753
  const remotelyOwned = remoteOwnership?.resources.some((resource) => resource.key === key && resource.kind === kind && resource.id === remote.id && resource.name === name && resource.ownership === "created");
2671
2754
  const markedProvision = existing !== null && hasUnrecordedProvisionIntent(existing, key, kind, name) && intentProofs.has(key);
2672
2755
  const ambiguousProvision = existing !== null && options?.authoritativeProvisionIntents === true && isLegacyUniqueProvisionAttempt(existing, key, kind, name) && hasAmbiguousProvisionIntent(existing, key, kind, remote.id, name);
2673
- if ((!recorded || recorded.id !== remote.id || recorded.kind !== kind) && !remotelyOwned && !markedProvision && !ambiguousProvision) throw new Error(`platform: ${kind} ${JSON.stringify(name)} already exists but is not owned by this installation. Resume the owning installation, or choose another installation name. Nothing was overwritten.`);
2756
+ if ((!recorded || recorded.id !== remote.id || recorded.kind !== kind) && !remotelyOwned && !markedProvision && !ambiguousProvision) throw new Error(existing !== null && hasUnrecordedProvisionIntent(existing, key, kind, name) ? `platform: ${kind} ${JSON.stringify(name)} already exists, and Void cannot tell whether this installation created it. Nothing was overwritten. If an earlier failed attempt of this installation created it, delete it in the Cloudflare dashboard, then rerun the command. Otherwise, choose another installation name.` : `platform: ${kind} ${JSON.stringify(name)} already exists but is not owned by this installation. Resume the owning installation, or choose another installation name. Nothing was overwritten.`);
2674
2757
  if (ambiguousProvision && !markedProvision && !remotelyOwned) {
2675
2758
  actions.push({
2676
2759
  action: "create",
@@ -3268,6 +3351,7 @@ function createPlatformInstallationSecrets(input) {
3268
3351
  return value;
3269
3352
  };
3270
3353
  const existingInstallation = input.existingInstallation === true;
3354
+ const generateIdentityKeys = input.generateIdentityKeys === true && !existingInstallation;
3271
3355
  const restoredIdentity = (explicit, name) => existingInstallation ? checkpoint[name] || explicit || "" : explicit || checkpoint[name] || "";
3272
3356
  const configured = (explicit, name) => explicit || checkpoint[name] || "";
3273
3357
  const authenticationSetupJson = configured(input.authenticationSetupJson ?? "", "authenticationSetupJson");
@@ -3279,7 +3363,7 @@ function createPlatformInstallationSecrets(input) {
3279
3363
  const setup = parsePlatformAuthenticationSetup(JSON.parse(authenticationSetupJson));
3280
3364
  if (!existingInstallation && createHash("sha256").update(authBootstrapCode).digest("hex") !== setup.bootstrapCredentialHash) throw new Error("platform: Valid installation setup authority and a matching administrator setup code are required.");
3281
3365
  }
3282
- const jwtSecret = restoredIdentity(input.jwtSecret || "", "jwtSecret");
3366
+ const jwtSecret = restoredIdentity(input.jwtSecret || "", "jwtSecret") || (generateIdentityKeys ? randomBytes(32).toString("base64") : "");
3283
3367
  const resolvedJwtSecret = input.existingInstallation ? jwtSecret : required(jwtSecret, "VOID_PLATFORM_JWT_SECRET");
3284
3368
  if (!existingInstallation && Buffer.byteLength(resolvedJwtSecret, "utf8") < 32) throw new Error("platform: VOID_PLATFORM_JWT_SECRET must contain at least 32 bytes of secret material unique to this installation.");
3285
3369
  const emailEnabled = input.emailEnabled === true;
@@ -3295,6 +3379,7 @@ function createPlatformInstallationSecrets(input) {
3295
3379
  const projectSecretKeyring = resolveProjectSecretKeyring({
3296
3380
  checkpoint,
3297
3381
  existingInstallation,
3382
+ generateIdentityKeys,
3298
3383
  projectSecretActiveKeyVersion: input.projectSecretActiveKeyVersion || "",
3299
3384
  projectSecretKey: input.projectSecretKey || "",
3300
3385
  projectSecretKeysJson: input.projectSecretKeysJson || "",
@@ -3356,7 +3441,7 @@ function resolveProjectSecretKeyring(input) {
3356
3441
  if (!explicitFullKeyring && (checkpointKeysJson !== "" || checkpointActiveVersion !== "") && (!checkpointKeysJson || !checkpointActiveVersion)) throw new Error("platform: Encrypted recovery material must contain both the project-secret keyring and its active version.");
3357
3442
  if (explicitFullKeyring) return normalizeProjectSecretKeyring(input.projectSecretKeysJson, input.projectSecretActiveKeyVersion, "VOID_PLATFORM_PROJECT_SECRET_KEYS_JSON", "VOID_PLATFORM_PROJECT_SECRET_ACTIVE_KEY_VERSION");
3358
3443
  if (checkpointKeysJson) return normalizeProjectSecretKeyring(checkpointKeysJson, checkpointActiveVersion, "encrypted recovery project-secret keyring", "encrypted recovery project-secret active version");
3359
- const v1Key = input.existingInstallation ? input.checkpoint.projectSecretKey || input.projectSecretKey || "" : input.required(input.projectSecretKey || input.checkpoint.projectSecretKey || "", "VOID_PLATFORM_PROJECT_SECRET_KEY");
3444
+ const v1Key = input.existingInstallation ? input.checkpoint.projectSecretKey || input.projectSecretKey || "" : input.required(input.projectSecretKey || input.checkpoint.projectSecretKey || (input.generateIdentityKeys ? randomBytes(32).toString("base64") : ""), "VOID_PLATFORM_PROJECT_SECRET_KEY");
3360
3445
  if (!v1Key) return {
3361
3446
  activeVersion: "",
3362
3447
  keysJson: "",
@@ -3559,13 +3644,15 @@ function createPlatformHealthProbeToken(ownershipNonce) {
3559
3644
  }
3560
3645
  //#endregion
3561
3646
  //#region src/cli/platform-installer-errors.ts
3647
+ const ANALYTICS_ENGINE_NOT_ENABLED = 10089;
3562
3648
  function assertCleanPlatformWrangler(result, operation) {
3563
3649
  if (result.status !== 0 || result.signal || result.error) {
3564
3650
  const diagnostic = cloudflareCommandDiagnostic({
3565
3651
  ...result,
3566
3652
  stderr: result.stderr.trim() || result.errors?.map((error) => error.message).join("\n") || ""
3567
3653
  });
3568
- throw new Error(`platform: Wrangler failed to ${operation} (${diagnostic || `exit ${result.status}`}).`);
3654
+ const analyticsEngineDisabled = result.errorCode === ANALYTICS_ENGINE_NOT_ENABLED || result.errors?.some((error) => error.code === ANALYTICS_ENGINE_NOT_ENABLED) || /\b10089\b/.test(diagnostic ?? "");
3655
+ throw new Error(`platform: Wrangler failed to ${operation} (${diagnostic || `exit ${result.status}`}).` + (analyticsEngineDisabled ? " If the Analytics Engine page offers no way to enable it, follow https://void.cloud/guide/platform/installation/prerequisites#if-cloudflare-rejects-analytics-engine, then retry." : ""));
3569
3656
  }
3570
3657
  }
3571
3658
  function assertPlatformTriggerStabilization(result, workerName, targetVersionId, operation) {
@@ -5200,7 +5287,7 @@ async function verifyInstalledPlatformAccessProtection(input) {
5200
5287
  }
5201
5288
  const existingWorkers = input.plan.actions.filter((action) => action.kind === "worker" && action.action === "update").map((action) => action.name);
5202
5289
  if (existingWorkers.length) {
5203
- const { verifyProtectionWorkerOrigins } = await import("./platform-auth-protection-D5wvsI-Y.mjs");
5290
+ const { verifyProtectionWorkerOrigins } = await import("./platform-auth-protection-BXwZiJSF.mjs");
5204
5291
  await verifyProtectionWorkerOrigins({
5205
5292
  request: input.client.request.bind(input.client),
5206
5293
  listWorkerDomains: input.client.listWorkerDomains.bind(input.client),
@@ -5544,9 +5631,10 @@ function projectZeroTrustUninstallState(setting, projectRows) {
5544
5631
  /** Read the project Zero Trust state from the platform database. */
5545
5632
  async function readPlatformProjectZeroTrustStateConnection(client) {
5546
5633
  const present = (await client.query(`SELECT to_regclass('public.platform_settings') IS NOT NULL AS "settings",
5547
- to_regclass('public.project_zero_trust') IS NOT NULL AS "projects"`)).rows[0];
5634
+ to_regclass('public.project_zero_trust') IS NOT NULL AS "projects",
5635
+ to_regclass('public.users') IS NOT NULL AS "users"`)).rows[0];
5548
5636
  if (!present) throw new Error("the database did not report its Zero Trust tables");
5549
- return projectZeroTrustUninstallState(present.settings ? (await client.query("SELECT \"value\" FROM \"platform_settings\" WHERE \"key\" = $1", [ZERO_TRUST_SETTING])).rows[0]?.value : void 0, present.projects ? (await client.query(`SELECT z."project_id", p."slug" AS "project_slug", p."status" AS "project_status",
5637
+ const state = projectZeroTrustUninstallState(present.settings ? (await client.query("SELECT \"value\" FROM \"platform_settings\" WHERE \"key\" = $1", [ZERO_TRUST_SETTING])).rows[0]?.value : void 0, present.projects ? (await client.query(`SELECT z."project_id", p."slug" AS "project_slug", p."status" AS "project_status",
5550
5638
  z."bypass_application_id", z."bypass_application_name",
5551
5639
  z."protected_application_id", z."protected_application_name"
5552
5640
  FROM "project_zero_trust" z
@@ -5556,6 +5644,12 @@ async function readPlatformProjectZeroTrustStateConnection(client) {
5556
5644
  OR z."protected_application_id" IS NOT NULL
5557
5645
  OR z."protected_application_name" IS NOT NULL
5558
5646
  ORDER BY z."project_id"`)).rows : []);
5647
+ if (!state.active) return state;
5648
+ const usersExist = present.users ? (await client.query("SELECT EXISTS(SELECT 1 FROM \"users\") AS \"present\"")).rows[0]?.present === true : false;
5649
+ return {
5650
+ ...state,
5651
+ usersExist
5652
+ };
5559
5653
  }
5560
5654
  /**
5561
5655
  * Take the Zero Trust configuration lock exclusively on this session. Every
@@ -5594,7 +5688,7 @@ async function liftPlatformProjectZeroTrustFenceConnection(client) {
5594
5688
  if (!await hasSettingsTable(client)) return;
5595
5689
  await client.query("DELETE FROM \"platform_settings\" WHERE \"key\" = $1", [UNINSTALL_SETTING]);
5596
5690
  }
5597
- function formatApplications(applications) {
5691
+ function formatZeroTrustApplications(applications) {
5598
5692
  const lines = applications.slice(0, LISTED_APPLICATIONS).map((application) => ` ${application.role}: ${application.name ?? "(name not recorded)"}${application.id ? ` (${application.id})` : ""}`);
5599
5693
  if (applications.length > LISTED_APPLICATIONS) lines.push(` …and ${applications.length - LISTED_APPLICATIONS} more`);
5600
5694
  return lines.join("\n");
@@ -5607,21 +5701,24 @@ function unfinishedDeletionLines(state) {
5607
5701
  /**
5608
5702
  * Refuse to uninstall while project Zero Trust still owns Access applications,
5609
5703
  * or while its state cannot be read. Runs before uninstall changes anything.
5704
+ * With `canTurnOff`, enabled Zero Trust on a platform no user has signed in to
5705
+ * is not refused: the applications uninstall must turn off are returned.
5610
5706
  */
5611
- async function assertProjectZeroTrustAllowsUninstall(read) {
5707
+ async function assertProjectZeroTrustAllowsUninstall(read, options = {}) {
5612
5708
  const state = await readPlatformDatabase(read, (reason) => [
5613
5709
  `platform: Refusing to uninstall because the project Zero Trust state could not be read from the platform database: ${reason}`,
5614
5710
  "Uninstall turns off the platform API, which Void needs to remove the Cloudflare Access applications that project Zero Trust created.",
5615
5711
  "Make the installation database reachable (export VOID_PLATFORM_DATABASE_URL if this machine has not recorded it), then retry. If project Zero Trust was ever enabled, run `void platform zero-trust disable` first."
5616
5712
  ].join("\n"));
5713
+ if (state.active && state.usersExist === false && options.canTurnOff) return { turnOff: state.applications };
5617
5714
  if (state.active) {
5618
5715
  const covered = state.destinations.length ? ` on ${state.destinations.join(" and ")}` : " on every hostname under the application domain";
5619
5716
  throw new Error([
5620
5717
  "platform: Refusing to uninstall while project Zero Trust is enabled or still changing.",
5621
5718
  `Uninstall turns off the platform API, so Void could no longer remove its Cloudflare Access applications, and Access would keep asking for sign-in${covered}, including hostnames Void does not serve.`,
5622
- "Run `void platform zero-trust disable`, rerun it until `void platform zero-trust status` reports Zero Trust disabled with no operation running, then retry the uninstall.",
5719
+ state.usersExist === false ? "No platform user exists yet. Run this command on the computer that installed the platform; it keeps the authority to turn off project Zero Trust." : "Run `void platform zero-trust disable`, rerun it until `void platform zero-trust status` reports Zero Trust disabled with no operation running, then retry the uninstall.",
5623
5720
  "If the platform API does not answer, bring it back with `void platform enable` or `void platform repair` first.",
5624
- ...state.applications.length ? ["Void-managed Access applications:", formatApplications(state.applications)] : [],
5721
+ ...state.applications.length ? ["Void-managed Access applications:", formatZeroTrustApplications(state.applications)] : [],
5625
5722
  ...unfinishedDeletionLines(state),
5626
5723
  DASHBOARD_HINT
5627
5724
  ].join("\n"));
@@ -5630,7 +5727,7 @@ async function assertProjectZeroTrustAllowsUninstall(read) {
5630
5727
  "platform: Refusing to uninstall while Void-managed Access applications from project Zero Trust remain.",
5631
5728
  "Zero Trust is disabled, but it could not remove the applications of some projects. Retry the uninstall once they are gone.",
5632
5729
  "Void-managed Access applications:",
5633
- formatApplications(state.applications),
5730
+ formatZeroTrustApplications(state.applications),
5634
5731
  ...state.applications.some((application) => !application.deleting) ? [`Hourly maintenance retries ${state.unfinishedDeletions.length ? "the other applications" : "them"} while the platform is enabled, and clears Void's records of any you delete in the dashboard.`] : [],
5635
5732
  ...unfinishedDeletionLines(state),
5636
5733
  DASHBOARD_HINT
@@ -5797,6 +5894,18 @@ function readPlatformProjectZeroTrustState(databaseUrl, installationId, ownershi
5797
5894
  function readPlatformProjectSlugs(databaseUrl, installationId, ownershipNonce) {
5798
5895
  return readClaimedPlatformDatabase(databaseUrl, installationId, ownershipNonce, readPlatformProjectSlugsConnection);
5799
5896
  }
5897
+ /** Read the first administrator setup row and whether any user exists. */
5898
+ async function readPlatformAdministratorSetupConnection(client) {
5899
+ const present = (await client.query(`SELECT to_regclass('public.platform_auth_bootstrap') IS NOT NULL AS "setup",
5900
+ to_regclass('public.users') IS NOT NULL AS "users"`)).rows[0];
5901
+ const setup = present?.setup ? (await client.query("SELECT \"request_hash\", \"consumed_at\" FROM \"platform_auth_bootstrap\" WHERE \"singleton\" = true")).rows[0] : void 0;
5902
+ const usersExist = present?.users ? (await client.query("SELECT EXISTS(SELECT 1 FROM \"users\") AS \"present\"")).rows[0]?.present === true : false;
5903
+ return {
5904
+ pending: !!setup && setup.consumed_at === null,
5905
+ usersExist,
5906
+ requestHash: setup?.request_hash ?? null
5907
+ };
5908
+ }
5800
5909
  /** Slugs of every project row, including projects whose deletion stopped partway. */
5801
5910
  async function readPlatformProjectSlugsConnection(client) {
5802
5911
  if ((await client.query(`SELECT to_regclass('public.projects') IS NOT NULL AS "present"`)).rows[0]?.present !== true) return [];
@@ -5927,6 +6036,7 @@ async function acquirePlatformDatabaseLifecycleLockConnection(client, installati
5927
6036
  protection: parsePlatformAccessProtection(settings.protection)
5928
6037
  } : null;
5929
6038
  };
6039
+ release.readAdministratorSetup = () => readPlatformAdministratorSetupConnection(client);
5930
6040
  release.readProjectZeroTrust = () => readPlatformProjectZeroTrustStateConnection(client);
5931
6041
  release.readProjects = () => readPlatformProjectSlugsConnection(client);
5932
6042
  release.lockProjectZeroTrust = () => lockPlatformProjectZeroTrustConnection(client);
@@ -6129,7 +6239,7 @@ async function claimPlatformDatabaseConnection(client, installationId, ownership
6129
6239
  await client.query(`INSERT INTO "__void_platform_installation"
6130
6240
  ("singleton", "installation_id", "ownership_nonce")
6131
6241
  VALUES (true, $1, $2)`, [installationId, ownershipNonce]);
6132
- } else if (claim.installation_id !== installationId || claim.ownership_nonce !== ownershipNonce) throw new Error(`platform: This PostgreSQL database is already claimed by Void platform installation ${claim.installation_id}. Use a dedicated empty database for each installation.`);
6242
+ } else if (claim.installation_id !== installationId || claim.ownership_nonce !== ownershipNonce) throw new Error(`platform: This PostgreSQL database is already claimed by Void platform installation ${claim.installation_id}. Uninstall keeps the database and its data, so use a dedicated empty database for each installation.`);
6133
6243
  await client.query("COMMIT");
6134
6244
  } catch (error) {
6135
6245
  await client.query("ROLLBACK").catch(() => ({ rows: [] }));
@@ -6723,7 +6833,7 @@ async function installPlatform(plan, secrets, existing, dependencies) {
6723
6833
  }
6724
6834
  }, dependencies.initializeAuthentication ?? initializeInstalledPlatformAuthentication)).setupPending) {
6725
6835
  setupPending = true;
6726
- dependencies.onProgress?.(`Administrator setup pending: ${state.plan.apiUrl}/setup`);
6836
+ dependencies.onProgress?.("Administrator setup prepared; its link and code are shown when this finishes");
6727
6837
  }
6728
6838
  state.manifest = recordInstallationStep(state.manifest, {
6729
6839
  key: authenticationStep,
@@ -6868,6 +6978,19 @@ async function installPlatform(plan, secrets, existing, dependencies) {
6868
6978
  function defaultConnectionId(kind) {
6869
6979
  return kind === "oidc" ? "company" : kind === "cloudflare-access" ? "access" : kind;
6870
6980
  }
6981
+ async function prompt(message, initialValue = "", masked = false) {
6982
+ if (!process.stdin.isTTY) throw new Error(`platform: ${message} Supply it through --auth-config and environment references.`);
6983
+ return masked ? platformPromptAnswer(await password({ message })) : platformPromptAnswer(await text$1({
6984
+ message,
6985
+ initialValue
6986
+ }));
6987
+ }
6988
+ function clientSecretFromEnvironment(name) {
6989
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) throw new Error("platform: Invalid clientSecretEnv reference.");
6990
+ const secret = process.env[name];
6991
+ if (!secret) throw new Error(`platform: ${name} must contain the login client secret.`);
6992
+ return secret;
6993
+ }
6871
6994
  function platformManagementAccessForAuthentication(selection, accountId, checkpoint) {
6872
6995
  if (!selection.authentication.accessProtection && (!selection.authentication.providers.includes("cloudflare-access") || selection.file && !selection.file.cloudflareAccess)) return;
6873
6996
  const spec = selection.file?.cloudflareAccess ?? (checkpoint?.accessSetupSpecJson ? parsePlatformAccessSetupSpec(JSON.parse(checkpoint.accessSetupSpecJson)) : void 0);
@@ -6944,13 +7067,6 @@ async function collectInstallationAuthentication(selection, plan, checkpoint, sa
6944
7067
  authBootstrapCode: checkpoint.authBootstrapCode ?? ""
6945
7068
  };
6946
7069
  }
6947
- const prompt = async (message, initialValue = "", masked = false) => {
6948
- if (!process.stdin.isTTY) throw new Error(`platform: ${message} Supply it through --auth-config and environment references.`);
6949
- return masked ? platformPromptAnswer(await password({ message })) : platformPromptAnswer(await text$1({
6950
- message,
6951
- initialValue
6952
- }));
6953
- };
6954
7070
  const entries = selection.file?.connections ?? selection.authentication.providers.map((kind) => ({
6955
7071
  configuration: {
6956
7072
  kind,
@@ -7000,11 +7116,8 @@ async function collectInstallationAuthentication(selection, plan, checkpoint, sa
7000
7116
  if (kind === "google" && config.allowedDomains === void 0 && !selection.file) config.allowedDomains = (await prompt("Allowed Google Workspace domains (comma-separated, or blank):")).split(",").map((value) => value.trim()).filter(Boolean);
7001
7117
  const parsed = parsePlatformLoginConfiguration(config);
7002
7118
  let secret;
7003
- if (entry.clientSecretEnv) {
7004
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(entry.clientSecretEnv)) throw new Error("platform: Invalid clientSecretEnv reference.");
7005
- secret = process.env[entry.clientSecretEnv];
7006
- if (!secret) throw new Error(`platform: ${entry.clientSecretEnv} must contain the login client secret.`);
7007
- } else if (stored?.clientSecret && stored.configuration?.clientId === parsed.clientId && stored.configuration?.issuer === parsed.issuer) secret = stored.clientSecret;
7119
+ if (entry.clientSecretEnv) secret = clientSecretFromEnvironment(entry.clientSecretEnv);
7120
+ else if (stored?.clientSecret && stored.configuration?.clientId === parsed.clientId && stored.configuration?.issuer === parsed.issuer) secret = stored.clientSecret;
7008
7121
  else secret = await prompt(`${config.label}: client secret:`, "", true);
7009
7122
  const connection = {
7010
7123
  configuration: parsed,
@@ -7109,6 +7222,87 @@ async function collectInstallationAuthentication(selection, plan, checkpoint, sa
7109
7222
  save(result);
7110
7223
  return result;
7111
7224
  }
7225
+ /** The hash the platform records for accepted login settings; a reset request does not change it. */
7226
+ function authenticationSetupRequestHash(setupJson) {
7227
+ const { resetPending: _, ...configuration } = parsePlatformAuthenticationSetup(JSON.parse(setupJson));
7228
+ return createHash("sha256").update(JSON.stringify(configuration)).digest("hex");
7229
+ }
7230
+ /**
7231
+ * Read a repair login file. It may change login settings, not methods or Access
7232
+ * protection: it lists the same connection IDs and kinds as the saved setup.
7233
+ */
7234
+ async function readLoginReplacementFile(file, authentication, setupJson) {
7235
+ const selection = await selectInstallationAuthentication(file);
7236
+ const installed = parsePlatformAuthenticationSetup(JSON.parse(setupJson)).connections;
7237
+ const connections = (entries) => entries.map(({ configuration: { id, kind } }) => JSON.stringify([id || defaultConnectionId(kind), kind])).sort().join("\n");
7238
+ if (connections(selection.file.connections) !== connections(installed) || !!selection.authentication.accessProtection !== !!authentication.accessProtection) throw new Error(`platform: Repair changes login settings, not which login methods or Access protection the platform uses. List each installed connection with its ID: ${installed.map(({ configuration }) => configuration.id).join(", ")}.`);
7239
+ return selection.file;
7240
+ }
7241
+ /**
7242
+ * New login settings for a first administrator setup that is still pending,
7243
+ * from a file or from prompts prefilled with the saved settings. A Cloudflare
7244
+ * Access login and Access protection stay as installed. The result resets
7245
+ * pending setup with a new setup code; the setup authority does not change.
7246
+ */
7247
+ async function replacePendingInstallationAuthentication(input) {
7248
+ const previous = parsePlatformAuthenticationSetup(JSON.parse(input.setupJson));
7249
+ const file = input.file ? await readLoginReplacementFile(input.file, input.authentication, input.setupJson) : void 0;
7250
+ console.log(`Login callback URL: ${formatUrl(`${input.apiUrl}/auth/callback`)}`);
7251
+ const savedConnection = (id) => previous.connections.find((connection) => connection.configuration.id === id);
7252
+ const connections = [];
7253
+ for (const entry of file?.connections ?? []) {
7254
+ const config = { ...entry.configuration };
7255
+ const kind = config.kind;
7256
+ if (!config.id) config.id = defaultConnectionId(kind);
7257
+ if (kind === "cloudflare-access") {
7258
+ const saved = savedConnection(config.id);
7259
+ if (saved?.configuration.kind !== "cloudflare-access") throw new Error("platform: Repair keeps the installed Cloudflare Access login. Use its installed connection ID.");
7260
+ connections.push(saved);
7261
+ continue;
7262
+ }
7263
+ if (!config.label) config.label = kind;
7264
+ if (kind === "oidc" && !config.issuer) config.issuer = await prompt(`${config.label}: issuer URL:`);
7265
+ if (!config.clientId) config.clientId = await prompt(`${config.label}: client ID:`);
7266
+ const configuration = parsePlatformLoginConfiguration(config);
7267
+ connections.push({
7268
+ configuration,
7269
+ clientSecret: entry.clientSecretEnv ? clientSecretFromEnvironment(entry.clientSecretEnv) : await prompt(`${configuration.label}: client secret:`, "", true)
7270
+ });
7271
+ }
7272
+ for (const saved of file ? [] : previous.connections) {
7273
+ const current = saved.configuration;
7274
+ if (current.kind === "cloudflare-access") {
7275
+ connections.push(saved);
7276
+ continue;
7277
+ }
7278
+ const issuer = current.kind === "oidc" ? await prompt(`${current.label}: issuer URL:`, current.issuer ?? "") : current.issuer;
7279
+ const clientId = await prompt(`${current.label}: client ID:`, current.clientId);
7280
+ const allowedDomains = current.kind === "google" ? (await prompt("Allowed Google Workspace domains (comma-separated, or blank):", current.allowedDomains.join(", "))).split(",").map((value) => value.trim()).filter(Boolean) : current.allowedDomains;
7281
+ connections.push({
7282
+ configuration: parsePlatformLoginConfiguration({
7283
+ ...current,
7284
+ ...issuer ? { issuer } : {},
7285
+ clientId,
7286
+ allowedDomains
7287
+ }),
7288
+ clientSecret: await prompt(`${current.label}: client secret (leave blank to keep it):`, "", true) || saved.clientSecret
7289
+ });
7290
+ }
7291
+ const ids = connections.map((connection) => connection.configuration.id);
7292
+ const authBootstrapCode = randomBytes(32).toString("hex");
7293
+ const setup = parsePlatformAuthenticationSetup({
7294
+ connections,
7295
+ administratorConnectionId: file?.administratorConnectionId ?? (ids.includes(previous.administratorConnectionId) ? previous.administratorConnectionId : ids[0]),
7296
+ admission: file?.admission ?? previous.admission,
7297
+ protection: previous.protection,
7298
+ resetPending: true,
7299
+ bootstrapCredentialHash: createHash("sha256").update(authBootstrapCode).digest("hex")
7300
+ });
7301
+ return {
7302
+ authenticationSetupJson: JSON.stringify(setup),
7303
+ authBootstrapCode
7304
+ };
7305
+ }
7112
7306
  //#endregion
7113
7307
  //#region src/cli/platform-saved-plans.ts
7114
7308
  function planId(args) {
@@ -7216,10 +7410,13 @@ function completePlatformSetupDraft(manifest) {
7216
7410
  }
7217
7411
  //#endregion
7218
7412
  //#region src/cli/platform-lifecycle.ts
7413
+ function isPlatformInstallDatabaseBound(checkpoint, manifest) {
7414
+ return Boolean(checkpoint?.databaseClaimState === "claimed" || manifest?.resources.some((resource) => resource.state === "ready" && (resource.kind === "hyperdrive" || resource.kind === "worker")) || manifest?.steps.some((step) => step.key.startsWith("database-schema:") || step.key.startsWith("database-migrations:")));
7415
+ }
7219
7416
  function resolvePlatformInstallDatabaseUrl(checkpoint, manifest = null) {
7220
7417
  const explicit = process.env.VOID_PLATFORM_DATABASE_URL || process.env.DATABASE_URL || "";
7221
7418
  const recorded = checkpoint?.databaseUrl || "";
7222
- if (checkpoint?.databaseClaimState === "claimed" || manifest?.resources.some((resource) => resource.state === "ready" && (resource.kind === "hyperdrive" || resource.kind === "worker")) || manifest?.steps.some((step) => step.key.startsWith("database-schema:") || step.key.startsWith("database-migrations:"))) {
7419
+ if (isPlatformInstallDatabaseBound(checkpoint, manifest)) {
7223
7420
  if (!recorded) {
7224
7421
  if (explicit) return explicit;
7225
7422
  throw new Error("platform: The claimed PostgreSQL database URL is missing from local recovery state. Export VOID_PLATFORM_DATABASE_URL with the original installation database before continuing.");
@@ -7231,14 +7428,14 @@ function resolvePlatformInstallDatabaseUrl(checkpoint, manifest = null) {
7231
7428
  }
7232
7429
  async function runPlatformLifecycleCommand(commandArgs) {
7233
7430
  if (commandArgs.subcommand === "domain") {
7234
- const { runPlatformDomainCommand } = await import("./platform-domain-zLXnW1G_.mjs");
7431
+ const { runPlatformDomainCommand } = await import("./platform-domain-CxpvUeo0.mjs");
7235
7432
  await runPlatformDomainCommand(commandArgs);
7236
7433
  return;
7237
7434
  }
7238
7435
  if (commandArgs.subcommand === "discover") {
7239
7436
  const client = new PlatformCloudflareClient((await resolvePlatformCloudflareCredential()).token);
7240
7437
  const accountId = await selectAccount(await readAccounts(client), commandArgs.account);
7241
- const { recoverPlatformInstallations } = await import("./platform-recovery-DeucI7A9.mjs");
7438
+ const { recoverPlatformInstallations } = await import("./platform-recovery-BUGOB4RC.mjs");
7242
7439
  const recovered = await recoverPlatformInstallations(client, accountId, void 0, commandArgs.selector);
7243
7440
  if (recovered.length === 0) {
7244
7441
  log.info(`No recoverable Void platform installations were found in ${accountId}.`);
@@ -7249,7 +7446,7 @@ async function runPlatformLifecycleCommand(commandArgs) {
7249
7446
  return;
7250
7447
  }
7251
7448
  if (commandArgs.subcommand !== "install") {
7252
- const { runInstalledPlatformCommand } = await import("./platform-management-CYrLvTGH.mjs");
7449
+ const { runInstalledPlatformCommand } = await import("./platform-management-DvBS83rC.mjs");
7253
7450
  await runInstalledPlatformCommand(commandArgs);
7254
7451
  return;
7255
7452
  }
@@ -7406,6 +7603,7 @@ async function runPlatformLifecycleCommand(commandArgs) {
7406
7603
  active.start(message);
7407
7604
  return active;
7408
7605
  };
7606
+ let usingUnboundDatabase = false;
7409
7607
  try {
7410
7608
  if (!existing && !args.plan) assertPlatformInstallationSlugAvailable(installationSlug);
7411
7609
  if (existing && !args.plan) {
@@ -7418,11 +7616,13 @@ async function runPlatformLifecycleCommand(commandArgs) {
7418
7616
  if (existing && !isPlatformSetupDraft(existing)) {
7419
7617
  if (!databaseUrl) throw new Error("platform: DATABASE_URL is required to verify an interrupted installation before resuming it.");
7420
7618
  progress = startProgress("Reading the saved installation");
7619
+ usingUnboundDatabase = !isPlatformInstallDatabaseBound(checkpoint, existing);
7421
7620
  const authoritative = await readPlatformDatabaseLifecycleManifest(databaseUrl, existing.id, existing.ownershipNonce);
7621
+ usingUnboundDatabase = false;
7422
7622
  progress?.stop("Read the saved installation.");
7423
7623
  progress = void 0;
7424
7624
  if (authoritative) {
7425
- const { selectAuthoritativePlatformManifest } = await import("./platform-management-CYrLvTGH.mjs");
7625
+ const { selectAuthoritativePlatformManifest } = await import("./platform-management-DvBS83rC.mjs");
7426
7626
  const selected = selectAuthoritativePlatformManifest(existing, authoritative);
7427
7627
  authoritativeProvisionIntents = true;
7428
7628
  if (!args.plan && JSON.stringify(selected) !== JSON.stringify(existing)) writeInstallationManifest(selected);
@@ -7750,6 +7950,8 @@ async function runPlatformLifecycleCommand(commandArgs) {
7750
7950
  r2SecretAccessKey
7751
7951
  });
7752
7952
  const hasFullProjectSecretKeyring = Boolean(checkpoint?.projectSecretKeysJson || checkpoint?.projectSecretActiveKeyVersion || process.env.VOID_PLATFORM_PROJECT_SECRET_KEYS_JSON || process.env.VOID_PLATFORM_PROJECT_SECRET_ACTIVE_KEY_VERSION);
7953
+ const generateIdentityKeys = credentialSetup && !!process.stdin.isTTY;
7954
+ const generatedKeys = generateIdentityKeys ? [configured("jwtSecret", "VOID_PLATFORM_JWT_SECRET") ? "" : "JWT signing key", configured("projectSecretKey", "VOID_PLATFORM_PROJECT_SECRET_KEY") || hasFullProjectSecretKeyring ? "" : "project encryption key"].filter(Boolean) : [];
7753
7955
  const secrets = createPlatformInstallationSecrets({
7754
7956
  configurableAuthentication: !!authenticationSelection.authentication.configurable,
7755
7957
  ...authenticationSecrets,
@@ -7766,24 +7968,35 @@ async function runPlatformLifecycleCommand(commandArgs) {
7766
7968
  r2AccessKeyId,
7767
7969
  r2SecretAccessKey,
7768
7970
  existingInstallation: !credentialSetup,
7769
- jwtSecret: await collect("jwtSecret", "VOID_PLATFORM_JWT_SECRET", "JWT signing key:", true, true, credentialSetup ? (value) => Buffer.byteLength(value, "utf8") >= 32 ? void 0 : "The JWT signing secret must contain at least 32 random bytes." : void 0),
7770
- projectSecretKey: configured("projectSecretKey", "VOID_PLATFORM_PROJECT_SECRET_KEY") || (hasFullProjectSecretKeyring ? "" : await collect("projectSecretKey", "VOID_PLATFORM_PROJECT_SECRET_KEY", "Project encryption key:", true, true, credentialSetup ? (value) => {
7971
+ generateIdentityKeys,
7972
+ jwtSecret: generateIdentityKeys ? configured("jwtSecret", "VOID_PLATFORM_JWT_SECRET") : await collect("jwtSecret", "VOID_PLATFORM_JWT_SECRET", "JWT signing key:", true, true, credentialSetup ? (value) => Buffer.byteLength(value, "utf8") >= 32 ? void 0 : "The JWT signing secret must contain at least 32 random bytes." : void 0),
7973
+ projectSecretKey: configured("projectSecretKey", "VOID_PLATFORM_PROJECT_SECRET_KEY") || (hasFullProjectSecretKeyring || generateIdentityKeys ? "" : await collect("projectSecretKey", "VOID_PLATFORM_PROJECT_SECRET_KEY", "Project encryption key:", true, true, credentialSetup ? (value) => {
7771
7974
  const key = Buffer.from(value, "base64");
7772
7975
  return key.length === 32 && key.toString("base64") === value ? void 0 : "The project encryption key must be canonical base64 encoding of exactly 32 bytes.";
7773
7976
  } : void 0)),
7774
7977
  projectSecretKeysJson: process.env.VOID_PLATFORM_PROJECT_SECRET_KEYS_JSON || "",
7775
7978
  projectSecretActiveKeyVersion: process.env.VOID_PLATFORM_PROJECT_SECRET_ACTIVE_KEY_VERSION || ""
7776
7979
  });
7980
+ if (generatedKeys.length) log.info(`Void generated the ${generatedKeys.join(" and ")} and keeps ${generatedKeys.length > 1 ? "them" : "it"} in this machine’s encrypted recovery data. ` + platformSetupLink({
7981
+ title: "About these keys",
7982
+ url: "https://void.cloud/guide/platform/installation/credentials#signing-and-encryption-keys"
7983
+ }));
7777
7984
  progress = startProgress("Installing the platform");
7778
7985
  if (credentialSetup) {
7779
7986
  saveValues({
7987
+ jwtSecret: secrets.jwtSecret,
7780
7988
  projectSecretKey: secrets.projectSecretKey,
7781
7989
  ...secrets.emailEnabled ? { emailSigningSecret: secrets.emailSigningSecret } : {}
7782
7990
  });
7783
7991
  existing = completePlatformSetupDraft(existing);
7784
7992
  }
7993
+ usingUnboundDatabase = !isPlatformInstallDatabaseBound(checkpoint, existing);
7785
7994
  const result = await installPlatform(plan, secrets, existing, {
7786
7995
  acquireDatabaseLifecycleLock: acquirePlatformDatabaseLifecycleLock,
7996
+ claimDatabase: async (...claim) => {
7997
+ await claimPlatformDatabase(...claim);
7998
+ usingUnboundDatabase = false;
7999
+ },
7787
8000
  client,
7788
8001
  operatorApiToken: managementApiToken || operatorCredential.token,
7789
8002
  runtime,
@@ -7827,7 +8040,7 @@ async function runPlatformLifecycleCommand(commandArgs) {
7827
8040
  })).actions.filter((action) => action.kind === "worker"),
7828
8041
  revalidateBeforeWorkerDeploy: (manifest, action) => revalidatePlatformInstallAction(client, plan, manifest, action),
7829
8042
  verifyExistingOwnership: async (manifest, databaseCheckpointExists) => {
7830
- const { verifyPlatformInstallationOwnership } = await import("./platform-management-CYrLvTGH.mjs");
8043
+ const { verifyPlatformInstallationOwnership } = await import("./platform-management-DvBS83rC.mjs");
7831
8044
  const retainedReplacementKeys = new Set(plan.actions.filter((action) => {
7832
8045
  const previous = manifest.resources.find((resource) => resource.key === action.key);
7833
8046
  return action.action === "create" && previous && previous.name !== action.name;
@@ -7857,7 +8070,9 @@ async function runPlatformLifecycleCommand(commandArgs) {
7857
8070
  if (result.setupPending && completedAuthCode) {
7858
8071
  log.info(`First administrator setup: ${formatUrl(`${plan.apiUrl}/setup`)}`);
7859
8072
  console.log(`One-time administrator setup code: ${completedAuthCode}`);
8073
+ log.info(`Login callback URL: ${formatUrl(`${plan.apiUrl}/auth/callback`)}`);
7860
8074
  log.info("Confirm your identity in the browser, then test and enable any additional login methods in Settings.");
8075
+ log.info(`If sign-in fails, run \`void platform repair ${result.manifest.id}\` to change the login settings.`);
7861
8076
  }
7862
8077
  if (!plan.deployment.application.zoneName) log.info("Apps will use workers.dev addresses. When your domain is ready: void platform domain set <domain>");
7863
8078
  log.info("Run `void account login`, then deploy an app with `void deploy --platform void`.");
@@ -7865,7 +8080,10 @@ async function runPlatformLifecycleCommand(commandArgs) {
7865
8080
  } catch (error) {
7866
8081
  progress?.error("Platform installation failed.");
7867
8082
  progress = void 0;
7868
- if (existing && !args.plan && !(error instanceof PlatformAccessBlockedError)) log.info(`Continue with: ${cliCommandName()} platform install --resume --name ${existing.id}`);
8083
+ if (existing && !args.plan && !(error instanceof PlatformAccessBlockedError)) {
8084
+ log.info(`Continue with: ${cliCommandName()} platform install --resume --name ${existing.id}`);
8085
+ if (usingUnboundDatabase) log.info("To use another PostgreSQL database, set VOID_PLATFORM_DATABASE_URL to its URL first.");
8086
+ }
7869
8087
  throw error;
7870
8088
  } finally {
7871
8089
  try {
@@ -7979,4 +8197,4 @@ async function promptSecret(message, masked = true, validate, placeholder) {
7979
8197
  return value;
7980
8198
  }
7981
8199
  //#endregion
7982
- export { revalidatePlatformInstallAction as A, hyperdriveOriginConfigHash as B, formatPlatformInstallPlan as C, platformAdoptionIntentHash as D, matchesRecordedR2Fingerprint as E, checkpointPlatformAccessResources as F, hyperdriveOriginMatches as H, parsePlatformAccessSetupSpec as I, preparePlatformAccessSetup as L, assertPlatformOperatorCredentialCanApplyPlan as M, resolvePlatformCloudflareCredential as N, r2BucketConfigHash as O, verifyPlatformCloudflareCredential as P, PlatformCloudflareApiError as R, dnsRecordConfigHash as S, legacyResourceConfigHash as T, verifyPlatformRuntimeTokenPermissions as U, hyperdriveOriginFromConnectionString as V, resolveBootstrapGitHubId as _, REMOTE_INSTALLATION_MANIFEST_KEY as a, recordPlatformWorkerTraffic as b, acquirePlatformDatabaseLifecycleLockConnection as c, readPlatformProjectZeroTrustState as d, assertProjectZeroTrustAllowsUninstall as f, createPlatformInstallationSecrets as g, createPlatformHealthProbeToken as h, verifyPlatformAiGatewayPlan as i, platformDatabaseNameFromOwnerUrl as j, resourceMarkerProblem as k, platformDatabaseClientUrl as l, loadPlatformRuntime as m, resolvePlatformInstallDatabaseUrl as n, installPlatform as o, readPlatformDatabase as p, runPlatformLifecycleCommand as r, acquirePlatformDatabaseLifecycleLock as s, assertNoninteractivePlatformMutationAcknowledged as t, readPlatformProjectSlugs as u, recordResource as v, hasProvisionIntentStep as w, createPlatformInstallPlan as x, platformTrafficAttachmentWorker as y, PlatformCloudflareClient as z };
8200
+ export { matchesRecordedR2Fingerprint as A, activatePlatformAccessCredentials as B, platformTrafficAttachmentWorker as C, formatPlatformInstallPlan as D, dnsRecordConfigHash as E, platformDatabaseNameFromOwnerUrl as F, PlatformCloudflareClient as G, parsePlatformAccessSetupSpec as H, turnOffInstalledProjectZeroTrust as I, hyperdriveOriginMatches as J, hyperdriveOriginConfigHash as K, assertPlatformOperatorCredentialCanApplyPlan as L, r2BucketConfigHash as M, resourceMarkerProblem as N, hasProvisionIntentStep as O, revalidatePlatformInstallAction as P, resolvePlatformCloudflareCredential as R, recordResource as S, createPlatformInstallPlan as T, preparePlatformAccessSetup as U, checkpointPlatformAccessResources as V, PlatformCloudflareApiError as W, verifyPlatformRuntimeTokenPermissions as Y, readPlatformDatabase as _, authenticationSetupRequestHash as a, createPlatformInstallationSecrets as b, REMOTE_INSTALLATION_MANIFEST_KEY as c, acquirePlatformDatabaseLifecycleLockConnection as d, platformDatabaseClientUrl as f, formatZeroTrustApplications as g, assertProjectZeroTrustAllowsUninstall as h, verifyPlatformAiGatewayPlan as i, platformAdoptionIntentHash as j, legacyResourceConfigHash as k, installPlatform as l, readPlatformProjectZeroTrustState as m, resolvePlatformInstallDatabaseUrl as n, readLoginReplacementFile as o, readPlatformProjectSlugs as p, hyperdriveOriginFromConnectionString as q, runPlatformLifecycleCommand as r, replacePendingInstallationAuthentication as s, assertNoninteractivePlatformMutationAcknowledged as t, acquirePlatformDatabaseLifecycleLock as u, loadPlatformRuntime as v, recordPlatformWorkerTraffic as w, resolveBootstrapGitHubId as x, createPlatformHealthProbeToken as y, verifyPlatformCloudflareCredential as z };