devpush 0.0.0-stage → 1.0.0-beta.4

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 (456) hide show
  1. package/README.md +22 -2
  2. package/agent-release.json +37 -0
  3. package/dist/package/LICENSE.md +21 -0
  4. package/dist/package/migrate.mjs +182 -0
  5. package/dist/package/plugins.mjs +125 -0
  6. package/dist/package/vite.mjs +439 -0
  7. package/package.json +168 -3
  8. package/public/apple-touch-icon.png +0 -0
  9. package/public/favicon.svg +1 -0
  10. package/public/logo-email.png +0 -0
  11. package/public/robots.txt +3 -0
  12. package/public/social.png +0 -0
  13. package/src/cloudflare-runtime.d.ts +78 -0
  14. package/src/components/about-dialog.tsx +70 -0
  15. package/src/components/copy-button.tsx +74 -0
  16. package/src/components/logo-mark.tsx +20 -0
  17. package/src/components/pending-collection.tsx +103 -0
  18. package/src/components/resource-avatar.tsx +65 -0
  19. package/src/components/route-error-state.tsx +95 -0
  20. package/src/components/section-navigation.tsx +39 -0
  21. package/src/components/settings-layout.tsx +111 -0
  22. package/src/components/storage-capabilities.tsx +11 -0
  23. package/src/components/table-pager.tsx +101 -0
  24. package/src/components/ui/alert-dialog.tsx +180 -0
  25. package/src/components/ui/alert.tsx +78 -0
  26. package/src/components/ui/avatar.tsx +58 -0
  27. package/src/components/ui/badge.tsx +49 -0
  28. package/src/components/ui/button-group.tsx +38 -0
  29. package/src/components/ui/button.tsx +67 -0
  30. package/src/components/ui/card.tsx +82 -0
  31. package/src/components/ui/chart.tsx +307 -0
  32. package/src/components/ui/checkbox.tsx +30 -0
  33. package/src/components/ui/combobox.tsx +256 -0
  34. package/src/components/ui/dialog.tsx +163 -0
  35. package/src/components/ui/dropdown-menu.tsx +110 -0
  36. package/src/components/ui/empty.tsx +65 -0
  37. package/src/components/ui/field.tsx +192 -0
  38. package/src/components/ui/input-group.tsx +118 -0
  39. package/src/components/ui/input-otp.tsx +70 -0
  40. package/src/components/ui/input.tsx +19 -0
  41. package/src/components/ui/label.tsx +24 -0
  42. package/src/components/ui/pagination.tsx +34 -0
  43. package/src/components/ui/popover.tsx +86 -0
  44. package/src/components/ui/select.tsx +145 -0
  45. package/src/components/ui/skeleton.tsx +19 -0
  46. package/src/components/ui/sonner.tsx +47 -0
  47. package/src/components/ui/switch.tsx +31 -0
  48. package/src/components/ui/table.tsx +81 -0
  49. package/src/components/ui/textarea.tsx +18 -0
  50. package/src/components/ui/tooltip.tsx +55 -0
  51. package/src/features/admin/admin-settings.tsx +1048 -0
  52. package/src/features/auth/current-user-context-query.ts +11 -0
  53. package/src/features/auth/login-form.tsx +442 -0
  54. package/src/features/auth/sign-out-menu-item.tsx +42 -0
  55. package/src/features/deployments/deployment-actions.tsx +336 -0
  56. package/src/features/deployments/deployment-alias-badge.tsx +35 -0
  57. package/src/features/deployments/deployment-detail.tsx +596 -0
  58. package/src/features/deployments/deployment-filters.ts +66 -0
  59. package/src/features/deployments/deployment-list.tsx +465 -0
  60. package/src/features/deployments/deployment-visit-links.tsx +104 -0
  61. package/src/features/deployments/deployments-page.tsx +376 -0
  62. package/src/features/logs/log-scroll-area.tsx +69 -0
  63. package/src/features/notifications/email-preferences.tsx +232 -0
  64. package/src/features/onboarding/profile-onboarding.tsx +162 -0
  65. package/src/features/plugins/contributions.tsx +118 -0
  66. package/src/features/projects/backup-job-logs.tsx +143 -0
  67. package/src/features/projects/backup-policy-delete-dialog.tsx +82 -0
  68. package/src/features/projects/backup-restore-actions.tsx +573 -0
  69. package/src/features/projects/backup-status-icon.tsx +33 -0
  70. package/src/features/projects/backup-target-list.tsx +222 -0
  71. package/src/features/projects/backup-timestamp.tsx +32 -0
  72. package/src/features/projects/backup-volume-picker.tsx +158 -0
  73. package/src/features/projects/compose-service-input.tsx +64 -0
  74. package/src/features/projects/deploy-readiness.ts +28 -0
  75. package/src/features/projects/environment-dot.tsx +91 -0
  76. package/src/features/projects/log-keyword-filter.tsx +48 -0
  77. package/src/features/projects/manual-deploy-dialog.tsx +563 -0
  78. package/src/features/projects/pending-section.ts +26 -0
  79. package/src/features/projects/project-backup-details-pending.tsx +305 -0
  80. package/src/features/projects/project-backup-details.tsx +936 -0
  81. package/src/features/projects/project-backup-policy.tsx +644 -0
  82. package/src/features/projects/project-backup-utils.ts +190 -0
  83. package/src/features/projects/project-backups-pending.tsx +136 -0
  84. package/src/features/projects/project-backups.tsx +876 -0
  85. package/src/features/projects/project-environment-settings.tsx +1128 -0
  86. package/src/features/projects/project-heading.tsx +68 -0
  87. package/src/features/projects/project-layout.tsx +188 -0
  88. package/src/features/projects/project-logs.tsx +461 -0
  89. package/src/features/projects/project-overview.tsx +280 -0
  90. package/src/features/projects/project-route-pending.tsx +1064 -0
  91. package/src/features/projects/project-routing-settings.tsx +663 -0
  92. package/src/features/projects/project-settings-section.tsx +64 -0
  93. package/src/features/projects/project-settings-utils.ts +101 -0
  94. package/src/features/projects/project-settings.tsx +1472 -0
  95. package/src/features/projects/repository-picker-dialog.tsx +404 -0
  96. package/src/features/repositories/new-project-dialog-launcher.tsx +44 -0
  97. package/src/features/repositories/new-project-dialog.tsx +1204 -0
  98. package/src/features/repositories/new-project-pending.tsx +28 -0
  99. package/src/features/repositories/repository-list-pending.tsx +59 -0
  100. package/src/features/servers/server-availability-alert.tsx +44 -0
  101. package/src/features/servers/server-availability.ts +29 -0
  102. package/src/features/servers/server-avatar.tsx +52 -0
  103. package/src/features/servers/server-detail-pending.tsx +315 -0
  104. package/src/features/servers/server-detail.tsx +855 -0
  105. package/src/features/servers/server-metric-chart-pending.tsx +38 -0
  106. package/src/features/servers/server-metric-chart.tsx +182 -0
  107. package/src/features/servers/server-metric-format.ts +50 -0
  108. package/src/features/servers/server-metrics-pending.tsx +75 -0
  109. package/src/features/servers/server-metrics.tsx +136 -0
  110. package/src/features/servers/team-servers.tsx +432 -0
  111. package/src/features/settings/settings-route-pending.tsx +406 -0
  112. package/src/features/teams/backup-storage-settings.tsx +483 -0
  113. package/src/features/teams/navigation.ts +51 -0
  114. package/src/features/teams/new-team-dialog.tsx +132 -0
  115. package/src/features/teams/pending-section.ts +19 -0
  116. package/src/features/teams/team-header.tsx +1286 -0
  117. package/src/features/teams/team-heading.tsx +57 -0
  118. package/src/features/teams/team-home.tsx +391 -0
  119. package/src/features/teams/team-invite.tsx +155 -0
  120. package/src/features/teams/team-route-pending.tsx +505 -0
  121. package/src/features/teams/team-settings.tsx +1555 -0
  122. package/src/features/teams/use-workspace-updates.ts +126 -0
  123. package/src/features/users/api-keys-settings.tsx +544 -0
  124. package/src/features/users/api-scope-selection.tsx +269 -0
  125. package/src/features/users/connected-apps-settings.tsx +449 -0
  126. package/src/features/users/mcp-consent.tsx +220 -0
  127. package/src/features/users/profile-picture.tsx +171 -0
  128. package/src/features/users/user-settings.tsx +1064 -0
  129. package/src/lib/account-settings-schema.ts +78 -0
  130. package/src/lib/action-feedback.ts +81 -0
  131. package/src/lib/admin-search-schema.ts +8 -0
  132. package/src/lib/agent-release.d.mts +21 -0
  133. package/src/lib/agent-release.mjs +90 -0
  134. package/src/lib/allowlist-schema.ts +79 -0
  135. package/src/lib/api-key-schema.ts +176 -0
  136. package/src/lib/auth/client.ts +20 -0
  137. package/src/lib/avatar-schema.ts +11 -0
  138. package/src/lib/backup-cron.ts +118 -0
  139. package/src/lib/backup-database.ts +41 -0
  140. package/src/lib/backup-feature.ts +2 -0
  141. package/src/lib/backup-paths.ts +7 -0
  142. package/src/lib/backup-policy-schema.ts +85 -0
  143. package/src/lib/backup-resource-schema.ts +12 -0
  144. package/src/lib/backup-restore-schema.ts +15 -0
  145. package/src/lib/backup-storage-schema.ts +55 -0
  146. package/src/lib/deferred-module.ts +38 -0
  147. package/src/lib/deployment-logs-query.ts +63 -0
  148. package/src/lib/display-name.ts +7 -0
  149. package/src/lib/error-message.ts +119 -0
  150. package/src/lib/form-errors.ts +10 -0
  151. package/src/lib/internal-redirect.ts +25 -0
  152. package/src/lib/manual-deploy-options-query.ts +21 -0
  153. package/src/lib/notification-preferences.ts +43 -0
  154. package/src/lib/onboarding.ts +11 -0
  155. package/src/lib/operation-logs-query.ts +51 -0
  156. package/src/lib/page-head.ts +14 -0
  157. package/src/lib/project-context-query.ts +257 -0
  158. package/src/lib/project-creation-schema.ts +64 -0
  159. package/src/lib/project-env-schema.ts +23 -0
  160. package/src/lib/project-environments-schema.ts +56 -0
  161. package/src/lib/project-routing-schema.ts +94 -0
  162. package/src/lib/project-runtime-config.ts +131 -0
  163. package/src/lib/project-settings-schema.ts +75 -0
  164. package/src/lib/public-api-schema.ts +128 -0
  165. package/src/lib/public-error.ts +337 -0
  166. package/src/lib/query.ts +13 -0
  167. package/src/lib/repository-selection-query.ts +11 -0
  168. package/src/lib/resource-cache.ts +30 -0
  169. package/src/lib/runtime-id.ts +19 -0
  170. package/src/lib/search.ts +14 -0
  171. package/src/lib/server-context-query.ts +43 -0
  172. package/src/lib/server-name-schema.ts +11 -0
  173. package/src/lib/settings-conflict.ts +45 -0
  174. package/src/lib/settings-version.ts +7 -0
  175. package/src/lib/team-context-query.ts +126 -0
  176. package/src/lib/team-context-schema.ts +8 -0
  177. package/src/lib/team-forms-schema.ts +45 -0
  178. package/src/lib/team-role.ts +13 -0
  179. package/src/lib/team-settings-schema.ts +48 -0
  180. package/src/lib/urls.ts +3 -0
  181. package/src/lib/use-avatar-input.ts +61 -0
  182. package/src/lib/use-refresh-error.ts +18 -0
  183. package/src/lib/use-settings-version.ts +23 -0
  184. package/src/lib/utils.ts +6 -0
  185. package/src/lib/validation.ts +5 -0
  186. package/src/lib/workspace-updates.ts +53 -0
  187. package/src/package/config.ts +10 -0
  188. package/src/package/env.mjs +54 -0
  189. package/src/package/migrate-api.d.ts +5 -0
  190. package/src/package/migrate.ts +56 -0
  191. package/src/package/plugins.ts +141 -0
  192. package/src/package/sqlite.mjs +2 -0
  193. package/src/package/vite-api.d.ts +5 -0
  194. package/src/package/vite.ts +324 -0
  195. package/src/package/watch-guard.cjs +26 -0
  196. package/src/plugins/client.ts +57 -0
  197. package/src/plugins/contract.ts +188 -0
  198. package/src/plugins/default.server.ts +4 -0
  199. package/src/plugins/default.ui.ts +4 -0
  200. package/src/plugins/operations.ts +45 -0
  201. package/src/plugins/server.ts +197 -0
  202. package/src/plugins/version.ts +1 -0
  203. package/src/routeTree.gen.ts +1275 -0
  204. package/src/router.tsx +46 -0
  205. package/src/routes/$teamSlug.index.tsx +53 -0
  206. package/src/routes/$teamSlug.new-project.tsx +13 -0
  207. package/src/routes/$teamSlug.projects.$projectName.backups.$backupId.tsx +40 -0
  208. package/src/routes/$teamSlug.projects.$projectName.backups.tsx +82 -0
  209. package/src/routes/$teamSlug.projects.$projectName.deployments.$deploymentId.tsx +52 -0
  210. package/src/routes/$teamSlug.projects.$projectName.deployments.tsx +86 -0
  211. package/src/routes/$teamSlug.projects.$projectName.logs.tsx +55 -0
  212. package/src/routes/$teamSlug.projects.$projectName.settings.tsx +51 -0
  213. package/src/routes/$teamSlug.projects.$projectName.tsx +106 -0
  214. package/src/routes/$teamSlug.projects.tsx +40 -0
  215. package/src/routes/$teamSlug.servers.$serverId.tsx +31 -0
  216. package/src/routes/$teamSlug.servers.index.tsx +59 -0
  217. package/src/routes/$teamSlug.servers.tsx +7 -0
  218. package/src/routes/$teamSlug.settings.tsx +56 -0
  219. package/src/routes/$teamSlug.tsx +86 -0
  220. package/src/routes/[.]well-known.oauth-authorization-server.api.auth.ts +13 -0
  221. package/src/routes/[.]well-known.oauth-protected-resource.mcp.ts +6 -0
  222. package/src/routes/__root.tsx +155 -0
  223. package/src/routes/admin.tsx +55 -0
  224. package/src/routes/api/agents/certificates.ts +21 -0
  225. package/src/routes/api/agents/credentials/rotate.ts +22 -0
  226. package/src/routes/api/agents/heartbeat.ts +42 -0
  227. package/src/routes/api/agents/job-logs.ts +22 -0
  228. package/src/routes/api/agents/jobs/complete.ts +22 -0
  229. package/src/routes/api/agents/jobs/next.ts +29 -0
  230. package/src/routes/api/agents/jobs/progress.ts +32 -0
  231. package/src/routes/api/agents/jobs/status.ts +22 -0
  232. package/src/routes/api/agents/register.ts +45 -0
  233. package/src/routes/api/agents/runtime-logs.ts +22 -0
  234. package/src/routes/api/agents/switch.ts +25 -0
  235. package/src/routes/api/auth/$.ts +38 -0
  236. package/src/routes/api/cron/backups.ts +33 -0
  237. package/src/routes/api/cron/maintenance.ts +30 -0
  238. package/src/routes/api/github/install.callback.ts +51 -0
  239. package/src/routes/api/github/install.ts +41 -0
  240. package/src/routes/api/github/webhook.ts +11 -0
  241. package/src/routes/api/health.ts +19 -0
  242. package/src/routes/api/install/agent.$token.ts +50 -0
  243. package/src/routes/api/install/agent.binary.ts +83 -0
  244. package/src/routes/api/install/agent.checksum.ts +45 -0
  245. package/src/routes/api/install/agent.ts +26 -0
  246. package/src/routes/api/plugins/$.ts +11 -0
  247. package/src/routes/api/v1/$.ts +15 -0
  248. package/src/routes/api/workspaces/updates.ts +8 -0
  249. package/src/routes/auth.login.tsx +50 -0
  250. package/src/routes/deployments.$deploymentId.unavailable.tsx +21 -0
  251. package/src/routes/index.tsx +23 -0
  252. package/src/routes/invites.$token.tsx +35 -0
  253. package/src/routes/mcp.ts +13 -0
  254. package/src/routes/oauth.consent.tsx +54 -0
  255. package/src/routes/onboarding.tsx +37 -0
  256. package/src/routes/onboarding.workspace.tsx +44 -0
  257. package/src/routes/settings.tsx +30 -0
  258. package/src/routes/uploads/avatars/$fileName.ts +44 -0
  259. package/src/server/admin/access.ts +67 -0
  260. package/src/server/admin/functions.ts +122 -0
  261. package/src/server/admin/service.ts +260 -0
  262. package/src/server/agents/backup-state.ts +210 -0
  263. package/src/server/agents/certificates.ts +96 -0
  264. package/src/server/agents/claims.ts +116 -0
  265. package/src/server/agents/cleanup.ts +52 -0
  266. package/src/server/agents/compatibility.ts +116 -0
  267. package/src/server/agents/completion.ts +83 -0
  268. package/src/server/agents/container-cleanup-state.ts +51 -0
  269. package/src/server/agents/deployment-state.ts +111 -0
  270. package/src/server/agents/job-payloads.ts +159 -0
  271. package/src/server/agents/jobs.ts +1293 -0
  272. package/src/server/agents/protocol.ts +400 -0
  273. package/src/server/agents/reconciliation.ts +43 -0
  274. package/src/server/agents/runtime-logs.ts +337 -0
  275. package/src/server/agents/runtime-state.ts +125 -0
  276. package/src/server/api/admission.ts +12 -0
  277. package/src/server/api/authorization.ts +115 -0
  278. package/src/server/api/credentials.ts +569 -0
  279. package/src/server/api/cursors.ts +72 -0
  280. package/src/server/api/errors.ts +24 -0
  281. package/src/server/api/functions.ts +60 -0
  282. package/src/server/api/handler.ts +274 -0
  283. package/src/server/api/mutation-operations.ts +913 -0
  284. package/src/server/api/operation-context.ts +139 -0
  285. package/src/server/api/operation-contract.ts +1099 -0
  286. package/src/server/api/operations.ts +64 -0
  287. package/src/server/api/read-operations.ts +891 -0
  288. package/src/server/api/retention.ts +33 -0
  289. package/src/server/auth/access.ts +109 -0
  290. package/src/server/auth/auth-methods.ts +28 -0
  291. package/src/server/auth/better-auth.ts +518 -0
  292. package/src/server/auth/context.ts +28 -0
  293. package/src/server/auth/email-change.ts +47 -0
  294. package/src/server/auth/email-delivery.ts +51 -0
  295. package/src/server/auth/provider-identity.ts +19 -0
  296. package/src/server/auth/provider-tokens.ts +27 -0
  297. package/src/server/auth/session.server.ts +19 -0
  298. package/src/server/backups/database-commands.ts +40 -0
  299. package/src/server/backups/destination.ts +12 -0
  300. package/src/server/backups/feature.ts +6 -0
  301. package/src/server/backups/policy-validation.ts +51 -0
  302. package/src/server/backups/queue-state.ts +137 -0
  303. package/src/server/backups/service.ts +918 -0
  304. package/src/server/backups/storage-functions.ts +42 -0
  305. package/src/server/backups/storage-state.ts +67 -0
  306. package/src/server/backups/storage.ts +237 -0
  307. package/src/server/config.ts +274 -0
  308. package/src/server/db/atomic.ts +16 -0
  309. package/src/server/db/d1-connection.ts +83 -0
  310. package/src/server/db/errors.ts +17 -0
  311. package/src/server/db/index.cloudflare.ts +15 -0
  312. package/src/server/db/index.ts +35 -0
  313. package/src/server/db/libsql-connection.ts +72 -0
  314. package/src/server/db/log-schema.ts +102 -0
  315. package/src/server/db/migrate-libsql.ts +29 -0
  316. package/src/server/db/migrate-sqlite.ts +47 -0
  317. package/src/server/db/migration-history.ts +12 -0
  318. package/src/server/db/migrations-logs/0000_initial.sql +48 -0
  319. package/src/server/db/migrations-logs/0001_runtime_log_order.sql +2 -0
  320. package/src/server/db/migrations-logs/meta/0000_snapshot.json +327 -0
  321. package/src/server/db/migrations-logs/meta/0001_snapshot.json +329 -0
  322. package/src/server/db/migrations-logs/meta/_journal.json +20 -0
  323. package/src/server/db/migrations-sqlite/0000_initial.sql +894 -0
  324. package/src/server/db/migrations-sqlite/0001_agent_indexes.sql +5 -0
  325. package/src/server/db/migrations-sqlite/meta/0000_snapshot.json +5816 -0
  326. package/src/server/db/migrations-sqlite/meta/0001_snapshot.json +5841 -0
  327. package/src/server/db/migrations-sqlite/meta/_journal.json +20 -0
  328. package/src/server/db/oauth-schema.ts +216 -0
  329. package/src/server/db/remote-config.ts +47 -0
  330. package/src/server/db/schema.ts +1218 -0
  331. package/src/server/db/sqlite-connection.ts +94 -0
  332. package/src/server/db/sqlite-files.ts +27 -0
  333. package/src/server/db/sqlite-params.ts +19 -0
  334. package/src/server/db/worker-connection.ts +47 -0
  335. package/src/server/deleted-resources.ts +133 -0
  336. package/src/server/deletion-recovery.ts +272 -0
  337. package/src/server/email/layout.tsx +85 -0
  338. package/src/server/email/notification-template.tsx +62 -0
  339. package/src/server/email/otp-template.tsx +48 -0
  340. package/src/server/email/service.ts +251 -0
  341. package/src/server/email/team-invite-template.tsx +59 -0
  342. package/src/server/email/theme.ts +10 -0
  343. package/src/server/github/http.ts +14 -0
  344. package/src/server/github/install-state.ts +48 -0
  345. package/src/server/github/service.ts +831 -0
  346. package/src/server/github/webhook-handler.ts +198 -0
  347. package/src/server/github/webhooks.ts +205 -0
  348. package/src/server/http/auth.ts +11 -0
  349. package/src/server/http/error-diagnostics.ts +66 -0
  350. package/src/server/http/plugin-request.ts +15 -0
  351. package/src/server/http/proxy-request.ts +53 -0
  352. package/src/server/http/public-errors.ts +48 -0
  353. package/src/server/http/rate-limit.ts +59 -0
  354. package/src/server/http/request-body.ts +54 -0
  355. package/src/server/http/request-ip.ts +54 -0
  356. package/src/server/http/security-headers.ts +36 -0
  357. package/src/server/install/agent-binary.ts +230 -0
  358. package/src/server/install/agent-script.ts +565 -0
  359. package/src/server/install/release-binary.ts +65 -0
  360. package/src/server/logs/budget.ts +72 -0
  361. package/src/server/logs/cleanup.ts +64 -0
  362. package/src/server/logs/deployments.ts +98 -0
  363. package/src/server/logs/operations.ts +145 -0
  364. package/src/server/logs/retention.ts +39 -0
  365. package/src/server/maintenance.ts +61 -0
  366. package/src/server/mcp/connections.ts +358 -0
  367. package/src/server/mcp/functions.ts +63 -0
  368. package/src/server/mcp/handler.ts +295 -0
  369. package/src/server/mcp/oauth-discovery.cloudflare.ts +6 -0
  370. package/src/server/mcp/oauth-discovery.ts +8 -0
  371. package/src/server/mcp/oauth-provider.ts +220 -0
  372. package/src/server/mcp/urls.ts +20 -0
  373. package/src/server/notifications/functions.ts +51 -0
  374. package/src/server/notifications/observe.ts +303 -0
  375. package/src/server/notifications/outbox.ts +335 -0
  376. package/src/server/notifications/preferences.ts +131 -0
  377. package/src/server/notifications/query.ts +16 -0
  378. package/src/server/plugins/scope.ts +17 -0
  379. package/src/server/plugins/server-context.ts +157 -0
  380. package/src/server/plugins/services.server.ts +10 -0
  381. package/src/server/plugins/workspace-access.ts +74 -0
  382. package/src/server/projects/backup-policy-state.ts +68 -0
  383. package/src/server/projects/backups.ts +219 -0
  384. package/src/server/projects/compose-metadata.ts +174 -0
  385. package/src/server/projects/configuration-state.ts +90 -0
  386. package/src/server/projects/context.ts +909 -0
  387. package/src/server/projects/creation-state.ts +46 -0
  388. package/src/server/projects/deletion-state.ts +69 -0
  389. package/src/server/projects/deployment-config.ts +139 -0
  390. package/src/server/projects/deployment-logs.ts +97 -0
  391. package/src/server/projects/deployments.ts +1123 -0
  392. package/src/server/projects/domain-state.ts +71 -0
  393. package/src/server/projects/environment-branches.ts +75 -0
  394. package/src/server/projects/environment-state.ts +158 -0
  395. package/src/server/projects/environments.ts +424 -0
  396. package/src/server/projects/functions.ts +748 -0
  397. package/src/server/projects/generated-hostname.ts +77 -0
  398. package/src/server/projects/hostnames.ts +244 -0
  399. package/src/server/projects/mappers.ts +457 -0
  400. package/src/server/projects/names.ts +41 -0
  401. package/src/server/projects/project-env-vars.ts +28 -0
  402. package/src/server/projects/queue-state.ts +118 -0
  403. package/src/server/projects/repository.ts +368 -0
  404. package/src/server/projects/route-state.ts +37 -0
  405. package/src/server/projects/runtime-logs.ts +97 -0
  406. package/src/server/projects/settings-context.ts +58 -0
  407. package/src/server/projects/settings.ts +738 -0
  408. package/src/server/projects/types.ts +355 -0
  409. package/src/server/realtime/agent-events.ts +32 -0
  410. package/src/server/realtime/node-broadcaster.ts +71 -0
  411. package/src/server/realtime/node-handler.ts +55 -0
  412. package/src/server/realtime/service.ts +125 -0
  413. package/src/server/realtime/workspace-object.cloudflare.ts +62 -0
  414. package/src/server/realtime/workspace-object.ts +3 -0
  415. package/src/server/resources/color.ts +13 -0
  416. package/src/server/runtime/background.ts +21 -0
  417. package/src/server/runtime/node-asset-cache-plugin.ts +12 -0
  418. package/src/server/runtime/node-scheduler-plugin.ts +17 -0
  419. package/src/server/runtime/node-scheduler.ts +51 -0
  420. package/src/server/runtime/platform-env.cloudflare.ts +17 -0
  421. package/src/server/runtime/platform-env.ts +19 -0
  422. package/src/server/runtime/scheduled-task-error.ts +9 -0
  423. package/src/server/scheduled-tasks.ts +28 -0
  424. package/src/server/secrets/crypto.ts +56 -0
  425. package/src/server/servers/functions.ts +233 -0
  426. package/src/server/servers/lifecycle.ts +122 -0
  427. package/src/server/servers/metric-history-schema.ts +24 -0
  428. package/src/server/servers/metric-history.ts +55 -0
  429. package/src/server/servers/monitoring.ts +592 -0
  430. package/src/server/servers/service.ts +1211 -0
  431. package/src/server/servers/status.ts +32 -0
  432. package/src/server/servers/switch.ts +96 -0
  433. package/src/server/settings-version.ts +43 -0
  434. package/src/server/storage/capabilities.ts +6 -0
  435. package/src/server/storage/local-object-store.cloudflare.ts +7 -0
  436. package/src/server/storage/local-object-store.ts +50 -0
  437. package/src/server/storage/object-store.ts +160 -0
  438. package/src/server/teams/creation.ts +51 -0
  439. package/src/server/teams/delete-policy.ts +13 -0
  440. package/src/server/teams/deletion-state.ts +80 -0
  441. package/src/server/teams/functions.ts +324 -0
  442. package/src/server/teams/invitations.ts +38 -0
  443. package/src/server/teams/membership.ts +86 -0
  444. package/src/server/teams/queries.ts +261 -0
  445. package/src/server/teams/service.ts +1127 -0
  446. package/src/server/teams/types.ts +101 -0
  447. package/src/server/uploads/avatar.ts +133 -0
  448. package/src/server/users/deletion-state.ts +93 -0
  449. package/src/server/users/functions.ts +160 -0
  450. package/src/server/users/onboarding.ts +17 -0
  451. package/src/server/users/service.ts +644 -0
  452. package/src/server.ts +55 -0
  453. package/src/styles/shadcn.css +653 -0
  454. package/src/styles/theme.css +76 -0
  455. package/src/styles.css +177 -0
  456. package/wrangler.template.jsonc +37 -0
