devpush 0.0.0-stage → 1.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (456) hide show
  1. package/README.md +22 -2
  2. package/agent-release.json +37 -0
  3. package/dist/package/LICENSE.md +21 -0
  4. package/dist/package/migrate.mjs +182 -0
  5. package/dist/package/plugins.mjs +125 -0
  6. package/dist/package/vite.mjs +439 -0
  7. package/package.json +168 -3
  8. package/public/apple-touch-icon.png +0 -0
  9. package/public/favicon.svg +1 -0
  10. package/public/logo-email.png +0 -0
  11. package/public/robots.txt +3 -0
  12. package/public/social.png +0 -0
  13. package/src/cloudflare-runtime.d.ts +78 -0
  14. package/src/components/about-dialog.tsx +70 -0
  15. package/src/components/copy-button.tsx +74 -0
  16. package/src/components/logo-mark.tsx +20 -0
  17. package/src/components/pending-collection.tsx +103 -0
  18. package/src/components/resource-avatar.tsx +65 -0
  19. package/src/components/route-error-state.tsx +95 -0
  20. package/src/components/section-navigation.tsx +39 -0
  21. package/src/components/settings-layout.tsx +111 -0
  22. package/src/components/storage-capabilities.tsx +11 -0
  23. package/src/components/table-pager.tsx +101 -0
  24. package/src/components/ui/alert-dialog.tsx +180 -0
  25. package/src/components/ui/alert.tsx +78 -0
  26. package/src/components/ui/avatar.tsx +58 -0
  27. package/src/components/ui/badge.tsx +49 -0
  28. package/src/components/ui/button-group.tsx +38 -0
  29. package/src/components/ui/button.tsx +67 -0
  30. package/src/components/ui/card.tsx +82 -0
  31. package/src/components/ui/chart.tsx +307 -0
  32. package/src/components/ui/checkbox.tsx +30 -0
  33. package/src/components/ui/combobox.tsx +256 -0
  34. package/src/components/ui/dialog.tsx +163 -0
  35. package/src/components/ui/dropdown-menu.tsx +110 -0
  36. package/src/components/ui/empty.tsx +65 -0
  37. package/src/components/ui/field.tsx +192 -0
  38. package/src/components/ui/input-group.tsx +118 -0
  39. package/src/components/ui/input-otp.tsx +70 -0
  40. package/src/components/ui/input.tsx +19 -0
  41. package/src/components/ui/label.tsx +24 -0
  42. package/src/components/ui/pagination.tsx +34 -0
  43. package/src/components/ui/popover.tsx +86 -0
  44. package/src/components/ui/select.tsx +145 -0
  45. package/src/components/ui/skeleton.tsx +19 -0
  46. package/src/components/ui/sonner.tsx +47 -0
  47. package/src/components/ui/switch.tsx +31 -0
  48. package/src/components/ui/table.tsx +81 -0
  49. package/src/components/ui/textarea.tsx +18 -0
  50. package/src/components/ui/tooltip.tsx +55 -0
  51. package/src/features/admin/admin-settings.tsx +1048 -0
  52. package/src/features/auth/current-user-context-query.ts +11 -0
  53. package/src/features/auth/login-form.tsx +442 -0
  54. package/src/features/auth/sign-out-menu-item.tsx +42 -0
  55. package/src/features/deployments/deployment-actions.tsx +336 -0
  56. package/src/features/deployments/deployment-alias-badge.tsx +35 -0
  57. package/src/features/deployments/deployment-detail.tsx +596 -0
  58. package/src/features/deployments/deployment-filters.ts +66 -0
  59. package/src/features/deployments/deployment-list.tsx +465 -0
  60. package/src/features/deployments/deployment-visit-links.tsx +104 -0
  61. package/src/features/deployments/deployments-page.tsx +376 -0
  62. package/src/features/logs/log-scroll-area.tsx +69 -0
  63. package/src/features/notifications/email-preferences.tsx +232 -0
  64. package/src/features/onboarding/profile-onboarding.tsx +162 -0
  65. package/src/features/plugins/contributions.tsx +118 -0
  66. package/src/features/projects/backup-job-logs.tsx +143 -0
  67. package/src/features/projects/backup-policy-delete-dialog.tsx +82 -0
  68. package/src/features/projects/backup-restore-actions.tsx +573 -0
  69. package/src/features/projects/backup-status-icon.tsx +33 -0
  70. package/src/features/projects/backup-target-list.tsx +222 -0
  71. package/src/features/projects/backup-timestamp.tsx +32 -0
  72. package/src/features/projects/backup-volume-picker.tsx +158 -0
  73. package/src/features/projects/compose-service-input.tsx +64 -0
  74. package/src/features/projects/deploy-readiness.ts +28 -0
  75. package/src/features/projects/environment-dot.tsx +91 -0
  76. package/src/features/projects/log-keyword-filter.tsx +48 -0
  77. package/src/features/projects/manual-deploy-dialog.tsx +563 -0
  78. package/src/features/projects/pending-section.ts +26 -0
  79. package/src/features/projects/project-backup-details-pending.tsx +305 -0
  80. package/src/features/projects/project-backup-details.tsx +936 -0
  81. package/src/features/projects/project-backup-policy.tsx +644 -0
  82. package/src/features/projects/project-backup-utils.ts +190 -0
  83. package/src/features/projects/project-backups-pending.tsx +136 -0
  84. package/src/features/projects/project-backups.tsx +876 -0
  85. package/src/features/projects/project-environment-settings.tsx +1128 -0
  86. package/src/features/projects/project-heading.tsx +68 -0
  87. package/src/features/projects/project-layout.tsx +188 -0
  88. package/src/features/projects/project-logs.tsx +461 -0
  89. package/src/features/projects/project-overview.tsx +280 -0
  90. package/src/features/projects/project-route-pending.tsx +1064 -0
  91. package/src/features/projects/project-routing-settings.tsx +663 -0
  92. package/src/features/projects/project-settings-section.tsx +64 -0
  93. package/src/features/projects/project-settings-utils.ts +101 -0
  94. package/src/features/projects/project-settings.tsx +1472 -0
  95. package/src/features/projects/repository-picker-dialog.tsx +404 -0
  96. package/src/features/repositories/new-project-dialog-launcher.tsx +44 -0
  97. package/src/features/repositories/new-project-dialog.tsx +1204 -0
  98. package/src/features/repositories/new-project-pending.tsx +28 -0
  99. package/src/features/repositories/repository-list-pending.tsx +59 -0
  100. package/src/features/servers/server-availability-alert.tsx +44 -0
  101. package/src/features/servers/server-availability.ts +29 -0
  102. package/src/features/servers/server-avatar.tsx +52 -0
  103. package/src/features/servers/server-detail-pending.tsx +315 -0
  104. package/src/features/servers/server-detail.tsx +855 -0
  105. package/src/features/servers/server-metric-chart-pending.tsx +38 -0
  106. package/src/features/servers/server-metric-chart.tsx +182 -0
  107. package/src/features/servers/server-metric-format.ts +50 -0
  108. package/src/features/servers/server-metrics-pending.tsx +75 -0
  109. package/src/features/servers/server-metrics.tsx +136 -0
  110. package/src/features/servers/team-servers.tsx +432 -0
  111. package/src/features/settings/settings-route-pending.tsx +406 -0
  112. package/src/features/teams/backup-storage-settings.tsx +483 -0
  113. package/src/features/teams/navigation.ts +51 -0
  114. package/src/features/teams/new-team-dialog.tsx +132 -0
  115. package/src/features/teams/pending-section.ts +19 -0
  116. package/src/features/teams/team-header.tsx +1286 -0
  117. package/src/features/teams/team-heading.tsx +57 -0
  118. package/src/features/teams/team-home.tsx +391 -0
  119. package/src/features/teams/team-invite.tsx +155 -0
  120. package/src/features/teams/team-route-pending.tsx +505 -0
  121. package/src/features/teams/team-settings.tsx +1555 -0
  122. package/src/features/teams/use-workspace-updates.ts +126 -0
  123. package/src/features/users/api-keys-settings.tsx +544 -0
  124. package/src/features/users/api-scope-selection.tsx +269 -0
  125. package/src/features/users/connected-apps-settings.tsx +449 -0
  126. package/src/features/users/mcp-consent.tsx +220 -0
  127. package/src/features/users/profile-picture.tsx +171 -0
  128. package/src/features/users/user-settings.tsx +1064 -0
  129. package/src/lib/account-settings-schema.ts +78 -0
  130. package/src/lib/action-feedback.ts +81 -0
  131. package/src/lib/admin-search-schema.ts +8 -0
  132. package/src/lib/agent-release.d.mts +21 -0
  133. package/src/lib/agent-release.mjs +90 -0
  134. package/src/lib/allowlist-schema.ts +79 -0
  135. package/src/lib/api-key-schema.ts +176 -0
  136. package/src/lib/auth/client.ts +20 -0
  137. package/src/lib/avatar-schema.ts +11 -0
  138. package/src/lib/backup-cron.ts +118 -0
  139. package/src/lib/backup-database.ts +41 -0
  140. package/src/lib/backup-feature.ts +2 -0
  141. package/src/lib/backup-paths.ts +7 -0
  142. package/src/lib/backup-policy-schema.ts +85 -0
  143. package/src/lib/backup-resource-schema.ts +12 -0
  144. package/src/lib/backup-restore-schema.ts +15 -0
  145. package/src/lib/backup-storage-schema.ts +55 -0
  146. package/src/lib/deferred-module.ts +38 -0
  147. package/src/lib/deployment-logs-query.ts +63 -0
  148. package/src/lib/display-name.ts +7 -0
  149. package/src/lib/error-message.ts +119 -0
  150. package/src/lib/form-errors.ts +10 -0
  151. package/src/lib/internal-redirect.ts +25 -0
  152. package/src/lib/manual-deploy-options-query.ts +21 -0
  153. package/src/lib/notification-preferences.ts +43 -0
  154. package/src/lib/onboarding.ts +11 -0
  155. package/src/lib/operation-logs-query.ts +51 -0
  156. package/src/lib/page-head.ts +14 -0
  157. package/src/lib/project-context-query.ts +257 -0
  158. package/src/lib/project-creation-schema.ts +64 -0
  159. package/src/lib/project-env-schema.ts +23 -0
  160. package/src/lib/project-environments-schema.ts +56 -0
  161. package/src/lib/project-routing-schema.ts +94 -0
  162. package/src/lib/project-runtime-config.ts +131 -0
  163. package/src/lib/project-settings-schema.ts +75 -0
  164. package/src/lib/public-api-schema.ts +128 -0
  165. package/src/lib/public-error.ts +337 -0
  166. package/src/lib/query.ts +13 -0
  167. package/src/lib/repository-selection-query.ts +11 -0
  168. package/src/lib/resource-cache.ts +30 -0
  169. package/src/lib/runtime-id.ts +19 -0
  170. package/src/lib/search.ts +14 -0
  171. package/src/lib/server-context-query.ts +43 -0
  172. package/src/lib/server-name-schema.ts +11 -0
  173. package/src/lib/settings-conflict.ts +45 -0
  174. package/src/lib/settings-version.ts +7 -0
  175. package/src/lib/team-context-query.ts +126 -0
  176. package/src/lib/team-context-schema.ts +8 -0
  177. package/src/lib/team-forms-schema.ts +45 -0
  178. package/src/lib/team-role.ts +13 -0
  179. package/src/lib/team-settings-schema.ts +48 -0
  180. package/src/lib/urls.ts +3 -0
  181. package/src/lib/use-avatar-input.ts +61 -0
  182. package/src/lib/use-refresh-error.ts +18 -0
  183. package/src/lib/use-settings-version.ts +23 -0
  184. package/src/lib/utils.ts +6 -0
  185. package/src/lib/validation.ts +5 -0
  186. package/src/lib/workspace-updates.ts +53 -0
  187. package/src/package/config.ts +10 -0
  188. package/src/package/env.mjs +54 -0
  189. package/src/package/migrate-api.d.ts +5 -0
  190. package/src/package/migrate.ts +56 -0
  191. package/src/package/plugins.ts +141 -0
  192. package/src/package/sqlite.mjs +2 -0
  193. package/src/package/vite-api.d.ts +5 -0
  194. package/src/package/vite.ts +324 -0
  195. package/src/package/watch-guard.cjs +26 -0
  196. package/src/plugins/client.ts +57 -0
  197. package/src/plugins/contract.ts +188 -0
  198. package/src/plugins/default.server.ts +4 -0
  199. package/src/plugins/default.ui.ts +4 -0
  200. package/src/plugins/operations.ts +45 -0
  201. package/src/plugins/server.ts +197 -0
  202. package/src/plugins/version.ts +1 -0
  203. package/src/routeTree.gen.ts +1275 -0
  204. package/src/router.tsx +46 -0
  205. package/src/routes/$teamSlug.index.tsx +53 -0
  206. package/src/routes/$teamSlug.new-project.tsx +13 -0
  207. package/src/routes/$teamSlug.projects.$projectName.backups.$backupId.tsx +40 -0
  208. package/src/routes/$teamSlug.projects.$projectName.backups.tsx +82 -0
  209. package/src/routes/$teamSlug.projects.$projectName.deployments.$deploymentId.tsx +52 -0
  210. package/src/routes/$teamSlug.projects.$projectName.deployments.tsx +86 -0
  211. package/src/routes/$teamSlug.projects.$projectName.logs.tsx +55 -0
  212. package/src/routes/$teamSlug.projects.$projectName.settings.tsx +51 -0
  213. package/src/routes/$teamSlug.projects.$projectName.tsx +106 -0
  214. package/src/routes/$teamSlug.projects.tsx +40 -0
  215. package/src/routes/$teamSlug.servers.$serverId.tsx +31 -0
  216. package/src/routes/$teamSlug.servers.index.tsx +59 -0
  217. package/src/routes/$teamSlug.servers.tsx +7 -0
  218. package/src/routes/$teamSlug.settings.tsx +56 -0
  219. package/src/routes/$teamSlug.tsx +86 -0
  220. package/src/routes/[.]well-known.oauth-authorization-server.api.auth.ts +13 -0
  221. package/src/routes/[.]well-known.oauth-protected-resource.mcp.ts +6 -0
  222. package/src/routes/__root.tsx +155 -0
  223. package/src/routes/admin.tsx +55 -0
  224. package/src/routes/api/agents/certificates.ts +21 -0
  225. package/src/routes/api/agents/credentials/rotate.ts +22 -0
  226. package/src/routes/api/agents/heartbeat.ts +42 -0
  227. package/src/routes/api/agents/job-logs.ts +22 -0
  228. package/src/routes/api/agents/jobs/complete.ts +22 -0
  229. package/src/routes/api/agents/jobs/next.ts +29 -0
  230. package/src/routes/api/agents/jobs/progress.ts +32 -0
  231. package/src/routes/api/agents/jobs/status.ts +22 -0
  232. package/src/routes/api/agents/register.ts +45 -0
  233. package/src/routes/api/agents/runtime-logs.ts +22 -0
  234. package/src/routes/api/agents/switch.ts +25 -0
  235. package/src/routes/api/auth/$.ts +38 -0
  236. package/src/routes/api/cron/backups.ts +33 -0
  237. package/src/routes/api/cron/maintenance.ts +30 -0
  238. package/src/routes/api/github/install.callback.ts +51 -0
  239. package/src/routes/api/github/install.ts +41 -0
  240. package/src/routes/api/github/webhook.ts +11 -0
  241. package/src/routes/api/health.ts +19 -0
  242. package/src/routes/api/install/agent.$token.ts +50 -0
  243. package/src/routes/api/install/agent.binary.ts +83 -0
  244. package/src/routes/api/install/agent.checksum.ts +45 -0
  245. package/src/routes/api/install/agent.ts +26 -0
  246. package/src/routes/api/plugins/$.ts +11 -0
  247. package/src/routes/api/v1/$.ts +15 -0
  248. package/src/routes/api/workspaces/updates.ts +8 -0
  249. package/src/routes/auth.login.tsx +50 -0
  250. package/src/routes/deployments.$deploymentId.unavailable.tsx +21 -0
  251. package/src/routes/index.tsx +23 -0
  252. package/src/routes/invites.$token.tsx +35 -0
  253. package/src/routes/mcp.ts +13 -0
  254. package/src/routes/oauth.consent.tsx +54 -0
  255. package/src/routes/onboarding.tsx +37 -0
  256. package/src/routes/onboarding.workspace.tsx +44 -0
  257. package/src/routes/settings.tsx +30 -0
  258. package/src/routes/uploads/avatars/$fileName.ts +44 -0
  259. package/src/server/admin/access.ts +67 -0
  260. package/src/server/admin/functions.ts +122 -0
  261. package/src/server/admin/service.ts +260 -0
  262. package/src/server/agents/backup-state.ts +210 -0
  263. package/src/server/agents/certificates.ts +96 -0
  264. package/src/server/agents/claims.ts +116 -0
  265. package/src/server/agents/cleanup.ts +52 -0
  266. package/src/server/agents/compatibility.ts +116 -0
  267. package/src/server/agents/completion.ts +83 -0
  268. package/src/server/agents/container-cleanup-state.ts +51 -0
  269. package/src/server/agents/deployment-state.ts +111 -0
  270. package/src/server/agents/job-payloads.ts +159 -0
  271. package/src/server/agents/jobs.ts +1293 -0
  272. package/src/server/agents/protocol.ts +400 -0
  273. package/src/server/agents/reconciliation.ts +43 -0
  274. package/src/server/agents/runtime-logs.ts +337 -0
  275. package/src/server/agents/runtime-state.ts +125 -0
  276. package/src/server/api/admission.ts +12 -0
  277. package/src/server/api/authorization.ts +115 -0
  278. package/src/server/api/credentials.ts +569 -0
  279. package/src/server/api/cursors.ts +72 -0
  280. package/src/server/api/errors.ts +24 -0
  281. package/src/server/api/functions.ts +60 -0
  282. package/src/server/api/handler.ts +274 -0
  283. package/src/server/api/mutation-operations.ts +913 -0
  284. package/src/server/api/operation-context.ts +139 -0
  285. package/src/server/api/operation-contract.ts +1099 -0
  286. package/src/server/api/operations.ts +64 -0
  287. package/src/server/api/read-operations.ts +891 -0
  288. package/src/server/api/retention.ts +33 -0
  289. package/src/server/auth/access.ts +109 -0
  290. package/src/server/auth/auth-methods.ts +28 -0
  291. package/src/server/auth/better-auth.ts +518 -0
  292. package/src/server/auth/context.ts +28 -0
  293. package/src/server/auth/email-change.ts +47 -0
  294. package/src/server/auth/email-delivery.ts +51 -0
  295. package/src/server/auth/provider-identity.ts +19 -0
  296. package/src/server/auth/provider-tokens.ts +27 -0
  297. package/src/server/auth/session.server.ts +19 -0
  298. package/src/server/backups/database-commands.ts +40 -0
  299. package/src/server/backups/destination.ts +12 -0
  300. package/src/server/backups/feature.ts +6 -0
  301. package/src/server/backups/policy-validation.ts +51 -0
  302. package/src/server/backups/queue-state.ts +137 -0
  303. package/src/server/backups/service.ts +918 -0
  304. package/src/server/backups/storage-functions.ts +42 -0
  305. package/src/server/backups/storage-state.ts +67 -0
  306. package/src/server/backups/storage.ts +237 -0
  307. package/src/server/config.ts +274 -0
  308. package/src/server/db/atomic.ts +16 -0
  309. package/src/server/db/d1-connection.ts +83 -0
  310. package/src/server/db/errors.ts +17 -0
  311. package/src/server/db/index.cloudflare.ts +15 -0
  312. package/src/server/db/index.ts +35 -0
  313. package/src/server/db/libsql-connection.ts +72 -0
  314. package/src/server/db/log-schema.ts +102 -0
  315. package/src/server/db/migrate-libsql.ts +29 -0
  316. package/src/server/db/migrate-sqlite.ts +47 -0
  317. package/src/server/db/migration-history.ts +12 -0
  318. package/src/server/db/migrations-logs/0000_initial.sql +48 -0
  319. package/src/server/db/migrations-logs/0001_runtime_log_order.sql +2 -0
  320. package/src/server/db/migrations-logs/meta/0000_snapshot.json +327 -0
  321. package/src/server/db/migrations-logs/meta/0001_snapshot.json +329 -0
  322. package/src/server/db/migrations-logs/meta/_journal.json +20 -0
  323. package/src/server/db/migrations-sqlite/0000_initial.sql +894 -0
  324. package/src/server/db/migrations-sqlite/0001_agent_indexes.sql +5 -0
  325. package/src/server/db/migrations-sqlite/meta/0000_snapshot.json +5816 -0
  326. package/src/server/db/migrations-sqlite/meta/0001_snapshot.json +5841 -0
  327. package/src/server/db/migrations-sqlite/meta/_journal.json +20 -0
  328. package/src/server/db/oauth-schema.ts +216 -0
  329. package/src/server/db/remote-config.ts +47 -0
  330. package/src/server/db/schema.ts +1218 -0
  331. package/src/server/db/sqlite-connection.ts +94 -0
  332. package/src/server/db/sqlite-files.ts +27 -0
  333. package/src/server/db/sqlite-params.ts +19 -0
  334. package/src/server/db/worker-connection.ts +47 -0
  335. package/src/server/deleted-resources.ts +133 -0
  336. package/src/server/deletion-recovery.ts +272 -0
  337. package/src/server/email/layout.tsx +85 -0
  338. package/src/server/email/notification-template.tsx +62 -0
  339. package/src/server/email/otp-template.tsx +48 -0
  340. package/src/server/email/service.ts +251 -0
  341. package/src/server/email/team-invite-template.tsx +59 -0
  342. package/src/server/email/theme.ts +10 -0
  343. package/src/server/github/http.ts +14 -0
  344. package/src/server/github/install-state.ts +48 -0
  345. package/src/server/github/service.ts +831 -0
  346. package/src/server/github/webhook-handler.ts +198 -0
  347. package/src/server/github/webhooks.ts +205 -0
  348. package/src/server/http/auth.ts +11 -0
  349. package/src/server/http/error-diagnostics.ts +66 -0
  350. package/src/server/http/plugin-request.ts +15 -0
  351. package/src/server/http/proxy-request.ts +53 -0
  352. package/src/server/http/public-errors.ts +48 -0
  353. package/src/server/http/rate-limit.ts +59 -0
  354. package/src/server/http/request-body.ts +54 -0
  355. package/src/server/http/request-ip.ts +54 -0
  356. package/src/server/http/security-headers.ts +36 -0
  357. package/src/server/install/agent-binary.ts +230 -0
  358. package/src/server/install/agent-script.ts +565 -0
  359. package/src/server/install/release-binary.ts +65 -0
  360. package/src/server/logs/budget.ts +72 -0
  361. package/src/server/logs/cleanup.ts +64 -0
  362. package/src/server/logs/deployments.ts +98 -0
  363. package/src/server/logs/operations.ts +145 -0
  364. package/src/server/logs/retention.ts +39 -0
  365. package/src/server/maintenance.ts +61 -0
  366. package/src/server/mcp/connections.ts +358 -0
  367. package/src/server/mcp/functions.ts +63 -0
  368. package/src/server/mcp/handler.ts +295 -0
  369. package/src/server/mcp/oauth-discovery.cloudflare.ts +6 -0
  370. package/src/server/mcp/oauth-discovery.ts +8 -0
  371. package/src/server/mcp/oauth-provider.ts +220 -0
  372. package/src/server/mcp/urls.ts +20 -0
  373. package/src/server/notifications/functions.ts +51 -0
  374. package/src/server/notifications/observe.ts +303 -0
  375. package/src/server/notifications/outbox.ts +335 -0
  376. package/src/server/notifications/preferences.ts +131 -0
  377. package/src/server/notifications/query.ts +16 -0
  378. package/src/server/plugins/scope.ts +17 -0
  379. package/src/server/plugins/server-context.ts +157 -0
  380. package/src/server/plugins/services.server.ts +10 -0
  381. package/src/server/plugins/workspace-access.ts +74 -0
  382. package/src/server/projects/backup-policy-state.ts +68 -0
  383. package/src/server/projects/backups.ts +219 -0
  384. package/src/server/projects/compose-metadata.ts +174 -0
  385. package/src/server/projects/configuration-state.ts +90 -0
  386. package/src/server/projects/context.ts +909 -0
  387. package/src/server/projects/creation-state.ts +46 -0
  388. package/src/server/projects/deletion-state.ts +69 -0
  389. package/src/server/projects/deployment-config.ts +139 -0
  390. package/src/server/projects/deployment-logs.ts +97 -0
  391. package/src/server/projects/deployments.ts +1123 -0
  392. package/src/server/projects/domain-state.ts +71 -0
  393. package/src/server/projects/environment-branches.ts +75 -0
  394. package/src/server/projects/environment-state.ts +158 -0
  395. package/src/server/projects/environments.ts +424 -0
  396. package/src/server/projects/functions.ts +748 -0
  397. package/src/server/projects/generated-hostname.ts +77 -0
  398. package/src/server/projects/hostnames.ts +244 -0
  399. package/src/server/projects/mappers.ts +457 -0
  400. package/src/server/projects/names.ts +41 -0
  401. package/src/server/projects/project-env-vars.ts +28 -0
  402. package/src/server/projects/queue-state.ts +118 -0
  403. package/src/server/projects/repository.ts +368 -0
  404. package/src/server/projects/route-state.ts +37 -0
  405. package/src/server/projects/runtime-logs.ts +97 -0
  406. package/src/server/projects/settings-context.ts +58 -0
  407. package/src/server/projects/settings.ts +738 -0
  408. package/src/server/projects/types.ts +355 -0
  409. package/src/server/realtime/agent-events.ts +32 -0
  410. package/src/server/realtime/node-broadcaster.ts +71 -0
  411. package/src/server/realtime/node-handler.ts +55 -0
  412. package/src/server/realtime/service.ts +125 -0
  413. package/src/server/realtime/workspace-object.cloudflare.ts +62 -0
  414. package/src/server/realtime/workspace-object.ts +3 -0
  415. package/src/server/resources/color.ts +13 -0
  416. package/src/server/runtime/background.ts +21 -0
  417. package/src/server/runtime/node-asset-cache-plugin.ts +12 -0
  418. package/src/server/runtime/node-scheduler-plugin.ts +17 -0
  419. package/src/server/runtime/node-scheduler.ts +51 -0
  420. package/src/server/runtime/platform-env.cloudflare.ts +17 -0
  421. package/src/server/runtime/platform-env.ts +19 -0
  422. package/src/server/runtime/scheduled-task-error.ts +9 -0
  423. package/src/server/scheduled-tasks.ts +28 -0
  424. package/src/server/secrets/crypto.ts +56 -0
  425. package/src/server/servers/functions.ts +233 -0
  426. package/src/server/servers/lifecycle.ts +122 -0
  427. package/src/server/servers/metric-history-schema.ts +24 -0
  428. package/src/server/servers/metric-history.ts +55 -0
  429. package/src/server/servers/monitoring.ts +592 -0
  430. package/src/server/servers/service.ts +1211 -0
  431. package/src/server/servers/status.ts +32 -0
  432. package/src/server/servers/switch.ts +96 -0
  433. package/src/server/settings-version.ts +43 -0
  434. package/src/server/storage/capabilities.ts +6 -0
  435. package/src/server/storage/local-object-store.cloudflare.ts +7 -0
  436. package/src/server/storage/local-object-store.ts +50 -0
  437. package/src/server/storage/object-store.ts +160 -0
  438. package/src/server/teams/creation.ts +51 -0
  439. package/src/server/teams/delete-policy.ts +13 -0
  440. package/src/server/teams/deletion-state.ts +80 -0
  441. package/src/server/teams/functions.ts +324 -0
  442. package/src/server/teams/invitations.ts +38 -0
  443. package/src/server/teams/membership.ts +86 -0
  444. package/src/server/teams/queries.ts +261 -0
  445. package/src/server/teams/service.ts +1127 -0
  446. package/src/server/teams/types.ts +101 -0
  447. package/src/server/uploads/avatar.ts +133 -0
  448. package/src/server/users/deletion-state.ts +93 -0
  449. package/src/server/users/functions.ts +160 -0
  450. package/src/server/users/onboarding.ts +17 -0
  451. package/src/server/users/service.ts +644 -0
  452. package/src/server.ts +55 -0
  453. package/src/styles/shadcn.css +653 -0
  454. package/src/styles/theme.css +76 -0
  455. package/src/styles.css +177 -0
  456. package/wrangler.template.jsonc +37 -0
