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,337 @@
1
+ import { createHash, randomUUID } from 'node:crypto'
2
+
3
+ import { and, desc, eq, inArray, lt, sql } from 'drizzle-orm'
4
+
5
+ import { db, logsDb } from '#devpush/server/db'
6
+ import { runtimeLogs } from '#devpush/server/db/log-schema'
7
+ import { deployments, environments, projects } from '#devpush/server/db/schema'
8
+ import { getPluginServices } from '#devpush/server/plugins/services.server'
9
+ import { admitRuntimeLogs, reserveLogBudget } from '#devpush/server/logs/budget'
10
+ import { requireAgentServer } from '#devpush/server/servers/service'
11
+ import {
12
+ runtimeLogRetentionMs,
13
+ runtimeLogPruneBatchSize,
14
+ } from '#devpush/server/logs/retention'
15
+
16
+ export type RuntimeLogLine = {
17
+ runtimeId: string
18
+ service: string
19
+ observedAt: string
20
+ content: string
21
+ }
22
+
23
+ const maxRuntimeLogLines = 500
24
+ const maxRuntimeLogContentLength = 16_384
25
+ const maxRuntimeLogRowsPerRuntime = 10_000
26
+
27
+ export async function recordRuntimeLogs({
28
+ serverId,
29
+ token,
30
+ logs,
31
+ }: {
32
+ serverId: string
33
+ token: string
34
+ logs: Array<RuntimeLogLine>
35
+ }) {
36
+ const server = await requireAgentServer({ serverId, token })
37
+
38
+ const normalizedLogs = normalizeRuntimeLogs(logs)
39
+
40
+ if (normalizedLogs.length === 0) {
41
+ return { ok: true, accepted: 0, inserted: 0 }
42
+ }
43
+
44
+ const assignments = await listAllowedRuntimes({
45
+ serverId,
46
+ runtimeIds: Array.from(new Set(normalizedLogs.map((log) => log.runtimeId))),
47
+ })
48
+ let rows = normalizedLogs.flatMap((log) => {
49
+ const assignment = assignments.get(log.runtimeId)
50
+ if (!assignment) return []
51
+ return [
52
+ {
53
+ id: randomUUID(),
54
+ projectId: assignment.projectId,
55
+ environmentId: assignment.environmentId,
56
+ serverId,
57
+ runtimeId: log.runtimeId,
58
+ service: log.service,
59
+ observedAt: new Date(Math.min(log.observedAt.getTime(), Date.now())),
60
+ content: log.content,
61
+ searchContent: log.content.toLowerCase(),
62
+ contentHash: hashRuntimeLogLine(log),
63
+ },
64
+ ]
65
+ })
66
+
67
+ if (rows.length === 0) {
68
+ return { ok: true, accepted: 0, inserted: 0 }
69
+ }
70
+
71
+ const acknowledged = rows.length
72
+ let dropped = 0
73
+ const limits = getPluginServices().runtimeLogLimits?.(server.teamId)
74
+ if (limits) {
75
+ // Replayed batches must not consume another allowance after their rows were stored.
76
+ const known = new Set<string>()
77
+ for (let start = 0; start < rows.length; start += 80) {
78
+ const existing = await logsDb
79
+ .select({ hash: runtimeLogs.contentHash })
80
+ .from(runtimeLogs)
81
+ .where(
82
+ and(
83
+ eq(runtimeLogs.serverId, serverId),
84
+ inArray(
85
+ runtimeLogs.contentHash,
86
+ rows.slice(start, start + 80).map((row) => row.contentHash),
87
+ ),
88
+ ),
89
+ )
90
+ for (const row of existing) known.add(row.hash)
91
+ }
92
+ rows = rows.filter((row) => {
93
+ if (known.has(row.contentHash)) return false
94
+ known.add(row.contentHash)
95
+ return true
96
+ })
97
+ if (rows.length && !(await admitRuntimeLogs(server.teamId, limits, rows))) {
98
+ dropped = rows.length
99
+ const first = rows[0]!
100
+ rows = []
101
+ // One visible notice per workspace/day. Never block agent delivery on a quota refusal.
102
+ if (
103
+ await reserveLogBudget({
104
+ teamId: server.teamId,
105
+ kind: 'notice',
106
+ amount: 1,
107
+ limit: 1,
108
+ windowMs: 86_400_000,
109
+ })
110
+ ) {
111
+ const content =
112
+ 'Runtime log limit reached. Some logs were dropped; collection resumes when the allowance resets. Applications and deployments are unaffected.'
113
+ rows.push({
114
+ ...first,
115
+ id: randomUUID(),
116
+ service: 'devpush',
117
+ content,
118
+ searchContent: content.toLowerCase(),
119
+ observedAt: new Date(),
120
+ contentHash: createHash('sha256')
121
+ .update(
122
+ `log-limit:${server.teamId}:${Math.floor(Date.now() / 86_400_000)}`,
123
+ )
124
+ .digest('hex'),
125
+ })
126
+ }
127
+ }
128
+ }
129
+
130
+ // Keep each statement below D1's parameter limit. A partially completed
131
+ // request is safe to replay because each line has a stable content hash.
132
+ let inserted = 0
133
+ for (let offset = 0; offset < rows.length; offset += 32) {
134
+ const statements = []
135
+ for (
136
+ let start = offset;
137
+ start < Math.min(offset + 32, rows.length);
138
+ start += 8
139
+ ) {
140
+ statements.push(
141
+ logsDb
142
+ .insert(runtimeLogs)
143
+ .values(rows.slice(start, Math.min(start + 8, rows.length)))
144
+ .onConflictDoNothing({
145
+ target: [
146
+ runtimeLogs.serverId,
147
+ runtimeLogs.runtimeId,
148
+ runtimeLogs.contentHash,
149
+ ],
150
+ })
151
+ .returning({ id: runtimeLogs.id }),
152
+ )
153
+ }
154
+ const [first, ...rest] = statements
155
+ if (first) {
156
+ const batch = await logsDb.batch([first, ...rest])
157
+ inserted += batch.reduce((count, result) => count + result.length, 0)
158
+ }
159
+ }
160
+
161
+ await pruneRuntimeLogs({
162
+ serverId,
163
+ runtimeIds: Array.from(new Set(rows.map((row) => row.runtimeId))),
164
+ now: new Date(),
165
+ })
166
+
167
+ return {
168
+ ok: true,
169
+ accepted: acknowledged,
170
+ inserted,
171
+ ...(dropped ? { dropped } : {}),
172
+ }
173
+ }
174
+
175
+ function normalizeRuntimeLogs(logs: Array<RuntimeLogLine>) {
176
+ return logs
177
+ .slice(0, maxRuntimeLogLines)
178
+ .map((log) => {
179
+ const runtimeId = log.runtimeId.trim()
180
+ const service = log.service.trim() || 'runtime'
181
+ const content = log.content.slice(0, maxRuntimeLogContentLength)
182
+ const observedAt = new Date(log.observedAt)
183
+
184
+ if (
185
+ runtimeId.length === 0 ||
186
+ runtimeId.length > 128 ||
187
+ service.length > 128 ||
188
+ content.length === 0 ||
189
+ Number.isNaN(observedAt.getTime())
190
+ ) {
191
+ return null
192
+ }
193
+
194
+ return {
195
+ runtimeId,
196
+ service,
197
+ observedAt,
198
+ content,
199
+ }
200
+ })
201
+ .filter((log): log is NonNullable<typeof log> => log !== null)
202
+ }
203
+
204
+ async function listAllowedRuntimes({
205
+ serverId,
206
+ runtimeIds,
207
+ }: {
208
+ serverId: string
209
+ runtimeIds: Array<string>
210
+ }) {
211
+ const assignments = new Map<
212
+ string,
213
+ { projectId: string; environmentId: string }
214
+ >()
215
+ for (let offset = 0; offset < runtimeIds.length; offset += 80) {
216
+ const rows = await db
217
+ .select({
218
+ runtimeId: environments.runtimeId,
219
+ projectId: projects.id,
220
+ environmentId: environments.id,
221
+ })
222
+ .from(environments)
223
+ .innerJoin(projects, eq(projects.id, environments.projectId))
224
+ .where(
225
+ and(
226
+ eq(
227
+ sql`coalesce(${environments.serverId}, ${projects.defaultServerId})`,
228
+ serverId,
229
+ ),
230
+ eq(environments.status, 'active'),
231
+ eq(projects.status, 'active'),
232
+ inArray(
233
+ environments.runtimeId,
234
+ runtimeIds.slice(offset, offset + 80),
235
+ ),
236
+ ),
237
+ )
238
+ for (const row of rows) assignments.set(row.runtimeId, row)
239
+ // A durable capture can arrive after replacement or project deletion.
240
+ // Historical deployment ownership, never the agent's claim alone, permits
241
+ // delivery from the original server. Existing display/retention guards stay.
242
+ const historical = await db
243
+ .selectDistinct({
244
+ runtimeId: deployments.runtimeId,
245
+ projectId: projects.id,
246
+ environmentId: environments.id,
247
+ })
248
+ .from(deployments)
249
+ .innerJoin(environments, eq(environments.id, deployments.environmentId))
250
+ .innerJoin(projects, eq(projects.id, environments.projectId))
251
+ .where(
252
+ and(
253
+ eq(deployments.serverId, serverId),
254
+ inArray(deployments.runtimeId, runtimeIds.slice(offset, offset + 80)),
255
+ ),
256
+ )
257
+ for (const row of historical) {
258
+ if (!assignments.has(row.runtimeId)) assignments.set(row.runtimeId, row)
259
+ }
260
+ }
261
+ return assignments
262
+ }
263
+
264
+ async function pruneRuntimeLogs({
265
+ serverId,
266
+ runtimeIds,
267
+ now,
268
+ }: {
269
+ serverId: string
270
+ runtimeIds: Array<string>
271
+ now: Date
272
+ }) {
273
+ if (runtimeIds.length === 0) {
274
+ return
275
+ }
276
+
277
+ const cutoff = new Date(now.getTime() - runtimeLogRetentionMs)
278
+
279
+ for (const runtimeId of runtimeIds) {
280
+ const scope = and(
281
+ eq(runtimeLogs.serverId, serverId),
282
+ eq(runtimeLogs.runtimeId, runtimeId),
283
+ )
284
+ // Bound cleanup work independently of how long the agent was disconnected.
285
+ await logsDb.delete(runtimeLogs).where(
286
+ inArray(
287
+ runtimeLogs.id,
288
+ logsDb
289
+ .select({ id: runtimeLogs.id })
290
+ .from(runtimeLogs)
291
+ .where(and(scope, lt(runtimeLogs.observedAt, cutoff)))
292
+ .orderBy(runtimeLogs.observedAt, runtimeLogs.id)
293
+ .limit(runtimeLogPruneBatchSize),
294
+ ),
295
+ )
296
+ await logsDb
297
+ .delete(runtimeLogs)
298
+ .where(
299
+ inArray(
300
+ runtimeLogs.id,
301
+ logsDb
302
+ .select({ id: runtimeLogs.id })
303
+ .from(runtimeLogs)
304
+ .where(scope)
305
+ .orderBy(
306
+ desc(runtimeLogs.observedAt),
307
+ desc(runtimeLogs.createdAt),
308
+ desc(runtimeLogs.id),
309
+ )
310
+ .offset(maxRuntimeLogRowsPerRuntime)
311
+ .limit(runtimeLogPruneBatchSize),
312
+ ),
313
+ )
314
+ }
315
+ }
316
+
317
+ function hashRuntimeLogLine({
318
+ runtimeId,
319
+ service,
320
+ observedAt,
321
+ content,
322
+ }: {
323
+ runtimeId: string
324
+ service: string
325
+ observedAt: Date
326
+ content: string
327
+ }) {
328
+ return createHash('sha256')
329
+ .update(runtimeId)
330
+ .update('\0')
331
+ .update(service)
332
+ .update('\0')
333
+ .update(observedAt.toISOString())
334
+ .update('\0')
335
+ .update(content)
336
+ .digest('hex')
337
+ }
@@ -0,0 +1,125 @@
1
+ import { sql } from 'drizzle-orm'
2
+ import type { SQL } from 'drizzle-orm'
3
+ import type { AtomicStore } from '#devpush/server/db/atomic'
4
+ import {
5
+ agentJobs,
6
+ agentRuntimeObservations,
7
+ deployments,
8
+ environments,
9
+ projects,
10
+ } from '#devpush/server/db/schema'
11
+ import { cleanupVolumeRetentionMs } from './cleanup'
12
+ import type { ReportedRuntime } from './reconciliation'
13
+
14
+ export async function reconcileRuntimeState(
15
+ store: AtomicStore,
16
+ input: {
17
+ serverId: string
18
+ runtimeIds: string[]
19
+ runtimes: ReportedRuntime[]
20
+ now: Date
21
+ },
22
+ ) {
23
+ const now = input.now.getTime()
24
+ const services = new Map(
25
+ input.runtimes.map((runtime) => [runtime.runtimeId, runtime.services]),
26
+ )
27
+ const reportedServices = JSON.stringify([...services.keys()])
28
+ const observations = []
29
+ for (let offset = 0; offset < input.runtimeIds.length; offset += 8) {
30
+ const rows = input.runtimeIds.slice(offset, offset + 8).map(
31
+ (runtimeId) => sql`(
32
+ ${crypto.randomUUID()}, ${input.serverId}, ${runtimeId},
33
+ ${services.has(runtimeId) ? JSON.stringify(services.get(runtimeId)) : null}, ${now}, ${now}, ${now}, ${now})`,
34
+ )
35
+ observations.push(
36
+ store.run(sql`insert into ${agentRuntimeObservations}
37
+ (id, server_id, runtime_id, services, first_seen_at, last_seen_at, created_at, updated_at)
38
+ values ${sql.join(rows, sql`, `)} on conflict(server_id, runtime_id) do update set
39
+ last_seen_at = excluded.last_seen_at, updated_at = excluded.updated_at,
40
+ services = case when runtime_id in (select value from json_each(${reportedServices}))
41
+ then excluded.services else services end`),
42
+ )
43
+ }
44
+ // Decide whether a runtime is still needed inside the same write batch that
45
+ // enqueues cleanup. Assignment or deployment changes cannot race this check.
46
+ const needed = sql`exists(select 1 from ${environments} as env join ${projects} as project on project.id = env.project_id
47
+ where env.runtime_id = ob.runtime_id and env.status = 'active'
48
+ and coalesce(env.server_id, project.default_server_id) = ${input.serverId})
49
+ or exists(select 1 from ${deployments} as deployment
50
+ where deployment.runtime_id = ob.runtime_id and deployment.server_id = ${input.serverId}
51
+ and deployment.status in ('queued', 'running'))`
52
+ const orphan = sql`substr(ob.runtime_id, 1, 3) = 'dp_'
53
+ and not (${needed})
54
+ and not exists(select 1 from ${agentJobs} as job
55
+ where job.server_id = ${input.serverId} and job.type in ('deprovision', 'purge')
56
+ and job.status in ('queued', 'running') and json_type(job.payload, '$.runtimeIds') = 'array'
57
+ and exists(select 1 from json_each(job.payload, '$.runtimeIds') as runtime
58
+ where runtime.type = 'text' and runtime.value = ob.runtime_id))`
59
+ const cleanupId = crypto.randomUUID()
60
+ const purgeId = crypto.randomUUID()
61
+ const queue = (
62
+ id: string,
63
+ type: 'deprovision' | 'purge',
64
+ eligible: SQL,
65
+ reason: string,
66
+ ) =>
67
+ store.run(sql`
68
+ insert into ${agentJobs} (id, type, status, server_id, payload, max_attempts, available_at, created_at, updated_at)
69
+ select ${id}, ${type}, 'queued', ${input.serverId},
70
+ json_object('schemaVersion', 1, 'runtimeIds', json_group_array(runtime_id),
71
+ 'removeVolumes', json(${type === 'purge' ? 'true' : 'false'}), 'reason', ${reason}),
72
+ 10, ${now}, ${now}, ${now}
73
+ from (select ob.runtime_id from ${agentRuntimeObservations} as ob
74
+ where ob.server_id = ${input.serverId} and ${eligible} and ${orphan} order by ob.runtime_id limit 200)
75
+ having count(*) > 0`)
76
+ const cleanup = queue(
77
+ cleanupId,
78
+ 'deprovision',
79
+ sql`ob.cleanup_queued_at is null and ob.first_seen_at <= ${now - 10 * 60 * 1000}
80
+ and ob.runtime_id in (select value from json_each(${JSON.stringify(input.runtimeIds)}))`,
81
+ 'devpush detected a Compose runtime that is no longer assigned.',
82
+ )
83
+ const markCleanup =
84
+ store.all(sql`update ${agentRuntimeObservations} set cleanup_queued_at = ${now}, updated_at = ${now}
85
+ where server_id = ${input.serverId} and runtime_id in (
86
+ select runtime.value from ${agentJobs} as job, json_each(job.payload, '$.runtimeIds') as runtime where job.id = ${cleanupId})
87
+ returning runtime_id`)
88
+ const purge = queue(
89
+ purgeId,
90
+ 'purge',
91
+ sql`ob.cleanup_queued_at <= ${now - cleanupVolumeRetentionMs} and ob.purge_queued_at is null`,
92
+ 'devpush detected retained volumes for a Compose runtime that is no longer assigned.',
93
+ )
94
+ const markPurge =
95
+ store.all(sql`update ${agentRuntimeObservations} set purge_queued_at = ${now}, updated_at = ${now}
96
+ where server_id = ${input.serverId} and runtime_id in (
97
+ select runtime.value from ${agentJobs} as job, json_each(job.payload, '$.runtimeIds') as runtime where job.id = ${purgeId})
98
+ returning runtime_id`)
99
+ // A reused runtime starts a new orphan lifecycle when it is detached again.
100
+ // Pending cleanup alone is not ownership and must not reset the grace period.
101
+ const resetOwned = store.run(sql`update ${agentRuntimeObservations} as ob
102
+ set cleanup_queued_at = null, purge_queued_at = null, first_seen_at = ${now}, updated_at = ${now}
103
+ where ob.server_id = ${input.serverId} and (${needed})`)
104
+ const tail = [resetOwned, cleanup, markCleanup, purge, markPurge] as const
105
+ const [first, ...rest] = observations
106
+ const results = first
107
+ ? await store.batch([first, ...rest, ...tail])
108
+ : await store.batch(tail)
109
+ const runtimeIds: string[] = []
110
+ for (const result of [results.at(-3), results.at(-1)]) {
111
+ if (!Array.isArray(result))
112
+ throw new Error('Invalid runtime reconciliation result.')
113
+ for (const row of result as unknown[]) {
114
+ const id = Array.isArray(row)
115
+ ? row[0]
116
+ : typeof row === 'object' && row !== null && 'runtime_id' in row
117
+ ? row.runtime_id
118
+ : undefined
119
+ if (typeof id !== 'string')
120
+ throw new Error('Invalid runtime reconciliation result.')
121
+ runtimeIds.push(id)
122
+ }
123
+ }
124
+ return runtimeIds
125
+ }
@@ -0,0 +1,12 @@
1
+ import type { SQL } from 'drizzle-orm'
2
+ import type { AtomicStore } from '#devpush/server/db/atomic'
3
+
4
+ // Domain services commit these statements in the same transaction as their
5
+ // effect. The guard is checked by the effect itself, after any provider reads.
6
+ export type ApiMutationAdmission = {
7
+ guard: SQL
8
+ statements: ReturnType<AtomicStore['run']>[]
9
+ resourceId: string
10
+ secondaryResourceId?: string
11
+ idempotencyId?: string
12
+ }
@@ -0,0 +1,115 @@
1
+ import { sql } from 'drizzle-orm'
2
+ import { apiRoleRank, apiScopeDefinitions } from '#devpush/lib/api-key-schema'
3
+ import { PublicError } from '#devpush/lib/public-error'
4
+ import type { ApiScope } from '#devpush/lib/api-key-schema'
5
+ import type { TeamRole } from '#devpush/lib/team-role'
6
+ import {
7
+ apiGrants,
8
+ apiKeys,
9
+ projects,
10
+ teamMembers,
11
+ teams,
12
+ user,
13
+ } from '#devpush/server/db/schema'
14
+
15
+ export type ApiPrincipal = {
16
+ userId: string
17
+ teamId: string
18
+ membershipId: string
19
+ roleCeiling: TeamRole
20
+ projectIds: string[] | null
21
+ scopes: string[]
22
+ credentialId: string
23
+ credentialKind?: 'api_key' | 'oauth'
24
+ providerGrantId?: string | null
25
+ }
26
+
27
+ export function requireApiScope(
28
+ principal: ApiPrincipal,
29
+ scope: string,
30
+ minimumRole: TeamRole = 'member',
31
+ ) {
32
+ const definition = Object.hasOwn(apiScopeDefinitions, scope)
33
+ ? apiScopeDefinitions[scope as ApiScope]
34
+ : undefined
35
+ if (
36
+ !definition ||
37
+ !principal.scopes.includes(scope) ||
38
+ apiRoleRank[principal.roleCeiling] <
39
+ Math.max(apiRoleRank[definition.minimumRole], apiRoleRank[minimumRole])
40
+ ) {
41
+ throw new PublicError('FORBIDDEN')
42
+ }
43
+ }
44
+
45
+ export function requireApiProject(principal: ApiPrincipal, projectId: string) {
46
+ if (
47
+ principal.projectIds !== null &&
48
+ !principal.projectIds.includes(projectId)
49
+ ) {
50
+ throw new PublicError('FORBIDDEN')
51
+ }
52
+ }
53
+
54
+ /** Recheck live credentials and membership at the SQL admission point. */
55
+ export function apiAuthorizationCondition(
56
+ principal: ApiPrincipal,
57
+ options: {
58
+ scope?: string
59
+ projectId?: string
60
+ teamId?: string
61
+ minimumRole?: TeamRole
62
+ allowDeletedProject?: boolean
63
+ } = {},
64
+ ) {
65
+ if (options.teamId && options.teamId !== principal.teamId) return sql`0`
66
+ if (
67
+ options.projectId &&
68
+ principal.projectIds !== null &&
69
+ !principal.projectIds.includes(options.projectId)
70
+ )
71
+ return sql`0`
72
+ const definition =
73
+ options.scope && Object.hasOwn(apiScopeDefinitions, options.scope)
74
+ ? apiScopeDefinitions[options.scope as ApiScope]
75
+ : undefined
76
+ if (
77
+ options.scope &&
78
+ (!definition || !principal.scopes.includes(options.scope))
79
+ )
80
+ return sql`0`
81
+ const rank = Math.max(
82
+ apiRoleRank[options.minimumRole ?? 'member'],
83
+ apiRoleRank[definition?.minimumRole ?? 'member'],
84
+ )
85
+ if (apiRoleRank[principal.roleCeiling] < rank) return sql`0`
86
+ const credential = principal.credentialKind === 'oauth' ? apiGrants : apiKeys
87
+ const expirationGuard =
88
+ principal.credentialKind === 'oauth'
89
+ ? sql`credential.expires_at > cast(unixepoch('subsec') * 1000 as integer)`
90
+ : sql`(credential.expires_at is null or credential.expires_at > cast(unixepoch('subsec') * 1000 as integer))`
91
+ const scopeGuard = options.scope
92
+ ? sql`exists(select 1 from json_each(credential.scopes) where value = ${options.scope})`
93
+ : sql`1`
94
+ const projectGuard = options.projectId
95
+ ? sql`exists(select 1 from ${projects} as granted_project where granted_project.id = ${options.projectId} and granted_project.team_id = ${principal.teamId} and ${options.allowDeletedProject ? sql`granted_project.status in ('active','deleted')` : sql`granted_project.status = 'active'`})
96
+ and (credential.project_ids is null or exists(select 1 from json_each(credential.project_ids) where value = ${options.projectId}))`
97
+ : sql`1`
98
+ const roleRank = (column: ReturnType<typeof sql>) =>
99
+ sql`case ${column} when 'owner' then 2 when 'admin' then 1 else 0 end`
100
+ return sql`exists (
101
+ select 1 from ${credential} as credential
102
+ join ${teamMembers} as membership on membership.id = credential.membership_id
103
+ join ${user} as actor on actor.id = credential.user_id
104
+ join ${teams} as actor_team on actor_team.id = credential.team_id
105
+ where credential.id = ${principal.credentialId}
106
+ and credential.user_id = ${principal.userId} and credential.team_id = ${principal.teamId}
107
+ and credential.membership_id = ${principal.membershipId}
108
+ and membership.user_id = credential.user_id and membership.team_id = credential.team_id
109
+ and actor.status = 'active' and actor_team.status = 'active'
110
+ and credential.revoked_at is null and ${expirationGuard}
111
+ and ${roleRank(sql`membership.role`)} >= ${rank}
112
+ and ${roleRank(sql`credential.role_ceiling`)} >= ${rank}
113
+ and ${scopeGuard} and ${projectGuard}
114
+ )`
115
+ }