@@ -0,0 +1,32 @@
1
+ export type ServerStatus =
2
+ | 'pending'
3
+ | 'connected'
4
+ | 'disconnected'
5
+ | 'disabled'
6
+ | 'deleted'
7
+
8
+ export const serverDisconnectedAfterMs = 2 * 60 * 1000
9
+
10
+ export function getEffectiveServerStatus({
11
+ status,
12
+ lastSeenAt,
13
+ now = new Date(),
14
+ }: {
15
+ status: ServerStatus
16
+ lastSeenAt: Date | null
17
+ now?: Date
18
+ }): ServerStatus {
19
+ if (status !== 'connected') {
20
+ return status
21
+ }
22
+
23
+ if (!lastSeenAt) {
24
+ return 'disconnected'
25
+ }
26
+
27
+ if (now.getTime() - lastSeenAt.getTime() > serverDisconnectedAfterMs) {
28
+ return 'disconnected'
29
+ }
30
+
31
+ return 'connected'
32
+ }
@@ -0,0 +1,96 @@
1
+ import { createHash, createHmac, timingSafeEqual } from 'node:crypto'
2
+ import { and, eq, inArray, or, sql } from 'drizzle-orm'
3
+ import { config } from '#devpush/server/config'
4
+ import { db } from '#devpush/server/db'
5
+ import {
6
+ agentJobs,
7
+ deployments,
8
+ environments,
9
+ projects,
10
+ servers,
11
+ } from '#devpush/server/db/schema'
12
+ import { AgentRequestError } from '#devpush/server/agents/protocol'
13
+
14
+ function sign(value: string) {
15
+ return createHmac('sha256', config.BETTER_AUTH_SECRET)
16
+ .update(value)
17
+ .digest('hex')
18
+ }
19
+
20
+ export async function switchServerControlPlane(input: {
21
+ serverId: string
22
+ token: string
23
+ action: 'prepare' | 'rollback'
24
+ ticket?: string
25
+ }) {
26
+ const hash = createHash('sha256').update(input.token).digest('hex')
27
+ const auth = and(
28
+ eq(servers.id, input.serverId),
29
+ or(
30
+ eq(servers.agentCredentialHash, hash),
31
+ eq(servers.pendingAgentCredentialHash, hash),
32
+ ),
33
+ )
34
+ const now = new Date()
35
+ if (input.action === 'rollback') {
36
+ const [value, signature] = (input.ticket ?? '').split('.')
37
+ if (
38
+ !value ||
39
+ !signature ||
40
+ !/^[a-f0-9]{64}$/.test(signature) ||
41
+ !timingSafeEqual(
42
+ Buffer.from(signature, 'hex'),
43
+ Buffer.from(sign(value), 'hex'),
44
+ )
45
+ ) {
46
+ throw new AgentRequestError(401, 'Invalid rollback ticket.')
47
+ }
48
+ const ticket = JSON.parse(Buffer.from(value, 'base64url').toString()) as {
49
+ id: string
50
+ at: number
51
+ }
52
+ if (ticket.id !== input.serverId || !Number.isSafeInteger(ticket.at))
53
+ throw new AgentRequestError(401, 'Invalid rollback ticket.')
54
+ const rows = await db
55
+ .update(servers)
56
+ .set({ status: 'connected', updatedAt: now })
57
+ .where(
58
+ and(
59
+ auth,
60
+ eq(servers.status, 'disabled'),
61
+ eq(servers.updatedAt, new Date(ticket.at)),
62
+ ),
63
+ )
64
+ .returning({ id: servers.id })
65
+ if (!rows.length)
66
+ throw new AgentRequestError(
67
+ 409,
68
+ 'Source state changed; rollback requires operator review.',
69
+ )
70
+ return { serverId: input.serverId }
71
+ }
72
+ // One conditional write both validates emptiness and disables subsequent claims.
73
+ const rows = await db
74
+ .update(servers)
75
+ .set({ status: 'disabled', updatedAt: now })
76
+ .where(
77
+ and(
78
+ auth,
79
+ eq(servers.status, 'connected'),
80
+ sql`not exists (select 1 from ${projects} where ${projects.defaultServerId} = ${input.serverId})`,
81
+ sql`not exists (select 1 from ${environments} where ${environments.serverId} = ${input.serverId})`,
82
+ sql`not exists (select 1 from ${agentJobs} where ${agentJobs.serverId} = ${input.serverId} and ${inArray(agentJobs.status, ['queued', 'running'])})`,
83
+ sql`not exists (select 1 from ${deployments} where ${deployments.serverId} = ${input.serverId})`,
84
+ ),
85
+ )
86
+ .returning({ id: servers.id })
87
+ if (!rows.length)
88
+ throw new AgentRequestError(
89
+ 409,
90
+ 'Source must be connected and have no projects, deployments, or outstanding jobs.',
91
+ )
92
+ const value = Buffer.from(
93
+ JSON.stringify({ id: input.serverId, at: now.getTime() }),
94
+ ).toString('base64url')
95
+ return { serverId: input.serverId, ticket: `${value}.${sign(value)}` }
96
+ }
@@ -0,0 +1,43 @@
1
+ import { sql } from 'drizzle-orm'
2
+ import { PublicError } from '#devpush/lib/public-error'
3
+ import type { SQL } from 'drizzle-orm'
4
+ import type { AtomicStore } from '#devpush/server/db/atomic'
5
+
6
+ export function projectSettingsGuard(
7
+ projectId: string,
8
+ userId: string,
9
+ settingsVersion: number,
10
+ manager = false,
11
+ ): SQL {
12
+ return sql`exists(select 1 from projects p join teams t on t.id = p.team_id join team_members m on m.team_id = t.id join user u on u.id = m.user_id where p.id = ${projectId} and p.status = 'active' and p.settings_version = ${settingsVersion} and t.status = 'active' and u.status = 'active' and m.user_id = ${userId} ${manager ? sql`and m.role in ('owner','admin')` : sql``})`
13
+ }
14
+
15
+ export function teamSettingsAccess(teamId: string, userId: string): SQL {
16
+ return sql`exists(select 1 from teams t join team_members m on m.team_id = t.id join user u on u.id = m.user_id where t.id = ${teamId} and t.status = 'active' and u.status = 'active' and m.user_id = ${userId} and m.role in ('owner','admin'))`
17
+ }
18
+
19
+ /** A failed guard violates NOT NULL, rolling back every statement in a D1 batch. */
20
+ export function advanceProjectSettings(
21
+ store: AtomicStore,
22
+ projectId: string,
23
+ guard: SQL,
24
+ ) {
25
+ return store.run(
26
+ sql`update projects set settings_version = case when ${guard} then settings_version + 1 else null end where id = ${projectId}`,
27
+ )
28
+ }
29
+
30
+ export function rethrowSettingsConflict(error: unknown): never {
31
+ if (error && typeof error === 'object') {
32
+ if (
33
+ 'message' in error &&
34
+ typeof error.message === 'string' &&
35
+ /NOT NULL constraint failed: (projects|teams|user|servers|backup_configs|backup_storage)\.settings_version/i.test(
36
+ error.message,
37
+ )
38
+ )
39
+ throw new PublicError('SETTINGS_CONFLICT')
40
+ if ('cause' in error && error.cause) rethrowSettingsConflict(error.cause)
41
+ }
42
+ throw error
43
+ }
@@ -0,0 +1,6 @@
1
+ import { createServerFn } from '@tanstack/react-start'
2
+ import { isObjectStorageConfigured } from './object-store'
3
+
4
+ export const getStorageCapabilities = createServerFn({ method: 'GET' }).handler(
5
+ async () => ({ imageUploads: isObjectStorageConfigured() }),
6
+ )
@@ -0,0 +1,7 @@
1
+ import type { ObjectStore } from './object-store'
2
+
3
+ export function createLocalObjectStore(): ObjectStore {
4
+ throw new Error(
5
+ 'Object storage is not configured. Cloudflare requires an R2 binding or S3-compatible storage.',
6
+ )
7
+ }
@@ -0,0 +1,50 @@
1
+ import { access, mkdir, readFile, unlink, writeFile } from 'node:fs/promises'
2
+ import path from 'node:path'
3
+ import type { ObjectStore } from './object-store'
4
+
5
+ export function createLocalObjectStore(): ObjectStore {
6
+ const root = path.join(process.cwd(), 'public', 'uploads')
7
+
8
+ return {
9
+ async delete(key) {
10
+ try {
11
+ await unlink(resolveLocalKey(root, key))
12
+ } catch (error) {
13
+ if (!isMissingFile(error)) throw error
14
+ }
15
+ },
16
+ async exists(key) {
17
+ try {
18
+ await access(resolveLocalKey(root, key))
19
+ return true
20
+ } catch (error) {
21
+ if (isMissingFile(error)) return false
22
+ throw error
23
+ }
24
+ },
25
+ async get(key) {
26
+ return { body: await readFile(resolveLocalKey(root, key)) }
27
+ },
28
+ async put(key, body) {
29
+ const filePath = resolveLocalKey(root, key)
30
+ await mkdir(path.dirname(filePath), { recursive: true })
31
+ await writeFile(filePath, body)
32
+ },
33
+ }
34
+ }
35
+
36
+ function resolveLocalKey(root: string, key: string) {
37
+ if (!/^[A-Za-z0-9][A-Za-z0-9_./-]*$/.test(key) || key.includes('..')) {
38
+ throw new Error('Invalid object key.')
39
+ }
40
+
41
+ return path.join(root, key)
42
+ }
43
+
44
+ function isMissingFile(error: unknown) {
45
+ return (
46
+ error instanceof Error &&
47
+ 'code' in error &&
48
+ (error as NodeJS.ErrnoException).code === 'ENOENT'
49
+ )
50
+ }
@@ -0,0 +1,160 @@
1
+ import { createLocalObjectStore } from '#devpush/server/storage/local-object-store'
2
+
3
+ import { config } from '#devpush/server/config'
4
+ import {
5
+ isCloudflareRuntime,
6
+ getR2ObjectStore,
7
+ } from '#devpush/server/runtime/platform-env'
8
+ import type { S3Client } from '@aws-sdk/client-s3'
9
+
10
+ type StoredObject = {
11
+ body: Buffer
12
+ contentType?: string
13
+ }
14
+
15
+ export interface ObjectStore {
16
+ delete: (key: string) => Promise<void>
17
+ exists: (key: string) => Promise<boolean>
18
+ get: (key: string) => Promise<StoredObject>
19
+ put: (key: string, body: Uint8Array, contentType?: string) => Promise<void>
20
+ }
21
+
22
+ let store: ObjectStore | undefined
23
+
24
+ export function getObjectStore() {
25
+ const r2 = getR2ObjectStore()
26
+
27
+ if (r2) {
28
+ return createR2ObjectStore(r2)
29
+ }
30
+
31
+ // Node may pool a client; Worker invocations must not share its transport I/O.
32
+ if (isCloudflareRuntime()) {
33
+ if (!isRemoteObjectStorageConfigured())
34
+ throw new Error('Object storage is not configured.')
35
+ return createS3ObjectStore()
36
+ }
37
+ store ??= isRemoteObjectStorageConfigured()
38
+ ? createS3ObjectStore()
39
+ : createLocalObjectStore()
40
+
41
+ return store
42
+ }
43
+
44
+ export function isObjectStorageConfigured() {
45
+ return (
46
+ !isCloudflareRuntime() ||
47
+ Boolean(getR2ObjectStore()) ||
48
+ isRemoteObjectStorageConfigured()
49
+ )
50
+ }
51
+
52
+ function createR2ObjectStore(bucket: R2Bucket): ObjectStore {
53
+ return {
54
+ async delete(key) {
55
+ await bucket.delete(key)
56
+ },
57
+ async exists(key) {
58
+ return Boolean(await bucket.head(key))
59
+ },
60
+ async get(key) {
61
+ const object = await bucket.get(key)
62
+ if (!object) throw new Error(`Object ${key} was not found.`)
63
+
64
+ return {
65
+ body: Buffer.from(await object.arrayBuffer()),
66
+ contentType: object.httpMetadata?.contentType,
67
+ }
68
+ },
69
+ async put(key, body, contentType) {
70
+ await bucket.put(key, body, {
71
+ httpMetadata: contentType ? { contentType } : undefined,
72
+ })
73
+ },
74
+ }
75
+ }
76
+
77
+ function isRemoteObjectStorageConfigured() {
78
+ return Boolean(
79
+ config.DEVPUSH_STORAGE_ENDPOINT &&
80
+ config.DEVPUSH_STORAGE_BUCKET &&
81
+ config.DEVPUSH_STORAGE_ACCESS_KEY_ID &&
82
+ config.DEVPUSH_STORAGE_SECRET_ACCESS_KEY,
83
+ )
84
+ }
85
+
86
+ function createS3ObjectStore(): ObjectStore {
87
+ const bucket = config.DEVPUSH_STORAGE_BUCKET!
88
+ let clientPromise: Promise<S3Client> | undefined
89
+ const getClient = () =>
90
+ (clientPromise ??= import('@aws-sdk/client-s3').then(
91
+ ({ S3Client }) =>
92
+ new S3Client({
93
+ endpoint: config.DEVPUSH_STORAGE_ENDPOINT,
94
+ region: config.DEVPUSH_STORAGE_REGION ?? 'auto',
95
+ credentials: {
96
+ accessKeyId: config.DEVPUSH_STORAGE_ACCESS_KEY_ID!,
97
+ secretAccessKey: config.DEVPUSH_STORAGE_SECRET_ACCESS_KEY!,
98
+ },
99
+ }),
100
+ ))
101
+
102
+ return {
103
+ async delete(key) {
104
+ const [{ DeleteObjectCommand }, client] = await Promise.all([
105
+ import('@aws-sdk/client-s3'),
106
+ getClient(),
107
+ ])
108
+ await client.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }))
109
+ },
110
+ async exists(key) {
111
+ try {
112
+ const [{ HeadObjectCommand }, client] = await Promise.all([
113
+ import('@aws-sdk/client-s3'),
114
+ getClient(),
115
+ ])
116
+ await client.send(new HeadObjectCommand({ Bucket: bucket, Key: key }))
117
+ return true
118
+ } catch (error) {
119
+ if (
120
+ error instanceof Error &&
121
+ 'name' in error &&
122
+ (error.name === 'NotFound' || error.name === 'NoSuchKey')
123
+ ) {
124
+ return false
125
+ }
126
+ throw error
127
+ }
128
+ },
129
+ async get(key) {
130
+ const [{ GetObjectCommand }, client] = await Promise.all([
131
+ import('@aws-sdk/client-s3'),
132
+ getClient(),
133
+ ])
134
+ const result = await client.send(
135
+ new GetObjectCommand({ Bucket: bucket, Key: key }),
136
+ )
137
+
138
+ if (!result.Body) throw new Error(`Object ${key} has no content.`)
139
+
140
+ return {
141
+ body: Buffer.from(await result.Body.transformToByteArray()),
142
+ contentType: result.ContentType,
143
+ }
144
+ },
145
+ async put(key, body, contentType) {
146
+ const [{ PutObjectCommand }, client] = await Promise.all([
147
+ import('@aws-sdk/client-s3'),
148
+ getClient(),
149
+ ])
150
+ await client.send(
151
+ new PutObjectCommand({
152
+ Body: body,
153
+ Bucket: bucket,
154
+ ContentType: contentType,
155
+ Key: key,
156
+ }),
157
+ )
158
+ },
159
+ }
160
+ }
@@ -0,0 +1,51 @@
1
+ import { sql } from 'drizzle-orm'
2
+ import type { AtomicStore } from '#devpush/server/db/atomic'
3
+ import { teamMembers, teams, user } from '#devpush/server/db/schema'
4
+
5
+ type TeamInput = {
6
+ userId: string
7
+ teamId: string
8
+ name: string
9
+ baseSlug: string
10
+ }
11
+
12
+ function teamSlug(input: TeamInput) {
13
+ return sql`case when exists(select 1 from ${teams} where slug = ${input.baseSlug})
14
+ then ${`${input.baseSlug.slice(0, 32)}-${input.teamId.slice(0, 7)}`}
15
+ else ${input.baseSlug} end`
16
+ }
17
+
18
+ export async function insertOwnedTeam(store: AtomicStore, input: TeamInput) {
19
+ const now = Date.now()
20
+ const [created] = await store.batch([
21
+ store.all(sql`insert into ${teams} (id, name, slug, created_at, updated_at)
22
+ select ${input.teamId}, ${input.name}, ${teamSlug(input)}, ${now}, ${now}
23
+ from ${user} where id = ${input.userId} and status = 'active' returning id`),
24
+ store.run(sql`insert into ${teamMembers} (id, team_id, user_id, role, created_at)
25
+ select ${crypto.randomUUID()}, id, ${input.userId}, 'owner', ${now}
26
+ from ${teams} where id = ${input.teamId}`),
27
+ ])
28
+ return created.length === 1
29
+ }
30
+
31
+ export async function bootstrapOwnedTeam(store: AtomicStore, input: TeamInput) {
32
+ const now = Date.now()
33
+ const activeMemberships = sql`select membership.team_id from ${teamMembers} as membership
34
+ join ${teams} as member_team on member_team.id = membership.team_id
35
+ where membership.user_id = ${input.userId} and member_team.status = 'active'`
36
+ const [, , updated] = await store.batch([
37
+ store.run(sql`insert into ${teams} (id, name, slug, created_at, updated_at)
38
+ select ${input.teamId}, ${input.name}, ${teamSlug(input)}, ${now}, ${now}
39
+ from ${user} where id = ${input.userId} and status = 'active'
40
+ and not exists(${activeMemberships})`),
41
+ store.run(sql`insert into ${teamMembers} (id, team_id, user_id, role, created_at)
42
+ select ${crypto.randomUUID()}, id, ${input.userId}, 'owner', ${now}
43
+ from ${teams} where id = ${input.teamId}`),
44
+ store.all(sql`update ${user} set default_team_id = case
45
+ when default_team_id in (${activeMemberships}) then default_team_id
46
+ else (${activeMemberships} order by membership.created_at desc limit 1) end,
47
+ updated_at = ${now}
48
+ where id = ${input.userId} and status = 'active' returning id`),
49
+ ])
50
+ return updated.length === 1
51
+ }
@@ -0,0 +1,13 @@
1
+ import type { TeamRole } from '#devpush/lib/team-role'
2
+
3
+ export function canDeleteTeam({
4
+ defaultUserIds,
5
+ }: {
6
+ defaultUserIds: Array<string>
7
+ }) {
8
+ return defaultUserIds.length === 0
9
+ }
10
+
11
+ export function canDeleteProject(role: TeamRole) {
12
+ return role === 'owner' || role === 'admin'
13
+ }
@@ -0,0 +1,80 @@
1
+ import { releaseBackupCleanupRuns } from '#devpush/server/backups/queue-state'
2
+ import { sql } from 'drizzle-orm'
3
+ import type { SQL } from 'drizzle-orm'
4
+ import type { AtomicStore } from '#devpush/server/db/atomic'
5
+ import { runtimeCleanupInsert } from '#devpush/server/agents/cleanup'
6
+
7
+ export function teamDeletionStatements(input: {
8
+ teamIds: SQL
9
+ guard: SQL
10
+ reason: string
11
+ message: string
12
+ now: Date
13
+ clearDefaults?: boolean
14
+ }): [SQL, ...SQL[]] {
15
+ const now = input.now.getTime()
16
+ const projects = sql`select id from projects where team_id in (${input.teamIds})`
17
+ const servers = sql`select id from servers where team_id in (${input.teamIds})`
18
+ const environments = sql`select id from environments where project_id in (${projects})`
19
+ return [
20
+ releaseBackupCleanupRuns({
21
+ jobIds: sql`select id from agent_jobs where server_id in (${servers})`,
22
+ guard: input.guard,
23
+ message: input.message,
24
+ now: input.now,
25
+ }),
26
+ sql`update agent_jobs set status = 'canceled', error_message = ${input.message}, finished_at = ${now}, updated_at = ${now}
27
+ where server_id in (${servers}) and type in ('deploy', 'backup', 'restore', 'backup_cleanup')
28
+ and status in ('queued', 'running') and ${input.guard}`,
29
+ sql`update backup_runs set status = 'canceled', error_message = ${input.message}, finished_at = ${now}, updated_at = ${now}
30
+ where server_id in (${servers}) and status in ('queued', 'running') and ${input.guard}`,
31
+ runtimeCleanupInsert({
32
+ projectIds: projects,
33
+ guard: input.guard,
34
+ reason: input.reason,
35
+ now: input.now,
36
+ }),
37
+ sql`update environments set status = 'deleted', is_production = 0, updated_at = ${now}
38
+ where project_id in (${projects}) and ${input.guard}`,
39
+ sql`update deployments set status = 'canceled', phase = 'canceled', error_message = ${input.message}, finished_at = ${now}
40
+ where environment_id in (${environments}) and status in ('queued', 'running') and ${input.guard}`,
41
+ sql`update projects set status = 'deleted', updated_at = ${now} where team_id in (${input.teamIds}) and ${input.guard}`,
42
+ sql`update servers set status = 'deleted', updated_at = ${now} where team_id in (${input.teamIds}) and ${input.guard}`,
43
+ ...(input.clearDefaults
44
+ ? [
45
+ sql`update user set default_team_id = null, updated_at = ${now}
46
+ where default_team_id in (${input.teamIds}) and ${input.guard}`,
47
+ ]
48
+ : []),
49
+ sql`update teams set status = 'deleted', updated_at = ${now} where id in (${input.teamIds}) and ${input.guard} returning id`,
50
+ ]
51
+ }
52
+
53
+ export async function deleteTeamState(
54
+ store: AtomicStore,
55
+ input: {
56
+ teamId: string
57
+ userId: string
58
+ now: Date
59
+ },
60
+ ) {
61
+ const guard = sql`exists(select 1 from teams t join team_members m on m.team_id = t.id
62
+ join user u on u.id = m.user_id where t.id = ${input.teamId} and t.status = 'active'
63
+ and m.user_id = ${input.userId} and m.role = 'owner' and u.status = 'active')
64
+ and not exists(select 1 from user where default_team_id = ${input.teamId} and status = 'active')`
65
+ const [first, ...rest] = teamDeletionStatements({
66
+ teamIds: sql`select id from teams where id = ${input.teamId}`,
67
+ guard,
68
+ reason: 'Team deleted.',
69
+ message: 'Canceled because the team was deleted.',
70
+ now: input.now,
71
+ })
72
+ const results = await store.batch([
73
+ store.run(first),
74
+ ...rest.map((query, index) =>
75
+ index === rest.length - 1 ? store.all(query) : store.run(query),
76
+ ),
77
+ ])
78
+ const changed = results.at(-1)
79
+ return Array.isArray(changed) && changed.length === 1
80
+ }