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,13 @@
1
+ const resourceColors = ['lime', 'sky', 'blue', 'green'] as const
2
+
3
+ export type ResourceColor = (typeof resourceColors)[number]
4
+
5
+ export function getResourceColor(id: string): ResourceColor {
6
+ let hash = 0
7
+
8
+ for (const char of id) {
9
+ hash = (hash * 31 + char.charCodeAt(0)) >>> 0
10
+ }
11
+
12
+ return resourceColors[hash % resourceColors.length] ?? resourceColors[0]
13
+ }
@@ -0,0 +1,21 @@
1
+ import { safeErrorDiagnostics } from '#devpush/server/http/error-diagnostics'
2
+ import { AsyncLocalStorage } from 'node:async_hooks'
3
+
4
+ type Defer = (promise: Promise<unknown>) => void
5
+ const scope = new AsyncLocalStorage<Defer>()
6
+
7
+ export function withBackgroundTasks<T>(defer: Defer, work: () => T): T {
8
+ return scope.run(defer, work)
9
+ }
10
+ /** Workers keep the task alive with waitUntil; Node runs it independently of the response. */
11
+ export function deferBackgroundTask(work: () => Promise<unknown>) {
12
+ const pending = Promise.resolve()
13
+ .then(work)
14
+ .catch((error: unknown) => {
15
+ console.warn(
16
+ 'Background task failed; persisted work will be retried.',
17
+ safeErrorDiagnostics(error),
18
+ )
19
+ })
20
+ scope.getStore()?.(pending)
21
+ }
@@ -0,0 +1,12 @@
1
+ import { definePlugin } from 'nitro'
2
+
3
+ export function preventAssetErrorCaching(response: Response, url: URL) {
4
+ if (response.status >= 400 && url.pathname.startsWith('/assets/'))
5
+ response.headers.set('Cache-Control', 'no-store')
6
+ }
7
+
8
+ export default definePlugin((app) => {
9
+ app.hooks.hook('response', (response, event) => {
10
+ preventAssetErrorCaching(response, new URL(event.req.url))
11
+ })
12
+ })
@@ -0,0 +1,17 @@
1
+ import { withPluginScope } from '#devpush/server/plugins/scope'
2
+ import { definePlugin } from 'nitro'
3
+ import { assertRuntimeSecurity, config } from '#devpush/server/config'
4
+ import { withDatabaseScope } from '#devpush/server/db'
5
+ import { runScheduledTasks } from '#devpush/server/scheduled-tasks'
6
+ import { startNodeScheduler } from './node-scheduler'
7
+
8
+ export default definePlugin((app) => {
9
+ if (process.env.NODE_ENV !== 'production') return
10
+ assertRuntimeSecurity(config)
11
+ const stop = startNodeScheduler((maintenance) =>
12
+ withPluginScope(() =>
13
+ withDatabaseScope(() => runScheduledTasks(maintenance)),
14
+ ),
15
+ )
16
+ app.hooks.hook('close', stop)
17
+ })
@@ -0,0 +1,51 @@
1
+ import { safeErrorDiagnostics } from '#devpush/server/http/error-diagnostics'
2
+ import { ScheduledTaskError } from './scheduled-task-error'
3
+ /** One minute-aligned timer, with no overlapping runs and retry after failure. */
4
+ export function startNodeScheduler(
5
+ run: (maintenance: boolean) => Promise<void>,
6
+ ) {
7
+ let stopped = false
8
+ let timer: ReturnType<typeof setTimeout> | undefined
9
+ let lastMaintenanceHour: number | undefined
10
+ let active: Promise<void> = Promise.resolve()
11
+ function schedule() {
12
+ if (stopped) return
13
+ timer = setTimeout(
14
+ () => {
15
+ const hour = Math.floor(Date.now() / 3_600_000)
16
+ const maintenance = hour !== lastMaintenanceHour
17
+ active = Promise.resolve()
18
+ .then(() => run(maintenance))
19
+ .then(() => {
20
+ if (maintenance) lastMaintenanceHour = hour
21
+ })
22
+ .catch((error: unknown) => {
23
+ if (
24
+ maintenance &&
25
+ error instanceof ScheduledTaskError &&
26
+ error.maintenanceCompleted
27
+ )
28
+ lastMaintenanceHour = hour
29
+ console.error(
30
+ 'Scheduled tasks failed; retrying on the next minute tick.',
31
+ {
32
+ errors: (error instanceof AggregateError
33
+ ? error.errors.slice(0, 4)
34
+ : [error]
35
+ ).map(safeErrorDiagnostics),
36
+ },
37
+ )
38
+ })
39
+ .finally(schedule)
40
+ },
41
+ 60_000 - (Date.now() % 60_000),
42
+ )
43
+ timer.unref()
44
+ }
45
+ schedule()
46
+ return async () => {
47
+ stopped = true
48
+ clearTimeout(timer)
49
+ await active
50
+ }
51
+ }
@@ -0,0 +1,17 @@
1
+ import { env } from 'cloudflare:workers'
2
+
3
+ export function isCloudflareRuntime() {
4
+ return true
5
+ }
6
+
7
+ export function getR2ObjectStore() {
8
+ return env.DEVPUSH_OBJECTS
9
+ }
10
+
11
+ export function getWorkerVersionMetadata() {
12
+ return env.CF_VERSION_METADATA
13
+ }
14
+
15
+ export function getWorkspaceUpdates() {
16
+ return env.DEVPUSH_WORKSPACE_UPDATES
17
+ }
@@ -0,0 +1,19 @@
1
+ import { nodeBroadcaster } from '#devpush/server/realtime/node-broadcaster'
2
+
3
+ export function isCloudflareRuntime() {
4
+ return false
5
+ }
6
+
7
+ export function getR2ObjectStore(): R2Bucket | undefined {
8
+ return undefined
9
+ }
10
+
11
+ export function getWorkerVersionMetadata():
12
+ | { id: string; tag: string }
13
+ | undefined {
14
+ return undefined
15
+ }
16
+
17
+ export function getWorkspaceUpdates(): WorkspaceUpdatesNamespace | undefined {
18
+ return nodeBroadcaster
19
+ }
@@ -0,0 +1,9 @@
1
+ /** Preserve hourly progress when an unrelated minute task fails. */
2
+ export class ScheduledTaskError extends AggregateError {
3
+ constructor(
4
+ errors: unknown[],
5
+ readonly maintenanceCompleted: boolean,
6
+ ) {
7
+ super(errors, 'Scheduled tasks failed.')
8
+ }
9
+ }
@@ -0,0 +1,28 @@
1
+ import { ScheduledTaskError } from '#devpush/server/runtime/scheduled-task-error'
2
+ import { maintainOperationalNotifications } from '#devpush/server/notifications/observe'
3
+ import { config } from '#devpush/server/config'
4
+ import { queueDueBackupSchedules } from '#devpush/server/backups/service'
5
+ import { runMaintenance } from '#devpush/server/maintenance'
6
+
7
+ /** Shared by the Node timer and Cloudflare scheduled handler. */
8
+ export async function runScheduledTasks(maintenance: boolean) {
9
+ const tasks: Array<{ name: string; run: () => Promise<unknown> }> = [
10
+ { name: 'notifications', run: maintainOperationalNotifications },
11
+ ...(config.DEVPUSH_BACKUPS_ENABLED
12
+ ? [{ name: 'backups', run: queueDueBackupSchedules }]
13
+ : []),
14
+ ...(maintenance ? [{ name: 'maintenance', run: runMaintenance }] : []),
15
+ ]
16
+ const results = await Promise.allSettled(
17
+ tasks.map((task) => Promise.resolve().then(() => task.run())),
18
+ )
19
+ const failures = results.filter((result) => result.status === 'rejected')
20
+ if (failures.length)
21
+ throw new ScheduledTaskError(
22
+ failures.map((result) => result.reason),
23
+ tasks.some(
24
+ (task, index) =>
25
+ task.name === 'maintenance' && results[index]?.status === 'fulfilled',
26
+ ),
27
+ )
28
+ }
@@ -0,0 +1,56 @@
1
+ import {
2
+ createCipheriv,
3
+ createDecipheriv,
4
+ createHash,
5
+ randomBytes,
6
+ } from 'node:crypto'
7
+
8
+ import { config } from '#devpush/server/config'
9
+
10
+ export function encryptSecret(value: string) {
11
+ const iv = randomBytes(12)
12
+ const cipher = createCipheriv('aes-256-gcm', encryptionKey(), iv)
13
+ const encrypted = Buffer.concat([
14
+ cipher.update(value, 'utf8'),
15
+ cipher.final(),
16
+ ])
17
+ const tag = cipher.getAuthTag()
18
+
19
+ return [
20
+ 'v1',
21
+ iv.toString('base64url'),
22
+ tag.toString('base64url'),
23
+ encrypted.toString('base64url'),
24
+ ].join(':')
25
+ }
26
+
27
+ export function decryptSecret(payload: string) {
28
+ const [version, ivValue, tagValue, encryptedValue] = payload.split(':')
29
+
30
+ if (version !== 'v1' || !ivValue || !tagValue || !encryptedValue) {
31
+ throw new Error('Invalid encrypted secret payload.')
32
+ }
33
+
34
+ const decipher = createDecipheriv(
35
+ 'aes-256-gcm',
36
+ encryptionKey(),
37
+ Buffer.from(ivValue, 'base64url'),
38
+ )
39
+ decipher.setAuthTag(Buffer.from(tagValue, 'base64url'))
40
+ const decrypted = Buffer.concat([
41
+ decipher.update(Buffer.from(encryptedValue, 'base64url')),
42
+ decipher.final(),
43
+ ])
44
+
45
+ return decrypted.toString('utf8')
46
+ }
47
+
48
+ function encryptionKey() {
49
+ if (!config.DEVPUSH_SECRET_ENCRYPTION_KEY) {
50
+ throw new Error('DEVPUSH_SECRET_ENCRYPTION_KEY is required.')
51
+ }
52
+
53
+ return createHash('sha256')
54
+ .update(config.DEVPUSH_SECRET_ENCRYPTION_KEY)
55
+ .digest()
56
+ }
@@ -0,0 +1,233 @@
1
+ import {
2
+ getAuthSession,
3
+ requireAuthSession,
4
+ } from '#devpush/server/auth/session.server'
5
+ import { requireTeam } from '#devpush/server/teams/service'
6
+ import {
7
+ requireServerForTeam,
8
+ requireServerMetricHistoryForTeam,
9
+ createPendingServer,
10
+ createServerInstallCommand,
11
+ deleteTeamServer,
12
+ disableTeamServer,
13
+ enableTeamServer,
14
+ getServerDeleteDiagnostic,
15
+ updateTeamServer,
16
+ } from '#devpush/server/servers/service'
17
+ import { settingsVersionSchema } from '#devpush/lib/settings-version'
18
+ import { publicErrorsMiddleware } from '#devpush/server/http/public-errors'
19
+ import { serverNameSchema } from '#devpush/lib/server-name-schema'
20
+ import { createServerFn } from '@tanstack/react-start'
21
+ import { z } from 'zod'
22
+
23
+ const createServerSchema = z.object({
24
+ teamSlug: z.string().min(1),
25
+ name: serverNameSchema,
26
+ })
27
+
28
+ const serverInstallCommandSchema = z.object({
29
+ teamSlug: z.string().min(1),
30
+ serverId: z.string().min(1),
31
+ })
32
+
33
+ const updateServerSchema = z.object({
34
+ settingsVersion: settingsVersionSchema,
35
+ teamSlug: z.string().min(1),
36
+ serverId: z.string().min(1),
37
+ name: serverNameSchema,
38
+ })
39
+
40
+ const disableServerSchema = z.object({
41
+ teamSlug: z.string().min(1),
42
+ serverId: z.string().min(1),
43
+ confirmationName: z.string().min(1),
44
+ })
45
+
46
+ const enableServerSchema = z.object({
47
+ teamSlug: z.string().min(1),
48
+ serverId: z.string().min(1),
49
+ })
50
+
51
+ const deleteServerDiagnosticSchema = z.object({
52
+ teamSlug: z.string().min(1),
53
+ serverId: z.string().min(1),
54
+ })
55
+
56
+ const deleteServerSchema = z.object({
57
+ teamSlug: z.string().min(1),
58
+ serverId: z.string().min(1),
59
+ confirmationName: z.string().min(1),
60
+ cancelOngoingDeployments: z.boolean(),
61
+ })
62
+
63
+ export const createServer = createServerFn({ method: 'POST' })
64
+ .middleware([publicErrorsMiddleware])
65
+ .validator((data: { name: string; teamSlug: string }) =>
66
+ createServerSchema.parse(data),
67
+ )
68
+ .handler(async ({ data }) => {
69
+ const session = await requireAuthSession('Sign in before adding a server.')
70
+
71
+ return createPendingServer({
72
+ userId: session.user.id,
73
+ teamSlug: data.teamSlug,
74
+ name: data.name,
75
+ })
76
+ })
77
+
78
+ export const createInstallCommand = createServerFn({ method: 'POST' })
79
+ .middleware([publicErrorsMiddleware])
80
+ .validator((data: { serverId: string; teamSlug: string }) =>
81
+ serverInstallCommandSchema.parse(data),
82
+ )
83
+ .handler(async ({ data }) => {
84
+ const session = await requireAuthSession(
85
+ 'Sign in before enrolling a server.',
86
+ )
87
+
88
+ return createServerInstallCommand({
89
+ userId: session.user.id,
90
+ teamSlug: data.teamSlug,
91
+ serverId: data.serverId,
92
+ })
93
+ })
94
+
95
+ export const updateServer = createServerFn({ method: 'POST' })
96
+ .middleware([publicErrorsMiddleware])
97
+ .validator(
98
+ (data: {
99
+ settingsVersion: number
100
+ name: string
101
+ serverId: string
102
+ teamSlug: string
103
+ }) => updateServerSchema.parse(data),
104
+ )
105
+ .handler(async ({ data }) => {
106
+ const session = await requireAuthSession(
107
+ 'Sign in before updating a server.',
108
+ )
109
+
110
+ return updateTeamServer({
111
+ userId: session.user.id,
112
+ teamSlug: data.teamSlug,
113
+ serverId: data.serverId,
114
+ settingsVersion: data.settingsVersion,
115
+ name: data.name,
116
+ })
117
+ })
118
+
119
+ export const disableServer = createServerFn({ method: 'POST' })
120
+ .middleware([publicErrorsMiddleware])
121
+ .validator(
122
+ (data: { confirmationName: string; serverId: string; teamSlug: string }) =>
123
+ disableServerSchema.parse(data),
124
+ )
125
+ .handler(async ({ data }) => {
126
+ const session = await requireAuthSession(
127
+ 'Sign in before disabling a server.',
128
+ )
129
+
130
+ await disableTeamServer({
131
+ userId: session.user.id,
132
+ teamSlug: data.teamSlug,
133
+ serverId: data.serverId,
134
+ confirmationName: data.confirmationName,
135
+ })
136
+ })
137
+
138
+ export const enableServer = createServerFn({ method: 'POST' })
139
+ .middleware([publicErrorsMiddleware])
140
+ .validator((data: { serverId: string; teamSlug: string }) =>
141
+ enableServerSchema.parse(data),
142
+ )
143
+ .handler(async ({ data }) => {
144
+ const session = await requireAuthSession(
145
+ 'Sign in before enabling a server.',
146
+ )
147
+
148
+ await enableTeamServer({
149
+ userId: session.user.id,
150
+ teamSlug: data.teamSlug,
151
+ serverId: data.serverId,
152
+ })
153
+ })
154
+
155
+ export const getDeleteServerDiagnostic = createServerFn({ method: 'POST' })
156
+ .middleware([publicErrorsMiddleware])
157
+ .validator((data: { serverId: string; teamSlug: string }) =>
158
+ deleteServerDiagnosticSchema.parse(data),
159
+ )
160
+ .handler(async ({ data }) => {
161
+ const session = await requireAuthSession(
162
+ 'Sign in before deleting a server.',
163
+ )
164
+
165
+ return getServerDeleteDiagnostic({
166
+ userId: session.user.id,
167
+ teamSlug: data.teamSlug,
168
+ serverId: data.serverId,
169
+ })
170
+ })
171
+
172
+ export const deleteServer = createServerFn({ method: 'POST' })
173
+ .middleware([publicErrorsMiddleware])
174
+ .validator(
175
+ (data: {
176
+ cancelOngoingDeployments: boolean
177
+ confirmationName: string
178
+ serverId: string
179
+ teamSlug: string
180
+ }) => deleteServerSchema.parse(data),
181
+ )
182
+ .handler(async ({ data }) => {
183
+ const session = await requireAuthSession(
184
+ 'Sign in before deleting a server.',
185
+ )
186
+
187
+ await deleteTeamServer({
188
+ userId: session.user.id,
189
+ teamSlug: data.teamSlug,
190
+ serverId: data.serverId,
191
+ confirmationName: data.confirmationName,
192
+ cancelOngoingDeployments: data.cancelOngoingDeployments,
193
+ })
194
+ })
195
+
196
+ export const getTeamServerContext = createServerFn({ method: 'GET' })
197
+ .middleware([publicErrorsMiddleware])
198
+ .validator((data: z.input<typeof serverInstallCommandSchema>) =>
199
+ serverInstallCommandSchema.parse(data),
200
+ )
201
+ .handler(async ({ data }) => {
202
+ const session = await getAuthSession()
203
+
204
+ if (!session) {
205
+ return null
206
+ }
207
+
208
+ const team = await requireTeam(session.user.id, data.teamSlug)
209
+ const server = await requireServerForTeam({
210
+ teamId: team.id,
211
+ serverId: data.serverId,
212
+ })
213
+
214
+ return {
215
+ team,
216
+ server,
217
+ }
218
+ })
219
+
220
+ export const getTeamServerMetricHistory = createServerFn({ method: 'GET' })
221
+ .middleware([publicErrorsMiddleware])
222
+ .validator((data: z.input<typeof serverInstallCommandSchema>) =>
223
+ serverInstallCommandSchema.parse(data),
224
+ )
225
+ .handler(async ({ data }) => {
226
+ const session = await getAuthSession()
227
+ if (!session) return null
228
+ const team = await requireTeam(session.user.id, data.teamSlug)
229
+ return requireServerMetricHistoryForTeam({
230
+ teamId: team.id,
231
+ serverId: data.serverId,
232
+ })
233
+ })
@@ -0,0 +1,122 @@
1
+ import { releaseBackupCleanupRuns } from '#devpush/server/backups/queue-state'
2
+ import type { ApiMutationAdmission } from '#devpush/server/api/admission'
3
+ import { sql } from 'drizzle-orm'
4
+ import type { AtomicStore } from '#devpush/server/db/atomic'
5
+ import {
6
+ serverAvailabilityWindows,
7
+ servers,
8
+ teamMembers,
9
+ teams,
10
+ user,
11
+ } from '#devpush/server/db/schema'
12
+
13
+ export async function changeServerEnabled(
14
+ store: AtomicStore,
15
+ input: {
16
+ userId: string
17
+ teamId: string
18
+ serverId: string
19
+ enabled: boolean
20
+ now: Date
21
+ admission?: ApiMutationAdmission
22
+ },
23
+ ) {
24
+ const now = input.now.getTime()
25
+ const mayManage = sql`exists(select 1 from ${teamMembers} as member
26
+ join ${user} as manager on manager.id = member.user_id
27
+ join ${teams} as managed_team on managed_team.id = member.team_id
28
+ where member.team_id = ${input.teamId} and member.user_id = ${input.userId}
29
+ and member.role in ('owner', 'admin') and manager.status = 'active' and managed_team.status = 'active') and ${input.admission?.guard ?? sql`1`}`
30
+ if (input.enabled) {
31
+ // Insert before changing status, so a racing request cannot open a second
32
+ // interval after the first request has already enabled the server.
33
+ const [, changed] = await store.batch([
34
+ store.run(sql`insert into ${serverAvailabilityWindows} (id, server_id, started_at, created_at)
35
+ select ${crypto.randomUUID()}, id, ${now}, ${now} from ${servers}
36
+ where id = ${input.serverId} and team_id = ${input.teamId} and status = 'disabled' and ${mayManage}
37
+ and not exists(select 1 from ${serverAvailabilityWindows} where server_id = ${input.serverId} and ended_at is null)`),
38
+ store.all(sql`update ${servers} set status = 'pending', updated_at = ${now}
39
+ where id = ${input.serverId} and team_id = ${input.teamId} and status = 'disabled' and ${mayManage} returning id`),
40
+ ...(input.admission?.statements ?? []),
41
+ ])
42
+ return changed.length === 1
43
+ }
44
+ const [changed] = await store.batch([
45
+ store.all(sql`update ${servers} set status = 'disabled', enrollment_token_hash = null,
46
+ enrollment_token_expires_at = null, updated_at = ${now}
47
+ where id = ${input.serverId} and team_id = ${input.teamId} and status <> 'deleted' and ${mayManage} returning id`),
48
+ ...(input.admission?.statements ?? []),
49
+ store.run(sql`update ${serverAvailabilityWindows} set ended_at = ${now}
50
+ where server_id = ${input.serverId} and ended_at is null and ${mayManage}
51
+ and exists(select 1 from ${servers} where id = ${input.serverId} and team_id = ${input.teamId} and status = 'disabled')`),
52
+ ])
53
+ return changed.length === 1
54
+ }
55
+
56
+ export async function deleteServerState(
57
+ store: AtomicStore,
58
+ input: {
59
+ userId: string
60
+ teamId: string
61
+ serverId: string
62
+ cancelOngoingDeployments: boolean
63
+ now: Date
64
+ admission?: ApiMutationAdmission
65
+ },
66
+ ) {
67
+ const now = input.now.getTime()
68
+ const guard = sql`exists(select 1 from servers s
69
+ join teams t on t.id = s.team_id
70
+ join team_members m on m.team_id = t.id
71
+ join user u on u.id = m.user_id
72
+ where s.id = ${input.serverId} and s.team_id = ${input.teamId} and s.status <> 'deleted'
73
+ and t.status = 'active' and u.status = 'active'
74
+ and m.user_id = ${input.userId} and m.role in ('owner', 'admin')) and ${input.admission?.guard ?? sql`1`}`
75
+ const message = 'Canceled because the server was deleted.'
76
+ const changes: [
77
+ ReturnType<AtomicStore['run']>,
78
+ ...ReturnType<AtomicStore['run']>[],
79
+ ] = [
80
+ store.run(
81
+ releaseBackupCleanupRuns({
82
+ jobIds: sql`select id from agent_jobs where server_id = ${input.serverId}`,
83
+ guard,
84
+ message,
85
+ now: input.now,
86
+ }),
87
+ ),
88
+ store.run(sql`update agent_jobs set status = 'canceled', error_message = ${message},
89
+ finished_at = ${now}, updated_at = ${now} where server_id = ${input.serverId}
90
+ and type in ('backup', 'restore', 'backup_cleanup') and status in ('queued', 'running') and ${guard}`),
91
+ store.run(sql`update backup_runs set status = 'canceled', error_message = ${message},
92
+ finished_at = ${now}, updated_at = ${now} where server_id = ${input.serverId}
93
+ and status in ('queued', 'running') and ${guard}`),
94
+ ]
95
+ if (input.cancelOngoingDeployments)
96
+ changes.push(
97
+ store.run(sql`update deployments set status = 'canceled', phase = 'canceled',
98
+ error_message = ${message}, finished_at = ${now} where server_id = ${input.serverId}
99
+ and status in ('queued', 'running') and ${guard}`),
100
+ store.run(sql`update agent_jobs set status = 'canceled', error_message = ${message},
101
+ finished_at = ${now}, updated_at = ${now} where server_id = ${input.serverId}
102
+ and type = 'deploy' and status in ('queued', 'running') and ${guard}`),
103
+ )
104
+ const results = await store.batch([
105
+ ...changes,
106
+ store.run(
107
+ sql`delete from server_metrics where server_id = ${input.serverId} and ${guard}`,
108
+ ),
109
+ store.run(
110
+ sql`delete from server_heartbeats where server_id = ${input.serverId} and ${guard}`,
111
+ ),
112
+ store.run(sql`update server_availability_windows set ended_at = ${now}
113
+ where server_id = ${input.serverId} and ended_at is null and ${guard}`),
114
+ store.all(sql`update servers set status = 'deleted', updated_at = ${now}
115
+ ${input.cancelOngoingDeployments ? sql`, enrollment_token_hash = null, enrollment_token_expires_at = null` : sql``}
116
+ where id = ${input.serverId} and ${guard} returning id`),
117
+ ...(input.admission?.statements ?? []),
118
+ ])
119
+ // The optional deployment cancellations change the number of statements.
120
+ const changed = results.at(-1 - (input.admission?.statements.length ?? 0))
121
+ return Array.isArray(changed) && changed.length === 1
122
+ }
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod'
2
+
3
+ const metricValuesSchema = z.object({
4
+ cpuPercent: z.number().min(0).max(100).optional(),
5
+ memoryUsedBytes: z.number().int().min(0).optional(),
6
+ memoryTotalBytes: z.number().int().min(0).optional(),
7
+ diskUsedBytes: z.number().int().min(0).optional(),
8
+ diskTotalBytes: z.number().int().min(0).optional(),
9
+ networkReceiveBytesPerSecond: z.number().min(0).optional(),
10
+ networkTransmitBytesPerSecond: z.number().min(0).optional(),
11
+ diskReadBytesPerSecond: z.number().min(0).optional(),
12
+ diskWriteBytesPerSecond: z.number().min(0).optional(),
13
+ })
14
+
15
+ export const metricSampleSchema = z.object({
16
+ id: z.string().regex(/^[a-f0-9]{32}$/),
17
+ observedAt: z.string().datetime(),
18
+ metrics: metricValuesSchema.refine(
19
+ (values) => Object.values(values).some(Number.isFinite),
20
+ 'Metric sample must contain a measurement.',
21
+ ),
22
+ })
23
+
24
+ export type AgentMetricSample = z.infer<typeof metricSampleSchema>