void 0.24.0 → 0.26.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.
Files changed (199) hide show
  1. package/README.md +19 -106
  2. package/dist/{account-cmd-vJhh5lrb.mjs → account-cmd-CDry3B5V.mjs} +5 -5
  3. package/dist/{application-analysis-BVsVqc11.mjs → application-analysis-DC2yPvzY.mjs} +1 -1
  4. package/dist/{application-routing-B7QEjqSR.mjs → application-routing-C9khgLiS.mjs} +1 -1
  5. package/dist/{auth-DX3JURcX.mjs → auth-B60QDbjg.mjs} +1 -1
  6. package/dist/{auth-CQkSDS50.mjs → auth-D52yG7hl.mjs} +3 -3
  7. package/dist/{auth-link-D75t9wO2.mjs → auth-link-DjeOptYi.mjs} +5 -6
  8. package/dist/{auth-router-ClfhL1J7.mjs → auth-router-BRxIuqDi.mjs} +7 -7
  9. package/dist/{build-cmd-zMi1iFnS.mjs → build-cmd-Ce3C8huo.mjs} +6 -7
  10. package/dist/{cache-ZtQmyxVV.mjs → cache-CS8Yxr5a.mjs} +6 -7
  11. package/dist/{cancel-deploy-Box0kkEV.mjs → cancel-deploy-mZsD88Xa.mjs} +4 -6
  12. package/dist/{cf-access-BGe5sWXF.mjs → cf-access-YTlqvAsk.mjs} +1 -1
  13. package/dist/cf-build-output-ChY6-XWP.mjs +2 -0
  14. package/dist/cli/cli.mjs +45 -45
  15. package/dist/cli/cloudflare-operation-process.mjs +3 -3
  16. package/dist/cli/env-schema-probe.d.mts +1 -1
  17. package/dist/cli/env-schema-probe.mjs +2 -2
  18. package/dist/client-BxVixJBI.mjs +2 -0
  19. package/dist/{client-DKAsFA4X.mjs → client-mC-dFGDD.mjs} +103 -25
  20. package/dist/{cloudflare-cmd-CmY8dGkd.mjs → cloudflare-cmd-bhwCxAN6.mjs} +2 -2
  21. package/dist/{cloudflare-connect-IiR3ZieC.mjs → cloudflare-connect-CuhRgk3k.mjs} +2 -2
  22. package/dist/{cloudflare-operations-DzH-A966.mjs → cloudflare-operations-DYa3coIk.mjs} +2 -2
  23. package/dist/{cloudflare-operations-BCdXSsmo.mjs → cloudflare-operations-DoQrVHAt.mjs} +1 -1
  24. package/dist/{connect-B-usTnlF.mjs → connect-H6EvP62X.mjs} +12 -9
  25. package/dist/{create-project-CYOOAYcn.mjs → create-project-B2kk9Q0o.mjs} +6 -6
  26. package/dist/{create-project-FWbf5Olo.mjs → create-project-Dbb4uiN-.mjs} +1 -1
  27. package/dist/{db-Ck2eZ2iB.mjs → db-DpCKZto0.mjs} +35 -38
  28. package/dist/{delete-sK56qtzZ.mjs → delete-CxlSvsvW.mjs} +6 -7
  29. package/dist/{deploy-gCVaiJbS.mjs → deploy-CeEbnv4b.mjs} +9249 -8925
  30. package/dist/deploy-qmiLE1G1.mjs +2 -0
  31. package/dist/{dev-inbox-P0u4tM8Y.mjs → dev-inbox-D64ZKUod.mjs} +5 -6
  32. package/dist/{domain-DFfQE8jg.mjs → domain-B_9VgOm6.mjs} +8 -8
  33. package/dist/{email-CdnhUHvJ.mjs → email-BokHgjKj.mjs} +7 -7
  34. package/dist/{env-DmGdHi5i.mjs → env-BdTc-K2L.mjs} +7 -8
  35. package/dist/{env-public-BX_r8HR6.d.mts → env-public-DR7ZM6d-.d.mts} +1 -1
  36. package/dist/{env-validation-zZOCcQVZ.mjs → env-validation-cZBvGWaP.mjs} +1 -1
  37. package/dist/{fetch-stream-IvCYKyQL.mjs → fetch-stream-Nm3hkZbT.mjs} +8 -1
  38. package/dist/{gen-CshAkukR.mjs → gen-BhDlTpwW.mjs} +2 -4
  39. package/dist/gen-qCBwH3fZ.mjs +2 -0
  40. package/dist/{github-cmd-DftuEvIt.mjs → github-cmd-hhkTzlGS.mjs} +7 -16
  41. package/dist/{handler-CZ4nAylQ.d.mts → handler-BeK3weD4.d.mts} +1 -1
  42. package/dist/{help-Dotbf6iz.mjs → help-Bv44e2tm.mjs} +13 -9
  43. package/dist/help-Dw5f9Xrg.mjs +2 -0
  44. package/dist/index.d.mts +19 -15
  45. package/dist/index.mjs +2225 -2142
  46. package/dist/{info-B97bTX9N.mjs → info-DhtTKh65.mjs} +2 -4
  47. package/dist/{init-BTQEZZGJ.mjs → init-BMf4T03q.mjs} +17 -18
  48. package/dist/{limits-Bq5LG8Id.d.mts → limits-CFsDKQvU.d.mts} +1 -10
  49. package/dist/{limits-Cjuk2VPm.mjs → limits-D0vD1M9A.mjs} +1 -22
  50. package/dist/{link-B46sYH0K.mjs → link-BqYsSS_I.mjs} +6 -7
  51. package/dist/{list-BnPrIsU_.mjs → list-_kBToO-l.mjs} +10 -10
  52. package/dist/{live-Chw1eIMv.d.mts → live-iAuEH1hd.d.mts} +1 -1
  53. package/dist/{local-d1-2CMnpuW_.mjs → local-d1-fGxYrVD6.mjs} +2 -2
  54. package/dist/login-D7meQg_3.mjs +2 -0
  55. package/dist/{login-BuiMyUrx.mjs → login-DFuo-RWO.mjs} +9 -7
  56. package/dist/{logs-CZG9d-ic.mjs → logs-DYgnh6dH.mjs} +7 -8
  57. package/dist/migrate-CEu2X5DB.mjs +2 -0
  58. package/dist/{migrate-q4_njtaW.mjs → migrate-CV1on-yJ.mjs} +2 -4
  59. package/dist/{node-BkyRWRx8.mjs → node-C6Q6e1Es.mjs} +1 -1
  60. package/dist/{operator-args-B3BTmSoi.mjs → operator-args-DdY54Bgb.mjs} +1 -1
  61. package/dist/{operator-auth-7erYc6Lt.mjs → operator-auth-2dsGlTT5.mjs} +4 -8
  62. package/dist/{operator-client-CpHjTd0t.mjs → operator-client-BFqpp6z0.mjs} +3 -3
  63. package/dist/{operator-cmd-DGqPJUD3.mjs → operator-cmd-CUGHXrQf.mjs} +27 -9
  64. package/dist/operator-cmd-DRPvhpjM.mjs +2 -0
  65. package/dist/{output-CbM85T4k.mjs → output-BvdaehtE.mjs} +28 -4
  66. package/dist/pages/client.d.mts +4 -1
  67. package/dist/pages/client.mjs +120 -29
  68. package/dist/pages/index.mjs +1 -1
  69. package/dist/pages/islands-plugin.mjs +1 -1
  70. package/dist/pages/protocol.d.mts +2 -2
  71. package/dist/{plan-NPpwZ_kc.mjs → plan-CJGEeTEI.mjs} +2 -2
  72. package/dist/plan-COtUvdMA.mjs +2 -0
  73. package/dist/{platform-args-3k8X9-H_.mjs → platform-args-DSV2p4nY.mjs} +4 -2
  74. package/dist/platform-args-Dcy_5b_Y.mjs +2 -0
  75. package/dist/{platform-auth-config-DDrxUy4m.mjs → platform-auth-config-Bosx7U1h.mjs} +20 -19
  76. package/dist/{platform-auth-protection-BLadnnct.mjs → platform-auth-protection-DcbL_LW_.mjs} +8 -8
  77. package/dist/{platform-auth-recovery-CmCzX2Fl.mjs → platform-auth-recovery-WfTZ8K6o.mjs} +7 -7
  78. package/dist/{platform-cmd-DMDEc9uS.mjs → platform-cmd-DbKtQb_4.mjs} +73 -38
  79. package/dist/{platform-cmd-pMTRhvYs.mjs → platform-cmd-dyFUof0t.mjs} +1 -1
  80. package/dist/platform-discovery-BC0HCKYt.mjs +41 -0
  81. package/dist/{platform-domain-Di6EYrQl.mjs → platform-domain-Bsq43AJp.mjs} +5 -4
  82. package/dist/{platform-lifecycle-BFdSi9Qt.mjs → platform-lifecycle--5gWmDoI.mjs} +1 -1
  83. package/dist/{platform-lifecycle-BBoNzojY.mjs → platform-lifecycle-Cfi0o6xM.mjs} +4605 -3527
  84. package/dist/platform-management-Bbwf6-Dq.mjs +2 -0
  85. package/dist/{platform-management-CWOQOWXm.mjs → platform-management-BrF9STZp.mjs} +211 -31
  86. package/dist/{platform-plans-config-Vl9BP3H4.mjs → platform-plans-config-DU-8nC2Y.mjs} +3 -3
  87. package/dist/{platform-recovery-BJnQJEtQ.mjs → platform-recovery-DQAqAQYu.mjs} +3 -3
  88. package/dist/{platform-registry-DkPPXfp_.mjs → platform-registry-P387v42A.mjs} +18 -11
  89. package/dist/prepare-BBknoZI1.mjs +2 -0
  90. package/dist/{prepare-CBLT3CHH.mjs → prepare-BMrRWLwP.mjs} +3 -4
  91. package/dist/{prepare-DnaKBi--.mjs → prepare-WoUubFKL.mjs} +1 -1
  92. package/dist/{project-DsblYifa.mjs → project-BiaPQ7ur.mjs} +1 -1
  93. package/dist/{project-DKG5Ra8p.mjs → project-DQstYTxO.mjs} +1 -1
  94. package/dist/{project-cmd-DxnXstAq.mjs → project-cmd-BbH2LptN.mjs} +21 -22
  95. package/dist/{project-team-BmHcaALJ.mjs → project-team-Yiww3-go.mjs} +8 -9
  96. package/dist/{project-token-GionZlBI.mjs → project-token-BrtT2NkY.mjs} +6 -9
  97. package/dist/{project-zero-trust-JLCjVQA7.mjs → project-zero-trust-nYJ2V2nc.mjs} +5 -6
  98. package/dist/{prompts-BNUoTiaf.mjs → prompts-6OSVA7DJ.mjs} +58 -15
  99. package/dist/{prompts-2TGE6oXx.mjs → prompts-ZAuJlyCj.mjs} +1 -1
  100. package/dist/{protocol-CjF_iI9X.d.mts → protocol-BX8t2jiw.d.mts} +1 -1
  101. package/dist/{provision-CVku1JFU.mjs → provision-DMTPG8Rh.mjs} +1 -1
  102. package/dist/{provision-CaoxO_U7.mjs → provision-VeqAC77A.mjs} +18 -8
  103. package/dist/{requests-BOjIVAeF.mjs → requests-TIz_jtSu.mjs} +5 -6
  104. package/dist/{rollback-CwR3pLj3.mjs → rollback-dBqICPzj.mjs} +9 -10
  105. package/dist/{runner-Ol0TNjk6.mjs → runner--2KF6Kua.mjs} +1 -1
  106. package/dist/{runner-CXA9Fh8h.mjs → runner-voIltZrY.mjs} +1 -1
  107. package/dist/runtime/ai.d.mts +10 -11
  108. package/dist/runtime/ai.mjs +13 -18
  109. package/dist/runtime/client-react.mjs +1 -1
  110. package/dist/runtime/client-solid.mjs +1 -1
  111. package/dist/runtime/client-svelte.mjs +1 -1
  112. package/dist/runtime/client-vue.mjs +1 -1
  113. package/dist/runtime/client.mjs +1 -1
  114. package/dist/runtime/email/testing.mjs +1 -1
  115. package/dist/runtime/email.mjs +35 -17
  116. package/dist/runtime/env-public.d.mts +1 -1
  117. package/dist/runtime/fetch-stream.mjs +1 -1
  118. package/dist/runtime/handler.d.mts +1 -1
  119. package/dist/runtime/limits.d.mts +2 -2
  120. package/dist/runtime/limits.mjs +2 -2
  121. package/dist/runtime/live-client.d.mts +1 -1
  122. package/dist/runtime/live.d.mts +1 -1
  123. package/dist/runtime/migration-handler.mjs +33 -7
  124. package/dist/runtime/routing.d.mts +1 -1
  125. package/dist/runtime/routing.mjs +1 -1
  126. package/dist/runtime/sandbox.d.mts +1 -1
  127. package/dist/runtime/sandbox.mjs +12 -22
  128. package/dist/runtime/validator.d.mts +1 -1
  129. package/dist/runtime/ws-server.d.mts +1 -1
  130. package/dist/runtime/ws.d.mts +1 -1
  131. package/dist/{sandbox-XZAqzFlG.d.mts → sandbox-C5LFoJMf.d.mts} +7 -7
  132. package/dist/{scan-C7okrLyM.mjs → scan-C8dA7iM4.mjs} +10 -6
  133. package/dist/{secret-ByWJWBUG.mjs → secret-AKnNXNP5.mjs} +15 -16
  134. package/dist/{skills-C1PlclPt.mjs → skills-BPQUGClu.mjs} +1 -1
  135. package/dist/{subcommand-prompt-klFCfcor.mjs → subcommand-prompt-B6WS9Aoz.mjs} +2 -2
  136. package/dist/sveltekit.d.mts +1 -1
  137. package/dist/sveltekit.mjs +4 -9
  138. package/dist/validate-pna7R3Pv.mjs +2 -0
  139. package/dist/{validate-ctOrgiS3.mjs → validate-ymhap6M_.mjs} +1 -1
  140. package/dist/{wrangler-7K-bW_DL.mjs → wrangler-DhUerFkW.mjs} +10 -15
  141. package/dist/{ws-CL1w7GXU.d.mts → ws-C79j4aED.d.mts} +1 -1
  142. package/package.json +21 -21
  143. package/sandbox.Dockerfile +1 -1
  144. package/skills/void/SKILL.md +5 -2
  145. package/skills/void/docs/guide/ai.md +78 -137
  146. package/skills/void/docs/guide/app-types.md +1 -1
  147. package/skills/void/docs/guide/auth.md +0 -2
  148. package/skills/void/docs/guide/database/d1.md +7 -7
  149. package/skills/void/docs/guide/database.md +15 -0
  150. package/skills/void/docs/guide/deployment.md +58 -124
  151. package/skills/void/docs/guide/env-vars.md +1 -5
  152. package/skills/void/docs/guide/index.md +19 -18
  153. package/skills/void/docs/guide/kv.md +0 -2
  154. package/skills/void/docs/guide/live.md +2 -0
  155. package/skills/void/docs/guide/pages-routing/actions-and-forms.md +49 -47
  156. package/skills/void/docs/guide/pages-routing/overview.md +2 -2
  157. package/skills/void/docs/guide/platform/administration/email.md +0 -2
  158. package/skills/void/docs/guide/platform/administration/operations.md +11 -22
  159. package/skills/void/docs/guide/platform/administration/plans.md +1 -5
  160. package/skills/void/docs/guide/platform/administration/zero-trust.md +1 -4
  161. package/skills/void/docs/guide/platform/development/schema-ci.md +7 -1
  162. package/skills/void/docs/guide/platform/installation/credentials.md +2 -2
  163. package/skills/void/docs/guide/platform/installation/first-deployment.md +5 -1
  164. package/skills/void/docs/guide/platform/installation/maintenance.md +33 -11
  165. package/skills/void/docs/guide/platform/installation/prerequisites.md +79 -7
  166. package/skills/void/docs/guide/platform/installation/setup.md +1 -1
  167. package/skills/void/docs/guide/platform/installation/uninstall.md +44 -11
  168. package/skills/void/docs/guide/quickstart.md +37 -80
  169. package/skills/void/docs/guide/sandboxes.md +75 -66
  170. package/skills/void/docs/guide/self-hosted-platform.md +20 -0
  171. package/skills/void/docs/guide/websockets.md +0 -2
  172. package/skills/void/docs/integrations/frameworks/overview.md +2 -2
  173. package/skills/void/docs/integrations/frameworks/sveltekit.md +40 -25
  174. package/skills/void/docs/integrations/nodejs-bun-deno.md +7 -2
  175. package/skills/void/docs/reference/api/resources.md +38 -14
  176. package/skills/void/docs/reference/api.md +35 -34
  177. package/skills/void/docs/reference/cli/auth.md +3 -1
  178. package/skills/void/docs/reference/cli/deploy.md +1 -1
  179. package/skills/void/docs/reference/cli/platform-config.md +2 -2
  180. package/skills/void/docs/reference/cli/platform-installation.md +14 -12
  181. package/skills/void/docs/reference/cli/platform-operations.md +4 -8
  182. package/skills/void/docs/reference/cli/platform.md +9 -2
  183. package/skills/void/docs/reference/cli.md +1 -1
  184. package/skills/void/docs/reference/resource-inference.md +12 -8
  185. package/dist/cf-build-output-_0HNWysu.mjs +0 -2
  186. package/dist/client-B9UBI03V.mjs +0 -2
  187. package/dist/deploy-irP5q0-x.mjs +0 -2
  188. package/dist/gen-DvQTWzDU.mjs +0 -2
  189. package/dist/help-mabuIV98.mjs +0 -2
  190. package/dist/login-DIy9Sb-U.mjs +0 -2
  191. package/dist/migrate-BCiJ8w0f.mjs +0 -2
  192. package/dist/plan-D1Q5rf-r.mjs +0 -2
  193. package/dist/platform-args-DdnM6WIf.mjs +0 -2
  194. package/dist/platform-management-DG47jys8.mjs +0 -2
  195. package/dist/prepare-CnjcTV2s.mjs +0 -2
  196. package/dist/validate-Dq_L3s0S.mjs +0 -2
  197. package/skills/void/docs/guide/env-migration.md +0 -109
  198. package/dist/{cf-build-output-BPqOT964.mjs → cf-build-output-U8o1Gl6X.mjs} +1 -1
  199. package/dist/{env-validation-C_8tPdhs.mjs → env-validation-vBSQ8gw5.mjs} +1 -1
