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,355 @@
1
+ import type {
2
+ BackupStorageReference,
3
+ BackupStorageSummary,
4
+ } from '#devpush/lib/backup-storage-schema'
5
+ import type {
6
+ BackupDatabaseDump,
7
+ BackupRetentionPolicy,
8
+ BackupVolumeSelection,
9
+ } from '#devpush/server/backups/service'
10
+ import type { getResourceColor } from '#devpush/server/resources/color'
11
+ import type { ServerStatus } from '#devpush/server/servers/status'
12
+ import type {
13
+ ProjectSummary,
14
+ ServerSummary,
15
+ TeamSummary,
16
+ UserContext,
17
+ } from '#devpush/server/teams/types'
18
+ import type { ProjectComposeMetadata } from './compose-metadata'
19
+ import type { ProjectRuntimeConfig } from '#devpush/lib/project-runtime-config'
20
+
21
+ export type RepositorySelectionContext = {
22
+ github:
23
+ | {
24
+ connected: true
25
+ login: string
26
+ appConfigured: boolean
27
+ installUrl: string | null
28
+ installations: Array<{
29
+ id: number
30
+ accountLogin: string
31
+ accountType: string
32
+ repositorySelection: string
33
+ }>
34
+ repositories: Array<{
35
+ id: number
36
+ installationId: number
37
+ name: string
38
+ fullName: string
39
+ owner: string
40
+ private: boolean
41
+ defaultBranch: string
42
+ updatedAt: string
43
+ permissions: {
44
+ push: boolean
45
+ }
46
+ }>
47
+ }
48
+ | {
49
+ connected: false
50
+ login: null
51
+ appConfigured: boolean
52
+ installUrl: string | null
53
+ installations: []
54
+ repositories: []
55
+ }
56
+ }
57
+
58
+ export type CreatedProject = {
59
+ id: string
60
+ name: string
61
+ githubRepo: string
62
+ composePath: string | null
63
+ }
64
+
65
+ export type ProjectPublicRoute = {
66
+ host: string
67
+ service: string
68
+ port: number
69
+ path: string
70
+ https: boolean
71
+ stripPrefix: boolean
72
+ }
73
+
74
+ export type ProjectRoute = {
75
+ service: string
76
+ port: number
77
+ path: string
78
+ stripPrefix: boolean
79
+ environmentId?: string
80
+ }
81
+
82
+ export type ProjectDomain = {
83
+ id: string
84
+ host: string
85
+ environmentId: string
86
+ basePath: string | null
87
+ https: boolean
88
+ status: 'pending' | 'active' | 'disabled' | 'failed'
89
+ message: string | null
90
+ verifiedAt: Date | null
91
+ }
92
+
93
+ export type ProjectEnvironmentVariable = {
94
+ key: string
95
+ value: string
96
+ environment?: string
97
+ }
98
+
99
+ export type ProjectEnvironmentConfig = {
100
+ id: string
101
+ name: string
102
+ slug: string
103
+ branch: string
104
+ color: string
105
+ position: number
106
+ status: 'active' | 'deleted'
107
+ effectiveServerId: string | null
108
+ serverId: string | null
109
+ defaultHost?: string | null
110
+ defaultRouteHttps?: boolean
111
+ }
112
+
113
+ export type ProjectBackupConfig = {
114
+ settingsVersion: number
115
+ id: string
116
+ name: string
117
+ environmentId: string
118
+ enabled: boolean
119
+ schedule: ProjectBackupSchedule
120
+ destination: BackupStorageReference
121
+ retention: BackupRetentionPolicy
122
+ volumes: BackupVolumeSelection
123
+ databaseDumps: Array<BackupDatabaseDump>
124
+ }
125
+
126
+ export type BackupDestinationInput = BackupStorageReference
127
+
128
+ export type ProjectBackupSchedule = {
129
+ cron: string
130
+ }
131
+
132
+ export type ProjectBackupRun = {
133
+ id: string
134
+ configId: string | null
135
+ localRecoveryAvailable: boolean
136
+ status: 'queued' | 'running' | 'succeeded' | 'failed' | 'canceled'
137
+ environmentId: string
138
+ environmentName: string
139
+ environmentColor: string
140
+ serverId: string | null
141
+ serverName: string | null
142
+ errorMessage: string | null
143
+ startedAt: string | null
144
+ finishedAt: string | null
145
+ createdAt: string
146
+ databaseTargets?: Array<BackupDatabaseDump>
147
+ artifacts: Array<ProjectBackupArtifact>
148
+ }
149
+
150
+ export type ProjectBackupRestore = {
151
+ artifacts?: Array<{
152
+ artifactId: string
153
+ type: 'volume' | 'database'
154
+ targetVolume?: string
155
+ targetService?: string
156
+ }>
157
+
158
+ serverId?: string | null
159
+ serverName?: string | null
160
+ id: string
161
+ backupRunId: string
162
+ environmentId: string
163
+ status: ProjectBackupRun['status']
164
+ errorMessage: string | null
165
+ createdAt: string
166
+ startedAt: string | null
167
+ finishedAt: string | null
168
+ }
169
+
170
+ export type ProjectBackupArtifact = {
171
+ id: string
172
+ type: 'manifest' | 'volume' | 'database'
173
+ storage: 'local' | 'remote'
174
+ objectKey: string
175
+ checksumSha256: string
176
+ sizeBytes: number
177
+ createdAt: string
178
+ }
179
+
180
+ export type DeploymentSummary = {
181
+ isServing: boolean
182
+ id: string
183
+ status: 'queued' | 'running' | 'succeeded' | 'failed' | 'canceled'
184
+ phase: string | null
185
+ trigger: 'user' | 'webhook' | 'api' | 'rollback'
186
+ gitSha: string
187
+ gitBranch: string
188
+ githubRepo: string
189
+ deploymentHost: string | null
190
+ deploymentRouteHttps: boolean
191
+ commitMessage: string | null
192
+ errorMessage: string | null
193
+ createdAt: string
194
+ startedAt: string | null
195
+ finishedAt: string | null
196
+ environmentName: string
197
+ environmentSlug: string
198
+ environmentColor: string
199
+ serverId: string | null
200
+ serverName: string
201
+ serverStatus: ServerStatus
202
+ projectName: string
203
+ projectColor: ReturnType<typeof getResourceColor>
204
+ }
205
+
206
+ export type ProjectContext = {
207
+ userContext: UserContext
208
+ team: TeamSummary
209
+ project: {
210
+ settingsVersion: number
211
+ id: string
212
+ name: string
213
+ githubInstallationId: number | null
214
+ githubRepoId: number | null
215
+ githubRepo: string
216
+ defaultServerId: string | null
217
+ avatarUrl: string | null
218
+ runtimeConfig: ProjectRuntimeConfig
219
+ color: ReturnType<typeof getResourceColor>
220
+ }
221
+ teamProjects: Array<ProjectSummary>
222
+ routes: Array<ProjectRoute>
223
+ composeMetadata: ProjectComposeMetadata | null
224
+ domains: Array<ProjectDomain>
225
+ envVars: Array<ProjectEnvironmentVariable>
226
+ backupStorage?: Array<BackupStorageSummary>
227
+ backupConfigs: Array<ProjectBackupConfig>
228
+ backupRuns: Array<ProjectBackupRun>
229
+ backupRestores: Array<ProjectBackupRestore>
230
+ environment: {
231
+ id: string
232
+ name: string
233
+ slug: string
234
+ branch: string
235
+ color: string
236
+ position: number
237
+ defaultHost: string | null
238
+ serverId: string | null
239
+ serverName: string | null
240
+ serverStatus: ServerStatus | null
241
+ serverAgentUpdate: ServerSummary['agentUpdate'] | null
242
+ envVars: Array<ProjectEnvironmentVariable>
243
+ publicRoutes: Array<ProjectPublicRoute>
244
+ runtime: {
245
+ observedAt: string
246
+ services: Array<{
247
+ name: string
248
+ containerName: string
249
+ state: string
250
+ health: string | null
251
+ restartCount: number
252
+ startedAt: string | null
253
+ exitCode: number
254
+ }>
255
+ } | null
256
+ }
257
+ environments: Array<ProjectEnvironmentConfig>
258
+ deployments: Array<DeploymentSummary>
259
+ servers: Array<ServerSummary>
260
+ }
261
+
262
+ export type ProjectContextScope =
263
+ | 'backups'
264
+ | 'deployments'
265
+ | 'full'
266
+ | 'overview'
267
+ | 'settings'
268
+ | 'shell'
269
+
270
+ export type ProjectSectionContext =
271
+ | {
272
+ scope: 'overview'
273
+ projectId: string
274
+ deployments: ProjectContext['deployments']
275
+ runtime: ProjectContext['environment']['runtime']
276
+ }
277
+ | {
278
+ scope: 'deployments'
279
+ projectId: string
280
+ deployments: ProjectContext['deployments']
281
+ }
282
+ | {
283
+ scope: 'settings'
284
+ context: ProjectContext
285
+ projectId: string
286
+ composeMetadata: ProjectContext['composeMetadata']
287
+ domains: ProjectContext['domains']
288
+ envVars: ProjectContext['envVars']
289
+ }
290
+ | {
291
+ scope: 'backups'
292
+ projectId: string
293
+ backupStorage?: ProjectContext['backupStorage']
294
+ backupConfigs: ProjectContext['backupConfigs']
295
+ backupRuns: ProjectContext['backupRuns']
296
+ backupRestores: ProjectContext['backupRestores']
297
+ composeMetadata: ProjectContext['composeMetadata']
298
+ }
299
+
300
+ export type PluginContext = {
301
+ userContext: UserContext
302
+ team: TeamSummary
303
+ project: ProjectContext['project']
304
+ teamProjects: Array<ProjectSummary>
305
+ latestDeployments: Array<DeploymentSummary>
306
+ environment: ProjectContext['environment']
307
+ environments: ProjectContext['environments']
308
+ servers: Array<ServerSummary>
309
+ deployment: DeploymentSummary
310
+ visitAliases: Array<{ type: string; hostname: string; https: boolean }>
311
+ logs: Array<{
312
+ seq: number
313
+ stream: string
314
+ content: string
315
+ createdAt: string
316
+ }>
317
+ }
318
+
319
+ export type DeploymentSectionContext = {
320
+ projectId: string
321
+ latestDeployments: Array<DeploymentSummary>
322
+ deployment: DeploymentSummary
323
+ visitAliases: Array<{ type: string; hostname: string; https: boolean }>
324
+ }
325
+
326
+ export type ProjectLogEntry = {
327
+ source: 'deployment' | 'runtime'
328
+ deploymentId: string | null
329
+ runtimeId: string | null
330
+ service: string | null
331
+ stream: string
332
+ seq: number
333
+ content: string
334
+ createdAt: string
335
+ }
336
+
337
+ export type ProjectLogsContext = ProjectContext & {
338
+ logs: Array<ProjectLogEntry>
339
+ hasAnyLogs: boolean
340
+ filters: {
341
+ branch: string | null
342
+ date: string | null
343
+ deploymentId: string | null
344
+ environmentId: string | null
345
+ keyword: string | null
346
+ }
347
+ }
348
+
349
+ export type ProjectLogsSectionContext = {
350
+ projectId: string
351
+ deployments: ProjectContext['deployments']
352
+ logs: ProjectLogsContext['logs']
353
+ hasAnyLogs: boolean
354
+ filters: ProjectLogsContext['filters']
355
+ }
@@ -0,0 +1,32 @@
1
+ import { notifyServer } from './service'
2
+
3
+ export function agentEventKind(path: string, result: unknown) {
4
+ if (path === '/api/agents/heartbeat' || path === '/api/agents/register')
5
+ return 'servers' as const
6
+ if (path === '/api/agents/runtime-logs') return 'logs' as const
7
+ if (path === '/api/agents/jobs/next') {
8
+ return result && typeof result === 'object' && 'job' in result && result.job
9
+ ? ('deployments' as const)
10
+ : null
11
+ }
12
+ if (['/api/agents/jobs/progress', '/api/agents/jobs/complete'].includes(path))
13
+ return 'deployments' as const
14
+ return null
15
+ }
16
+
17
+ export async function notifyAgentMutation(
18
+ path: string,
19
+ payload: unknown,
20
+ result: unknown,
21
+ ) {
22
+ const kind = agentEventKind(path, result)
23
+ if (!kind) return
24
+ const source = path === '/api/agents/register' ? result : payload
25
+ if (
26
+ source &&
27
+ typeof source === 'object' &&
28
+ 'serverId' in source &&
29
+ typeof source.serverId === 'string'
30
+ )
31
+ await notifyServer(source.serverId, kind)
32
+ }
@@ -0,0 +1,71 @@
1
+ export type WorkspaceConnection = {
2
+ teamId: string
3
+ sessionId: string
4
+ expiresAt: number
5
+ }
6
+ export type NotificationPeer = {
7
+ id: string
8
+ send: (message: string) => unknown
9
+ close: (code: number, reason: string) => unknown
10
+ }
11
+
12
+ export function createNodeBroadcaster() {
13
+ const workspaces = new Map<
14
+ string,
15
+ Map<string, { peer: NotificationPeer; auth: WorkspaceConnection }>
16
+ >()
17
+ function remove(peer: NotificationPeer, teamId: string) {
18
+ const connections = workspaces.get(teamId)
19
+ connections?.delete(peer.id)
20
+ if (!connections?.size) workspaces.delete(teamId)
21
+ }
22
+ return {
23
+ add(peer: NotificationPeer, auth: WorkspaceConnection) {
24
+ let connections = workspaces.get(auth.teamId)
25
+ if (!connections) {
26
+ connections = new Map()
27
+ workspaces.set(auth.teamId, connections)
28
+ }
29
+ if (connections.size >= 2000) {
30
+ peer.close(1013, 'Workspace connection limit reached')
31
+ return
32
+ }
33
+ connections.set(peer.id, { peer, auth })
34
+ },
35
+ remove,
36
+ getByName(teamId: string) {
37
+ return {
38
+ async fetch() {
39
+ return new Response('Use the Node upgrade handler', { status: 426 })
40
+ },
41
+ async publish(kind: string, activeSessionIds: string[]) {
42
+ const authorized = new Set(activeSessionIds)
43
+ for (const { peer, auth } of workspaces.get(teamId)?.values() ?? []) {
44
+ if (
45
+ auth.expiresAt <= Date.now() ||
46
+ !authorized.has(auth.sessionId)
47
+ ) {
48
+ remove(peer, teamId)
49
+ peer.close(1008, 'Authorization expired')
50
+ continue
51
+ }
52
+ try {
53
+ peer.send(JSON.stringify({ type: 'changed', kind }))
54
+ } catch {
55
+ remove(peer, teamId)
56
+ peer.close(1011, 'Reconnect required')
57
+ }
58
+ }
59
+ },
60
+ }
61
+ },
62
+ }
63
+ }
64
+
65
+ // Nitro may load this module in separate HTTP/SSR bundles. Share the registry
66
+ // across those module copies and development reloads within one process.
67
+ const runtime = globalThis as typeof globalThis & {
68
+ __devpushWorkspaceBroadcaster?: ReturnType<typeof createNodeBroadcaster>
69
+ }
70
+ export const nodeBroadcaster = (runtime.__devpushWorkspaceBroadcaster ??=
71
+ createNodeBroadcaster())
@@ -0,0 +1,55 @@
1
+ import { defineWebSocketHandler } from 'nitro'
2
+ import { authorizeWorkspaceUpdates, connectWorkspaceUpdates } from './service'
3
+ import { nodeBroadcaster } from './node-broadcaster'
4
+ import type { WorkspaceConnection } from './node-broadcaster'
5
+
6
+ function connection(
7
+ context: Record<string, unknown>,
8
+ ): WorkspaceConnection | null {
9
+ return typeof context.teamId === 'string' &&
10
+ typeof context.sessionId === 'string' &&
11
+ typeof context.expiresAt === 'number'
12
+ ? {
13
+ teamId: context.teamId,
14
+ sessionId: context.sessionId,
15
+ expiresAt: context.expiresAt,
16
+ }
17
+ : null
18
+ }
19
+
20
+ export default defineWebSocketHandler(
21
+ {
22
+ async upgrade(request) {
23
+ const authorization = await authorizeWorkspaceUpdates(request)
24
+ if (authorization instanceof Response) return authorization
25
+ return { context: authorization }
26
+ },
27
+ open(peer) {
28
+ const auth = connection(peer.context)
29
+ if (!auth) {
30
+ peer.close(1008, 'Unauthorized')
31
+ return
32
+ }
33
+ nodeBroadcaster.add(peer, auth)
34
+ },
35
+ message(peer, message) {
36
+ const auth = connection(peer.context)
37
+ if (!auth || auth.expiresAt <= Date.now()) {
38
+ peer.close(1008, 'Authorization expired')
39
+ return
40
+ }
41
+ if (message.text() === 'ping') peer.send('pong')
42
+ else peer.close(1008, 'Notifications only')
43
+ },
44
+ close(peer) {
45
+ const auth = connection(peer.context)
46
+ if (auth) nodeBroadcaster.remove(peer, auth.teamId)
47
+ },
48
+ error(peer) {
49
+ const auth = connection(peer.context)
50
+ if (auth) nodeBroadcaster.remove(peer, auth.teamId)
51
+ peer.close(1011, 'Reconnect required')
52
+ },
53
+ },
54
+ (event) => connectWorkspaceUpdates(event.req),
55
+ )
@@ -0,0 +1,125 @@
1
+ import { and, eq, gt } from 'drizzle-orm'
2
+ import { auth } from '#devpush/server/auth/better-auth'
3
+ import { db } from '#devpush/server/db'
4
+ import {
5
+ session,
6
+ teamMembers,
7
+ teams,
8
+ user,
9
+ servers,
10
+ projects,
11
+ } from '#devpush/server/db/schema'
12
+ import { getWorkspaceUpdates } from '#devpush/server/runtime/platform-env'
13
+ import { resolveProxyRequest } from '#devpush/server/http/proxy-request'
14
+
15
+ export async function authorizeWorkspaceUpdates(request: Request) {
16
+ request = resolveProxyRequest(request)
17
+ const origin = request.headers.get('Origin')
18
+ const upgrade = request.headers.get('Upgrade')?.toLowerCase() === 'websocket'
19
+ if (
20
+ (upgrade && !origin) ||
21
+ (origin && origin !== new URL(request.url).origin)
22
+ )
23
+ return new Response('Origin not allowed', { status: 403 })
24
+ const current = await auth.api.getSession({ headers: request.headers })
25
+ if (!current) return new Response('Unauthorized', { status: 401 })
26
+ const slug = new URL(request.url).searchParams.get('team')
27
+ const [team] = await db
28
+ .select({ id: teams.id })
29
+ .from(teams)
30
+ .innerJoin(teamMembers, eq(teamMembers.teamId, teams.id))
31
+ .innerJoin(user, eq(user.id, teamMembers.userId))
32
+ .where(
33
+ and(
34
+ eq(teams.slug, slug ?? ''),
35
+ eq(teamMembers.userId, current.user.id),
36
+ eq(teams.status, 'active'),
37
+ eq(user.status, 'active'),
38
+ ),
39
+ )
40
+ .limit(1)
41
+ if (!team) return new Response('Workspace not found', { status: 404 })
42
+ return {
43
+ teamId: team.id,
44
+ sessionId: current.session.id,
45
+ expiresAt: new Date(current.session.expiresAt).getTime(),
46
+ }
47
+ }
48
+
49
+ export async function connectWorkspaceUpdates(request: Request) {
50
+ const authorization = await authorizeWorkspaceUpdates(request)
51
+ if (authorization instanceof Response) return authorization
52
+ const upgrade = request.headers.get('Upgrade')?.toLowerCase() === 'websocket'
53
+ const namespace = getWorkspaceUpdates()
54
+ if (!upgrade) return Response.json({ available: Boolean(namespace) })
55
+ if (!namespace) return new Response('Use HTTP polling', { status: 501 })
56
+ const headers = new Headers(request.headers)
57
+ headers.set('x-devpush-session', authorization.sessionId)
58
+ headers.set('x-devpush-session-expires', String(authorization.expiresAt))
59
+ return namespace
60
+ .getByName(authorization.teamId)
61
+ .fetch(new Request(request.url, { headers }))
62
+ }
63
+
64
+ // Notifications are hints after committed writes. Delivery failure must not fail
65
+ // an acknowledged mutation; slow recovery polling repairs missed notifications.
66
+ export async function notifyWorkspace(
67
+ teamId: string,
68
+ kind: 'deployments' | 'servers' | 'logs',
69
+ ) {
70
+ const namespace = getWorkspaceUpdates()
71
+ if (!namespace) return
72
+ try {
73
+ const active = await db
74
+ .select({ id: session.id })
75
+ .from(session)
76
+ .innerJoin(teamMembers, eq(teamMembers.userId, session.userId))
77
+ .innerJoin(user, eq(user.id, session.userId))
78
+ .innerJoin(teams, eq(teams.id, teamMembers.teamId))
79
+ .where(
80
+ and(
81
+ eq(teamMembers.teamId, teamId),
82
+ gt(session.expiresAt, new Date()),
83
+ eq(user.status, 'active'),
84
+ eq(teams.status, 'active'),
85
+ ),
86
+ )
87
+ await namespace.getByName(teamId).publish(
88
+ kind,
89
+ active.map((row) => row.id),
90
+ )
91
+ } catch (error) {
92
+ console.error('Workspace notification failed', error)
93
+ }
94
+ }
95
+
96
+ export async function notifyServer(
97
+ serverId: string,
98
+ kind: 'deployments' | 'servers' | 'logs',
99
+ ) {
100
+ if (!getWorkspaceUpdates()) return
101
+ try {
102
+ const [server] = await db
103
+ .select({ teamId: servers.teamId })
104
+ .from(servers)
105
+ .where(eq(servers.id, serverId))
106
+ .limit(1)
107
+ if (server) await notifyWorkspace(server.teamId, kind)
108
+ } catch (error) {
109
+ console.error('Server notification failed', error)
110
+ }
111
+ }
112
+
113
+ export async function notifyProject(projectId: string) {
114
+ if (!getWorkspaceUpdates()) return
115
+ try {
116
+ const [project] = await db
117
+ .select({ teamId: projects.teamId })
118
+ .from(projects)
119
+ .where(eq(projects.id, projectId))
120
+ .limit(1)
121
+ if (project) await notifyWorkspace(project.teamId, 'deployments')
122
+ } catch (error) {
123
+ console.error('Project notification failed', error)
124
+ }
125
+ }
@@ -0,0 +1,62 @@
1
+ import { DurableObject } from 'cloudflare:workers'
2
+ import type {
3
+ DurableObjectState,
4
+ WebSocket as CloudflareSocket,
5
+ } from '@cloudflare/workers-types'
6
+
7
+ export class WorkspaceUpdates extends DurableObject<Env> {
8
+ private readonly state: DurableObjectState
9
+
10
+ constructor(ctx: DurableObjectState, env: Env) {
11
+ super(ctx, env)
12
+ this.state = ctx
13
+ ctx.setWebSocketAutoResponse(
14
+ new WebSocketRequestResponsePair('ping', 'pong'),
15
+ )
16
+ }
17
+
18
+ async fetch(request: Request) {
19
+ if (request.headers.get('Upgrade')?.toLowerCase() !== 'websocket')
20
+ return new Response('WebSocket required', { status: 426 })
21
+ const sessionId = request.headers.get('x-devpush-session')
22
+ const expiresAt = Number(request.headers.get('x-devpush-session-expires'))
23
+ if (!sessionId || !Number.isFinite(expiresAt) || expiresAt <= Date.now())
24
+ return new Response('Unauthorized', { status: 401 })
25
+ if (this.state.getWebSockets().length >= 2000)
26
+ return new Response('Workspace connection limit reached', { status: 503 })
27
+ const pair = new WebSocketPair()
28
+ this.state.acceptWebSocket(pair[1])
29
+ pair[1].serializeAttachment({ sessionId, expiresAt })
30
+ return new Response(null, { status: 101, webSocket: pair[0] })
31
+ }
32
+
33
+ publish(kind: string, activeSessionIds: string[]) {
34
+ const authorized = new Set(activeSessionIds)
35
+ for (const socket of this.state.getWebSockets()) {
36
+ const attachment = socket.deserializeAttachment() as {
37
+ sessionId: string
38
+ expiresAt: number
39
+ } | null
40
+ if (
41
+ !attachment ||
42
+ attachment.expiresAt <= Date.now() ||
43
+ !authorized.has(attachment.sessionId)
44
+ ) {
45
+ socket.close(1008, 'Authorization expired')
46
+ continue
47
+ }
48
+ try {
49
+ socket.send(JSON.stringify({ type: 'changed', kind }))
50
+ } catch {
51
+ socket.close(1011, 'Reconnect required')
52
+ }
53
+ }
54
+ }
55
+
56
+ webSocketMessage(socket: CloudflareSocket) {
57
+ socket.close(1008, 'Notifications only')
58
+ }
59
+ webSocketClose(socket: CloudflareSocket, code: number, reason: string) {
60
+ socket.close([1005, 1006, 1015].includes(code) ? 1000 : code, reason)
61
+ }
62
+ }
@@ -0,0 +1,3 @@
1
+ // Cloudflare exports its Durable Object through this runtime alias.
2
+ // Node uses the Nitro upgrade handler and its in-memory broadcaster.
3
+ export class WorkspaceUpdates {}