@@ -0,0 +1,42 @@
1
+ import { settingsVersionSchema } from '#devpush/lib/settings-version'
2
+ import { publicErrorsMiddleware } from '#devpush/server/http/public-errors'
3
+ import { createServerFn } from '@tanstack/react-start'
4
+ import { z } from 'zod'
5
+ import { backupStorageSchema } from '#devpush/lib/backup-storage-schema'
6
+ import { requireAuthSession } from '#devpush/server/auth/session.server'
7
+ import { saveBackupStorage, deleteBackupStorage } from './storage'
8
+ import { requireBackupsEnabled } from './feature'
9
+
10
+ export const saveBackupStorageAction = createServerFn({ method: 'POST' })
11
+ .middleware([publicErrorsMiddleware])
12
+ .validator((data: unknown) =>
13
+ z
14
+ .object({
15
+ teamSlug: z.string().min(1),
16
+ settingsVersion: settingsVersionSchema.nullable(),
17
+ input: backupStorageSchema,
18
+ })
19
+ .parse(data),
20
+ )
21
+ .handler(async ({ data }) => {
22
+ requireBackupsEnabled()
23
+ const session = await requireAuthSession()
24
+ return saveBackupStorage({ userId: session.user.id, ...data })
25
+ })
26
+
27
+ export const deleteBackupStorageAction = createServerFn({ method: 'POST' })
28
+ .middleware([publicErrorsMiddleware])
29
+ .validator((data: unknown) =>
30
+ z
31
+ .object({
32
+ teamSlug: z.string().min(1),
33
+ settingsVersion: settingsVersionSchema,
34
+ storageId: z.string().min(1),
35
+ })
36
+ .parse(data),
37
+ )
38
+ .handler(async ({ data }) => {
39
+ requireBackupsEnabled()
40
+ const session = await requireAuthSession()
41
+ return deleteBackupStorage({ userId: session.user.id, ...data })
42
+ })
@@ -0,0 +1,67 @@
1
+ import { sql } from 'drizzle-orm'
2
+ import type { AtomicStore } from '#devpush/server/db/atomic'
3
+ import type { ApiMutationAdmission } from '#devpush/server/api/admission'
4
+ import { teamSettingsAccess } from '#devpush/server/settings-version'
5
+
6
+ type StorageValues = {
7
+ name: string
8
+ bucket: string
9
+ region: string
10
+ endpoint: string | null
11
+ accessKeyId: string
12
+ secretAccessKeyCiphertext: string
13
+ }
14
+
15
+ export async function saveBackupStorageState(
16
+ store: AtomicStore,
17
+ input: {
18
+ teamId: string
19
+ userId: string
20
+ storageId: string
21
+ settingsVersion: number | null
22
+ values: StorageValues
23
+ now: Date
24
+ admission?: ApiMutationAdmission
25
+ },
26
+ ) {
27
+ const v = input.values
28
+ const access = sql`${teamSettingsAccess(input.teamId, input.userId)} and ${input.admission?.guard ?? sql`1`}`
29
+ const now = input.now.getTime()
30
+ const effect =
31
+ input.settingsVersion === null
32
+ ? store.all(sql`insert into backup_storage (id,team_id,name,bucket,region,endpoint,access_key_id,secret_access_key_ciphertext,created_at,updated_at)
33
+ select ${input.storageId},${input.teamId},${v.name},${v.bucket},${v.region},${v.endpoint},${v.accessKeyId},${v.secretAccessKeyCiphertext},${now},${now} where ${access} returning id`)
34
+ : store.all(sql`update backup_storage set settings_version = settings_version + 1,
35
+ name = ${v.name},access_key_id = ${v.accessKeyId},secret_access_key_ciphertext = ${v.secretAccessKeyCiphertext},updated_at = ${now}
36
+ where id = ${input.storageId} and team_id = ${input.teamId} and settings_version = ${input.settingsVersion}
37
+ and bucket = ${v.bucket} and region = ${v.region} and coalesce(endpoint,'') = ${v.endpoint ?? ''} and ${access} returning id`)
38
+ const [rows] = await store.batch([
39
+ effect,
40
+ ...(input.admission?.statements ?? []),
41
+ ])
42
+ return rows.length === 1
43
+ }
44
+
45
+ export async function deleteBackupStorageState(
46
+ store: AtomicStore,
47
+ input: {
48
+ teamId: string
49
+ userId: string
50
+ storageId: string
51
+ settingsVersion: number
52
+ admission?: ApiMutationAdmission
53
+ },
54
+ ) {
55
+ // A cleanup completion can remove only local files. Remaining remote artifact
56
+ // rows, not the run's last-cleanup timestamp, decide whether credentials are needed.
57
+ const unreferenced = sql`not exists(select 1 from backup_configs where json_extract(destination,'$.storageId') = ${input.storageId})
58
+ and not exists(select 1 from backup_runs as run where json_extract(run.destination,'$.storageId') = ${input.storageId}
59
+ and (run.status in ('queued','running') or exists(select 1 from backup_artifacts where backup_run_id = run.id and storage = 'remote')))`
60
+ const [rows] = await store.batch([
61
+ store.all(sql`delete from backup_storage where id = ${input.storageId} and team_id = ${input.teamId}
62
+ and settings_version = ${input.settingsVersion} and ${teamSettingsAccess(input.teamId, input.userId)}
63
+ and ${input.admission?.guard ?? sql`1`} and ${unreferenced} returning id`),
64
+ ...(input.admission?.statements ?? []),
65
+ ])
66
+ return rows.length === 1
67
+ }
@@ -0,0 +1,237 @@
1
+ import { withWorkspaceAccess } from '#devpush/server/plugins/workspace-access'
2
+ import {
3
+ saveBackupStorageState,
4
+ deleteBackupStorageState,
5
+ } from './storage-state'
6
+ import { requireBackupsEnabled } from '#devpush/server/backups/feature'
7
+ import { PublicError } from '#devpush/lib/public-error'
8
+ import { and, asc, eq } from 'drizzle-orm'
9
+ import { backupStorageSchema } from '#devpush/lib/backup-storage-schema'
10
+ import type {
11
+ BackupStorageInput,
12
+ BackupStorageSummary,
13
+ BackupStorageReference,
14
+ } from '#devpush/lib/backup-storage-schema'
15
+ import { db } from '#devpush/server/db'
16
+ import { backupStorage } from '#devpush/server/db/schema'
17
+ import { encryptSecret } from '#devpush/server/secrets/crypto'
18
+ import { requireTeam, requireTeamManager } from '#devpush/server/teams/service'
19
+ import type { BackupDestination } from './service'
20
+
21
+ export async function listBackupStorage(
22
+ teamId: string,
23
+ includeAccessKey = false,
24
+ ): Promise<BackupStorageSummary[]> {
25
+ const rows = await db
26
+ .select({
27
+ settingsVersion: backupStorage.settingsVersion,
28
+ id: backupStorage.id,
29
+ name: backupStorage.name,
30
+ bucket: backupStorage.bucket,
31
+ region: backupStorage.region,
32
+ endpoint: backupStorage.endpoint,
33
+ accessKeyId: backupStorage.accessKeyId,
34
+ })
35
+ .from(backupStorage)
36
+ .where(eq(backupStorage.teamId, teamId))
37
+ .orderBy(asc(backupStorage.name))
38
+ return rows.map(({ accessKeyId, ...row }) => ({
39
+ ...row,
40
+ hasSecret: true,
41
+ ...(includeAccessKey ? { accessKeyId } : {}),
42
+ }))
43
+ }
44
+
45
+ export async function getBackupStorage({
46
+ userId,
47
+ teamSlug,
48
+ }: {
49
+ userId: string
50
+ teamSlug: string
51
+ }) {
52
+ const team = await requireTeam(userId, teamSlug)
53
+ return listBackupStorage(team.id, team.role !== 'member')
54
+ }
55
+
56
+ export async function saveBackupStorage({
57
+ userId,
58
+ teamSlug,
59
+ input,
60
+ settingsVersion,
61
+ }: {
62
+ userId: string
63
+ teamSlug: string
64
+ settingsVersion?: number | null
65
+ input: BackupStorageInput
66
+ }) {
67
+ requireBackupsEnabled()
68
+ const team = await requireTeamManager(userId, teamSlug)
69
+ return withWorkspaceAccess(
70
+ team.id,
71
+ 'backup.storage.save',
72
+ async () => {
73
+ const prepared = await prepareBackupStorage({
74
+ teamId: team.id,
75
+ input,
76
+ settingsVersion,
77
+ })
78
+ const changed = await saveBackupStorageState(db, {
79
+ teamId: team.id,
80
+ userId,
81
+ storageId: prepared.id,
82
+ settingsVersion: prepared.settingsVersion,
83
+ values: prepared.values,
84
+ now: new Date(),
85
+ })
86
+ if (!changed)
87
+ throw new PublicError(
88
+ prepared.settingsVersion === null ? 'FORBIDDEN' : 'SETTINGS_CONFLICT',
89
+ )
90
+ return listBackupStorage(team.id, true)
91
+ },
92
+ { kind: 'user', userId },
93
+ )
94
+ }
95
+
96
+ export async function resolveBackupDestination(
97
+ teamId: string,
98
+ reference: BackupStorageReference,
99
+ ): Promise<BackupDestination> {
100
+ if (!reference.storageId) throw new PublicError('BACKUP_STORAGE_REQUIRED')
101
+ const row = (
102
+ await db
103
+ .select()
104
+ .from(backupStorage)
105
+ .where(
106
+ and(
107
+ eq(backupStorage.id, reference.storageId),
108
+ eq(backupStorage.teamId, teamId),
109
+ ),
110
+ )
111
+ .limit(1)
112
+ ).at(0)
113
+ if (!row) throw new PublicError('BACKUP_STORAGE_TEAM_MISMATCH')
114
+ return {
115
+ storageId: row.id,
116
+ bucket: row.bucket,
117
+ region: row.region,
118
+ endpoint: row.endpoint ?? undefined,
119
+ prefix: reference.prefix,
120
+ accessKeyId: row.accessKeyId,
121
+ secretAccessKeyCiphertext: row.secretAccessKeyCiphertext,
122
+ }
123
+ }
124
+
125
+ export async function refreshBackupCredentials(
126
+ teamId: string,
127
+ snapshot: BackupDestination,
128
+ ): Promise<BackupDestination> {
129
+ if (!snapshot.storageId) throw new PublicError('BACKUP_STORAGE_REQUIRED')
130
+ const current = await resolveBackupDestination(teamId, {
131
+ storageId: snapshot.storageId,
132
+ })
133
+ return {
134
+ ...snapshot,
135
+ accessKeyId: current.accessKeyId,
136
+ secretAccessKeyCiphertext: current.secretAccessKeyCiphertext,
137
+ }
138
+ }
139
+
140
+ export async function deleteBackupStorage({
141
+ userId,
142
+ teamSlug,
143
+ storageId,
144
+ settingsVersion,
145
+ }: {
146
+ userId: string
147
+ teamSlug: string
148
+ settingsVersion?: number
149
+ storageId: string
150
+ }) {
151
+ requireBackupsEnabled()
152
+ const team = await requireTeamManager(userId, teamSlug)
153
+ return withWorkspaceAccess(
154
+ team.id,
155
+ 'backup.storage.delete',
156
+ async () => {
157
+ const current = await getOwnedBackupStorage(team.id, storageId)
158
+ if (!current) throw new PublicError('BACKUP_STORAGE_NOT_FOUND')
159
+ const expectedVersion = settingsVersion ?? current.settingsVersion
160
+ if (expectedVersion !== current.settingsVersion)
161
+ throw new PublicError('SETTINGS_CONFLICT')
162
+ const changed = await deleteBackupStorageState(db, {
163
+ teamId: team.id,
164
+ userId,
165
+ storageId,
166
+ settingsVersion: expectedVersion,
167
+ })
168
+ if (!changed) {
169
+ await requireTeamManager(userId, teamSlug)
170
+ const latest = await getOwnedBackupStorage(team.id, storageId)
171
+ if (!latest || latest.settingsVersion !== expectedVersion)
172
+ throw new PublicError('SETTINGS_CONFLICT')
173
+ throw new PublicError('BACKUP_STORAGE_IN_USE')
174
+ }
175
+ return listBackupStorage(team.id, true)
176
+ },
177
+ { kind: 'user', userId },
178
+ )
179
+ }
180
+
181
+ export async function getOwnedBackupStorage(teamId: string, storageId: string) {
182
+ const [row] = await db
183
+ .select()
184
+ .from(backupStorage)
185
+ .where(
186
+ and(eq(backupStorage.id, storageId), eq(backupStorage.teamId, teamId)),
187
+ )
188
+ .limit(1)
189
+ return row
190
+ }
191
+
192
+ export async function prepareBackupStorage({
193
+ teamId,
194
+ input,
195
+ settingsVersion,
196
+ newStorageId,
197
+ }: {
198
+ teamId: string
199
+ input: unknown
200
+ settingsVersion?: number | null
201
+ newStorageId?: string
202
+ }) {
203
+ const parsed = backupStorageSchema.parse(input)
204
+ const existing = parsed.id
205
+ ? await getOwnedBackupStorage(teamId, parsed.id)
206
+ : undefined
207
+ if (parsed.id && !existing) throw new PublicError('BACKUP_STORAGE_NOT_FOUND')
208
+ if (!existing && !parsed.secretAccessKey)
209
+ throw new PublicError('BACKUP_STORAGE_SECRET_REQUIRED')
210
+ if (
211
+ existing &&
212
+ (existing.bucket !== parsed.bucket ||
213
+ existing.region !== parsed.region ||
214
+ (existing.endpoint ?? '') !== parsed.endpoint)
215
+ ) {
216
+ throw new PublicError('BACKUP_STORAGE_LOCATION_IMMUTABLE')
217
+ }
218
+ const expectedVersion = existing
219
+ ? (settingsVersion ?? existing.settingsVersion)
220
+ : null
221
+ if (existing && expectedVersion !== existing.settingsVersion)
222
+ throw new PublicError('SETTINGS_CONFLICT')
223
+ return {
224
+ id: existing?.id ?? newStorageId ?? crypto.randomUUID(),
225
+ settingsVersion: expectedVersion,
226
+ values: {
227
+ name: parsed.name,
228
+ bucket: parsed.bucket,
229
+ region: parsed.region,
230
+ endpoint: parsed.endpoint || null,
231
+ accessKeyId: parsed.accessKeyId,
232
+ secretAccessKeyCiphertext: parsed.secretAccessKey
233
+ ? encryptSecret(parsed.secretAccessKey)
234
+ : existing!.secretAccessKeyCiphertext,
235
+ },
236
+ }
237
+ }
@@ -0,0 +1,274 @@
1
+ import agentRelease from '../../agent-release.json'
2
+ import { z } from 'zod'
3
+
4
+ function normalizeOptional(value: unknown) {
5
+ if (value === '') {
6
+ return undefined
7
+ }
8
+
9
+ if (typeof value !== 'string') {
10
+ return value
11
+ }
12
+
13
+ const trimmed = value.trim()
14
+ if (
15
+ (trimmed.startsWith('"') && trimmed.endsWith('"')) ||
16
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))
17
+ ) {
18
+ return trimmed.slice(1, -1)
19
+ }
20
+
21
+ return value
22
+ }
23
+
24
+ const optionalString = z.preprocess(
25
+ normalizeOptional,
26
+ z.string().min(1).optional(),
27
+ )
28
+ const optionalUrl = z.preprocess(normalizeOptional, z.string().url().optional())
29
+ const optionalEmail = z.preprocess(
30
+ (value) => (value === '' ? undefined : value),
31
+ z.string().email().optional(),
32
+ )
33
+ const envBoolean = z.preprocess(
34
+ (value) => (typeof value === 'string' ? value.toLowerCase() : value),
35
+ z.enum(['true', 'false']).transform((value) => value === 'true'),
36
+ )
37
+
38
+ const envSchema = z
39
+ .object({
40
+ DEVPUSH_DATABASE_URL: optionalString,
41
+ DEVPUSH_LOG_DATABASE_URL: optionalString,
42
+ DEVPUSH_DATABASE_AUTH_TOKEN: optionalString,
43
+ DEVPUSH_LOG_DATABASE_AUTH_TOKEN: optionalString,
44
+ DEVPUSH_DATABASE_PATH: z.string().min(1).default('.data/devpush.sqlite'),
45
+ DEVPUSH_LOG_DATABASE_PATH: z.string().min(1).default('.data/logs.sqlite'),
46
+ BETTER_AUTH_SECRET: z.string().min(1).default('dev-secret-change-me'),
47
+ DEVPUSH_BASE_URL: z.string().url().default('http://localhost:7429'),
48
+ BETTER_AUTH_URL: optionalUrl,
49
+ DEVPUSH_MCP_URL: optionalUrl,
50
+ DEVPUSH_ADMIN_EMAIL: optionalEmail,
51
+ EMAIL_FROM: optionalString,
52
+ RESEND_API_KEY: optionalString,
53
+ GOOGLE_CLIENT_ID: optionalString,
54
+ GOOGLE_CLIENT_SECRET: optionalString,
55
+ GITHUB_APP_CLIENT_ID: optionalString,
56
+ GITHUB_APP_CLIENT_SECRET: optionalString,
57
+ GITHUB_APP_ID: optionalString,
58
+ GITHUB_APP_NAME: optionalString,
59
+ GITHUB_APP_PRIVATE_KEY: optionalString,
60
+ GITHUB_APP_WEBHOOK_SECRET: optionalString,
61
+ DEVPUSH_SMOKE_GITHUB_TOKEN: optionalString,
62
+ DEVPUSH_AGENT_BINARY_DIR: optionalString,
63
+ DEVPUSH_RECOMMENDED_AGENT_VERSION: optionalString,
64
+ DEVPUSH_STORAGE_ENDPOINT: optionalString,
65
+ DEVPUSH_STORAGE_REGION: optionalString,
66
+ DEVPUSH_STORAGE_BUCKET: optionalString,
67
+ DEVPUSH_STORAGE_ACCESS_KEY_ID: optionalString,
68
+ DEVPUSH_STORAGE_SECRET_ACCESS_KEY: optionalString,
69
+ DEVPUSH_CRON_SECRET: optionalString,
70
+ DEVPUSH_BACKUPS_ENABLED: envBoolean.default(false),
71
+ DEVPUSH_AGENT_AUTO_UPDATE: envBoolean.default(false),
72
+ DEVPUSH_TRUST_PROXY: envBoolean.default(false),
73
+ DEVPUSH_CLIENT_IP_HEADER: z
74
+ .enum(['x-forwarded-for', 'x-real-ip'])
75
+ .default('x-forwarded-for'),
76
+ DEVPUSH_DEPLOYMENT_QUEUE_TTL_HOURS: z.coerce
77
+ .number()
78
+ .int()
79
+ .min(1)
80
+ .max(720)
81
+ .default(24),
82
+ DEVPUSH_DEPLOYMENT_LOG_MAX_LINES: z.coerce
83
+ .number()
84
+ .int()
85
+ .min(1)
86
+ .max(100_000)
87
+ .default(5_000),
88
+ DEVPUSH_DEPLOYMENT_LOG_LINE_BYTES: z.coerce
89
+ .number()
90
+ .int()
91
+ .min(64)
92
+ .max(65_536)
93
+ .default(2_048),
94
+ DEVPUSH_MONITORING_RETENTION_DAYS: z.coerce
95
+ .number()
96
+ .int()
97
+ .min(2)
98
+ .max(3650)
99
+ .default(7),
100
+ DEVPUSH_SECRET_ENCRYPTION_KEY: optionalString,
101
+ DEVPUSH_TRAEFIK_EMAIL: optionalEmail,
102
+ })
103
+ .superRefine((value, context) => {
104
+ for (const key of ['DEVPUSH_BASE_URL', 'BETTER_AUTH_URL'] as const) {
105
+ const raw = value[key]
106
+ if (!raw) continue
107
+ let url: URL
108
+ try {
109
+ url = new URL(raw)
110
+ } catch {
111
+ continue // The field schema reports malformed URLs.
112
+ }
113
+ if (
114
+ !['http:', 'https:'].includes(url.protocol) ||
115
+ url.username ||
116
+ url.password ||
117
+ url.pathname !== '/' ||
118
+ raw.includes('?') ||
119
+ raw.includes('#')
120
+ ) {
121
+ context.addIssue({
122
+ code: 'custom',
123
+ path: [key],
124
+ message: `${key} must be an HTTP(S) origin without credentials, path, query or fragment.`,
125
+ })
126
+ }
127
+ if (url.protocol === 'http:' && !isLocalMcpHostname(url.hostname)) {
128
+ context.addIssue({
129
+ code: 'custom',
130
+ path: [key],
131
+ message: `${key} must use HTTPS except for local development hosts.`,
132
+ })
133
+ }
134
+ }
135
+ let mcpUrl: URL
136
+ try {
137
+ mcpUrl = new URL(value.DEVPUSH_MCP_URL ?? '/mcp', value.DEVPUSH_BASE_URL)
138
+ } catch {
139
+ context.addIssue({
140
+ code: 'custom',
141
+ path: ['DEVPUSH_MCP_URL'],
142
+ message: 'DEVPUSH_MCP_URL requires a valid full endpoint and base URL.',
143
+ })
144
+ return
145
+ }
146
+ if (
147
+ mcpUrl.username ||
148
+ mcpUrl.password ||
149
+ mcpUrl.search ||
150
+ mcpUrl.hash ||
151
+ mcpUrl.href.includes('?') ||
152
+ mcpUrl.href.includes('#') ||
153
+ mcpUrl.pathname !== '/mcp' ||
154
+ !['http:', 'https:'].includes(mcpUrl.protocol)
155
+ ) {
156
+ context.addIssue({
157
+ code: 'custom',
158
+ path: ['DEVPUSH_MCP_URL'],
159
+ message:
160
+ 'DEVPUSH_MCP_URL must be the full HTTP(S) /mcp endpoint without credentials, query or fragment.',
161
+ })
162
+ }
163
+ if (mcpUrl.protocol === 'http:' && !isLocalMcpHostname(mcpUrl.hostname)) {
164
+ context.addIssue({
165
+ code: 'custom',
166
+ path: ['DEVPUSH_MCP_URL'],
167
+ message:
168
+ 'DEVPUSH_MCP_URL must use HTTPS except for local development hosts.',
169
+ })
170
+ }
171
+ const storageValues = [
172
+ value.DEVPUSH_STORAGE_ENDPOINT,
173
+ value.DEVPUSH_STORAGE_BUCKET,
174
+ value.DEVPUSH_STORAGE_ACCESS_KEY_ID,
175
+ value.DEVPUSH_STORAGE_SECRET_ACCESS_KEY,
176
+ ]
177
+
178
+ if (storageValues.some(Boolean) && storageValues.some((item) => !item)) {
179
+ context.addIssue({
180
+ code: 'custom',
181
+ path: ['DEVPUSH_STORAGE_ENDPOINT'],
182
+ message:
183
+ 'Endpoint, bucket, access key ID, and secret access key are all required for object storage.',
184
+ })
185
+ }
186
+ })
187
+ .transform((value) => ({
188
+ ...value,
189
+ BETTER_AUTH_URL: value.BETTER_AUTH_URL ?? value.DEVPUSH_BASE_URL,
190
+ DEVPUSH_MCP_URL: new URL(
191
+ value.DEVPUSH_MCP_URL ?? '/mcp',
192
+ value.DEVPUSH_BASE_URL,
193
+ ).toString(),
194
+ }))
195
+
196
+ export function parseConfig(env: NodeJS.ProcessEnv) {
197
+ return envSchema.parse(env)
198
+ }
199
+
200
+ function isLocalMcpHostname(hostname: string) {
201
+ const host = hostname
202
+ .toLowerCase()
203
+ .replace(/^\[|\]$/g, '')
204
+ .replace(/\.$/, '')
205
+ return (
206
+ host === 'localhost' ||
207
+ host.endsWith('.localhost') ||
208
+ host === 'host.lima.internal' ||
209
+ host === '::1' ||
210
+ /^127(?:\.\d{1,3}){3}$/.test(host)
211
+ )
212
+ }
213
+
214
+ export function assertRuntimeSecurity(value: ReturnType<typeof parseConfig>) {
215
+ const isLocal = [
216
+ value.DEVPUSH_BASE_URL,
217
+ value.BETTER_AUTH_URL,
218
+ value.DEVPUSH_MCP_URL,
219
+ ].every((url) => isLocalMcpHostname(new URL(url).hostname))
220
+
221
+ if (isLocal) {
222
+ return
223
+ }
224
+
225
+ if (
226
+ value.BETTER_AUTH_SECRET === 'dev-secret-change-me' ||
227
+ value.BETTER_AUTH_SECRET.startsWith('replace-with-') ||
228
+ value.BETTER_AUTH_SECRET.length < 32
229
+ ) {
230
+ throw new Error(
231
+ 'BETTER_AUTH_SECRET must be a generated secret with at least 32 characters.',
232
+ )
233
+ }
234
+
235
+ if (!value.DEVPUSH_SECRET_ENCRYPTION_KEY) {
236
+ throw new Error('DEVPUSH_SECRET_ENCRYPTION_KEY is required.')
237
+ }
238
+
239
+ if (value.DEVPUSH_SECRET_ENCRYPTION_KEY.length < 32) {
240
+ throw new Error(
241
+ 'DEVPUSH_SECRET_ENCRYPTION_KEY must contain at least 32 characters.',
242
+ )
243
+ }
244
+
245
+ if (value.DEVPUSH_SECRET_ENCRYPTION_KEY.startsWith('replace-with-')) {
246
+ throw new Error(
247
+ 'DEVPUSH_SECRET_ENCRYPTION_KEY must be a generated secret, not an example placeholder.',
248
+ )
249
+ }
250
+
251
+ if (value.DEVPUSH_SECRET_ENCRYPTION_KEY === value.BETTER_AUTH_SECRET) {
252
+ throw new Error(
253
+ 'DEVPUSH_SECRET_ENCRYPTION_KEY must differ from BETTER_AUTH_SECRET.',
254
+ )
255
+ }
256
+
257
+ if (
258
+ value.DEVPUSH_CRON_SECRET &&
259
+ (value.DEVPUSH_CRON_SECRET.length < 32 ||
260
+ value.DEVPUSH_CRON_SECRET.startsWith('replace-with-'))
261
+ ) {
262
+ throw new Error(
263
+ 'DEVPUSH_CRON_SECRET must be a generated secret with at least 32 characters.',
264
+ )
265
+ }
266
+
267
+ if (value.DEVPUSH_CRON_SECRET === value.BETTER_AUTH_SECRET) {
268
+ throw new Error('DEVPUSH_CRON_SECRET must differ from BETTER_AUTH_SECRET.')
269
+ }
270
+ }
271
+
272
+ export const config = parseConfig(process.env)
273
+
274
+ export const currentAgentVersion = agentRelease.version
@@ -0,0 +1,16 @@
1
+ import type { BatchItem, BatchResponse } from 'drizzle-orm/batch'
2
+ import type { SQL } from 'drizzle-orm'
3
+ import type { SQLiteRaw } from 'drizzle-orm/sqlite-core/query-builders/raw'
4
+
5
+ // Both drivers execute a prebuilt batch atomically. Queries must encode their
6
+ // guards in SQL; reads made before the batch are not an authorization snapshot.
7
+ export type AtomicStore = {
8
+ run: (query: SQL) => SQLiteRaw<unknown>
9
+ all: <T = unknown>(query: SQL) => SQLiteRaw<T[]>
10
+ batch: <
11
+ TItem extends BatchItem<'sqlite'>,
12
+ T extends Readonly<[TItem, ...TItem[]]>,
13
+ >(
14
+ queries: T,
15
+ ) => Promise<BatchResponse<T>>
16
+ }