devpush 0.0.0-stage → 1.0.0-beta.3

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 +6 -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 +170 -4
  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,41 @@
1
+ import { z } from 'zod'
2
+ import { backupResourceNameSchema } from './backup-resource-schema'
3
+
4
+ export const backupDatabaseEngines = [
5
+ {
6
+ value: 'postgres',
7
+ label: 'PostgreSQL',
8
+ },
9
+ {
10
+ value: 'mysql',
11
+ label: 'MySQL',
12
+ },
13
+ {
14
+ value: 'mariadb',
15
+ label: 'MariaDB',
16
+ },
17
+ {
18
+ value: 'mongo',
19
+ label: 'MongoDB',
20
+ },
21
+ ] as const
22
+ const engineSchema = z.enum(backupDatabaseEngines.map((engine) => engine.value))
23
+ export const backupDatabaseSchema = z.object({
24
+ service: backupResourceNameSchema,
25
+ engine: engineSchema,
26
+ database: z.string().trim().max(128).default(''),
27
+ })
28
+ export type BackupDatabase = z.infer<typeof backupDatabaseSchema>
29
+ function databaseBackupFilename(index: number, target: BackupDatabase) {
30
+ return `${String(index + 1).padStart(2, '0')}-${target.service}.${target.engine === 'mongo' ? 'archive' : 'sql'}.gz`
31
+ }
32
+
33
+ export function databaseArtifactTarget(
34
+ objectKey: string,
35
+ targets: BackupDatabase[],
36
+ ) {
37
+ const filename = objectKey.split('/').at(-1)
38
+ return targets.find(
39
+ (target, index) => filename === databaseBackupFilename(index, target),
40
+ )
41
+ }
@@ -0,0 +1,2 @@
1
+ export const backupsEnabled =
2
+ import.meta.env.VITE_DEVPUSH_BACKUPS_ENABLED === 'true'
@@ -0,0 +1,7 @@
1
+ export function getDefaultBackupPrefix(
2
+ projectId: string,
3
+ environmentId: string,
4
+ backupId: string,
5
+ ) {
6
+ return `projects/${projectId}/environments/${environmentId}/backups/${backupId}`
7
+ }
@@ -0,0 +1,85 @@
1
+ import { backupDatabaseSchema } from './backup-database'
2
+ import { backupStorageReferenceSchema } from './backup-storage-schema'
3
+ import { z } from 'zod'
4
+ import { backupResourceNameSchema } from './backup-resource-schema'
5
+ import { isValidCron } from '#devpush/lib/backup-cron'
6
+
7
+ export const backupScheduleSchema = z.object({
8
+ cron: z
9
+ .string()
10
+ .trim()
11
+ .refine(isValidCron, 'Enter a valid five-field cron expression.'),
12
+ })
13
+
14
+ export const backupVolumesSchema = z.object({
15
+ mode: z.enum(['none', 'allNamed', 'selected']),
16
+ names: z.array(backupResourceNameSchema),
17
+ })
18
+
19
+ export const backupRetentionSchema = z.object({
20
+ local: z.object({
21
+ count: z
22
+ .number()
23
+ .int('Enter a whole number.')
24
+ .min(0, 'Enter zero or a positive number.')
25
+ .max(10_000, 'Keep no more than 10,000 backups.'),
26
+ days: z
27
+ .number()
28
+ .int('Enter a whole number.')
29
+ .min(0, 'Enter zero or a positive number.')
30
+ .max(3650, 'Retention must be 3,650 days or fewer.'),
31
+ maxStorageGb: z
32
+ .number()
33
+ .min(0, 'Enter zero or a positive number.')
34
+ .max(100_000, 'Storage must be 100,000 GB or less.'),
35
+ }),
36
+ remote: z.object({
37
+ enabled: z.boolean(),
38
+ count: z
39
+ .number()
40
+ .int('Enter a whole number.')
41
+ .min(0, 'Enter zero or a positive number.')
42
+ .max(10_000, 'Keep no more than 10,000 backups.'),
43
+ days: z
44
+ .number()
45
+ .int('Enter a whole number.')
46
+ .min(0, 'Enter zero or a positive number.')
47
+ .max(3650, 'Retention must be 3,650 days or fewer.'),
48
+ maxStorageGb: z
49
+ .number()
50
+ .min(0, 'Enter zero or a positive number.')
51
+ .max(100_000, 'Storage must be 100,000 GB or less.'),
52
+ }),
53
+ })
54
+
55
+ export const backupConfigSchema = z.object({
56
+ id: z.string().trim().optional(),
57
+ name: z
58
+ .string()
59
+ .trim()
60
+ .min(1, 'Enter a backup policy name.')
61
+ .max(100, 'Backup policy names must be 100 characters or fewer.'),
62
+ environmentId: z.string().trim().min(1),
63
+ enabled: z.boolean(),
64
+ schedule: backupScheduleSchema,
65
+ destination: backupStorageReferenceSchema,
66
+ retention: backupRetentionSchema,
67
+ volumes: backupVolumesSchema,
68
+ databaseDumps: z.array(backupDatabaseSchema),
69
+ })
70
+
71
+ export type BackupPolicyInput = z.infer<typeof backupConfigSchema>
72
+
73
+ export function normalizeBackupVolumes(
74
+ value: unknown,
75
+ ): z.infer<typeof backupVolumesSchema> {
76
+ const parsed = backupVolumesSchema.safeParse(value)
77
+ if (
78
+ !parsed.success ||
79
+ parsed.data.mode === 'allNamed' ||
80
+ (parsed.data.mode === 'selected' && parsed.data.names.length === 0)
81
+ ) {
82
+ return { mode: 'allNamed', names: [] }
83
+ }
84
+ return parsed.data.mode === 'none' ? { mode: 'none', names: [] } : parsed.data
85
+ }
@@ -0,0 +1,12 @@
1
+ import { z } from 'zod'
2
+
3
+ // Docker/Compose names must not be interpreted as command-line options.
4
+ export const backupResourceNameSchema = z
5
+ .string()
6
+ .trim()
7
+ .min(1)
8
+ .max(128)
9
+ .regex(
10
+ /^[A-Za-z0-9][A-Za-z0-9_.-]*$/,
11
+ 'Select a valid Compose service or named volume.',
12
+ )
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod'
2
+ import { backupResourceNameSchema } from './backup-resource-schema'
3
+
4
+ export const backupRestoreArtifactSchema = z.discriminatedUnion('type', [
5
+ z.object({
6
+ artifactId: z.string().min(1),
7
+ type: z.literal('volume'),
8
+ targetVolume: backupResourceNameSchema,
9
+ }),
10
+ z.object({
11
+ artifactId: z.string().min(1),
12
+ type: z.literal('database'),
13
+ targetService: backupResourceNameSchema,
14
+ }),
15
+ ])
@@ -0,0 +1,55 @@
1
+ import { z } from 'zod'
2
+
3
+ const singleLine = z
4
+ .string()
5
+ .trim()
6
+ .refine((value) => !/[\r\n\0]/.test(value), 'Enter a single-line value.')
7
+ export const backupStorageSchema = z.object({
8
+ id: z.string().min(1).optional(),
9
+ name: z.string().trim().min(1, 'Enter a storage name.').max(100),
10
+ bucket: z
11
+ .string()
12
+ .trim()
13
+ .min(3)
14
+ .max(63)
15
+ .regex(
16
+ /^[a-z0-9][a-z0-9.-]*[a-z0-9]$/,
17
+ 'Enter a DNS-compatible bucket name.',
18
+ ),
19
+ region: singleLine.min(1).max(100),
20
+ endpoint: z.union([
21
+ z.literal(''),
22
+ z
23
+ .string()
24
+ .trim()
25
+ .url()
26
+ .refine(
27
+ (value) => ['http:', 'https:'].includes(new URL(value).protocol),
28
+ 'Use an HTTP or HTTPS endpoint.',
29
+ ),
30
+ ]),
31
+ accessKeyId: singleLine.min(1, 'Enter an access key ID.').max(256),
32
+ secretAccessKey: singleLine.max(1024),
33
+ })
34
+ export type BackupStorageInput = z.infer<typeof backupStorageSchema>
35
+ export type BackupStorageSummary = {
36
+ settingsVersion: number
37
+ id: string
38
+ name: string
39
+ bucket: string
40
+ region: string
41
+ endpoint: string | null
42
+ accessKeyId?: string
43
+ hasSecret: boolean
44
+ }
45
+ export type BackupStorageReference = { storageId?: string; prefix?: string }
46
+ export const backupStorageReferenceSchema = z.object({
47
+ storageId: z.string().trim().min(1).optional(),
48
+ prefix: singleLine
49
+ .max(512)
50
+ .refine(
51
+ (value) => !value.split('/').includes('..'),
52
+ 'Object prefixes cannot contain .. segments.',
53
+ )
54
+ .optional(),
55
+ })
@@ -0,0 +1,38 @@
1
+ import { useEffect, useSyncExternalStore } from 'react'
2
+
3
+ export type DeferredModule<T> = ReturnType<typeof createDeferredModule<T>>
4
+
5
+ // Pending views can reuse an already-loaded page without pulling that page into
6
+ // every route. A skeleton remains available while its module loads.
7
+ export function createDeferredModule<T>(importModule: () => Promise<T>) {
8
+ let value: T | undefined
9
+ let pending: Promise<void> | undefined
10
+ const listeners = new Set<() => void>()
11
+ const load = () => {
12
+ if (value !== undefined) return Promise.resolve()
13
+ return (pending ??= importModule().then(
14
+ (result) => {
15
+ value = result
16
+ for (const listener of listeners) listener()
17
+ },
18
+ () => {
19
+ pending = undefined
20
+ },
21
+ ))
22
+ }
23
+ function useModule(enabled: boolean) {
24
+ const module = useSyncExternalStore(
25
+ (listener) => {
26
+ listeners.add(listener)
27
+ return () => listeners.delete(listener)
28
+ },
29
+ () => value,
30
+ () => undefined,
31
+ )
32
+ useEffect(() => {
33
+ if (enabled) void load()
34
+ }, [enabled])
35
+ return module
36
+ }
37
+ return { load, useModule }
38
+ }
@@ -0,0 +1,63 @@
1
+ import { workspacePollInterval } from './workspace-updates'
2
+ import type { QueryClient } from '@tanstack/react-query'
3
+ import { queryOptions } from '@tanstack/react-query'
4
+ import { isRedirect, redirect } from '@tanstack/react-router'
5
+
6
+ import { getDeploymentLogs } from '#devpush/server/projects/functions'
7
+
8
+ export function deploymentLogsQueryOptions({
9
+ completed,
10
+ deploymentId,
11
+ projectName,
12
+ teamSlug,
13
+ queryClient,
14
+ }: {
15
+ completed: boolean
16
+ deploymentId: string
17
+ projectName: string
18
+ teamSlug: string
19
+ queryClient?: QueryClient
20
+ }) {
21
+ const runningLogs = completed
22
+ ? queryClient?.getQueryData<Awaited<ReturnType<typeof getDeploymentLogs>>>([
23
+ 'deployment-logs',
24
+ teamSlug,
25
+ projectName,
26
+ deploymentId,
27
+ false,
28
+ ])
29
+ : undefined
30
+ return queryOptions({
31
+ // These reads tolerate a brief network interruption. Most application
32
+ // queries disable retries, but one failed poll must not raise a log alert.
33
+ retry: (failures, error) => failures < 2 && !isRedirect(error),
34
+ retryDelay: (attempt) => Math.min(1_000 * 2 ** attempt, 4_000),
35
+ // Preserve this deployment's output while fetching its final snapshot.
36
+ initialData: runningLogs ?? undefined,
37
+ initialDataUpdatedAt: runningLogs ? 0 : undefined,
38
+ refetchOnWindowFocus: !completed,
39
+ refetchOnReconnect: !completed,
40
+ // Completion gets a fresh snapshot even if the last running poll just finished.
41
+ queryKey: [
42
+ 'deployment-logs',
43
+ teamSlug,
44
+ projectName,
45
+ deploymentId,
46
+ completed,
47
+ ],
48
+ refetchIntervalInBackground: false,
49
+ refetchInterval: () =>
50
+ completed ? false : workspacePollInterval(teamSlug, 2_000),
51
+ queryFn: async () => {
52
+ const logs = await getDeploymentLogs({
53
+ data: { deploymentId, projectName, teamSlug },
54
+ })
55
+
56
+ if (!logs) {
57
+ throw redirect({ to: '/auth/login' })
58
+ }
59
+
60
+ return logs
61
+ },
62
+ })
63
+ }
@@ -0,0 +1,7 @@
1
+ export function displayNameFromSlug(slug: string) {
2
+ return slug
3
+ .split(/[-_]+/)
4
+ .filter(Boolean)
5
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
6
+ .join(' ')
7
+ }
@@ -0,0 +1,119 @@
1
+ export function getErrorMessage(error: unknown, fallback: string) {
2
+ const message = getMessage(error)
3
+ if (!message) return fallback
4
+ return presentMessage(
5
+ error,
6
+ getValidationErrorMessage(message) ?? message,
7
+ fallback,
8
+ )
9
+ }
10
+
11
+ export function getFieldErrorMessage({
12
+ error,
13
+ fallback,
14
+ field,
15
+ }: {
16
+ error: unknown
17
+ fallback: string
18
+ field: string
19
+ }) {
20
+ const message = getMessage(error)
21
+ if (!message) return fallback
22
+
23
+ const validationIssue = getValidationIssues(message).find((issue) => {
24
+ const path = issue.path ?? []
25
+ return path.at(-1) === field || path.join('.') === field
26
+ })
27
+
28
+ return presentMessage(
29
+ error,
30
+ validationIssue?.message ?? getValidationErrorMessage(message) ?? message,
31
+ fallback,
32
+ )
33
+ }
34
+
35
+ function presentMessage(error: unknown, message: string, fallback: string) {
36
+ const status = getStatus(error)
37
+ if (status !== undefined && status >= 500) return fallback
38
+ if (status === 429)
39
+ return 'Too many attempts. Please wait a moment and try again.'
40
+ if (
41
+ /^(failed to fetch|fetch failed|load failed|networkerror\b|network request failed)/i.test(
42
+ message,
43
+ )
44
+ ) {
45
+ return 'Unable to connect. Check your connection and try again.'
46
+ }
47
+ // Framework, database, and provider diagnostics belong in logs. Callers
48
+ // supply the action-specific explanation; keep business validation intact.
49
+ if (
50
+ /^(HTTPError|Error|Unknown error|Internal server error|Internal error|Something went wrong|Bad gateway|Service unavailable|Gateway timeout)[.!]?$/i.test(
51
+ message,
52
+ ) ||
53
+ /^(Failed query:|D1_|SQLITE_|LibsqlError|Resend failed to send email:|TypeError:|ReferenceError:|SyntaxError:|Cannot read properties of|Unexpected token|Unexpected end of JSON|<!doctype|<html)/i.test(
54
+ message,
55
+ ) ||
56
+ /\n\s+at\s/.test(message)
57
+ )
58
+ return fallback
59
+ return message
60
+ }
61
+
62
+ function getStatus(error: unknown) {
63
+ if (!error || typeof error !== 'object') return undefined
64
+ if ('status' in error && typeof error.status === 'number') return error.status
65
+ if ('statusCode' in error && typeof error.statusCode === 'number')
66
+ return error.statusCode
67
+ return undefined
68
+ }
69
+
70
+ function getMessage(error: unknown) {
71
+ if (error instanceof Error) return error.message.trim()
72
+ if (typeof error === 'string') return error.trim()
73
+ if (
74
+ error &&
75
+ typeof error === 'object' &&
76
+ 'message' in error &&
77
+ typeof error.message === 'string'
78
+ ) {
79
+ return error.message.trim()
80
+ }
81
+
82
+ return ''
83
+ }
84
+
85
+ function getValidationErrorMessage(message: string) {
86
+ const issue = getValidationIssues(message).find((item) => item.message)
87
+ return issue?.message ?? null
88
+ }
89
+
90
+ function getValidationIssues(message: string): Array<{
91
+ message?: string
92
+ path?: Array<string | number>
93
+ }> {
94
+ try {
95
+ const issues = JSON.parse(message) as unknown
96
+ if (!Array.isArray(issues)) return []
97
+
98
+ return issues.filter(
99
+ (
100
+ item,
101
+ ): item is {
102
+ message?: string
103
+ path?: Array<string | number>
104
+ } => {
105
+ if (!item || typeof item !== 'object') return false
106
+ if ('message' in item && typeof item.message !== 'string') return false
107
+ if (!('path' in item)) return true
108
+ if (!Array.isArray(item.path)) return false
109
+
110
+ const path: Array<unknown> = item.path
111
+ return path.every(
112
+ (part) => typeof part === 'string' || typeof part === 'number',
113
+ )
114
+ },
115
+ )
116
+ } catch {
117
+ return []
118
+ }
119
+ }
@@ -0,0 +1,10 @@
1
+ export type FormErrors = Record<string, string | undefined>
2
+
3
+ export function formErrors(
4
+ issues: Array<{ path: PropertyKey[]; message: string }>,
5
+ ): FormErrors {
6
+ const errors: FormErrors = {}
7
+ for (const issue of issues)
8
+ errors[issue.path.map(String).join('.') || 'form'] ??= issue.message
9
+ return errors
10
+ }
@@ -0,0 +1,25 @@
1
+ const internalOrigin = 'https://devpush.invalid'
2
+
3
+ export function getSafeRedirectPath(value: string | null) {
4
+ if (!value || !value.startsWith('/')) {
5
+ return '/'
6
+ }
7
+
8
+ try {
9
+ const target = new URL(value, internalOrigin)
10
+ if (target.origin !== internalOrigin) {
11
+ return '/'
12
+ }
13
+
14
+ const path = `${target.pathname}${target.search}${target.hash}`
15
+
16
+ // Path normalization can produce a protocol-relative string such as `//host`.
17
+ if (new URL(path, internalOrigin).origin !== internalOrigin) {
18
+ return '/'
19
+ }
20
+
21
+ return path
22
+ } catch {
23
+ return '/'
24
+ }
25
+ }
@@ -0,0 +1,21 @@
1
+ import { queryOptions } from '@tanstack/react-query'
2
+ import { getProjectDeployOptions } from '#devpush/server/projects/functions'
3
+
4
+ export function manualDeployOptionsQueryOptions(params: {
5
+ teamSlug: string
6
+ projectName: string
7
+ branch?: string
8
+ environmentId: string
9
+ }) {
10
+ return queryOptions({
11
+ queryKey: [
12
+ 'manual-deploy-options',
13
+ params.teamSlug,
14
+ params.projectName,
15
+ params.environmentId,
16
+ params.branch ?? null,
17
+ ],
18
+ staleTime: 30_000,
19
+ queryFn: ({ signal }) => getProjectDeployOptions({ data: params, signal }),
20
+ })
21
+ }
@@ -0,0 +1,43 @@
1
+ import { z } from 'zod'
2
+
3
+ export const notificationCategories = [
4
+ {
5
+ id: 'deployments',
6
+ label: 'Deployment alerts',
7
+ description: 'Failed deployments and recovery after a failure.',
8
+ },
9
+ {
10
+ id: 'backups',
11
+ label: 'Backup and restore alerts',
12
+ description:
13
+ 'Failed backups or restores, and recovery after a backup failure.',
14
+ },
15
+ {
16
+ id: 'domains',
17
+ label: 'Domain alerts',
18
+ description: 'Domain verification failures and recovery.',
19
+ },
20
+ {
21
+ id: 'servers',
22
+ label: 'Server alerts',
23
+ description: 'Disconnected servers, low disk space and recovery.',
24
+ },
25
+ ] as const
26
+ export type NotificationCategory = (typeof notificationCategories)[number]['id']
27
+ export const notificationPreferencesSchema = z.object({
28
+ deployments: z.boolean(),
29
+ backups: z.boolean(),
30
+ domains: z.boolean(),
31
+ servers: z.boolean(),
32
+ })
33
+ export const defaultNotificationPreferences = {
34
+ deployments: true,
35
+ backups: true,
36
+ domains: true,
37
+ servers: true,
38
+ }
39
+ export const notificationScopeSchema = z.discriminatedUnion('kind', [
40
+ z.object({ kind: z.literal('workspace'), id: z.string().min(1) }),
41
+ z.object({ kind: z.literal('project'), id: z.string().min(1) }),
42
+ ])
43
+ export type NotificationScope = z.infer<typeof notificationScopeSchema>
@@ -0,0 +1,11 @@
1
+ import { getSafeRedirectPath } from './internal-redirect'
2
+
3
+ export function onboardingDestination(
4
+ path: string | undefined,
5
+ teamSlug: string,
6
+ ) {
7
+ const destination = getSafeRedirectPath(path ?? '/')
8
+ return destination === '/' || destination.startsWith('/onboarding')
9
+ ? `/${encodeURIComponent(teamSlug)}`
10
+ : destination
11
+ }
@@ -0,0 +1,51 @@
1
+ import { queryOptions } from '@tanstack/react-query'
2
+ import type { QueryClient } from '@tanstack/react-query'
3
+
4
+ type OperationLogs = { content: string; nextOffset: number }
5
+ export function operationLogsQueryOptions(
6
+ params: { teamSlug: string; type: 'backup' | 'restore'; id: string },
7
+ queryClient: QueryClient,
8
+ ) {
9
+ const queryKey = ['operation-logs', params.teamSlug, params.type, params.id]
10
+ return queryOptions({
11
+ queryKey,
12
+ retry: false,
13
+ staleTime: 5_000,
14
+ gcTime: 60_000,
15
+ refetchInterval: 5_000,
16
+ refetchIntervalInBackground: false,
17
+ refetchOnWindowFocus: true,
18
+ queryFn: async ({ signal }): Promise<OperationLogs> => {
19
+ let cached = queryClient.getQueryData<OperationLogs>(queryKey)
20
+ let offset = cached?.nextOffset ?? 0
21
+ const search = new URLSearchParams({
22
+ type: params.type,
23
+ id: params.id,
24
+ offset: String(offset),
25
+ })
26
+ let response = await fetch(`/api/agents/job-logs?${search}`, { signal })
27
+ if (response.status === 409 && cached) {
28
+ // Retention may remove chunks beyond an old cursor. Restart from the
29
+ // retained output, keeping the old display until that read succeeds.
30
+ cached = undefined
31
+ offset = 0
32
+ search.set('offset', '0')
33
+ response = await fetch(`/api/agents/job-logs?${search}`, { signal })
34
+ }
35
+ if (!response.ok) throw new Error('Unable to load logs. Try again.')
36
+ const result = (await response.json()) as OperationLogs
37
+ if (
38
+ typeof result.content !== 'string' ||
39
+ !Number.isSafeInteger(result.nextOffset) ||
40
+ result.nextOffset < offset ||
41
+ result.nextOffset <
42
+ offset + new TextEncoder().encode(result.content).length
43
+ )
44
+ throw new Error('Invalid operation log response. Try again.')
45
+ return {
46
+ content: (cached?.content ?? '') + result.content,
47
+ nextOffset: result.nextOffset,
48
+ }
49
+ },
50
+ })
51
+ }
@@ -0,0 +1,14 @@
1
+ /** Keep browser and social titles consistent across route heads. */
2
+ export function pageHead(...parts: Array<string | undefined>) {
3
+ const context = parts.filter((part) => part?.trim()).join(' – ')
4
+ const title = context ? `${context} | devpush` : 'devpush'
5
+
6
+ return {
7
+ meta: [
8
+ { title },
9
+ { name: 'title', content: title },
10
+ { property: 'og:title', content: title },
11
+ { name: 'twitter:title', content: title },
12
+ ],
13
+ }
14
+ }