@@ -0,0 +1,2 @@
1
+ import { c as runInstalledPlatformCommand, d as verifyPlatformInstallationOwnership, l as selectAuthoritativePlatformManifest } from "./platform-management-BrF9STZp.mjs";
2
+ export { runInstalledPlatformCommand, selectAuthoritativePlatformManifest, verifyPlatformInstallationOwnership };
@@ -1,9 +1,9 @@
1
- import { a as listInstallationManifests, f as readInstallationRecoverySecrets, g as resolveInstallationManifest, h as removePlatformConnectionForInstallation, m as removeInstallationRecoverySecrets, t as acquirePlatformLifecycleLock, y as writeInstallationManifest } from "./platform-registry-DkPPXfp_.mjs";
2
- import { A as confirm, B as note, H as outro, K as select, L as log, tt as isCancel } from "./prompts-BNUoTiaf.mjs";
3
- import { i as createSpinner } from "./output-CbM85T4k.mjs";
4
- import { A as PlatformCloudflareClient, C as revalidatePlatformInstallAction, E as resolvePlatformCloudflareCredential, M as hyperdriveOriginFromConnectionString, N as hyperdriveOriginMatches, P as verifyPlatformRuntimeTokenPermissions, S as platformProvisionIntentHash, T as assertPlatformOperatorCredentialCanApplyPlan, a as REMOTE_INSTALLATION_MANIFEST_KEY, b as formatPlatformInstallPlan, d as loadPlatformRuntime, g as assertProjectZeroTrustAllowsUninstall, h as resolveBootstrapGitHubId, j as hyperdriveOriginConfigHash, l as createPlatformInstallationSecrets, n as resolvePlatformInstallDatabaseUrl, o as acquirePlatformDatabaseLifecycleLock, p as readPlatformProjectZeroTrustState, t as assertNoninteractivePlatformMutationAcknowledged, u as installPlatform, v as trackedPlatformWorkerTraffic, w as platformDatabaseNameFromOwnerUrl, x as platformAdoptionIntentHash, y as createPlatformInstallPlan } from "./platform-lifecycle-BBoNzojY.mjs";
5
- import { createHash } from "node:crypto";
1
+ import { _ as removePlatformConnectionForInstallation, g as removeInstallationRecoverySecrets, m as readInstallationRecoverySecrets, o as listInstallationManifests, t as acquirePlatformLifecycleLock, v as resolveInstallationManifest, x as writeInstallationManifest } from "./platform-registry-P387v42A.mjs";
2
+ import { A as confirm, H as outro, K as select, L as log, tt as isCancel } from "./prompts-6OSVA7DJ.mjs";
3
+ import { c as import_picocolors, l as note, r as createSpinner } from "./output-BvdaehtE.mjs";
4
+ import { A as resolveBootstrapGitHubId, B as assertPlatformOperatorCredentialCanApplyPlan, C as purgeResourcesKeptReason, D as loadPlatformRuntime, E as runPurgeResourcesTeardown, F as formatPlatformInstallPlan, H as verifyPlatformCloudflareCredential, I as platformAdoptionIntentHash, J as hyperdriveOriginFromConnectionString, K as PlatformCloudflareClient, L as platformProvisionIntentHash, N as trackedPlatformWorkerTraffic, P as createPlatformInstallPlan, R as revalidatePlatformInstallAction, S as purgeResourcesHandlesEmailRouting, T as purgeResourcesPermissions, V as resolvePlatformCloudflareCredential, X as verifyPlatformRuntimeTokenPermissions, Y as hyperdriveOriginMatches, _ as describePurgeResourcesPlan, a as REMOTE_INSTALLATION_MANIFEST_KEY, b as isPurgedAccessResource, d as readPlatformProjectZeroTrustState, f as assertProjectZeroTrustAllowsUninstall, g as checkPurgeResourcesCredentials, h as assertNoPlatformProjects, k as createPlatformInstallationSecrets, m as PlatformResourceChangedError, n as resolvePlatformInstallDatabaseUrl, o as installPlatform, q as hyperdriveOriginConfigHash, s as acquirePlatformDatabaseLifecycleLock, t as assertNoninteractivePlatformMutationAcknowledged, u as readPlatformProjectSlugs, v as isPlatformTailWorker, w as purgeResourcesKeptResources, x as purgeResourcesAlwaysKept, y as isPurgeResourcesCandidate, z as platformDatabaseNameFromOwnerUrl } from "./platform-lifecycle-Cfi0o6xM.mjs";
6
5
  import { PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE, PLATFORM_R2_OWNERSHIP_MARKER_KEY, createPlatformResourceOwnershipMarker, parsePlatformInstallationManifest, parsePlatformResourceOwnershipMarker, recordInstallationResource, recordInstallationStep, setInstallationDesiredState, transitionInstallation } from "@void/platform";
