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,1064 @@
1
+ import { ImageUploadControls } from '#devpush/components/storage-capabilities'
2
+ import { CopyButton } from '#devpush/components/copy-button'
3
+ import { ProjectBackupsPending } from './project-backups-pending'
4
+ import { ProjectBackupDetailsPending } from './project-backup-details-pending'
5
+ import { ProjectLogs } from './project-logs'
6
+ import { DeploymentsPage } from '#devpush/features/deployments/deployments-page'
7
+ import { createDeferredModule } from '#devpush/lib/deferred-module'
8
+ import type { DeferredModule } from '#devpush/lib/deferred-module'
9
+ import {
10
+ DeploymentDetail,
11
+ DeploymentMetadata,
12
+ DeploymentLogsPending,
13
+ } from '#devpush/features/deployments/deployment-detail'
14
+ import { stringSearchValidator } from '#devpush/lib/search'
15
+ import type {
16
+ ProjectContext,
17
+ PluginContext,
18
+ } from '#devpush/server/projects/types'
19
+ import { useParams, useRouterState, useHydrated } from '@tanstack/react-router'
20
+ import { useQuery, useQueryClient } from '@tanstack/react-query'
21
+ import type { ReactNode } from 'react'
22
+
23
+ import { TeamPage } from '#devpush/features/teams/team-header'
24
+ import { getCachedTeamContexts } from '#devpush/lib/team-context-query'
25
+ import {
26
+ projectContextQueryOptions,
27
+ projectShellQueryOptions,
28
+ projectLogsQueryOptions,
29
+ } from '#devpush/lib/project-context-query'
30
+ import { ProjectOverview } from '#devpush/features/projects/project-overview'
31
+ import { DeploymentListPending } from '#devpush/features/deployments/deployment-list'
32
+ import { DeploymentVisitLinks } from '#devpush/features/deployments/deployment-visit-links'
33
+ import type { ProjectPendingSection } from '#devpush/features/projects/pending-section'
34
+ import { Skeleton } from '#devpush/components/ui/skeleton'
35
+ import { Button } from '#devpush/components/ui/button'
36
+ import { Input } from '#devpush/components/ui/input'
37
+ import { Textarea } from '#devpush/components/ui/textarea'
38
+ import { ResourceAvatar } from '#devpush/components/resource-avatar'
39
+ import {
40
+ Select,
41
+ SelectTrigger,
42
+ SelectValue,
43
+ } from '#devpush/components/ui/select'
44
+ import {
45
+ InputGroup,
46
+ InputGroupAddon,
47
+ InputGroupInput,
48
+ } from '#devpush/components/ui/input-group'
49
+ import {
50
+ CircleHelpIcon,
51
+ Clock3Icon,
52
+ LoaderIcon,
53
+ ServerIcon,
54
+ GitBranchIcon,
55
+ GitCommitHorizontalIcon,
56
+ ArrowRightIcon,
57
+ ArrowUpRightIcon,
58
+ MoreHorizontalIcon,
59
+ ChevronsUpDownIcon,
60
+ SettingsIcon,
61
+ PlusIcon,
62
+ RefreshCwIcon,
63
+ SearchIcon,
64
+ Trash2Icon,
65
+ UploadIcon,
66
+ } from 'lucide-react'
67
+ import {
68
+ Field,
69
+ FieldLabel,
70
+ FieldDescription,
71
+ FieldGroup,
72
+ } from '#devpush/components/ui/field'
73
+ import {
74
+ SettingsLayout,
75
+ SettingsSection,
76
+ } from '#devpush/components/settings-layout'
77
+ import { projectSettingsNavigationItems } from './project-settings-utils'
78
+ import { ProjectHeading } from './project-heading'
79
+
80
+ type PendingProjectContext = {
81
+ avatarUrl: string | null
82
+ color?:
83
+ | 'amber'
84
+ | 'blue'
85
+ | 'green'
86
+ | 'neutral'
87
+ | 'rose'
88
+ | 'lime'
89
+ | 'sky'
90
+ | 'zinc'
91
+ deployments?: ProjectContext['deployments']
92
+ environments?: ProjectContext['environments']
93
+ projectId?: string
94
+ githubRepo?: string
95
+ deploymentId: string | null
96
+ projectName: string
97
+ teamSlug: string
98
+ }
99
+
100
+ export function ProjectRoutePending({
101
+ section,
102
+ }: {
103
+ section: ProjectPendingSection
104
+ }) {
105
+ return (
106
+ <ProjectPendingPage>
107
+ <ProjectSectionPending section={section} />
108
+ </ProjectPendingPage>
109
+ )
110
+ }
111
+
112
+ export function ProjectSectionPending({
113
+ section,
114
+ }: {
115
+ section: ProjectPendingSection
116
+ }) {
117
+ const params = useParams({ strict: false })
118
+ const hydrated = useHydrated()
119
+ const queryClient = useQueryClient()
120
+ const projectName =
121
+ typeof params.projectName === 'string' ? params.projectName : 'Project'
122
+ const teamSlug =
123
+ typeof params.teamSlug === 'string' ? params.teamSlug : 'team'
124
+ const cachedProject = (
125
+ hydrated ? getCachedTeamContexts(queryClient, teamSlug) : []
126
+ )
127
+ .flatMap((context) => context.projects)
128
+ .find((project) => project.name === projectName)
129
+ const { data: shellResult } = useQuery({
130
+ ...projectShellQueryOptions({ projectName, teamSlug }),
131
+ enabled: false,
132
+ })
133
+ const { data: overviewResult } = useQuery({
134
+ ...projectContextQueryOptions(
135
+ { projectName, teamSlug, scope: 'overview' },
136
+ queryClient,
137
+ ),
138
+ enabled: false,
139
+ })
140
+ const cachedShell = hydrated ? shellResult : undefined
141
+ const cachedOverview = hydrated ? overviewResult : undefined
142
+ const pendingContext: PendingProjectContext = {
143
+ deployments: (hydrated
144
+ ? queryClient.getQueriesData<ProjectContext>({
145
+ queryKey: ['project-context', teamSlug, projectName],
146
+ })
147
+ : []
148
+ )
149
+ .sort(
150
+ ([first], [second]) =>
151
+ (queryClient.getQueryState(second)?.dataUpdatedAt ?? 0) -
152
+ (queryClient.getQueryState(first)?.dataUpdatedAt ?? 0),
153
+ )
154
+ .flatMap(([, value]) => value?.deployments ?? [])
155
+ .filter(
156
+ (item, index, items) =>
157
+ items.findIndex((other) => other.id === item.id) === index,
158
+ ),
159
+ environments: cachedShell?.environments,
160
+ projectId: cachedShell?.project.id ?? cachedProject?.id,
161
+ githubRepo: cachedShell?.project.githubRepo ?? cachedProject?.githubRepo,
162
+ avatarUrl:
163
+ cachedShell?.project.avatarUrl ?? cachedProject?.avatarUrl ?? null,
164
+ color: cachedShell?.project.color ?? cachedProject?.color,
165
+ deploymentId:
166
+ typeof params.deploymentId === 'string' ? params.deploymentId : null,
167
+ projectName,
168
+ teamSlug,
169
+ }
170
+ const search = useRouterState({ select: (state) => state.location.search })
171
+ const { data: logsResult } = useQuery({
172
+ ...projectLogsQueryOptions(
173
+ {
174
+ projectName,
175
+ teamSlug,
176
+ ...stringSearchValidator([
177
+ 'branch',
178
+ 'date',
179
+ 'deploymentId',
180
+ 'environmentId',
181
+ 'keyword',
182
+ ] as const)(search),
183
+ },
184
+ queryClient,
185
+ ),
186
+ enabled: false,
187
+ })
188
+ const cachedLogs = hydrated ? logsResult : undefined
189
+ if (section === 'logs' && cachedLogs)
190
+ return <ProjectLogs context={cachedLogs} />
191
+ const scope = section === 'backup' ? 'backups' : section
192
+ const sectionResult = queryClient.getQueryData<ProjectContext>([
193
+ 'project-context',
194
+ teamSlug,
195
+ projectName,
196
+ scope,
197
+ ])
198
+ const cached = hydrated ? sectionResult : undefined
199
+ if (cached && section === 'settings')
200
+ return (
201
+ <DeferredPage
202
+ module={projectSettingsModule}
203
+ fallback={<ProjectSettingsPending context={pendingContext} />}
204
+ >
205
+ {(loaded) => <loaded.ProjectSettings context={cached} />}
206
+ </DeferredPage>
207
+ )
208
+ if (cached && section === 'deployments')
209
+ return (
210
+ <DeploymentsPage
211
+ context={cached}
212
+ filters={stringSearchValidator([
213
+ 'branch', 'date', 'environment', 'status',
214
+ ] as const)(search)}
215
+ />
216
+ )
217
+ if (cached && section === 'backups')
218
+ return (
219
+ <DeferredPage
220
+ module={projectBackupsModule}
221
+ fallback={
222
+ <ProjectBackupsPending
223
+ heading={<ProjectHeadingPending context={pendingContext} crumbs={['Backups']} />}
224
+ canManage={cachedShell ? cachedShell.team.role !== 'member' : undefined}
225
+ />
226
+ }
227
+ >
228
+ {(loaded) => (
229
+ <loaded.ProjectBackups
230
+ context={cached}
231
+ filters={stringSearchValidator([
232
+ 'environment', 'server', 'status', 'time',
233
+ ] as const)(search)}
234
+ />
235
+ )}
236
+ </DeferredPage>
237
+ )
238
+ if (cached && section === 'backup' && typeof params.backupId === 'string')
239
+ return (
240
+ <DeferredPage
241
+ module={projectBackupDetailsModule}
242
+ fallback={
243
+ <BackupDetailPending
244
+ context={pendingContext}
245
+ canManage={cachedShell ? cachedShell.team.role !== 'member' : undefined}
246
+ />
247
+ }
248
+ >
249
+ {(loaded) => (
250
+ <loaded.ProjectBackupDetails context={cached} backupId={String(params.backupId)} />
251
+ )}
252
+ </DeferredPage>
253
+ )
254
+ const deploymentResult = queryClient.getQueryData<PluginContext>([
255
+ 'deployment-context',
256
+ teamSlug,
257
+ projectName,
258
+ params.deploymentId,
259
+ ])
260
+ const deployment = hydrated ? deploymentResult : undefined
261
+ if (section === 'deployment' && deployment)
262
+ return <DeploymentDetail context={deployment} />
263
+ let content
264
+
265
+ if (section === 'settings') {
266
+ content = <ProjectSettingsPending context={pendingContext} />
267
+ } else if (section === 'deployment') {
268
+ content = <DeploymentDetailPending context={pendingContext} />
269
+ } else if (section === 'backup') {
270
+ content = (
271
+ <BackupDetailPending
272
+ context={pendingContext}
273
+ canManage={cachedShell ? cachedShell.team.role !== 'member' : undefined}
274
+ />
275
+ )
276
+ } else if (section === 'logs') {
277
+ content = <ProjectLogsPending context={pendingContext} />
278
+ } else if (section === 'deployments') {
279
+ content = <DeploymentsPending context={pendingContext} />
280
+ } else if (section === 'backups') {
281
+ content = (
282
+ <ProjectBackupsPending
283
+ heading={
284
+ <ProjectHeadingPending
285
+ context={pendingContext}
286
+ crumbs={['Backups']}
287
+ />
288
+ }
289
+ canManage={cachedShell ? cachedShell.team.role !== 'member' : undefined}
290
+ />
291
+ )
292
+ } else {
293
+ content =
294
+ cachedOverview || cachedShell ? (
295
+ <ProjectOverview
296
+ context={cachedOverview ?? cachedShell!}
297
+ deploymentsPending={!cachedOverview}
298
+ />
299
+ ) : (
300
+ <ProjectOverviewPending context={pendingContext} />
301
+ )
302
+ }
303
+
304
+ return content
305
+ }
306
+
307
+ const projectSettingsModule = createDeferredModule(() => import('./project-settings'))
308
+ const projectBackupsModule = createDeferredModule(() => import('./project-backups'))
309
+ const projectBackupDetailsModule = createDeferredModule(() => import('./project-backup-details'))
310
+
311
+ export function preloadProjectPendingPage(section: ProjectPendingSection) {
312
+ if (section === 'settings') void projectSettingsModule.load()
313
+ else if (section === 'backups') void projectBackupsModule.load()
314
+ else if (section === 'backup') void projectBackupDetailsModule.load()
315
+ }
316
+
317
+ function DeferredPage<T>({ module, fallback, children }: {
318
+ module: DeferredModule<T>
319
+ fallback: ReactNode
320
+ children: (loaded: T) => ReactNode
321
+ }) {
322
+ const loaded = module.useModule(true)
323
+ return loaded ? children(loaded) : fallback
324
+ }
325
+
326
+ function ProjectPendingPage({ children }: { children: ReactNode }) {
327
+ return (
328
+ <TeamPage contentClassName="space-y-8" projectNavigation pending>
329
+ {children}
330
+ </TeamPage>
331
+ )
332
+ }
333
+
334
+ function ProjectOverviewPending({
335
+ context,
336
+ }: {
337
+ context: PendingProjectContext
338
+ }) {
339
+ return (
340
+ <div className="space-y-8" aria-label="Loading project overview">
341
+ <ProjectHeadingPending context={context} />
342
+
343
+ <section className="space-y-4">
344
+ <SectionHeadingPending action label="Environments" />
345
+ <div className="grid gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
346
+ {Array.from({ length: 1 }, (_, index) => (
347
+ <div
348
+ className="rounded-xl border bg-card p-4 text-sm text-card-foreground"
349
+ key={index}
350
+ >
351
+ <Skeleton className="mb-4 h-4 w-28" />
352
+ <div className="space-y-1">
353
+ <Skeleton className="h-5 w-4/5" />
354
+ <Skeleton className="h-5 w-24" />
355
+ </div>
356
+ </div>
357
+ ))}
358
+ </div>
359
+ </section>
360
+
361
+ <section className="space-y-4">
362
+ <SectionHeadingPending label="Latest deployments" />
363
+ <DeploymentListPending label="Latest deployments loading" />
364
+ </section>
365
+ </div>
366
+ )
367
+ }
368
+
369
+ function DeploymentsPending({ context }: { context: PendingProjectContext }) {
370
+ return (
371
+ <div className="space-y-8" aria-label="Loading deployments">
372
+ <ProjectHeadingPending context={context} crumbs={['Deployments']} />
373
+ <section className="space-y-4">
374
+ <div className="flex flex-wrap items-center gap-2">
375
+ <PendingFilter
376
+ name="environment"
377
+ label="All environments"
378
+ unknownSelection
379
+ />
380
+ <PendingFilter name="status" label="All statuses" />
381
+ <PendingFilter name="date" label="All dates" />
382
+ <PendingFilter name="branch" label="All branches" />
383
+ </div>
384
+ <DeploymentListPending label="Deployments loading" />
385
+ </section>
386
+ </div>
387
+ )
388
+ }
389
+
390
+ function DeploymentDetailPending({
391
+ context,
392
+ }: {
393
+ context: PendingProjectContext
394
+ }) {
395
+ const deployment = context.deployments?.find(
396
+ (item) => item.id === context.deploymentId,
397
+ )
398
+ return (
399
+ <div className="space-y-8" aria-label="Loading deployment">
400
+ <ProjectHeadingPending
401
+ context={context}
402
+ crumbs={[
403
+ 'Deployments',
404
+ context.deploymentId?.slice(0, 7) ?? 'Deployment',
405
+ ]}
406
+ monoLast
407
+ controls={
408
+ <div className="flex items-center gap-2">
409
+ <DeploymentVisitLinks canVisit={false} visitUrl={null} aliases={[]} />
410
+ <Button
411
+ variant="outline"
412
+ size="icon"
413
+ disabled
414
+ aria-label="Deployment options"
415
+ >
416
+ <MoreHorizontalIcon />
417
+ </Button>
418
+ </div>
419
+ }
420
+ />
421
+
422
+ {deployment ? (
423
+ <DeploymentMetadata
424
+ deployment={deployment}
425
+ deployments={context.deployments ?? []}
426
+ teamSlug={context.teamSlug}
427
+ projectName={context.projectName}
428
+ />
429
+ ) : (
430
+ <section className="space-y-4 text-sm">
431
+ <div className="flex min-h-5 flex-wrap items-center gap-x-8 gap-y-2">
432
+ <div className="w-28 shrink-0">
433
+ <MetadataItemPending icon={<CircleHelpIcon />} textWidth="w-20" />
434
+ </div>
435
+ <div className="min-w-16">
436
+ <MetadataItemPending icon={<Clock3Icon />} textWidth="w-8" />
437
+ </div>
438
+ <div className="h-5 min-w-20" />
439
+ </div>
440
+ <div className="flex min-h-5 flex-wrap items-center gap-x-8 gap-y-2">
441
+ <MetadataItemPending
442
+ icon={<Skeleton className="size-2.5 shrink-0 rounded-full" />}
443
+ textWidth="w-20"
444
+ />
445
+ <MetadataItemPending icon={<ServerIcon />} textWidth="w-36" />
446
+ <MetadataItemPending icon={<GitBranchIcon />} textWidth="w-10" />
447
+ <MetadataItemPending
448
+ icon={<GitCommitHorizontalIcon />}
449
+ iconSize={20}
450
+ textWidth="w-40"
451
+ />
452
+ </div>
453
+ </section>
454
+ )}
455
+ <section className="space-y-4">
456
+ <DeploymentLogAreaPending />
457
+ <div className="flex min-h-8 items-start justify-end">
458
+ <Button variant="outline" size="sm" disabled>
459
+ All logs
460
+ <ArrowRightIcon data-icon="inline-end" />
461
+ </Button>
462
+ </div>
463
+ </section>
464
+ </div>
465
+ )
466
+ }
467
+
468
+ function MetadataItemPending({
469
+ icon,
470
+ iconSize = 16,
471
+ textWidth,
472
+ }: {
473
+ icon: ReactNode
474
+ iconSize?: 16 | 20
475
+ textWidth: string
476
+ }) {
477
+ return (
478
+ <div
479
+ className={`flex h-5 items-center gap-x-2 text-muted-foreground [&>svg]:shrink-0 [&>svg]:opacity-50 ${iconSize === 20 ? '[&>svg]:size-5' : '[&>svg]:size-4'}`}
480
+ >
481
+ {icon}
482
+ <Skeleton className={`h-4 ${textWidth}`} />
483
+ </div>
484
+ )
485
+ }
486
+ function DeploymentLogAreaPending() {
487
+ return (
488
+ <div className="relative h-[500px] rounded-lg border p-2" aria-busy="true">
489
+ <DeploymentLogsPending />
490
+ </div>
491
+ )
492
+ }
493
+
494
+ function ProjectLogsPending({ context }: { context: PendingProjectContext }) {
495
+ const keyword = useRouterState({
496
+ select: (state) => state.location.search.keyword,
497
+ })
498
+ return (
499
+ <div className="space-y-8" aria-label="Loading project logs">
500
+ <ProjectHeadingPending context={context} crumbs={['Logs']} />
501
+ <section>
502
+ <div className="space-y-4">
503
+ <div className="flex flex-wrap items-center gap-2">
504
+ <InputGroup className="w-48">
505
+ <InputGroupAddon>
506
+ <SearchIcon />
507
+ </InputGroupAddon>
508
+ <InputGroupInput
509
+ aria-label="Find in logs"
510
+ placeholder="Find in logs"
511
+ value={typeof keyword === 'string' ? keyword : ''}
512
+ readOnly
513
+ disabled
514
+ />
515
+ </InputGroup>
516
+ <PendingFilter
517
+ name="deploymentId"
518
+ label="All deployments"
519
+ unknownSelection
520
+ />
521
+ <PendingFilter
522
+ name="environmentId"
523
+ label="All environments"
524
+ unknownSelection
525
+ />
526
+ <PendingFilter name="date" label="All dates" />
527
+ <PendingFilter name="branch" label="All branches" />
528
+ <Button
529
+ className="ml-auto"
530
+ variant="ghost"
531
+ size="icon"
532
+ disabled
533
+ aria-label="Refresh"
534
+ >
535
+ <RefreshCwIcon />
536
+ </Button>
537
+ </div>
538
+ <LogAreaPending />
539
+ </div>
540
+ </section>
541
+ </div>
542
+ )
543
+ }
544
+
545
+ function ProjectSettingsPending({
546
+ context,
547
+ }: {
548
+ context: PendingProjectContext
549
+ }) {
550
+ return (
551
+ <div className="space-y-8" aria-label="Loading project settings">
552
+ <ProjectHeadingPending context={context} crumbs={['Project settings']} />
553
+ <SettingsLayout navigation={projectSettingsNavigationItems}>
554
+ <ProjectGeneralPending context={context} />
555
+ <BuildDeployPending />
556
+ <EnvironmentSettingsPending context={context} />
557
+ <EnvironmentVariablesPending />
558
+ <ServerSettingsPending context={context} />
559
+ <RouteSettingsPending />
560
+ <DomainSettingsPending />
561
+ <DangerSettingsPending />
562
+ </SettingsLayout>
563
+ </div>
564
+ )
565
+ }
566
+
567
+ function ProjectGeneralPending({
568
+ context,
569
+ }: {
570
+ context: PendingProjectContext
571
+ }) {
572
+ return (
573
+ <SettingsSection
574
+ id="general"
575
+ title="General"
576
+ description="General settings for the project including the Git repository."
577
+ footer={<SettingsFooterPending />}
578
+ >
579
+ <div className="space-y-8">
580
+ <Field>
581
+ <FieldLabel>Avatar</FieldLabel>
582
+ <div className="flex items-center gap-x-2">
583
+ <ResourceAvatar
584
+ name={context.projectName}
585
+ src={context.avatarUrl ?? undefined}
586
+ color={context.color}
587
+ size="md"
588
+ />
589
+ <ImageUploadControls>
590
+ <Button variant="outline" size="sm" disabled>
591
+ <UploadIcon data-icon="inline-start" />
592
+ Upload image
593
+ </Button>
594
+ </ImageUploadControls>
595
+ {context.avatarUrl ? (
596
+ <Button
597
+ variant="destructive"
598
+ size="icon-sm"
599
+ disabled
600
+ aria-label="Remove image"
601
+ >
602
+ <Trash2Icon />
603
+ </Button>
604
+ ) : null}
605
+ </div>
606
+ </Field>
607
+ <Field>
608
+ <FieldLabel>Project name</FieldLabel>
609
+ <Input value={context.projectName} readOnly disabled />
610
+ </Field>
611
+ <Field>
612
+ <FieldLabel>Git repository</FieldLabel>
613
+ <div className="flex items-center">
614
+ <Button
615
+ variant="outline"
616
+ disabled
617
+ className="rounded-r-none border-r-0 font-normal"
618
+ >
619
+ {context.githubRepo ?? <Skeleton className="h-4 w-40" />}
620
+ <ChevronsUpDownIcon data-icon="inline-end" />
621
+ </Button>
622
+ <Button
623
+ variant="outline"
624
+ size="icon"
625
+ className="rounded-l-none"
626
+ disabled
627
+ aria-label="View on GitHub"
628
+ >
629
+ <ArrowUpRightIcon />
630
+ </Button>
631
+ </div>
632
+ </Field>
633
+ <Field>
634
+ <FieldLabel>Project ID</FieldLabel>
635
+ <InputGroup>
636
+ <InputGroupInput
637
+ className="font-mono text-[13px] text-muted-foreground"
638
+ value={context.projectId ?? ''}
639
+ readOnly
640
+ disabled={!context.projectId}
641
+ />
642
+ <InputGroupAddon align="inline-end">
643
+ <CopyButton
644
+ value={context.projectId ?? ''}
645
+ label="Copy project ID"
646
+ inputGroup
647
+ disabled={!context.projectId}
648
+ />
649
+ </InputGroupAddon>
650
+ </InputGroup>
651
+ <FieldDescription>
652
+ A unique identifier used in API, environment variables, etc.
653
+ </FieldDescription>
654
+ </Field>
655
+ </div>
656
+ </SettingsSection>
657
+ )
658
+ }
659
+
660
+ function BuildDeployPending() {
661
+ return (
662
+ <SettingsSection
663
+ id="build-and-deploy"
664
+ title="Build & Deploy"
665
+ description="Configure the Compose file, deployment commands, and health checks."
666
+ footer={<SettingsFooterPending />}
667
+ >
668
+ <FieldGroup>
669
+ <SettingsFieldPending
670
+ label="Compose file path"
671
+ description="Repository-relative path to the Compose file. Defaults to compose.yml."
672
+ />
673
+ <div className="grid gap-4 md:grid-cols-[minmax(0,2fr)_minmax(14rem,1fr)]">
674
+ <SettingsFieldPending
675
+ label="Pre-deploy command"
676
+ multiline
677
+ description={
678
+ <>
679
+ Runs once before services are updated. For example,{' '}
680
+ <code>php artisan migrate --force</code>. Leave empty to skip.
681
+ </>
682
+ }
683
+ />
684
+ <SettingsFieldPending
685
+ label="Pre-deploy service"
686
+ description="Service to run the pre-deploy command."
687
+ />
688
+ </div>
689
+ <SettingsFieldPending
690
+ label="Deploy command"
691
+ multiline
692
+ placeholder="docker compose up -d --no-build"
693
+ description={
694
+ <>
695
+ Runs on the server to update services after images and the
696
+ pre-deploy command are ready. When empty, uses{' '}
697
+ <code>docker compose up -d --no-build</code>.
698
+ </>
699
+ }
700
+ />
701
+ <SettingsFieldPending
702
+ label="Rollback command"
703
+ multiline
704
+ placeholder="docker compose up -d --no-build"
705
+ description="Runs on the server when deploying a previous revision. When empty, uses the deploy command."
706
+ />
707
+ <SettingsFieldPending
708
+ label="Command timeout"
709
+ description="Maximum runtime for either command: 60–7,200 seconds."
710
+ />
711
+ <SettingsFieldPending
712
+ label="Health check path"
713
+ description="Path requested after deployment to confirm the service is ready."
714
+ />
715
+ </FieldGroup>
716
+ </SettingsSection>
717
+ )
718
+ }
719
+
720
+ function SettingsFieldPending({
721
+ multiline = false,
722
+ placeholder,
723
+ description,
724
+ label,
725
+ }: {
726
+ multiline?: boolean
727
+ placeholder?: string
728
+ description?: ReactNode
729
+ label: string
730
+ }) {
731
+ return (
732
+ <Field>
733
+ <FieldLabel>{label}</FieldLabel>
734
+ {multiline ? (
735
+ <Textarea
736
+ aria-label={label}
737
+ disabled
738
+ rows={1}
739
+ className="min-h-8 resize-none overflow-hidden py-1.5 font-mono"
740
+ placeholder={placeholder}
741
+ />
742
+ ) : (
743
+ <Skeleton className="h-8 w-full" />
744
+ )}
745
+ {description ? <FieldDescription>{description}</FieldDescription> : null}
746
+ </Field>
747
+ )
748
+ }
749
+
750
+ function SettingsFooterPending() {
751
+ return (
752
+ <>
753
+ <Button variant="ghost" disabled>
754
+ Cancel
755
+ </Button>
756
+ <Button disabled>Save</Button>
757
+ </>
758
+ )
759
+ }
760
+
761
+ function EnvironmentSettingsPending({
762
+ context,
763
+ }: {
764
+ context: PendingProjectContext
765
+ }) {
766
+ return (
767
+ <SettingsTablePending
768
+ title="Environments"
769
+ description="Deployments will be associated with the first environment with a matching branch pattern."
770
+ columns="grid-cols-[repeat(3,minmax(0,1fr))_2rem]"
771
+ headers={['Name', 'Identifier', 'Branch', '']}
772
+ rows={context.environments?.length ?? 1}
773
+ values={context.environments?.map((environment) => [
774
+ environment.name,
775
+ environment.slug,
776
+ environment.branch,
777
+ null,
778
+ ])}
779
+ action="Add environment"
780
+ />
781
+ )
782
+ }
783
+
784
+ function EnvironmentVariablesPending() {
785
+ return (
786
+ <SettingsTablePending
787
+ title="Environment variables"
788
+ description="Variables available at build time and runtime. Changes will take effect after a new deployment."
789
+ columns="grid-cols-[200px_minmax(0,1fr)_220px_2rem]"
790
+ headers={['Key', 'Value', 'Environment', '']}
791
+ rows={1}
792
+ action="Add variable"
793
+ />
794
+ )
795
+ }
796
+
797
+ function ServerSettingsPending({
798
+ context,
799
+ }: {
800
+ context: PendingProjectContext
801
+ }) {
802
+ return (
803
+ <SettingsTablePending
804
+ title="Servers"
805
+ description="Choose a default server, then override individual environments when needed."
806
+ columns="grid-cols-[minmax(0,1fr)_minmax(0,1fr)]"
807
+ headers={['Scope', 'Server']}
808
+ rows={1 + (context.environments?.length ?? 1)}
809
+ values={[
810
+ ['Default', null],
811
+ ...(context.environments?.map((environment) => [
812
+ environment.name,
813
+ null,
814
+ ]) ?? [[null, null]]),
815
+ ]}
816
+ />
817
+ )
818
+ }
819
+
820
+ function RouteSettingsPending() {
821
+ return (
822
+ <SettingsTablePending
823
+ title="Routes"
824
+ description="Default routes apply to every environment. Environment routes add paths or override the default target for the same path."
825
+ columns="grid-cols-[minmax(8rem,0.8fr)_minmax(0,1.1fr)_1rem_minmax(0,1.1fr)_minmax(5.5rem,0.4fr)_3.5rem_2.5rem]"
826
+ headers={['Scope', 'Path', '', 'Service', 'Port', 'Strip', '']}
827
+ rows={1}
828
+ action="Add route"
829
+ />
830
+ )
831
+ }
832
+
833
+ function DomainSettingsPending() {
834
+ return (
835
+ <SettingsTablePending
836
+ title="Domains"
837
+ description="Custom domain names associated with this project's environments."
838
+ columns="grid-cols-[minmax(0,1.4fr)_minmax(0,1.05fr)_minmax(0,1.05fr)_3.5rem_5.5rem_2.5rem]"
839
+ headers={['Host', 'Environment', 'Base route', 'HTTPS', '', '']}
840
+ rows={1}
841
+ action="Add domain"
842
+ />
843
+ )
844
+ }
845
+
846
+ function DangerSettingsPending() {
847
+ return (
848
+ <SettingsSection
849
+ id="danger"
850
+ title="Danger"
851
+ description="Permanently delete the project and all of its configuration, environments and deployments."
852
+ >
853
+ <Button variant="destructive" disabled>
854
+ Delete project
855
+ </Button>
856
+ </SettingsSection>
857
+ )
858
+ }
859
+
860
+ function SettingsTablePending({
861
+ action,
862
+ columns,
863
+ description,
864
+ headers,
865
+ rows,
866
+ title,
867
+ values,
868
+ }: {
869
+ action?: string
870
+ columns: string
871
+ description: string
872
+ headers: Array<string>
873
+ rows: number
874
+ values?: Array<Array<string | null>>
875
+ title: string
876
+ }) {
877
+ return (
878
+ <SettingsSection
879
+ id={title.toLowerCase().replaceAll(' ', '-')}
880
+ title={title}
881
+ description={description}
882
+ footer={<SettingsFooterPending />}
883
+ >
884
+ <div
885
+ className={`grid gap-x-2 border-b ${title === 'Routes' || title === 'Domains' ? 'pb-2' : 'pb-3'} text-sm font-medium ${columns}`}
886
+ >
887
+ {headers.map((header, index) => (
888
+ <span key={`${header}:${index}`}>{header}</span>
889
+ ))}
890
+ </div>
891
+ {Array.from({ length: rows }, (_, row) => (
892
+ <div
893
+ className={`grid min-h-12 items-center gap-x-2 border-b py-2 ${columns}`}
894
+ key={row}
895
+ >
896
+ {headers.map((header, column) =>
897
+ values?.[row]?.[column] != null ? (
898
+ title === 'Servers' ? (
899
+ <span className="text-sm" key={column}>
900
+ {values[row][column]}
901
+ </span>
902
+ ) : (
903
+ <Input
904
+ key={column}
905
+ value={values[row][column] ?? ''}
906
+ readOnly
907
+ disabled
908
+ />
909
+ )
910
+ ) : (
911
+ <Skeleton
912
+ className={
913
+ header === '' ? 'size-8 justify-self-end' : 'h-8 w-full'
914
+ }
915
+ key={column}
916
+ />
917
+ ),
918
+ )}
919
+ </div>
920
+ ))}
921
+ {action ? (
922
+ <Button className="mt-3" variant="outline" disabled>
923
+ <PlusIcon data-icon="inline-start" />
924
+ {action}
925
+ </Button>
926
+ ) : null}
927
+ </SettingsSection>
928
+ )
929
+ }
930
+
931
+ function BackupDetailPending({
932
+ context,
933
+ canManage,
934
+ }: {
935
+ context: PendingProjectContext
936
+ canManage?: boolean
937
+ }) {
938
+ return (
939
+ <ProjectBackupDetailsPending
940
+ canManage={canManage}
941
+ heading={
942
+ <ProjectHeadingPending context={context} crumbs={['Backups', null]} />
943
+ }
944
+ />
945
+ )
946
+ }
947
+
948
+ function ProjectHeadingPending({
949
+ controls,
950
+ context,
951
+ crumbs = [],
952
+ monoLast = false,
953
+ }: {
954
+ controls?: ReactNode
955
+ context: PendingProjectContext
956
+ crumbs?: Array<string | null>
957
+ monoLast?: boolean
958
+ }) {
959
+ return (
960
+ <ProjectHeading
961
+ teamSlug={context.teamSlug}
962
+ project={{
963
+ avatarUrl: context.avatarUrl,
964
+ color: context.color,
965
+ name: context.projectName,
966
+ }}
967
+ crumbs={crumbs.map((crumb, index) =>
968
+ crumb ? (
969
+ <span
970
+ className={
971
+ monoLast && index === crumbs.length - 1
972
+ ? 'truncate font-mono'
973
+ : 'truncate'
974
+ }
975
+ >
976
+ {crumb}
977
+ </span>
978
+ ) : (
979
+ <Skeleton className="h-8 w-32" />
980
+ ),
981
+ )}
982
+ actions={controls}
983
+ />
984
+ )
985
+ }
986
+
987
+ function SectionHeadingPending({
988
+ action = false,
989
+ label,
990
+ }: {
991
+ action?: boolean
992
+ label: string
993
+ }) {
994
+ return (
995
+ <div className="flex h-8 items-center justify-between gap-4">
996
+ <h2 className="text-base font-medium">{label}</h2>
997
+ {action ? (
998
+ <Button disabled>
999
+ <SettingsIcon data-icon="inline-start" />
1000
+ Manage environments
1001
+ </Button>
1002
+ ) : null}
1003
+ </div>
1004
+ )
1005
+ }
1006
+
1007
+ function LogAreaPending() {
1008
+ return (
1009
+ <div className="relative h-[500px] rounded-lg border p-2" aria-busy="true">
1010
+ <div
1011
+ role="status"
1012
+ className="flex h-full items-center justify-center gap-2 text-sm text-muted-foreground"
1013
+ >
1014
+ <LoaderIcon
1015
+ className="size-4 shrink-0 animate-spin"
1016
+ aria-hidden="true"
1017
+ />
1018
+ Loading logs
1019
+ </div>
1020
+ </div>
1021
+ )
1022
+ }
1023
+
1024
+ function PendingFilter({
1025
+ name,
1026
+ label,
1027
+ className,
1028
+ unknownSelection = false,
1029
+ }: {
1030
+ name: string
1031
+ label: string
1032
+ className?: string
1033
+ unknownSelection?: boolean
1034
+ }) {
1035
+ const search = useRouterState({ select: (state) => state.location.search })
1036
+ const value = (search as Record<string, unknown>)[name]
1037
+ const selected = typeof value === 'string' && value !== 'all' && value !== ''
1038
+ const dates: Record<string, string> = {
1039
+ today: 'Today',
1040
+ '24h': 'Last 24 hours',
1041
+ '7d': 'Last 7 days',
1042
+ '30d': 'Last 30 days',
1043
+ }
1044
+ const selectedLabel = selected
1045
+ ? name === 'date'
1046
+ ? (dates[value] ?? label)
1047
+ : name === 'status'
1048
+ ? value.charAt(0).toUpperCase() + value.slice(1)
1049
+ : value
1050
+ : label
1051
+ return (
1052
+ <Select disabled value={selected ? value : 'all'}>
1053
+ <SelectTrigger className={className} aria-label={label}>
1054
+ <SelectValue placeholder={label}>
1055
+ {selected && unknownSelection ? (
1056
+ <Skeleton className="h-4 w-24" />
1057
+ ) : (
1058
+ selectedLabel
1059
+ )}
1060
+ </SelectValue>
1061
+ </SelectTrigger>
1062
+ </Select>
1063
+ )
1064
+ }