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,257 @@
1
+ import { workspacePollInterval } from './workspace-updates'
2
+ import { invalidateResourceQueries } from './resource-cache'
3
+ import { queryOptions } from '@tanstack/react-query'
4
+ import { redirect } from '@tanstack/react-router'
5
+ import type { QueryClient } from '@tanstack/react-query'
6
+
7
+ import {
8
+ getDeployment,
9
+ getProject,
10
+ getProjectLogs,
11
+ getProjectSection,
12
+ } from '#devpush/server/projects/functions'
13
+ import type {
14
+ PluginContext,
15
+ ProjectContext,
16
+ ProjectLogsContext,
17
+ ProjectSectionContext,
18
+ } from '#devpush/server/projects/types'
19
+
20
+ export type ProjectContextKeyParams = {
21
+ projectName: string
22
+ teamSlug: string
23
+ }
24
+
25
+ export type ProjectContextParams = ProjectContextKeyParams & {
26
+ scope: ProjectSectionContext['scope']
27
+ }
28
+
29
+ function projectContextQueryKey({
30
+ projectName,
31
+ scope,
32
+ teamSlug,
33
+ }: ProjectContextParams) {
34
+ return ['project-context', teamSlug, projectName, scope] as const
35
+ }
36
+
37
+ export function projectContextQueryOptions(
38
+ params: ProjectContextParams,
39
+ queryClient: QueryClient,
40
+ ) {
41
+ return queryOptions({
42
+ queryKey: projectContextQueryKey(params),
43
+ refetchIntervalInBackground: false,
44
+ refetchOnWindowFocus: ['overview', 'deployments', 'backups'].includes(
45
+ params.scope,
46
+ ),
47
+ refetchInterval: (query) => {
48
+ if (!['overview', 'deployments', 'backups'].includes(params.scope))
49
+ return false
50
+ if (
51
+ query.state.data?.deployments.some((deployment) =>
52
+ ['queued', 'running'].includes(deployment.status),
53
+ ) ||
54
+ (params.scope === 'backups' &&
55
+ (query.state.data?.backupRuns.some((run) =>
56
+ ['queued', 'running'].includes(run.status),
57
+ ) ||
58
+ query.state.data?.backupRestores.some((restore) =>
59
+ ['queued', 'running'].includes(restore.status),
60
+ )))
61
+ )
62
+ return workspacePollInterval(params.teamSlug, 2_000)
63
+ return workspacePollInterval(params.teamSlug, 15_000)
64
+ },
65
+ queryFn: async () => {
66
+ const [shell, section] = await loadProjectSection(
67
+ queryClient,
68
+ params,
69
+ () => getProjectSection({ data: params }),
70
+ )
71
+
72
+ return mergeProjectSection(shell, section)
73
+ },
74
+ })
75
+ }
76
+
77
+ export function projectShellQueryOptions(params: ProjectContextKeyParams) {
78
+ return queryOptions({
79
+ queryKey: ['project-context', params.teamSlug, params.projectName, 'shell'],
80
+ queryFn: () => fetchProjectContext(params),
81
+ })
82
+ }
83
+
84
+ export function pluginContextQueryOptions(
85
+ params: ProjectContextKeyParams & { deploymentId: string },
86
+ queryClient: QueryClient,
87
+ ) {
88
+ return queryOptions({
89
+ refetchIntervalInBackground: false,
90
+ refetchOnWindowFocus: true,
91
+ refetchInterval: (query) =>
92
+ ['queued', 'running'].includes(query.state.data?.deployment.status ?? '')
93
+ ? workspacePollInterval(params.teamSlug, 2_000)
94
+ : workspacePollInterval(params.teamSlug, 15_000),
95
+ queryKey: [
96
+ 'deployment-context',
97
+ params.teamSlug,
98
+ params.projectName,
99
+ params.deploymentId,
100
+ ],
101
+ queryFn: async (): Promise<PluginContext> => {
102
+ const [shell, section] = await loadProjectSection(
103
+ queryClient,
104
+ params,
105
+ () => getDeployment({ data: params }),
106
+ )
107
+
108
+ return {
109
+ userContext: shell.userContext,
110
+ team: shell.team,
111
+ project: shell.project,
112
+ teamProjects: shell.teamProjects,
113
+ latestDeployments: section.latestDeployments,
114
+ environment: shell.environment,
115
+ environments: shell.environments,
116
+ servers: shell.servers,
117
+ deployment: section.deployment,
118
+ visitAliases: section.visitAliases,
119
+ logs: [],
120
+ }
121
+ },
122
+ })
123
+ }
124
+
125
+ export type ProjectLogsQueryParams = ProjectContextKeyParams & {
126
+ branch?: string | null
127
+ date?: string | null
128
+ deploymentId?: string | null
129
+ environmentId?: string | null
130
+ keyword?: string | null
131
+ }
132
+
133
+ export function projectLogsQueryOptions(
134
+ params: ProjectLogsQueryParams,
135
+ queryClient: QueryClient,
136
+ ) {
137
+ return queryOptions({
138
+ refetchIntervalInBackground: false,
139
+ refetchInterval: () => workspacePollInterval(params.teamSlug, 2_000),
140
+ queryKey: [
141
+ 'project-logs',
142
+ params.teamSlug,
143
+ params.projectName,
144
+ params.branch ?? null,
145
+ params.date ?? null,
146
+ params.deploymentId ?? null,
147
+ params.environmentId ?? null,
148
+ params.keyword ?? null,
149
+ ],
150
+ queryFn: async (): Promise<ProjectLogsContext> => {
151
+ const [shell, section] = await loadProjectSection(
152
+ queryClient,
153
+ params,
154
+ () => getProjectLogs({ data: params }),
155
+ )
156
+
157
+ return { ...shell, ...section }
158
+ },
159
+ })
160
+ }
161
+
162
+ export function invalidateProjectLogs(
163
+ queryClient: QueryClient,
164
+ params: ProjectLogsQueryParams,
165
+ ) {
166
+ return queryClient.invalidateQueries({
167
+ queryKey: [
168
+ 'project-logs',
169
+ params.teamSlug,
170
+ params.projectName,
171
+ params.branch ?? null,
172
+ params.date ?? null,
173
+ params.deploymentId ?? null,
174
+ params.environmentId ?? null,
175
+ params.keyword ?? null,
176
+ ],
177
+ })
178
+ }
179
+
180
+ export async function setProjectContextQueryData(
181
+ queryClient: QueryClient,
182
+ context: ProjectContext,
183
+ scope: ProjectSectionContext['scope'],
184
+ ) {
185
+ await invalidateResourceQueries(queryClient, context.team.slug)
186
+ const prefix = ['project-context', context.team.slug, context.project.name]
187
+
188
+ queryClient.setQueryData([...prefix, 'shell'], context)
189
+ queryClient.setQueryData([...prefix, scope], context)
190
+ }
191
+
192
+ export function invalidateProjectContext(
193
+ queryClient: QueryClient,
194
+ params: ProjectContextKeyParams,
195
+ ) {
196
+ return queryClient.invalidateQueries({
197
+ queryKey: ['project-context', params.teamSlug, params.projectName],
198
+ })
199
+ }
200
+
201
+ async function fetchProjectContext(
202
+ params: ProjectContextKeyParams,
203
+ ): Promise<ProjectContext> {
204
+ const context = await getProject({ data: params })
205
+
206
+ if (!context) {
207
+ throw redirect({ to: '/auth/login' })
208
+ }
209
+
210
+ return context
211
+ }
212
+
213
+ async function loadProjectSection<T extends { projectId: string }>(
214
+ queryClient: QueryClient,
215
+ params: ProjectContextKeyParams,
216
+ loadSection: () => Promise<T | null>,
217
+ ): Promise<[ProjectContext, T]> {
218
+ const [shell, section] = await Promise.all([
219
+ queryClient.fetchQuery(projectShellQueryOptions(params)),
220
+ loadSection(),
221
+ ])
222
+
223
+ if (!section) throw redirect({ to: '/auth/login' })
224
+ if (section.projectId !== shell.project.id) {
225
+ throw new Error('Project context changed while loading.')
226
+ }
227
+
228
+ return [shell, section]
229
+ }
230
+
231
+ function mergeProjectSection(
232
+ shell: ProjectContext,
233
+ section: ProjectSectionContext,
234
+ ): ProjectContext {
235
+ if (section.scope === 'overview') {
236
+ return {
237
+ ...shell,
238
+ deployments: section.deployments,
239
+ environment: { ...shell.environment, runtime: section.runtime },
240
+ }
241
+ }
242
+
243
+ if (section.scope === 'deployments') {
244
+ return { ...shell, deployments: section.deployments }
245
+ }
246
+
247
+ if (section.scope === 'settings') return section.context
248
+
249
+ return {
250
+ ...shell,
251
+ backupStorage: section.backupStorage,
252
+ backupConfigs: section.backupConfigs,
253
+ backupRuns: section.backupRuns,
254
+ backupRestores: section.backupRestores,
255
+ composeMetadata: section.composeMetadata,
256
+ }
257
+ }
@@ -0,0 +1,64 @@
1
+ import { z } from 'zod'
2
+ import { projectMachineNameSchema } from './project-settings-schema'
3
+ import {
4
+ maxLifecycleCommandLength,
5
+ repositoryComposePathSchema,
6
+ } from '#devpush/lib/project-runtime-config'
7
+
8
+ export const envVarSchema = z.object({
9
+ key: z
10
+ .string()
11
+ .trim()
12
+ .min(1, 'Enter a variable key.')
13
+ .regex(
14
+ /^[A-Za-z_][A-Za-z0-9_]*$/,
15
+ 'Environment variable keys must use shell-compatible names.',
16
+ ),
17
+ value: z.string(),
18
+ environment: z.string().trim().optional(),
19
+ })
20
+
21
+ export const createProjectSchema = z.object({
22
+ teamSlug: z.string().min(1),
23
+ installationId: z.number().int().positive(),
24
+ serverId: z.string().trim().optional().nullable(),
25
+ composePath: repositoryComposePathSchema.or(z.literal('')).optional(),
26
+ readinessPath: z
27
+ .string()
28
+ .trim()
29
+ .refine((value) => !value || value.startsWith('/'), {
30
+ message: 'Health check paths must start with /.',
31
+ })
32
+ .optional(),
33
+ releaseCommand: z
34
+ .string()
35
+ .trim()
36
+ .max(
37
+ maxLifecycleCommandLength,
38
+ 'Commands must be 16,384 characters or fewer.',
39
+ )
40
+ .optional(),
41
+ releaseService: z.string().trim().optional(),
42
+ envVars: z
43
+ .array(envVarSchema)
44
+ .superRefine((rows, ctx) => {
45
+ const keys = new Set<string>()
46
+ rows.forEach((row, index) => {
47
+ const key = JSON.stringify([row.environment || 'production', row.key])
48
+ if (keys.has(key))
49
+ ctx.addIssue({
50
+ code: 'custom',
51
+ path: [index, 'key'],
52
+ message:
53
+ 'Environment variable keys must be unique per environment.',
54
+ })
55
+ keys.add(key)
56
+ })
57
+ })
58
+ .optional(),
59
+ projectName: projectMachineNameSchema,
60
+ repoFullName: z
61
+ .string()
62
+ .min(1)
63
+ .regex(/^[^/\s]+\/[^/\s]+$/, 'Select a valid GitHub repository.'),
64
+ })
@@ -0,0 +1,23 @@
1
+ import { settingsVersionSchema } from '#devpush/lib/settings-version'
2
+ import { z } from 'zod'
3
+ import { envVarSchema } from './project-creation-schema'
4
+
5
+ export const projectEnvVarsSchema = z.object({
6
+ settingsVersion: settingsVersionSchema,
7
+ projectId: z.string().min(1),
8
+ teamSlug: z.string().min(1),
9
+ projectName: z.string().min(1),
10
+ envVars: z.array(envVarSchema).superRefine((rows, ctx) => {
11
+ const seen = new Set<string>()
12
+ rows.forEach((row, index) => {
13
+ const key = JSON.stringify([row.environment || '', row.key])
14
+ if (seen.has(key))
15
+ ctx.addIssue({
16
+ code: 'custom',
17
+ path: [index, 'key'],
18
+ message: 'Environment variable keys must be unique per environment.',
19
+ })
20
+ seen.add(key)
21
+ })
22
+ }),
23
+ })
@@ -0,0 +1,56 @@
1
+ import { settingsVersionSchema } from '#devpush/lib/settings-version'
2
+ import { z } from 'zod'
3
+
4
+ const projectEnvironmentSchema = z.object({
5
+ id: z.string().trim().min(1),
6
+ name: z
7
+ .string()
8
+ .trim()
9
+ .min(1, 'Enter an environment name.')
10
+ .max(255, 'Names must be 255 characters or fewer.'),
11
+ slug: z
12
+ .string()
13
+ .trim()
14
+ .min(2, 'Enter an identifier with at least two characters.')
15
+ .max(255, 'Identifiers must be 255 characters or fewer.')
16
+ .regex(
17
+ /^[A-Za-z0-9][A-Za-z0-9._-]*[A-Za-z0-9]$/,
18
+ 'Environment identifiers can only contain letters, numbers, hyphens, underscores and dots. They cannot start or end with a dot, underscore or hyphen.',
19
+ ),
20
+ branch: z
21
+ .string()
22
+ .trim()
23
+ .min(1, 'Enter a branch pattern.')
24
+ .max(255, 'Branch patterns must be 255 characters or fewer.'),
25
+ color: z.string().trim().min(1).max(32),
26
+ position: z.number().int().nonnegative(),
27
+ status: z.enum(['active', 'deleted']),
28
+ })
29
+
30
+ export const projectEnvironmentsSchema = z.object({
31
+ settingsVersion: settingsVersionSchema,
32
+ projectId: z.string().min(1),
33
+ teamSlug: z.string().min(1),
34
+ projectName: z.string().min(1),
35
+ environments: z
36
+ .array(projectEnvironmentSchema)
37
+ .min(1, 'Production environment is required.')
38
+ .superRefine((rows, ctx) => {
39
+ const seen = new Set<string>()
40
+ rows.forEach((row, index) => {
41
+ if (row.status !== 'active') return
42
+ if (seen.has(row.slug))
43
+ ctx.addIssue({
44
+ code: 'custom',
45
+ path: [index, 'slug'],
46
+ message: 'Environment identifiers must be unique.',
47
+ })
48
+ seen.add(row.slug)
49
+ })
50
+ if (!seen.has('production'))
51
+ ctx.addIssue({
52
+ code: 'custom',
53
+ message: 'Production environment is required.',
54
+ })
55
+ }),
56
+ })
@@ -0,0 +1,94 @@
1
+ import { settingsVersionSchema } from '#devpush/lib/settings-version'
2
+ import { z } from 'zod'
3
+
4
+ const routeSchema = z
5
+ .object({
6
+ service: z
7
+ .string()
8
+ .trim()
9
+ .min(1, 'Enter a service name.')
10
+ .regex(
11
+ /^[A-Za-z0-9._-]+$/,
12
+ 'Route services can only contain letters, numbers, dots, underscores, and hyphens.',
13
+ ),
14
+ port: z
15
+ .number()
16
+ .int('Enter a whole-number port.')
17
+ .min(1, 'Route ports must be between 1 and 65535.')
18
+ .max(65535, 'Route ports must be between 1 and 65535.'),
19
+ path: z
20
+ .string()
21
+ .trim()
22
+ .min(1, 'Enter a route path.')
23
+ .regex(/^\//, 'Route paths must start with /.')
24
+ .regex(
25
+ /^[^\s`?#]+$/,
26
+ 'Route paths cannot contain spaces, backticks, queries or fragments.',
27
+ ),
28
+ stripPrefix: z.boolean().optional().default(false),
29
+ environmentId: z.string().trim().min(1).optional(),
30
+ })
31
+ .superRefine((route, context) => {
32
+ if (route.stripPrefix && route.path.includes(',')) {
33
+ context.addIssue({
34
+ code: 'custom',
35
+ path: ['path'],
36
+ message: 'Comma paths cannot use prefix stripping.',
37
+ })
38
+ }
39
+ })
40
+
41
+ const domainSchema = z.object({
42
+ host: z
43
+ .string()
44
+ .toLowerCase()
45
+ .trim()
46
+ .min(1, 'Enter a domain hostname.')
47
+ .max(253, 'Hostnames must be 253 characters or fewer.')
48
+ .regex(
49
+ /^(?!-)[a-z0-9-]{1,63}(?<!-)(\.(?!-)[a-z0-9-]{1,63}(?<!-))*$/,
50
+ 'Domains must be valid DNS hostnames without protocol, path, wildcard, or port.',
51
+ ),
52
+ environmentId: z.string().trim().min(1),
53
+ basePath: z.string().trim(),
54
+ https: z.boolean(),
55
+ })
56
+
57
+ export const projectRoutesSchema = z.object({
58
+ settingsVersion: settingsVersionSchema,
59
+ projectId: z.string().min(1),
60
+ teamSlug: z.string().min(1),
61
+ projectName: z.string().min(1),
62
+ routes: z.array(routeSchema).superRefine((routes, ctx) => {
63
+ const seen = new Set<string>()
64
+ routes.forEach((route, index) => {
65
+ const key = JSON.stringify([route.environmentId ?? null, route.path])
66
+ if (seen.has(key))
67
+ ctx.addIssue({
68
+ code: 'custom',
69
+ path: [index, 'path'],
70
+ message: 'Each route path can only be configured once per scope.',
71
+ })
72
+ seen.add(key)
73
+ })
74
+ }),
75
+ })
76
+
77
+ export const projectDomainsSchema = z.object({
78
+ settingsVersion: settingsVersionSchema,
79
+ projectId: z.string().min(1),
80
+ teamSlug: z.string().min(1),
81
+ projectName: z.string().min(1),
82
+ domains: z.array(domainSchema).superRefine((domains, ctx) => {
83
+ const seen = new Set<string>()
84
+ domains.forEach((domain, index) => {
85
+ if (seen.has(domain.host))
86
+ ctx.addIssue({
87
+ code: 'custom',
88
+ path: [index, 'host'],
89
+ message: 'Each domain can only be added once.',
90
+ })
91
+ seen.add(domain.host)
92
+ })
93
+ }),
94
+ })
@@ -0,0 +1,131 @@
1
+ import { PublicError } from '#devpush/lib/public-error'
2
+ import { z } from 'zod'
3
+
4
+ export const maxLifecycleCommandLength = 16 * 1024
5
+
6
+ const lifecycleCommandSchema = z
7
+ .string()
8
+ .trim()
9
+ .max(
10
+ maxLifecycleCommandLength,
11
+ 'Commands must be 16,384 characters or fewer.',
12
+ )
13
+ .nullable()
14
+
15
+ export const repositoryComposePathSchema = z
16
+ .string()
17
+ .trim()
18
+ .min(1)
19
+ .max(255, 'Compose paths must be 255 characters or fewer.')
20
+ .refine(isRepositoryRelativePath, {
21
+ message: 'Compose paths must stay within the repository.',
22
+ })
23
+
24
+ const composeRuntimeConfigSchema = z.strictObject({
25
+ type: z.literal('compose'),
26
+ path: repositoryComposePathSchema.nullable(),
27
+ releaseCommand: lifecycleCommandSchema,
28
+ releaseService: z.string().trim().min(1).nullable(),
29
+ readinessPath: z
30
+ .string()
31
+ .trim()
32
+ .regex(/^\//, 'Readiness paths must start with /.')
33
+ .nullable(),
34
+ deployCommand: lifecycleCommandSchema,
35
+ rollbackCommand: lifecycleCommandSchema,
36
+ timeoutSeconds: z
37
+ .number()
38
+ .int('Enter a whole number of seconds.')
39
+ .min(60, 'Timeout must be at least 60 seconds.')
40
+ .max(7200, 'Timeout must be 7,200 seconds or fewer.'),
41
+ })
42
+
43
+ export const projectRuntimeConfigSchema = composeRuntimeConfigSchema
44
+
45
+ export type ComposeRuntimeConfig = z.infer<typeof composeRuntimeConfigSchema>
46
+
47
+ // Add future runtime kinds as new members of this discriminated union.
48
+ export type ProjectRuntimeConfig = ComposeRuntimeConfig
49
+
50
+ export function createComposeRuntimeConfig(
51
+ overrides: Partial<Omit<ComposeRuntimeConfig, 'type'>> = {},
52
+ ): ComposeRuntimeConfig {
53
+ return {
54
+ type: 'compose',
55
+ path: null,
56
+ releaseCommand: null,
57
+ releaseService: null,
58
+ readinessPath: null,
59
+ deployCommand: null,
60
+ rollbackCommand: null,
61
+ timeoutSeconds: 1800,
62
+ ...overrides,
63
+ }
64
+ }
65
+
66
+ export function parseProjectRuntimeConfig(
67
+ value: unknown,
68
+ ): ProjectRuntimeConfig | null {
69
+ const result = projectRuntimeConfigSchema.safeParse(value)
70
+ return result.success ? result.data : null
71
+ }
72
+
73
+ export function requireProjectRuntimeConfig(
74
+ value: unknown,
75
+ ): ProjectRuntimeConfig {
76
+ const config = parseProjectRuntimeConfig(value)
77
+ if (!config) {
78
+ throw new PublicError('PROJECT_RUNTIME_CONFIG_INVALID')
79
+ }
80
+ return config
81
+ }
82
+
83
+ function isRepositoryRelativePath(value: string) {
84
+ if (
85
+ value.startsWith('/') ||
86
+ value.includes('\\') ||
87
+ Array.from(value).some((character) => {
88
+ const code = character.charCodeAt(0)
89
+ return code <= 31 || code === 127
90
+ })
91
+ ) {
92
+ return false
93
+ }
94
+
95
+ const segments = value.split('/')
96
+ return segments.every(
97
+ (segment) => segment.length > 0 && segment !== '.' && segment !== '..',
98
+ )
99
+ }
100
+
101
+ export function hostLifecycleCommandsChanged(
102
+ current: ProjectRuntimeConfig,
103
+ next: ProjectRuntimeConfig,
104
+ ) {
105
+ return (
106
+ current.deployCommand !== next.deployCommand ||
107
+ current.rollbackCommand !== next.rollbackCommand
108
+ )
109
+ }
110
+
111
+ export function hostLifecycleCommandsUseChangedSource({
112
+ currentRepoFullName,
113
+ currentRuntimeConfig,
114
+ nextRepoFullName,
115
+ nextRuntimeConfig,
116
+ }: {
117
+ currentRepoFullName: string
118
+ currentRuntimeConfig: ProjectRuntimeConfig
119
+ nextRepoFullName: string
120
+ nextRuntimeConfig: ProjectRuntimeConfig
121
+ }) {
122
+ const hasHostCommand = Boolean(
123
+ nextRuntimeConfig.deployCommand || nextRuntimeConfig.rollbackCommand,
124
+ )
125
+
126
+ return (
127
+ hasHostCommand &&
128
+ (nextRepoFullName !== currentRepoFullName ||
129
+ nextRuntimeConfig.path !== currentRuntimeConfig.path)
130
+ )
131
+ }
@@ -0,0 +1,75 @@
1
+ import { settingsVersionSchema } from '#devpush/lib/settings-version'
2
+ import { z } from 'zod'
3
+ import { avatarDataUrlSchema } from '#devpush/lib/avatar-schema'
4
+ import { projectRuntimeConfigSchema } from '#devpush/lib/project-runtime-config'
5
+ import { formErrors } from './form-errors'
6
+ import type { FormErrors } from './form-errors'
7
+
8
+ export const projectMachineNameSchema = z
9
+ .string()
10
+ .trim()
11
+ .min(2, 'Enter a project name with at least 2 characters.')
12
+ .max(100, 'Project names must be 100 characters or fewer.')
13
+ .regex(
14
+ /^[A-Za-z0-9][A-Za-z0-9._-]*[A-Za-z0-9]$/,
15
+ 'Project names must start and end with a letter or number and only use letters, numbers, dots, underscores, or hyphens.',
16
+ )
17
+
18
+ export const projectSettingsSchema = z.object({
19
+ settingsVersion: settingsVersionSchema,
20
+ projectId: z.string().min(1),
21
+ section: z.enum(['general', 'build']),
22
+ teamSlug: z.string().min(1),
23
+ currentProjectName: z.string().min(1),
24
+ projectName: projectMachineNameSchema,
25
+ repoFullName: z
26
+ .string()
27
+ .regex(/^[^/\s]+\/[^/\s]+$/, 'Select a valid GitHub repository.'),
28
+ installationId: z.number().int().positive().nullable(),
29
+ avatarDataUrl: avatarDataUrlSchema,
30
+ deleteAvatar: z.boolean().optional(),
31
+ runtimeConfig: projectRuntimeConfigSchema,
32
+ })
33
+
34
+ export function projectSettingsErrors(error: unknown): FormErrors | null {
35
+ if (!(error instanceof Error)) return null
36
+ if (error.message === 'Project names must be unique within a team.')
37
+ return { projectName: error.message }
38
+ if (
39
+ [
40
+ 'Upload a PNG, JPEG, or WebP image.',
41
+ 'Avatars must be 2 MB or smaller.',
42
+ 'The uploaded file does not match its image type.',
43
+ ].includes(error.message)
44
+ )
45
+ return { avatarDataUrl: error.message }
46
+ if (
47
+ [
48
+ 'Select a GitHub App installation for this repository.',
49
+ 'Connect GitHub before changing the repository.',
50
+ 'Install the GitHub app for this repository to continue.',
51
+ 'You need push access to use this repository.',
52
+ ].includes(error.message)
53
+ )
54
+ return { repoFullName: error.message }
55
+ return null
56
+ }
57
+
58
+ export function projectValidationErrors(
59
+ issues: Array<{ path: PropertyKey[]; message: string }>,
60
+ ): FormErrors {
61
+ const errors = formErrors(issues)
62
+ if (errors.installationId) {
63
+ errors.repoFullName ??=
64
+ 'Select a valid GitHub App installation for this repository.'
65
+ delete errors.installationId
66
+ }
67
+ for (const key of ['teamSlug', 'currentProjectName']) {
68
+ if (errors[key]) {
69
+ errors.form ??=
70
+ 'This project could not be identified. Reload the page and try again.'
71
+ delete errors[key]
72
+ }
73
+ }
74
+ return errors
75
+ }