6
+ import { createHash } from "node:crypto";
7
7
  //#region src/cli/platform-management.ts
8
8
  async function verifyPlatformInstallationOwnership(client, manifest, options = {}) {
9
9
  const ready = manifest.resources.filter((resource) => resource.state === "ready" && isOwnershipVerifiableKind(resource.kind) && !options.retainedReplacementKeys?.has(resource.key));
@@ -23,6 +23,22 @@ function selectAuthoritativePlatformManifest(local, authoritative) {
23
23
  if (authoritative.id !== local.id || authoritative.ownershipNonce !== local.ownershipNonce || authoritative.account.id !== local.account.id) throw new Error("platform: The authoritative lifecycle state belongs to another installation.");
24
24
  return authoritative;
25
25
  }
26
+ function commandArgument(value) {
27
+ return /^[a-zA-Z0-9_./:@-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
28
+ }
29
+ function platformLifecycleResumeCommand(args, installationId) {
30
+ const command = [
31
+ "void",
32
+ "platform",
33
+ args.subcommand,
34
+ commandArgument(installationId)
35
+ ];
36
+ if (args.runtime) command.push("--runtime", commandArgument(args.runtime));
37
+ if (args.subcommand === "rollback" && args.fromRuntime) command.push("--from-runtime", commandArgument(args.fromRuntime));
38
+ if ("dashboardUrl" in args && args.dashboardUrl) command.push("--dashboard-url", commandArgument(args.dashboardUrl));
39
+ if (args.yes) command.push("--yes");
40
+ return command.join(" ");
41
+ }
26
42
  async function runInstalledPlatformCommand(args) {
27
43
  if (args.subcommand !== "upgrade" && args.subcommand !== "rollback" && args.subcommand !== "repair" && args.subcommand !== "disable" && args.subcommand !== "enable" && args.subcommand !== "uninstall") throw new Error(`platform: Unsupported lifecycle command ${args.subcommand}.`);
28
44
  assertNoninteractivePlatformMutationAcknowledged(args, process.stdin.isTTY === true);
@@ -37,6 +53,13 @@ async function runInstalledPlatformCommand(args) {
37
53
  const manifest = await resolveInstalledPlatformManifest(args.selector);
38
54
  const operatorCredential = await resolvePlatformCloudflareCredential({ savedManagementToken: readInstallationRecoverySecrets(manifest)?.managementApiToken });
39
55
  const operatorClient = new PlatformCloudflareClient(operatorCredential.token);
56
+ await verifyPlatformCloudflareCredential(operatorClient, {
57
+ account: manifest.account,
58
+ source: operatorCredential.source,
59
+ r2Buckets: args.subcommand === "disable" || args.subcommand === "uninstall" ? [] : manifest.resources.filter((resource) => resource.kind === "r2-bucket").map((resource) => resource.name),
60
+ allowMissingR2Buckets: args.subcommand === "repair"
61
+ });
62
+ markPhase("operator credential");
40
63
  const runtime = args.subcommand === "uninstall" || args.subcommand === "disable" ? null : loadPlatformRuntime(args.runtime);
41
64
  const rollbackSourceRuntime = args.subcommand === "rollback" ? loadPlatformRuntime(args.fromRuntime) : null;
42
65
  let progress;
@@ -62,7 +85,12 @@ async function runInstalledPlatformCommand(args) {
62
85
  readProjectZeroTrust: databaseLock ? databaseLock.readProjectZeroTrust : () => readPlatformProjectZeroTrustState(databaseUrl, manifest.id, manifest.ownershipNonce),
63
86
  lockProjectZeroTrust: databaseLock?.lockProjectZeroTrust,
64
87
  fenceProjectZeroTrust: databaseLock?.fenceProjectZeroTrust,
65
- liftProjectZeroTrustFence: databaseLock?.liftProjectZeroTrustFence
88
+ liftProjectZeroTrustFence: databaseLock?.liftProjectZeroTrustFence,
89
+ readProjects: databaseLock ? databaseLock.readProjects : () => readPlatformProjectSlugs(databaseUrl, manifest.id, manifest.ownershipNonce),
90
+ credential: {
91
+ source: operatorCredential.source,
92
+ aiGatewayToken: !!gatewayToken
93
+ }
66
94
  });
67
95
  return;
68
96
  }
@@ -94,20 +122,17 @@ async function runInstalledPlatformCommand(args) {
94
122
  markPhase("plan");
95
123
  note(formatPlatformInstallPlan(plan), args.subcommand === "upgrade" ? "Upgrade plan" : args.subcommand === "rollback" ? "Rollback plan" : args.subcommand === "enable" ? "Enable plan" : "Repair plan");
96
124
  note([
97
- `Version: ${runtime.manifest.platformVersion}`,
125
+ `Version: ${manifest.platformVersion} → ${runtime.manifest.platformVersion}`,
98
126
  `Source: ${runtime.source}`,
99
127
  `Revision: ${runtime.manifest.sourceRevision ?? "not recorded"}`,
100
128
  `Manifest SHA-256: ${runtime.manifestSha256}`
101
129
  ].join("\n"), "Platform runtime");
130
+ log.info(manifest.desiredState === "disabled" && args.subcommand !== "enable" ? "Application traffic will stay paused because this installation is disabled." : "Application traffic may pause briefly during a safe cutover.");
102
131
  if (args.plan) {
103
132
  outro("No Cloudflare or local installation state was changed.");
104
133
  return;
105
134
  }
106
135
  assertPlatformOperatorCredentialCanApplyPlan(operatorCredential, plan);
107
- progress = createSpinner({ onCancel: () => {
108
- if (!process.emit("SIGINT")) process.exit(130);
109
- } });
110
- progress.start(args.subcommand === "upgrade" ? "Upgrading the platform" : args.subcommand === "rollback" ? "Rolling back the platform" : args.subcommand === "enable" ? "Enabling the platform" : "Repairing the platform");
111
136
  const repairingMissingRoutingCheckpoint = args.subcommand === "repair" && plan.actions.some((action) => action.key === "routing-kv" && action.action === "create" && action.id === null);
112
137
  await verifyPlatformMutationOwnership(client, manifest, new Set(manifest.resources.filter((resource) => isOwnershipVerifiableKind(resource.kind) && !plan.actions.some((action) => action.key === resource.key && action.action === "create" && action.name !== resource.name)).map((resource) => resource.key)), {
113
138
  allowMissingRoutingCheckpoint: repairingMissingRoutingCheckpoint,
@@ -116,7 +141,6 @@ async function runInstalledPlatformCommand(args) {
116
141
  });
117
142
  markPhase("ownership");
118
143
  const installation = args.subcommand === "enable" ? setInstallationDesiredState(manifest, "enabled", (/* @__PURE__ */ new Date()).toISOString()) : manifest;
119
- if (installation !== manifest) writeInstallationManifest(installation);
120
144
  const bootstrapGitHubLogin = process.env.VOID_PLATFORM_ADMIN_GITHUB_LOGIN || recovery?.bootstrapGitHubLogin || "";
121
145
  const bootstrapGitHubId = process.env.VOID_PLATFORM_ADMIN_GITHUB_ID || recovery?.bootstrapGitHubId || (bootstrapGitHubLogin ? await resolveBootstrapGitHubId(bootstrapGitHubLogin) : "");
122
146
  const secrets = createPlatformInstallationSecrets({
@@ -148,6 +172,11 @@ async function runInstalledPlatformCommand(args) {
148
172
  });
149
173
  markPhase("runtime token");
150
174
  if (!databaseLock?.readAuthenticationState) throw new Error("platform: The database lifecycle lock cannot read authoritative authentication settings.");
175
+ progress = createSpinner({ onCancel: () => {
176
+ if (!process.emit("SIGINT")) process.exit(130);
177
+ } });
178
+ progress.start(args.subcommand === "upgrade" ? "Upgrading the platform" : args.subcommand === "rollback" ? "Rolling back the platform" : args.subcommand === "enable" ? "Enabling the platform" : "Repairing the platform");
179
+ if (installation !== manifest) writeInstallationManifest(installation);
151
180
  const result = await installPlatform(plan, secrets, installation, {
152
181
  acquireDatabaseLifecycleLock: async () => Object.assign(async () => {}, {
153
182
  checkpoint: databaseLock?.checkpoint,
@@ -159,12 +188,14 @@ async function runInstalledPlatformCommand(args) {
159
188
  verifyDatabase: databaseLock?.verifyDatabase
160
189
  }),
161
190
  client,
191
+ operatorApiToken: operatorCredential.token,
162
192
  databaseRollbackSource: rollbackSourceRuntime ?? void 0,
163
193
  onProgress: (message) => {
164
194
  markPhase(message);
165
195
  progress?.message(message);
166
196
  },
167
197
  runtime,
198
+ operation: args.subcommand,
168
199
  replanWorkers: async (recoveryManifest) => (await createPlatformInstallPlan(client, planInput, recoveryManifest, {
169
200
  authoritativeProvisionIntents: true,
170
201
  repairMissing: true,
@@ -183,14 +214,22 @@ async function runInstalledPlatformCommand(args) {
183
214
  progress?.stop(result.waitingForDns || result.waitingForSandboxDrain ? "Platform operation paused; progress is saved." : `Platform ${args.subcommand} complete.`);
184
215
  progress = void 0;
185
216
  if (result.waitingForDns) {
186
- note(`Wait for ${deployment.application.zoneName} to become active, then rerun this command.`, "Waiting for nameservers");
217
+ note(`Wait for ${deployment.application.zoneName} to become active, then resume:\n${platformLifecycleResumeCommand(args, manifest.id)}`, "Waiting for nameservers");
187
218
  return;
188
219
  }
189
220
  if (result.waitingForSandboxDrain) {
190
- 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 lifecycle command afterward to enable traffic.", "Waiting for managed Sandbox drain");
221
+ note([
222
+ "Application traffic is paused. Saved progress is ready to resume.",
223
+ "1. Review remaining Sandbox work: void platform system sandbox-drain --plan",
224
+ "2. Apply: void platform system sandbox-drain --yes (follow nextCursor with --cursor '<value>' until complete: true).",
225
+ `3. Resume: ${platformLifecycleResumeCommand(args, manifest.id)}`,
226
+ `Administrator status: ${plan.apiUrl}/admin/status`
227
+ ].join("\n"), "Waiting for managed Sandbox drain");
191
228
  return;
192
229
  }
193
- log.success(args.subcommand === "rollback" ? `${deployment.displayName} is running compatible runtime ${runtime.manifest.platformVersion}; PostgreSQL remains at schema ${rollbackSourceRuntime.manifest.database.schemaDigest}.` : `${deployment.displayName} is healthy at ${plan.apiUrl}.`);
230
+ log.success(`${deployment.displayName}: runtime ${runtime.manifest.platformVersion} ${args.subcommand === "rollback" ? "restored" : "ready"}.`);
231
+ log.info(result.manifest.desiredState === "disabled" ? "Application traffic remains paused. Enable the installation when you are ready." : `Platform verification passed. Traffic is enabled at ${plan.apiUrl}.`);
232
+ if (args.subcommand === "rollback") log.info("PostgreSQL migrations were retained; rollback changes the runtime only.");
194
233
  if (result.emailOperationId) log.info(`Shared email routing ready (operation ${result.emailOperationId}).`);
195
234
  if (result.setupPending) {
196
235
  const code = readInstallationRecoverySecrets(result.manifest)?.authBootstrapCode;
@@ -226,8 +265,15 @@ async function runInstalledPlatformCommand(args) {
226
265
  reportResult?.();
227
266
  markPhase("complete");
228
267
  } catch (error) {
268
+ const operationStarted = progress !== void 0;
229
269
  progress?.error(`Platform ${args.subcommand} failed.`);
230
270
  progress = void 0;
271
+ if (operationStarted && args.subcommand !== "uninstall" && args.subcommand !== "disable") note([
272
+ "Traffic may remain paused. Inspect the saved state before continuing:",
273
+ `void platform status ${commandArgument(manifest.id)}`,
274
+ "After resolving the error, resume with the same runtime:",
275
+ platformLifecycleResumeCommand(args, manifest.id)
276
+ ].join("\n"), "Recovery");
231
277
  throw error;
232
278
  } finally {
233
279
  try {
@@ -276,7 +322,7 @@ async function disablePlatform(client, initialManifest, planOnly, dependencies =
276
322
  const reconciled = await recoverDurablyMarkedProvisionIntents(client, initialManifest);
277
323
  const writeLifecycleCheckpoint = (checkpointClient, checkpointManifest) => writeLifecycleCheckpointWithoutDatabase(checkpointClient, checkpointManifest, dependencies.checkpoint);
278
324
  const writeRemoteManifestIfPresent = (checkpointClient, checkpointManifest) => writeRemoteManifestIfPresentWithoutDatabase(checkpointClient, checkpointManifest, dependencies.checkpoint);
279
- note("Block platform traffic through installation-owned routing storage. Retain routes and custom domains for explicit dashboard cleanup because Cloudflare cannot condition their deletion on an immutable generation. Workers and all data remain installed.", "Disable plan");
325
+ note("Block all platform traffic and the platform API. Routes, custom domains, Workers, and data stay installed.", "Disable plan");
280
326
  if (planOnly) {
281
327
  outro("No Cloudflare or local installation state was changed.");
282
328
  return;
@@ -321,14 +367,33 @@ async function uninstallPlatform(client, initialManifest, args, dependencies = {
321
367
  const writeLifecycleCheckpoint = (checkpointClient, checkpointManifest) => writeLifecycleCheckpointWithoutDatabase(checkpointClient, checkpointManifest, dependencies.checkpoint);
322
368
  const writeRemoteManifestIfPresent = (checkpointClient, checkpointManifest) => writeRemoteManifestIfPresentWithoutDatabase(checkpointClient, checkpointManifest, dependencies.checkpoint);
323
369
  const retainData = !args.purgeData;
324
- const hasRoutingCheckpoint = manifest.resources.some((resource) => resource.key === "routing-kv" && resource.state === "ready");
370
+ const purgeResources = args.purgeResources;
371
+ const routingKv = manifest.resources.find((resource) => resource.key === "routing-kv");
372
+ const reopenedRouting = purgeResources && routingKv?.state === "retained" && isPurgeResourcesCandidate(manifest, routingKv);
373
+ const hasRoutingCheckpoint = routingKv?.state === "ready" || reopenedRouting;
325
374
  const createdZone = manifest.resources.find((resource) => resource.key === "zone" && resource.ownership === "created");
326
375
  const interactive = dependencies.interactive ?? process.stdin.isTTY;
327
376
  const confirmAction = dependencies.confirm ?? promptForConfirmation;
328
377
  if (createdZone && args.deleteZone === true) throw new Error(`platform: Refusing to delete Cloudflare zone ${JSON.stringify(createdZone.name)} automatically. A zone can contain unrelated DNS, settings, and products that the installer cannot exhaustively prove ownership of. Uninstall with --keep-zone, inspect the retained zone in Cloudflare, and delete it manually if appropriate.`);
329
378
  await assertProjectZeroTrustAllowsUninstall(dependencies.readProjectZeroTrust);
330
- const dataMessage = retainData ? "Retain installer-owned Workers, storage, dispatch, AI logs, and Hyperdrive by default." : args.purgeData ? "Delete safely identifiable installer-owned data (--purge-data); retain Workers, R2, AI Gateway, and external PostgreSQL for manual cleanup." : "Retain installer-owned data; external PostgreSQL is never deleted.";
331
- note(`Disable all platform traffic through installation-owned routing storage.\nRetain DNS records, routes, and custom domains for explicit dashboard cleanup.\nDelete installer-owned Queues unless a retained Worker still uses them.\n${dataMessage}\n${createdZone ? `Keep zone ${createdZone.name} for explicit manual cleanup.` : "Never delete the adopted application zone."}${(manifest.authenticationResources ?? []).length ? "\nRetain Access resources for deliberate cleanup:\n" + manifest.authenticationResources.map((resource) => ` ${resource.ownership} ${resource.kind}: ${resource.name} (${resource.id}, account ${resource.accountId})`).join("\n") : ""}`, "Uninstall plan");
379
+ if (purgeResources) await assertNoPlatformProjects(dependencies.readProjects);
380
+ const purgeResourcesClient = purgeResources ? requirePurgeResourcesClient(client) : null;
381
+ if (args.plan || interactive && !args.yes) note(purgeResources ? purgeResourcesPlanText(manifest) : [
382
+ "Disable platform traffic.",
383
+ retainData ? "Keep Workers and data." : "Delete eligible data; keep Workers, R2, and AI Gateway.",
384
+ "Delete unused Queues.",
385
+ ...describeKeptResources(manifest, plannedKeptResources(manifest, retainData, hasRoutingCheckpoint), "Keep for manual cleanup:", "Keep Access resources:")
386
+ ].join("\n"), import_picocolors.default.bold("Uninstall plan"));
387
+ if (purgeResourcesClient) {
388
+ const credentialCheck = checkPurgeResourcesCredentials(purgeResourcesClient, manifest, dependencies.credential ?? {
389
+ source: "environment",
390
+ aiGatewayToken: false
391
+ });
392
+ if (args.plan) await credentialCheck.catch((error) => {
393
+ log.warn(error instanceof Error ? error.message : String(error));
394
+ });
395
+ else await credentialCheck;
396
+ }
332
397
  if (args.plan) {
333
398
  outro("No Cloudflare or local installation state was changed.");
334
399
  return;
@@ -336,20 +401,29 @@ async function uninstallPlatform(client, initialManifest, args, dependencies = {
336
401
  if (manifest !== initialManifest) writeInstallationManifest(manifest);
337
402
  if (!interactive && !args.yes) throw new Error("platform: Non-interactive `void platform uninstall` requires --yes to acknowledge Cloudflare changes. Run with --plan first to inspect them.");
338
403
  if (interactive && !args.yes) {
339
- if (!await confirmAction(args.purgeData ? "Permanently purge installer-owned platform data?" : "Uninstall this platform?", false)) {
404
+ if (!await confirmAction(purgeResources ? "Permanently delete every Cloudflare resource this installation created?" : args.purgeData ? "Permanently purge installer-owned platform data?" : "Uninstall this platform?", false)) {
340
405
  outro("No changes made.");
341
406
  return;
342
407
  }
343
408
  }
344
- const resourcesToDelete = new Set(manifest.resources.filter((resource) => shouldDeleteResource(resource, retainData) && (hasRoutingCheckpoint || resource.kind === "zone") && !(resource.kind === "dns-record" && !manifest.resources.some((entry) => entry.key === "zone"))).map((resource) => resource.key));
409
+ log.step(`Uninstalling ${manifest.deployment.displayName}...`);
410
+ const purgeResourcesKept = new Set(purgeResources ? purgeResourcesKeptResources(manifest).map((resource) => resource.key) : []);
411
+ const resourcesToDelete = new Set(manifest.resources.filter((resource) => (shouldDeleteResource(resource, retainData) || purgeResources && (resource.state === "ready" || isPurgeResourcesCandidate(manifest, resource)) && !purgeResourcesKept.has(resource.key) && isOwnershipVerifiableKind(resource.kind) && resource.kind !== "dns-record" && !hasLifecycleStep(manifest, `uninstall:purge-resources:r2-marker:${resource.key}`)) && (hasRoutingCheckpoint || resource.kind === "zone") && !(resource.kind === "dns-record" && !manifest.resources.some((entry) => entry.key === "zone"))).map((resource) => resource.key));
345
412
  const ownershipTargets = /* @__PURE__ */ new Set([
346
413
  ...hasRoutingCheckpoint ? ["routing-kv"] : [],
347
414
  "zone",
348
415
  ...manifest.resources.filter((resource) => resource.kind === "worker").map((resource) => resource.key),
349
416
  ...resourcesToDelete
350
417
  ]);
418
+ const proofManifest = {
419
+ ...manifest,
420
+ resources: manifest.resources.map((resource) => resource.state === "retained" && resourcesToDelete.has(resource.key) ? {
421
+ ...resource,
422
+ state: "ready"
423
+ } : resource)
424
+ };
351
425
  let missingResources;
352
- if (hasRoutingCheckpoint) missingResources = await verifyPlatformMutationOwnership(client, manifest, ownershipTargets, {
426
+ if (hasRoutingCheckpoint) missingResources = await verifyPlatformMutationOwnership(client, proofManifest, ownershipTargets, {
353
427
  allowDeletedRoutingCheckpoint: args.purgeData,
354
428
  databaseUrl: dependencies.databaseUrl
355
429
  });
@@ -371,6 +445,7 @@ async function uninstallPlatform(client, initialManifest, args, dependencies = {
371
445
  let next = manifest;
372
446
  try {
373
447
  await assertProjectZeroTrustAllowsUninstall(dependencies.readProjectZeroTrust);
448
+ if (purgeResources) await assertNoPlatformProjects(dependencies.readProjects);
374
449
  fenced = true;
375
450
  await fenceProjectZeroTrust();
376
451
  next = transitionInstallation(manifest, "uninstalling", (/* @__PURE__ */ new Date()).toISOString());
@@ -381,7 +456,7 @@ async function uninstallPlatform(client, initialManifest, args, dependencies = {
381
456
  }
382
457
  try {
383
458
  await writeRemoteManifestIfPresent(client, next);
384
- const routing = next.resources.find((resource) => resource.key === "routing-kv" && resource.state === "ready");
459
+ const routing = hasRoutingCheckpoint ? requireResource(next, "routing-kv") : void 0;
385
460
  disablingTraffic = true;
386
461
  if (routing) await client.putKvValue(next.account.id, routing.id, PLATFORM_DISABLED_KEY, PLATFORM_DISABLED_VALUE);
387
462
  await releaseProjectZeroTrust();
@@ -405,6 +480,23 @@ async function uninstallPlatform(client, initialManifest, args, dependencies = {
405
480
  await writeLifecycleCheckpoint(client, next);
406
481
  }
407
482
  }
483
+ if (purgeResourcesClient && hasRoutingCheckpoint) next = await runPurgeResourcesTeardown({
484
+ client: purgeResourcesClient,
485
+ checkpoint: async (checkpointManifest) => {
486
+ next = checkpointManifest;
487
+ await writeLifecycleCheckpoint(client, checkpointManifest);
488
+ },
489
+ verify: (checkpointManifest, keys) => verifyPlatformMutationOwnership(client, checkpointManifest, keys, {
490
+ allowDeletedRoutingCheckpoint: true,
491
+ databaseUrl: dependencies.databaseUrl
492
+ }),
493
+ bucketGeneration: async (checkpointManifest, bucket) => {
494
+ const live = (await client.listR2Buckets(checkpointManifest.account.id)).find((entry) => entry.id === bucket.id && entry.name === bucket.name);
495
+ if (!live) return null;
496
+ return live.fingerprint === void 0 ? "unknown" : r2BucketConfigHash(bucket.name, live.fingerprint);
497
+ },
498
+ readProjects: () => dependencies.readProjects()
499
+ }, next);
408
500
  const deletionPriority = {
409
501
  worker: 0,
410
502
  queue: 1,
@@ -418,7 +510,11 @@ async function uninstallPlatform(client, initialManifest, args, dependencies = {
418
510
  };
419
511
  const resources = [...next.resources].sort((left, right) => resourceDeletionPriority(left, deletionPriority) - resourceDeletionPriority(right, deletionPriority));
420
512
  const queuesInUse = hasRoutingCheckpoint && resources.some((resource) => resource.kind === "queue" && shouldDeleteResource(resource, retainData)) && await hasLiveInstallationWorker(client, next);
421
- for (const resource of resources) {
513
+ for (const listed of resources) {
514
+ const resource = reopenedRouting && listed.key === "routing-kv" ? {
515
+ ...listed,
516
+ state: "ready"
517
+ } : listed;
422
518
  if (resource.state !== "ready") continue;
423
519
  if (!hasRoutingCheckpoint && resource.kind !== "zone") {
424
520
  next = updateResourceState(next, resource.key, "retained");
@@ -426,7 +522,8 @@ async function uninstallPlatform(client, initialManifest, args, dependencies = {
426
522
  continue;
427
523
  }
428
524
  if (!shouldDeleteResource(resource, retainData)) {
429
- next = updateResourceState(next, resource.key, "retained");
525
+ const removed = resource.kind === "ai-gateway" && resource.ownership === "created" && !hasNamedResource(await client.listAiGateways(next.account.id), resource);
526
+ next = updateResourceState(next, resource.key, removed ? "deleted" : "retained");
430
527
  await writeLifecycleCheckpoint(client, next);
431
528
  continue;
432
529
  }
@@ -443,7 +540,7 @@ async function uninstallPlatform(client, initialManifest, args, dependencies = {
443
540
  await writeLifecycleCheckpoint(client, next);
444
541
  continue;
445
542
  }
446
- await client.deleteResource(next.account.id, resource.kind, resource.id, zone?.id);
543
+ await client.deleteResource(next.account.id, resource, zone?.id);
447
544
  next = updateResourceState(next, resource.key, "deleted");
448
545
  await writeLifecycleCheckpoint(client, next);
449
546
  }
@@ -453,7 +550,9 @@ async function uninstallPlatform(client, initialManifest, args, dependencies = {
453
550
  writeInstallationManifest(completed);
454
551
  if (!retainData) removeInstallationRecoverySecrets(next);
455
552
  removePlatformConnectionForInstallation(next.id);
456
- log.success(retainData ? `${next.deployment.displayName} was uninstalled; data resources were retained and recorded in ${next.id}.` : `${next.deployment.displayName} was uninstalled.`);
553
+ const kept = describeKeptResources(next, next.resources.filter((resource) => resource.state === "retained"), "Cloudflare resources:", "Access resources:", purgeResources && purgeResourcesHandlesEmailRouting(next));
554
+ if (kept.length) note(kept.join("\n"), import_picocolors.default.bold("Kept resources"));
555
+ log.success(`Uninstalled ${next.deployment.displayName}.`);
457
556
  } catch (error) {
458
557
  await stopProjectZeroTrustHold().catch(() => {});
459
558
  next = transitionInstallation(next, "failed", (/* @__PURE__ */ new Date()).toISOString());
@@ -531,6 +630,87 @@ const DATA_RESOURCE_KINDS = /* @__PURE__ */ new Set([
531
630
  function isOwnershipVerifiableKind(kind) {
532
631
  return kind === "ai-gateway" || kind === "dispatch-namespace" || kind === "dns-record" || kind === "hyperdrive" || kind === "kv-namespace" || kind === "queue" || kind === "r2-bucket" || kind === "worker" || kind === "zone";
533
632
  }
633
+ const KEPT_RESOURCE_ORDER = [
634
+ "dns-record",
635
+ "worker-route",
636
+ "worker-custom-domain",
637
+ "queue",
638
+ "worker",
639
+ "dispatch-namespace",
640
+ "kv-namespace",
641
+ "hyperdrive",
642
+ "r2-bucket",
643
+ "ai-gateway",
644
+ "analytics-dataset",
645
+ "zone"
646
+ ];
647
+ /** Resources an uninstall with these options leaves in the account. */
648
+ function plannedKeptResources(manifest, retainData, hasRoutingCheckpoint) {
649
+ const workersRemain = manifest.resources.some((resource) => resource.kind === "worker" && resource.state === "ready");
650
+ return manifest.resources.filter((resource) => resource.state === "retained" || resource.state === "ready" && (!hasRoutingCheckpoint && resource.kind !== "zone" || !shouldDeleteResource(resource, retainData) || resource.kind === "queue" && workersRemain));
651
+ }
652
+ function keptResourceRank(manifest, resource) {
653
+ return isPlatformTailWorker(manifest, resource) ? KEPT_RESOURCE_ORDER.indexOf("dispatch-namespace") + .5 : KEPT_RESOURCE_ORDER.indexOf(resource.kind);
654
+ }
655
+ function purgeResourcesPlanText(manifest) {
656
+ const permissions = purgeResourcesPermissions(manifest);
657
+ const keepManifest = {
658
+ ...manifest,
659
+ authenticationResources: (manifest.authenticationResources ?? []).filter((resource) => resource.ownership !== "created" || purgeResourcesKeptReason(manifest, `access:${resource.key}`) !== null)
660
+ };
661
+ return [
662
+ "Disable platform traffic.",
663
+ ...describePurgeResourcesPlan(manifest),
664
+ ...describeKeptResources(keepManifest, purgeResourcesKeptResources(manifest), "Keep for manual cleanup:", "Keep Access resources you connected:", purgeResourcesHandlesEmailRouting(manifest)),
665
+ "Always kept:",
666
+ ...purgeResourcesAlwaysKept(),
667
+ ...permissions.length ? [`API token needs: ${permissions.join(", ")}. Browser login cannot delete DNS records or Access resources.`] : []
668
+ ].join("\n");
669
+ }
670
+ function requirePurgeResourcesClient(client) {
671
+ if ([
672
+ "deleteAccessResource",
673
+ "deleteDispatchNamespaceScript",
674
+ "deleteEmailRoutingRule",
675
+ "deleteQueueConsumer",
676
+ "deleteR2Object",
677
+ "deleteWorkerDomain",
678
+ "deleteWorkerRoute",
679
+ "emptyR2Bucket",
680
+ "forceDeleteWorker",
681
+ "getAccessResource",
682
+ "getEmailRoutingCatchAll",
683
+ "listDispatchNamespaceScripts",
684
+ "listEmailRoutingRules",
685
+ "listQueueConsumers",
686
+ "listWorkerDependents",
687
+ "listWorkerRoutes",
688
+ "resetEmailRoutingCatchAll"
689
+ ].some((method) => typeof client[method] !== "function")) throw new Error("platform: This Cloudflare client cannot run --purge-resources.");
690
+ return client;
691
+ }
692
+ function describeKeptResources(manifest, resources, heading, accessHeading, emailRulesRemoved = false) {
693
+ const sorted = [...resources].sort((left, right) => keptResourceRank(manifest, left) - keptResourceRank(manifest, right));
694
+ const isTraffic = (resource) => KEPT_RESOURCE_ORDER.indexOf(resource.kind) < KEPT_RESOURCE_ORDER.indexOf("queue");
695
+ const why = (key) => {
696
+ const reason = purgeResourcesKeptReason(manifest, key);
697
+ return reason === null ? "" : `, kept because ${reason}`;
698
+ };
699
+ const line = (resource) => ` ${resource.kind}: ${resource.name}${resource.name === resource.id ? "" : ` (${resource.id})`}${why(resource.key)}`;
700
+ const { capabilities, emailSenderDomain, emailSharedZoneId, resources: names } = manifest.deployment;
701
+ const email = capabilities.email ? [emailRulesRemoved && emailSenderDomain ? ` Email Routing for ${emailSenderDomain} (zone ${emailSharedZoneId}): its settings and the sender domain's MX, SPF, and DKIM records.` : emailSenderDomain ? ` Email Routing for ${emailSenderDomain} (zone ${emailSharedZoneId}): its routing DNS records and any catch-all rule that sends mail to ${names.emailGatewayWorker}.` : ` Email Routing: any catch-all rule that sends mail to ${names.emailGatewayWorker}.`] : [];
702
+ const remainingAccess = (manifest.authenticationResources ?? []).filter((resource) => !isPurgedAccessResource(manifest, resource));
703
+ const accessLine = (resource) => ` ${resource.ownership} ${resource.kind}: ${resource.name} (${resource.id}, account ${resource.accountId})${why(`access:${resource.key}`)}`;
704
+ const keptAccess = (resource) => purgeResourcesKeptReason(manifest, `access:${resource.key}`) !== null;
705
+ const kept = [
706
+ ...sorted.filter(isTraffic).map(line),
707
+ ...remainingAccess.filter(keptAccess).map(accessLine),
708
+ ...email,
709
+ ...sorted.filter((resource) => !isTraffic(resource)).map(line)
710
+ ];
711
+ const access = remainingAccess.filter((resource) => !keptAccess(resource)).map(accessLine);
712
+ return [...kept.length ? [heading, ...kept] : [], ...access.length ? [accessHeading, ...access] : []];
713
+ }
534
714
  function shouldDeleteResource(resource, retainData) {
535
715
  if (resource.state !== "ready" || resource.ownership !== "created") return false;
536
716
  if (resource.kind === "zone") return false;
@@ -604,7 +784,7 @@ async function verifyPlatformMutationOwnership(client, manifest, targetKeys, opt
604
784
  const zone = manifest.resources.find((resource) => resource.key === "zone");
605
785
  const [aiGateways, dispatchNamespaces, hyperdrives, kvNamespaces, r2Buckets, workerNames] = await Promise.all([
606
786
  client.listAiGateways(accountId),
607
- client.listDispatchNamespaces(accountId),
787
+ targetResources.some((resource) => resource.kind === "dispatch-namespace") ? client.listDispatchNamespaces(accountId) : Promise.resolve([]),
608
788
  client.listHyperdriveConfigs(accountId),
609
789
  client.listKvNamespaces(accountId),
610
790
  client.listR2Buckets(accountId),
@@ -649,7 +829,7 @@ async function verifyPlatformMutationOwnership(client, manifest, targetKeys, opt
649
829
  name: resource.name
650
830
  })).digest("hex");
651
831
  const expectedHash = dnsRecordConfigHash(resource.name, "AAAA", "100::", true);
652
- if (liveHash !== resource.configHash && !(resource.configHash === legacyHash && liveHash === expectedHash)) throw ownershipError(`dns-record ${resource.name} changed configuration after installation`);
832
+ if (liveHash !== resource.configHash && !(resource.configHash === legacyHash && liveHash === expectedHash)) throw new PlatformResourceChangedError(resource.key, "its configuration changed after installation", ownershipError(`dns-record ${resource.name} changed configuration after installation`).message);
653
833
  }
654
834
  }
655
835
  } else if (resource.kind === "worker") {
@@ -688,7 +868,7 @@ function isPlatformManifestMonotonicExtension(earlier, later) {
688
868
  if (earlier.id !== later.id || earlier.ownershipNonce !== later.ownershipNonce || earlier.account.id !== later.account.id || earlier.createdAt !== later.createdAt || JSON.stringify(earlier.deployment) !== JSON.stringify(later.deployment) || Date.parse(later.updatedAt) < Date.parse(earlier.updatedAt)) return false;
689
869
  const resourcesAdvance = earlier.resources.every((resource) => {
690
870
  const next = later.resources.find((entry) => entry.key === resource.key);
691
- return next?.id === resource.id && next.kind === resource.kind && next.name === resource.name && next.ownership === resource.ownership && (next.state === resource.state || resource.state === "ready" && (next.state === "deleted" || next.state === "retained"));
871
+ return next?.id === resource.id && next.kind === resource.kind && next.name === resource.name && next.ownership === resource.ownership && (next.state === resource.state || resource.state === "ready" && (next.state === "deleted" || next.state === "retained") || resource.state === "retained" && next.state === "deleted");
692
872
  });
693
873
  const stepsAdvance = earlier.steps.every((step) => later.steps.some((entry) => entry.key === step.key && entry.completedAt === step.completedAt && entry.resultHash === step.resultHash));
694
874
  const authenticationResourcesAdvance = (earlier.authenticationResources ?? []).every((resource) => (later.authenticationResources ?? []).some((next) => JSON.stringify(next) === JSON.stringify(resource)));
@@ -791,4 +971,4 @@ async function promptForConfirmation(message, initialValue) {
791
971
  return answer;
792
972
  }
793
973
  //#endregion
794
- export { resolveInstalledPlatformManifest as a, selectAuthoritativePlatformManifest as c, recoverDurablyMarkedProvisionIntents as i, uninstallPlatform as l, disablePlatform as n, resolvePlatformRuntimeTokenToPreflight as o, isPlatformManifestMonotonicExtension as r, runInstalledPlatformCommand as s, assertPlatformRollbackCompatibility as t, verifyPlatformInstallationOwnership as u };
974
+ export { recoverDurablyMarkedProvisionIntents as a, runInstalledPlatformCommand as c, verifyPlatformInstallationOwnership as d, platformLifecycleResumeCommand as i, selectAuthoritativePlatformManifest as l, disablePlatform as n, resolveInstalledPlatformManifest as o, isPlatformManifestMonotonicExtension as r, resolvePlatformRuntimeTokenToPreflight as s, assertPlatformRollbackCompatibility as t, uninstallPlatform as u };
@@ -1,6 +1,6 @@
1
- import { A as confirm, K as select, X as text, tt as isCancel } from "./prompts-BNUoTiaf.mjs";
2
- import { n as OperatorClient, r as OperatorClientError } from "./operator-client-CpHjTd0t.mjs";
3
- import { a as resolveOperatorOrigin, r as readOperatorToken } from "./operator-auth-7erYc6Lt.mjs";
1
+ import { A as confirm, K as select, X as text, tt as isCancel } from "./prompts-6OSVA7DJ.mjs";
2
+ import { n as OperatorClient, r as OperatorClientError } from "./operator-client-BFqpp6z0.mjs";
3
+ import { a as resolveOperatorOrigin, r as readOperatorToken } from "./operator-auth-2dsGlTT5.mjs";
4
4
  import { stripVTControlCharacters } from "node:util";
5
5
  import { PLAN_EDITABLE_LIMIT_KEYS, PLAN_LIMITS, parseBuildInstanceType, parsePlanLimitsPatch, parsePlanName, parsePlatformPlanId } from "@void/platform/plans";
6
6
  import { readFile } from "node:fs/promises";
@@ -1,7 +1,7 @@
1
+ import { o as listInstallationManifests, t as acquirePlatformLifecycleLock, x as writeInstallationManifest, y as savePlatformConnection } from "./platform-registry-P387v42A.mjs";
1
2
  import { t as cfAccessHeaders } from "./cf-access-TWNzXltp.mjs";
2
- import { _ as savePlatformConnection, a as listInstallationManifests, t as acquirePlatformLifecycleLock, y as writeInstallationManifest } from "./platform-registry-DkPPXfp_.mjs";
3
- import { a as REMOTE_INSTALLATION_MANIFEST_KEY, y as createPlatformInstallPlan } from "./platform-lifecycle-BBoNzojY.mjs";
4
- import { i as recoverDurablyMarkedProvisionIntents, r as isPlatformManifestMonotonicExtension, u as verifyPlatformInstallationOwnership } from "./platform-management-CWOQOWXm.mjs";
3
+ import { P as createPlatformInstallPlan, a as REMOTE_INSTALLATION_MANIFEST_KEY } from "./platform-lifecycle-Cfi0o6xM.mjs";
4
+ import { a as recoverDurablyMarkedProvisionIntents, d as verifyPlatformInstallationOwnership, r as isPlatformManifestMonotonicExtension } from "./platform-management-BrF9STZp.mjs";
5
5
  import { parsePlatformInstallationManifest, setInstallationEndpoints } from "@void/platform";
6
6
  //#region src/cli/platform-recovery.ts
7
7
  async function recoverPlatformInstallations(client, accountId, verifyEndpoint = verifyInstallationEndpoint, selector = "") {
@@ -1,9 +1,9 @@
1
1
  import { a as join, u as resolve } from "./dist-BVqXeR_h.mjs";
2
2
  import { closeSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
3
+ import { parsePlatformInstallationManifest } from "@void/platform";
3
4
  import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
4
5
  import { homedir, hostname } from "node:os";
5
6
  import { Entry } from "@napi-rs/keyring";
6
- import { parsePlatformInstallationManifest } from "@void/platform";
7
7
  //#region src/cli/keyring.ts
8
8
  function readKeyringPassword(service, account) {
9
9
  try {
@@ -189,6 +189,19 @@ function getActivePlatformConnection(directory = platformRegistryDirectory()) {
189
189
  const registry = readRegistry(directory);
190
190
  return registry.connections.find((connection) => connection.id === registry.activeConnectionId) ?? (registry.connections.length === 1 ? registry.connections[0] : null);
191
191
  }
192
+ function platformConnectionSlug(connection, installations) {
193
+ const matches = installations.filter((manifest) => manifest.status !== "uninstalled" && manifest.id === connection.installationId && (connection.accountId === null || connection.accountId === manifest.account.id));
194
+ return matches.length === 1 ? matches[0].deployment.installationSlug : null;
195
+ }
196
+ /** Resolve saved platforms without selecting one or changing its credentials. */
197
+ function findPlatformConnections(selector, directory = platformRegistryDirectory(), connections = listPlatformConnections(directory)) {
198
+ let normalizedUrl = null;
199
+ try {
200
+ normalizedUrl = normalizePlatformApiUrl(selector);
201
+ } catch {}
202
+ const installations = connections.some((connection) => connection.installationId) ? listInstallationManifests(directory) : [];
203
+ return connections.filter((entry) => entry.id === selector || entry.installationId === selector || entry.displayName.toLowerCase() === selector.toLowerCase() || entry.apiUrl === normalizedUrl || platformConnectionSlug(entry, installations) === selector.toLowerCase());
204
+ }
192
205
  function savePlatformConnection(input, directory = platformRegistryDirectory()) {
193
206
  const apiUrl = normalizePlatformApiUrl(input.apiUrl);
194
207
  const id = platformConnectionId(apiUrl);
@@ -214,14 +227,7 @@ function savePlatformConnection(input, directory = platformRegistryDirectory())
214
227
  function selectPlatformConnection(selector, directory = platformRegistryDirectory()) {
215
228
  return withRegistryLock(directory, () => {
216
229
  const current = readRegistry(directory);
217
- const normalizedUrl = (() => {
218
- try {
219
- return normalizePlatformApiUrl(selector);
220
- } catch {
221
- return null;
222
- }
223
- })();
224
- const matches = current.connections.filter((entry) => entry.id === selector || entry.installationId === selector || entry.displayName.toLowerCase() === selector.toLowerCase() || normalizedUrl !== null && entry.apiUrl === normalizedUrl);
230
+ const matches = findPlatformConnections(selector, directory, current.connections);
225
231
  if (matches.length !== 1) throw new Error(matches.length === 0 ? `platform: No installed or connected platform matches ${JSON.stringify(selector)}.` : `platform: More than one platform matches ${JSON.stringify(selector)}. Use its id or URL.`);
226
232
  const selected = {
227
233
  ...matches[0],
@@ -273,7 +279,8 @@ function listInstallationManifests(directory = platformRegistryDirectory()) {
273
279
  return manifests.sort((left, right) => left.deployment.displayName.localeCompare(right.deployment.displayName));
274
280
  }
275
281
  function resolveInstallationManifest(selector, directory = platformRegistryDirectory()) {
276
- const manifests = listInstallationManifests(directory).filter((manifest) => manifest.status !== "uninstalled" && (!selector || manifest.id === selector || manifest.deployment.installationSlug === selector || manifest.deployment.displayName.toLowerCase() === selector.toLowerCase()));
282
+ const connection = selector ? listPlatformConnections(directory).find((entry) => entry.id === selector) : void 0;
283
+ const manifests = listInstallationManifests(directory).filter((manifest) => manifest.status !== "uninstalled" && (!selector || manifest.id === selector || manifest.id === connection?.installationId && (connection.accountId === null || connection.accountId === manifest.account.id) || manifest.deployment.installationSlug === selector || manifest.deployment.displayName.toLowerCase() === selector.toLowerCase()));
277
284
  if (manifests.length !== 1) throw new Error(manifests.length === 0 ? `platform: No local installation matches ${JSON.stringify(selector)}.` : "platform: Multiple local installations match; specify an installation id.");
278
285
  return manifests[0];
279
286
  }
@@ -428,4 +435,4 @@ function removePlatformConnectionForInstallation(installationId, directory = pla
428
435
  });
429
436
  }
430
437
  //#endregion
431
- export { writeKeyringPassword as C, readKeyringPassword as S, savePlatformConnection as _, listInstallationManifests as a, writePendingInstallationSecrets as b, platformCredentialAccount as c, promoteInstallationSecrets as d, readInstallationRecoverySecrets as f, resolveInstallationManifest as g, removePlatformConnectionForInstallation as h, latestPlatformWorkerCutoverGeneration as i, platformRegistryDirectory as l, removeInstallationRecoverySecrets as m, assertPlatformInstallationSlugAvailable as n, listPlatformConnections as o, readPendingInstallationSecrets as p, getActivePlatformConnection as r, normalizePlatformApiUrl as s, acquirePlatformLifecycleLock as t, platformWorkerSecretUpdateIntentState as u, selectPlatformConnection as v, deleteKeyringPassword as x, writeInstallationManifest as y };
438
+ export { deleteKeyringPassword as C, writePendingInstallationSecrets as S, writeKeyringPassword as T, removePlatformConnectionForInstallation as _, latestPlatformWorkerCutoverGeneration as a, selectPlatformConnection as b, normalizePlatformApiUrl as c, platformRegistryDirectory as d, platformWorkerSecretUpdateIntentState as f, removeInstallationRecoverySecrets as g, readPendingInstallationSecrets as h, getActivePlatformConnection as i, platformConnectionSlug as l, readInstallationRecoverySecrets as m, assertPlatformInstallationSlugAvailable as n, listInstallationManifests as o, promoteInstallationSecrets as p, findPlatformConnections as r, listPlatformConnections as s, acquirePlatformLifecycleLock as t, platformCredentialAccount as u, resolveInstallationManifest as v, readKeyringPassword as w, writeInstallationManifest as x, savePlatformConnection as y };
@@ -0,0 +1,2 @@
1
+ import { t as prepareProject } from "./prepare-WoUubFKL.mjs";
2
+ export { prepareProject };
@@ -1,10 +1,9 @@
1
- import { B as note, H as outro, L as log, N as intro } from "./prompts-BNUoTiaf.mjs";
2
- import { r as cliTitle } from "./output-CbM85T4k.mjs";
3
- import { t as prepareProject } from "./prepare-DnaKBi--.mjs";
1
+ import { H as outro, L as log } from "./prompts-6OSVA7DJ.mjs";
2
+ import { l as note } from "./output-BvdaehtE.mjs";
3
+ import { t as prepareProject } from "./prepare-WoUubFKL.mjs";
4
4
  //#region src/cli/prepare.ts
5
5
  async function runPrepareCommand(root, _args) {
6
6
  console.log();
7
- intro(cliTitle("prepare"));
8
7
  const result = await prepareProject(root);
9
8
  if (result.written.length > 0) note(result.written.join("\n"), "Wrote");
10
9
  if (result.removed.length > 0) note(result.removed.join("\n"), "Removed stale files");
@@ -6,7 +6,7 @@ import { i as generateDbDts, n as generateQueueTypes, t as generateRouteTypes }
6
6
  import { t as discoverSchema } from "./discover-BcPPiF1o.mjs";
7
7
  import { r as generateEnvTypes, t as findEnvFile } from "./env-types-D5kds_OZ.mjs";
8
8
  import { a as loadLocalEnv } from "./env-_v349JBd.mjs";
9
- import { t as analyzeApplication } from "./application-routing-B7QEjqSR.mjs";
9
+ import { t as analyzeApplication } from "./application-routing-C9khgLiS.mjs";
10
10
  import { n as generateVoidAuthDrizzleSchema, t as generateVoidAuthDatabaseModule } from "./drizzle-Beb2Am5S.mjs";
11
11
  import { existsSync, mkdirSync, rmSync } from "node:fs";
12
12
  import { fileURLToPath } from "node:url";
@@ -1,2 +1,2 @@
1
- import { l as writeProjectConfig, t as attachSelectedPlatform } from "./project-DKG5Ra8p.mjs";
1
+ import { l as writeProjectConfig, t as attachSelectedPlatform } from "./project-DQstYTxO.mjs";
2
2
  export { attachSelectedPlatform, writeProjectConfig };
@@ -1,5 +1,5 @@
1
1
  import { a as join, n as dirname } from "./dist-BVqXeR_h.mjs";
2
- import { o as listPlatformConnections, s as normalizePlatformApiUrl } from "./platform-registry-DkPPXfp_.mjs";
2
+ import { c as normalizePlatformApiUrl, s as listPlatformConnections } from "./platform-registry-P387v42A.mjs";
3
3
  import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { normalizeApplicationDomain } from "@void/platform";
5
5
  //#region src/cli/project.ts