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.
- package/README.md +22 -2
- package/agent-release.json +37 -0
- package/dist/package/LICENSE.md +21 -0
- package/dist/package/migrate.mjs +182 -0
- package/dist/package/plugins.mjs +125 -0
- package/dist/package/vite.mjs +439 -0
- package/package.json +168 -3
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon.svg +1 -0
- package/public/logo-email.png +0 -0
- package/public/robots.txt +3 -0
- package/public/social.png +0 -0
- package/src/cloudflare-runtime.d.ts +78 -0
- package/src/components/about-dialog.tsx +70 -0
- package/src/components/copy-button.tsx +74 -0
- package/src/components/logo-mark.tsx +20 -0
- package/src/components/pending-collection.tsx +103 -0
- package/src/components/resource-avatar.tsx +65 -0
- package/src/components/route-error-state.tsx +95 -0
- package/src/components/section-navigation.tsx +39 -0
- package/src/components/settings-layout.tsx +111 -0
- package/src/components/storage-capabilities.tsx +11 -0
- package/src/components/table-pager.tsx +101 -0
- package/src/components/ui/alert-dialog.tsx +180 -0
- package/src/components/ui/alert.tsx +78 -0
- package/src/components/ui/avatar.tsx +58 -0
- package/src/components/ui/badge.tsx +49 -0
- package/src/components/ui/button-group.tsx +38 -0
- package/src/components/ui/button.tsx +67 -0
- package/src/components/ui/card.tsx +82 -0
- package/src/components/ui/chart.tsx +307 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/combobox.tsx +256 -0
- package/src/components/ui/dialog.tsx +163 -0
- package/src/components/ui/dropdown-menu.tsx +110 -0
- package/src/components/ui/empty.tsx +65 -0
- package/src/components/ui/field.tsx +192 -0
- package/src/components/ui/input-group.tsx +118 -0
- package/src/components/ui/input-otp.tsx +70 -0
- package/src/components/ui/input.tsx +19 -0
- package/src/components/ui/label.tsx +24 -0
- package/src/components/ui/pagination.tsx +34 -0
- package/src/components/ui/popover.tsx +86 -0
- package/src/components/ui/select.tsx +145 -0
- package/src/components/ui/skeleton.tsx +19 -0
- package/src/components/ui/sonner.tsx +47 -0
- package/src/components/ui/switch.tsx +31 -0
- package/src/components/ui/table.tsx +81 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +55 -0
- package/src/features/admin/admin-settings.tsx +1048 -0
- package/src/features/auth/current-user-context-query.ts +11 -0
- package/src/features/auth/login-form.tsx +442 -0
- package/src/features/auth/sign-out-menu-item.tsx +42 -0
- package/src/features/deployments/deployment-actions.tsx +336 -0
- package/src/features/deployments/deployment-alias-badge.tsx +35 -0
- package/src/features/deployments/deployment-detail.tsx +596 -0
- package/src/features/deployments/deployment-filters.ts +66 -0
- package/src/features/deployments/deployment-list.tsx +465 -0
- package/src/features/deployments/deployment-visit-links.tsx +104 -0
- package/src/features/deployments/deployments-page.tsx +376 -0
- package/src/features/logs/log-scroll-area.tsx +69 -0
- package/src/features/notifications/email-preferences.tsx +232 -0
- package/src/features/onboarding/profile-onboarding.tsx +162 -0
- package/src/features/plugins/contributions.tsx +118 -0
- package/src/features/projects/backup-job-logs.tsx +143 -0
- package/src/features/projects/backup-policy-delete-dialog.tsx +82 -0
- package/src/features/projects/backup-restore-actions.tsx +573 -0
- package/src/features/projects/backup-status-icon.tsx +33 -0
- package/src/features/projects/backup-target-list.tsx +222 -0
- package/src/features/projects/backup-timestamp.tsx +32 -0
- package/src/features/projects/backup-volume-picker.tsx +158 -0
- package/src/features/projects/compose-service-input.tsx +64 -0
- package/src/features/projects/deploy-readiness.ts +28 -0
- package/src/features/projects/environment-dot.tsx +91 -0
- package/src/features/projects/log-keyword-filter.tsx +48 -0
- package/src/features/projects/manual-deploy-dialog.tsx +563 -0
- package/src/features/projects/pending-section.ts +26 -0
- package/src/features/projects/project-backup-details-pending.tsx +305 -0
- package/src/features/projects/project-backup-details.tsx +936 -0
- package/src/features/projects/project-backup-policy.tsx +644 -0
- package/src/features/projects/project-backup-utils.ts +190 -0
- package/src/features/projects/project-backups-pending.tsx +136 -0
- package/src/features/projects/project-backups.tsx +876 -0
- package/src/features/projects/project-environment-settings.tsx +1128 -0
- package/src/features/projects/project-heading.tsx +68 -0
- package/src/features/projects/project-layout.tsx +188 -0
- package/src/features/projects/project-logs.tsx +461 -0
- package/src/features/projects/project-overview.tsx +280 -0
- package/src/features/projects/project-route-pending.tsx +1064 -0
- package/src/features/projects/project-routing-settings.tsx +663 -0
- package/src/features/projects/project-settings-section.tsx +64 -0
- package/src/features/projects/project-settings-utils.ts +101 -0
- package/src/features/projects/project-settings.tsx +1472 -0
- package/src/features/projects/repository-picker-dialog.tsx +404 -0
- package/src/features/repositories/new-project-dialog-launcher.tsx +44 -0
- package/src/features/repositories/new-project-dialog.tsx +1204 -0
- package/src/features/repositories/new-project-pending.tsx +28 -0
- package/src/features/repositories/repository-list-pending.tsx +59 -0
- package/src/features/servers/server-availability-alert.tsx +44 -0
- package/src/features/servers/server-availability.ts +29 -0
- package/src/features/servers/server-avatar.tsx +52 -0
- package/src/features/servers/server-detail-pending.tsx +315 -0
- package/src/features/servers/server-detail.tsx +855 -0
- package/src/features/servers/server-metric-chart-pending.tsx +38 -0
- package/src/features/servers/server-metric-chart.tsx +182 -0
- package/src/features/servers/server-metric-format.ts +50 -0
- package/src/features/servers/server-metrics-pending.tsx +75 -0
- package/src/features/servers/server-metrics.tsx +136 -0
- package/src/features/servers/team-servers.tsx +432 -0
- package/src/features/settings/settings-route-pending.tsx +406 -0
- package/src/features/teams/backup-storage-settings.tsx +483 -0
- package/src/features/teams/navigation.ts +51 -0
- package/src/features/teams/new-team-dialog.tsx +132 -0
- package/src/features/teams/pending-section.ts +19 -0
- package/src/features/teams/team-header.tsx +1286 -0
- package/src/features/teams/team-heading.tsx +57 -0
- package/src/features/teams/team-home.tsx +391 -0
- package/src/features/teams/team-invite.tsx +155 -0
- package/src/features/teams/team-route-pending.tsx +505 -0
- package/src/features/teams/team-settings.tsx +1555 -0
- package/src/features/teams/use-workspace-updates.ts +126 -0
- package/src/features/users/api-keys-settings.tsx +544 -0
- package/src/features/users/api-scope-selection.tsx +269 -0
- package/src/features/users/connected-apps-settings.tsx +449 -0
- package/src/features/users/mcp-consent.tsx +220 -0
- package/src/features/users/profile-picture.tsx +171 -0
- package/src/features/users/user-settings.tsx +1064 -0
- package/src/lib/account-settings-schema.ts +78 -0
- package/src/lib/action-feedback.ts +81 -0
- package/src/lib/admin-search-schema.ts +8 -0
- package/src/lib/agent-release.d.mts +21 -0
- package/src/lib/agent-release.mjs +90 -0
- package/src/lib/allowlist-schema.ts +79 -0
- package/src/lib/api-key-schema.ts +176 -0
- package/src/lib/auth/client.ts +20 -0
- package/src/lib/avatar-schema.ts +11 -0
- package/src/lib/backup-cron.ts +118 -0
- package/src/lib/backup-database.ts +41 -0
- package/src/lib/backup-feature.ts +2 -0
- package/src/lib/backup-paths.ts +7 -0
- package/src/lib/backup-policy-schema.ts +85 -0
- package/src/lib/backup-resource-schema.ts +12 -0
- package/src/lib/backup-restore-schema.ts +15 -0
- package/src/lib/backup-storage-schema.ts +55 -0
- package/src/lib/deferred-module.ts +38 -0
- package/src/lib/deployment-logs-query.ts +63 -0
- package/src/lib/display-name.ts +7 -0
- package/src/lib/error-message.ts +119 -0
- package/src/lib/form-errors.ts +10 -0
- package/src/lib/internal-redirect.ts +25 -0
- package/src/lib/manual-deploy-options-query.ts +21 -0
- package/src/lib/notification-preferences.ts +43 -0
- package/src/lib/onboarding.ts +11 -0
- package/src/lib/operation-logs-query.ts +51 -0
- package/src/lib/page-head.ts +14 -0
- package/src/lib/project-context-query.ts +257 -0
- package/src/lib/project-creation-schema.ts +64 -0
- package/src/lib/project-env-schema.ts +23 -0
- package/src/lib/project-environments-schema.ts +56 -0
- package/src/lib/project-routing-schema.ts +94 -0
- package/src/lib/project-runtime-config.ts +131 -0
- package/src/lib/project-settings-schema.ts +75 -0
- package/src/lib/public-api-schema.ts +128 -0
- package/src/lib/public-error.ts +337 -0
- package/src/lib/query.ts +13 -0
- package/src/lib/repository-selection-query.ts +11 -0
- package/src/lib/resource-cache.ts +30 -0
- package/src/lib/runtime-id.ts +19 -0
- package/src/lib/search.ts +14 -0
- package/src/lib/server-context-query.ts +43 -0
- package/src/lib/server-name-schema.ts +11 -0
- package/src/lib/settings-conflict.ts +45 -0
- package/src/lib/settings-version.ts +7 -0
- package/src/lib/team-context-query.ts +126 -0
- package/src/lib/team-context-schema.ts +8 -0
- package/src/lib/team-forms-schema.ts +45 -0
- package/src/lib/team-role.ts +13 -0
- package/src/lib/team-settings-schema.ts +48 -0
- package/src/lib/urls.ts +3 -0
- package/src/lib/use-avatar-input.ts +61 -0
- package/src/lib/use-refresh-error.ts +18 -0
- package/src/lib/use-settings-version.ts +23 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/validation.ts +5 -0
- package/src/lib/workspace-updates.ts +53 -0
- package/src/package/config.ts +10 -0
- package/src/package/env.mjs +54 -0
- package/src/package/migrate-api.d.ts +5 -0
- package/src/package/migrate.ts +56 -0
- package/src/package/plugins.ts +141 -0
- package/src/package/sqlite.mjs +2 -0
- package/src/package/vite-api.d.ts +5 -0
- package/src/package/vite.ts +324 -0
- package/src/package/watch-guard.cjs +26 -0
- package/src/plugins/client.ts +57 -0
- package/src/plugins/contract.ts +188 -0
- package/src/plugins/default.server.ts +4 -0
- package/src/plugins/default.ui.ts +4 -0
- package/src/plugins/operations.ts +45 -0
- package/src/plugins/server.ts +197 -0
- package/src/plugins/version.ts +1 -0
- package/src/routeTree.gen.ts +1275 -0
- package/src/router.tsx +46 -0
- package/src/routes/$teamSlug.index.tsx +53 -0
- package/src/routes/$teamSlug.new-project.tsx +13 -0
- package/src/routes/$teamSlug.projects.$projectName.backups.$backupId.tsx +40 -0
- package/src/routes/$teamSlug.projects.$projectName.backups.tsx +82 -0
- package/src/routes/$teamSlug.projects.$projectName.deployments.$deploymentId.tsx +52 -0
- package/src/routes/$teamSlug.projects.$projectName.deployments.tsx +86 -0
- package/src/routes/$teamSlug.projects.$projectName.logs.tsx +55 -0
- package/src/routes/$teamSlug.projects.$projectName.settings.tsx +51 -0
- package/src/routes/$teamSlug.projects.$projectName.tsx +106 -0
- package/src/routes/$teamSlug.projects.tsx +40 -0
- package/src/routes/$teamSlug.servers.$serverId.tsx +31 -0
- package/src/routes/$teamSlug.servers.index.tsx +59 -0
- package/src/routes/$teamSlug.servers.tsx +7 -0
- package/src/routes/$teamSlug.settings.tsx +56 -0
- package/src/routes/$teamSlug.tsx +86 -0
- package/src/routes/[.]well-known.oauth-authorization-server.api.auth.ts +13 -0
- package/src/routes/[.]well-known.oauth-protected-resource.mcp.ts +6 -0
- package/src/routes/__root.tsx +155 -0
- package/src/routes/admin.tsx +55 -0
- package/src/routes/api/agents/certificates.ts +21 -0
- package/src/routes/api/agents/credentials/rotate.ts +22 -0
- package/src/routes/api/agents/heartbeat.ts +42 -0
- package/src/routes/api/agents/job-logs.ts +22 -0
- package/src/routes/api/agents/jobs/complete.ts +22 -0
- package/src/routes/api/agents/jobs/next.ts +29 -0
- package/src/routes/api/agents/jobs/progress.ts +32 -0
- package/src/routes/api/agents/jobs/status.ts +22 -0
- package/src/routes/api/agents/register.ts +45 -0
- package/src/routes/api/agents/runtime-logs.ts +22 -0
- package/src/routes/api/agents/switch.ts +25 -0
- package/src/routes/api/auth/$.ts +38 -0
- package/src/routes/api/cron/backups.ts +33 -0
- package/src/routes/api/cron/maintenance.ts +30 -0
- package/src/routes/api/github/install.callback.ts +51 -0
- package/src/routes/api/github/install.ts +41 -0
- package/src/routes/api/github/webhook.ts +11 -0
- package/src/routes/api/health.ts +19 -0
- package/src/routes/api/install/agent.$token.ts +50 -0
- package/src/routes/api/install/agent.binary.ts +83 -0
- package/src/routes/api/install/agent.checksum.ts +45 -0
- package/src/routes/api/install/agent.ts +26 -0
- package/src/routes/api/plugins/$.ts +11 -0
- package/src/routes/api/v1/$.ts +15 -0
- package/src/routes/api/workspaces/updates.ts +8 -0
- package/src/routes/auth.login.tsx +50 -0
- package/src/routes/deployments.$deploymentId.unavailable.tsx +21 -0
- package/src/routes/index.tsx +23 -0
- package/src/routes/invites.$token.tsx +35 -0
- package/src/routes/mcp.ts +13 -0
- package/src/routes/oauth.consent.tsx +54 -0
- package/src/routes/onboarding.tsx +37 -0
- package/src/routes/onboarding.workspace.tsx +44 -0
- package/src/routes/settings.tsx +30 -0
- package/src/routes/uploads/avatars/$fileName.ts +44 -0
- package/src/server/admin/access.ts +67 -0
- package/src/server/admin/functions.ts +122 -0
- package/src/server/admin/service.ts +260 -0
- package/src/server/agents/backup-state.ts +210 -0
- package/src/server/agents/certificates.ts +96 -0
- package/src/server/agents/claims.ts +116 -0
- package/src/server/agents/cleanup.ts +52 -0
- package/src/server/agents/compatibility.ts +116 -0
- package/src/server/agents/completion.ts +83 -0
- package/src/server/agents/container-cleanup-state.ts +51 -0
- package/src/server/agents/deployment-state.ts +111 -0
- package/src/server/agents/job-payloads.ts +159 -0
- package/src/server/agents/jobs.ts +1293 -0
- package/src/server/agents/protocol.ts +400 -0
- package/src/server/agents/reconciliation.ts +43 -0
- package/src/server/agents/runtime-logs.ts +337 -0
- package/src/server/agents/runtime-state.ts +125 -0
- package/src/server/api/admission.ts +12 -0
- package/src/server/api/authorization.ts +115 -0
- package/src/server/api/credentials.ts +569 -0
- package/src/server/api/cursors.ts +72 -0
- package/src/server/api/errors.ts +24 -0
- package/src/server/api/functions.ts +60 -0
- package/src/server/api/handler.ts +274 -0
- package/src/server/api/mutation-operations.ts +913 -0
- package/src/server/api/operation-context.ts +139 -0
- package/src/server/api/operation-contract.ts +1099 -0
- package/src/server/api/operations.ts +64 -0
- package/src/server/api/read-operations.ts +891 -0
- package/src/server/api/retention.ts +33 -0
- package/src/server/auth/access.ts +109 -0
- package/src/server/auth/auth-methods.ts +28 -0
- package/src/server/auth/better-auth.ts +518 -0
- package/src/server/auth/context.ts +28 -0
- package/src/server/auth/email-change.ts +47 -0
- package/src/server/auth/email-delivery.ts +51 -0
- package/src/server/auth/provider-identity.ts +19 -0
- package/src/server/auth/provider-tokens.ts +27 -0
- package/src/server/auth/session.server.ts +19 -0
- package/src/server/backups/database-commands.ts +40 -0
- package/src/server/backups/destination.ts +12 -0
- package/src/server/backups/feature.ts +6 -0
- package/src/server/backups/policy-validation.ts +51 -0
- package/src/server/backups/queue-state.ts +137 -0
- package/src/server/backups/service.ts +918 -0
- package/src/server/backups/storage-functions.ts +42 -0
- package/src/server/backups/storage-state.ts +67 -0
- package/src/server/backups/storage.ts +237 -0
- package/src/server/config.ts +274 -0
- package/src/server/db/atomic.ts +16 -0
- package/src/server/db/d1-connection.ts +83 -0
- package/src/server/db/errors.ts +17 -0
- package/src/server/db/index.cloudflare.ts +15 -0
- package/src/server/db/index.ts +35 -0
- package/src/server/db/libsql-connection.ts +72 -0
- package/src/server/db/log-schema.ts +102 -0
- package/src/server/db/migrate-libsql.ts +29 -0
- package/src/server/db/migrate-sqlite.ts +47 -0
- package/src/server/db/migration-history.ts +12 -0
- package/src/server/db/migrations-logs/0000_initial.sql +48 -0
- package/src/server/db/migrations-logs/0001_runtime_log_order.sql +2 -0
- package/src/server/db/migrations-logs/meta/0000_snapshot.json +327 -0
- package/src/server/db/migrations-logs/meta/0001_snapshot.json +329 -0
- package/src/server/db/migrations-logs/meta/_journal.json +20 -0
- package/src/server/db/migrations-sqlite/0000_initial.sql +894 -0
- package/src/server/db/migrations-sqlite/0001_agent_indexes.sql +5 -0
- package/src/server/db/migrations-sqlite/meta/0000_snapshot.json +5816 -0
- package/src/server/db/migrations-sqlite/meta/0001_snapshot.json +5841 -0
- package/src/server/db/migrations-sqlite/meta/_journal.json +20 -0
- package/src/server/db/oauth-schema.ts +216 -0
- package/src/server/db/remote-config.ts +47 -0
- package/src/server/db/schema.ts +1218 -0
- package/src/server/db/sqlite-connection.ts +94 -0
- package/src/server/db/sqlite-files.ts +27 -0
- package/src/server/db/sqlite-params.ts +19 -0
- package/src/server/db/worker-connection.ts +47 -0
- package/src/server/deleted-resources.ts +133 -0
- package/src/server/deletion-recovery.ts +272 -0
- package/src/server/email/layout.tsx +85 -0
- package/src/server/email/notification-template.tsx +62 -0
- package/src/server/email/otp-template.tsx +48 -0
- package/src/server/email/service.ts +251 -0
- package/src/server/email/team-invite-template.tsx +59 -0
- package/src/server/email/theme.ts +10 -0
- package/src/server/github/http.ts +14 -0
- package/src/server/github/install-state.ts +48 -0
- package/src/server/github/service.ts +831 -0
- package/src/server/github/webhook-handler.ts +198 -0
- package/src/server/github/webhooks.ts +205 -0
- package/src/server/http/auth.ts +11 -0
- package/src/server/http/error-diagnostics.ts +66 -0
- package/src/server/http/plugin-request.ts +15 -0
- package/src/server/http/proxy-request.ts +53 -0
- package/src/server/http/public-errors.ts +48 -0
- package/src/server/http/rate-limit.ts +59 -0
- package/src/server/http/request-body.ts +54 -0
- package/src/server/http/request-ip.ts +54 -0
- package/src/server/http/security-headers.ts +36 -0
- package/src/server/install/agent-binary.ts +230 -0
- package/src/server/install/agent-script.ts +565 -0
- package/src/server/install/release-binary.ts +65 -0
- package/src/server/logs/budget.ts +72 -0
- package/src/server/logs/cleanup.ts +64 -0
- package/src/server/logs/deployments.ts +98 -0
- package/src/server/logs/operations.ts +145 -0
- package/src/server/logs/retention.ts +39 -0
- package/src/server/maintenance.ts +61 -0
- package/src/server/mcp/connections.ts +358 -0
- package/src/server/mcp/functions.ts +63 -0
- package/src/server/mcp/handler.ts +295 -0
- package/src/server/mcp/oauth-discovery.cloudflare.ts +6 -0
- package/src/server/mcp/oauth-discovery.ts +8 -0
- package/src/server/mcp/oauth-provider.ts +220 -0
- package/src/server/mcp/urls.ts +20 -0
- package/src/server/notifications/functions.ts +51 -0
- package/src/server/notifications/observe.ts +303 -0
- package/src/server/notifications/outbox.ts +335 -0
- package/src/server/notifications/preferences.ts +131 -0
- package/src/server/notifications/query.ts +16 -0
- package/src/server/plugins/scope.ts +17 -0
- package/src/server/plugins/server-context.ts +157 -0
- package/src/server/plugins/services.server.ts +10 -0
- package/src/server/plugins/workspace-access.ts +74 -0
- package/src/server/projects/backup-policy-state.ts +68 -0
- package/src/server/projects/backups.ts +219 -0
- package/src/server/projects/compose-metadata.ts +174 -0
- package/src/server/projects/configuration-state.ts +90 -0
- package/src/server/projects/context.ts +909 -0
- package/src/server/projects/creation-state.ts +46 -0
- package/src/server/projects/deletion-state.ts +69 -0
- package/src/server/projects/deployment-config.ts +139 -0
- package/src/server/projects/deployment-logs.ts +97 -0
- package/src/server/projects/deployments.ts +1123 -0
- package/src/server/projects/domain-state.ts +71 -0
- package/src/server/projects/environment-branches.ts +75 -0
- package/src/server/projects/environment-state.ts +158 -0
- package/src/server/projects/environments.ts +424 -0
- package/src/server/projects/functions.ts +748 -0
- package/src/server/projects/generated-hostname.ts +77 -0
- package/src/server/projects/hostnames.ts +244 -0
- package/src/server/projects/mappers.ts +457 -0
- package/src/server/projects/names.ts +41 -0
- package/src/server/projects/project-env-vars.ts +28 -0
- package/src/server/projects/queue-state.ts +118 -0
- package/src/server/projects/repository.ts +368 -0
- package/src/server/projects/route-state.ts +37 -0
- package/src/server/projects/runtime-logs.ts +97 -0
- package/src/server/projects/settings-context.ts +58 -0
- package/src/server/projects/settings.ts +738 -0
- package/src/server/projects/types.ts +355 -0
- package/src/server/realtime/agent-events.ts +32 -0
- package/src/server/realtime/node-broadcaster.ts +71 -0
- package/src/server/realtime/node-handler.ts +55 -0
- package/src/server/realtime/service.ts +125 -0
- package/src/server/realtime/workspace-object.cloudflare.ts +62 -0
- package/src/server/realtime/workspace-object.ts +3 -0
- package/src/server/resources/color.ts +13 -0
- package/src/server/runtime/background.ts +21 -0
- package/src/server/runtime/node-asset-cache-plugin.ts +12 -0
- package/src/server/runtime/node-scheduler-plugin.ts +17 -0
- package/src/server/runtime/node-scheduler.ts +51 -0
- package/src/server/runtime/platform-env.cloudflare.ts +17 -0
- package/src/server/runtime/platform-env.ts +19 -0
- package/src/server/runtime/scheduled-task-error.ts +9 -0
- package/src/server/scheduled-tasks.ts +28 -0
- package/src/server/secrets/crypto.ts +56 -0
- package/src/server/servers/functions.ts +233 -0
- package/src/server/servers/lifecycle.ts +122 -0
- package/src/server/servers/metric-history-schema.ts +24 -0
- package/src/server/servers/metric-history.ts +55 -0
- package/src/server/servers/monitoring.ts +592 -0
- package/src/server/servers/service.ts +1211 -0
- package/src/server/servers/status.ts +32 -0
- package/src/server/servers/switch.ts +96 -0
- package/src/server/settings-version.ts +43 -0
- package/src/server/storage/capabilities.ts +6 -0
- package/src/server/storage/local-object-store.cloudflare.ts +7 -0
- package/src/server/storage/local-object-store.ts +50 -0
- package/src/server/storage/object-store.ts +160 -0
- package/src/server/teams/creation.ts +51 -0
- package/src/server/teams/delete-policy.ts +13 -0
- package/src/server/teams/deletion-state.ts +80 -0
- package/src/server/teams/functions.ts +324 -0
- package/src/server/teams/invitations.ts +38 -0
- package/src/server/teams/membership.ts +86 -0
- package/src/server/teams/queries.ts +261 -0
- package/src/server/teams/service.ts +1127 -0
- package/src/server/teams/types.ts +101 -0
- package/src/server/uploads/avatar.ts +133 -0
- package/src/server/users/deletion-state.ts +93 -0
- package/src/server/users/functions.ts +160 -0
- package/src/server/users/onboarding.ts +17 -0
- package/src/server/users/service.ts +644 -0
- package/src/server.ts +55 -0
- package/src/styles/shadcn.css +653 -0
- package/src/styles/theme.css +76 -0
- package/src/styles.css +177 -0
- package/wrangler.template.jsonc +37 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type ServerStatus =
|
|
2
|
+
| 'pending'
|
|
3
|
+
| 'connected'
|
|
4
|
+
| 'disconnected'
|
|
5
|
+
| 'disabled'
|
|
6
|
+
| 'deleted'
|
|
7
|
+
|
|
8
|
+
export const serverDisconnectedAfterMs = 2 * 60 * 1000
|
|
9
|
+
|
|
10
|
+
export function getEffectiveServerStatus({
|
|
11
|
+
status,
|
|
12
|
+
lastSeenAt,
|
|
13
|
+
now = new Date(),
|
|
14
|
+
}: {
|
|
15
|
+
status: ServerStatus
|
|
16
|
+
lastSeenAt: Date | null
|
|
17
|
+
now?: Date
|
|
18
|
+
}): ServerStatus {
|
|
19
|
+
if (status !== 'connected') {
|
|
20
|
+
return status
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!lastSeenAt) {
|
|
24
|
+
return 'disconnected'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (now.getTime() - lastSeenAt.getTime() > serverDisconnectedAfterMs) {
|
|
28
|
+
return 'disconnected'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return 'connected'
|
|
32
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { createHash, createHmac, timingSafeEqual } from 'node:crypto'
|
|
2
|
+
import { and, eq, inArray, or, sql } from 'drizzle-orm'
|
|
3
|
+
import { config } from '#devpush/server/config'
|
|
4
|
+
import { db } from '#devpush/server/db'
|
|
5
|
+
import {
|
|
6
|
+
agentJobs,
|
|
7
|
+
deployments,
|
|
8
|
+
environments,
|
|
9
|
+
projects,
|
|
10
|
+
servers,
|
|
11
|
+
} from '#devpush/server/db/schema'
|
|
12
|
+
import { AgentRequestError } from '#devpush/server/agents/protocol'
|
|
13
|
+
|
|
14
|
+
function sign(value: string) {
|
|
15
|
+
return createHmac('sha256', config.BETTER_AUTH_SECRET)
|
|
16
|
+
.update(value)
|
|
17
|
+
.digest('hex')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function switchServerControlPlane(input: {
|
|
21
|
+
serverId: string
|
|
22
|
+
token: string
|
|
23
|
+
action: 'prepare' | 'rollback'
|
|
24
|
+
ticket?: string
|
|
25
|
+
}) {
|
|
26
|
+
const hash = createHash('sha256').update(input.token).digest('hex')
|
|
27
|
+
const auth = and(
|
|
28
|
+
eq(servers.id, input.serverId),
|
|
29
|
+
or(
|
|
30
|
+
eq(servers.agentCredentialHash, hash),
|
|
31
|
+
eq(servers.pendingAgentCredentialHash, hash),
|
|
32
|
+
),
|
|
33
|
+
)
|
|
34
|
+
const now = new Date()
|
|
35
|
+
if (input.action === 'rollback') {
|
|
36
|
+
const [value, signature] = (input.ticket ?? '').split('.')
|
|
37
|
+
if (
|
|
38
|
+
!value ||
|
|
39
|
+
!signature ||
|
|
40
|
+
!/^[a-f0-9]{64}$/.test(signature) ||
|
|
41
|
+
!timingSafeEqual(
|
|
42
|
+
Buffer.from(signature, 'hex'),
|
|
43
|
+
Buffer.from(sign(value), 'hex'),
|
|
44
|
+
)
|
|
45
|
+
) {
|
|
46
|
+
throw new AgentRequestError(401, 'Invalid rollback ticket.')
|
|
47
|
+
}
|
|
48
|
+
const ticket = JSON.parse(Buffer.from(value, 'base64url').toString()) as {
|
|
49
|
+
id: string
|
|
50
|
+
at: number
|
|
51
|
+
}
|
|
52
|
+
if (ticket.id !== input.serverId || !Number.isSafeInteger(ticket.at))
|
|
53
|
+
throw new AgentRequestError(401, 'Invalid rollback ticket.')
|
|
54
|
+
const rows = await db
|
|
55
|
+
.update(servers)
|
|
56
|
+
.set({ status: 'connected', updatedAt: now })
|
|
57
|
+
.where(
|
|
58
|
+
and(
|
|
59
|
+
auth,
|
|
60
|
+
eq(servers.status, 'disabled'),
|
|
61
|
+
eq(servers.updatedAt, new Date(ticket.at)),
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
.returning({ id: servers.id })
|
|
65
|
+
if (!rows.length)
|
|
66
|
+
throw new AgentRequestError(
|
|
67
|
+
409,
|
|
68
|
+
'Source state changed; rollback requires operator review.',
|
|
69
|
+
)
|
|
70
|
+
return { serverId: input.serverId }
|
|
71
|
+
}
|
|
72
|
+
// One conditional write both validates emptiness and disables subsequent claims.
|
|
73
|
+
const rows = await db
|
|
74
|
+
.update(servers)
|
|
75
|
+
.set({ status: 'disabled', updatedAt: now })
|
|
76
|
+
.where(
|
|
77
|
+
and(
|
|
78
|
+
auth,
|
|
79
|
+
eq(servers.status, 'connected'),
|
|
80
|
+
sql`not exists (select 1 from ${projects} where ${projects.defaultServerId} = ${input.serverId})`,
|
|
81
|
+
sql`not exists (select 1 from ${environments} where ${environments.serverId} = ${input.serverId})`,
|
|
82
|
+
sql`not exists (select 1 from ${agentJobs} where ${agentJobs.serverId} = ${input.serverId} and ${inArray(agentJobs.status, ['queued', 'running'])})`,
|
|
83
|
+
sql`not exists (select 1 from ${deployments} where ${deployments.serverId} = ${input.serverId})`,
|
|
84
|
+
),
|
|
85
|
+
)
|
|
86
|
+
.returning({ id: servers.id })
|
|
87
|
+
if (!rows.length)
|
|
88
|
+
throw new AgentRequestError(
|
|
89
|
+
409,
|
|
90
|
+
'Source must be connected and have no projects, deployments, or outstanding jobs.',
|
|
91
|
+
)
|
|
92
|
+
const value = Buffer.from(
|
|
93
|
+
JSON.stringify({ id: input.serverId, at: now.getTime() }),
|
|
94
|
+
).toString('base64url')
|
|
95
|
+
return { serverId: input.serverId, ticket: `${value}.${sign(value)}` }
|
|
96
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { sql } from 'drizzle-orm'
|
|
2
|
+
import { PublicError } from '#devpush/lib/public-error'
|
|
3
|
+
import type { SQL } from 'drizzle-orm'
|
|
4
|
+
import type { AtomicStore } from '#devpush/server/db/atomic'
|
|
5
|
+
|
|
6
|
+
export function projectSettingsGuard(
|
|
7
|
+
projectId: string,
|
|
8
|
+
userId: string,
|
|
9
|
+
settingsVersion: number,
|
|
10
|
+
manager = false,
|
|
11
|
+
): SQL {
|
|
12
|
+
return sql`exists(select 1 from projects p join teams t on t.id = p.team_id join team_members m on m.team_id = t.id join user u on u.id = m.user_id where p.id = ${projectId} and p.status = 'active' and p.settings_version = ${settingsVersion} and t.status = 'active' and u.status = 'active' and m.user_id = ${userId} ${manager ? sql`and m.role in ('owner','admin')` : sql``})`
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function teamSettingsAccess(teamId: string, userId: string): SQL {
|
|
16
|
+
return sql`exists(select 1 from teams t join team_members m on m.team_id = t.id join user u on u.id = m.user_id where t.id = ${teamId} and t.status = 'active' and u.status = 'active' and m.user_id = ${userId} and m.role in ('owner','admin'))`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** A failed guard violates NOT NULL, rolling back every statement in a D1 batch. */
|
|
20
|
+
export function advanceProjectSettings(
|
|
21
|
+
store: AtomicStore,
|
|
22
|
+
projectId: string,
|
|
23
|
+
guard: SQL,
|
|
24
|
+
) {
|
|
25
|
+
return store.run(
|
|
26
|
+
sql`update projects set settings_version = case when ${guard} then settings_version + 1 else null end where id = ${projectId}`,
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function rethrowSettingsConflict(error: unknown): never {
|
|
31
|
+
if (error && typeof error === 'object') {
|
|
32
|
+
if (
|
|
33
|
+
'message' in error &&
|
|
34
|
+
typeof error.message === 'string' &&
|
|
35
|
+
/NOT NULL constraint failed: (projects|teams|user|servers|backup_configs|backup_storage)\.settings_version/i.test(
|
|
36
|
+
error.message,
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
throw new PublicError('SETTINGS_CONFLICT')
|
|
40
|
+
if ('cause' in error && error.cause) rethrowSettingsConflict(error.cause)
|
|
41
|
+
}
|
|
42
|
+
throw error
|
|
43
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { access, mkdir, readFile, unlink, writeFile } from 'node:fs/promises'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import type { ObjectStore } from './object-store'
|
|
4
|
+
|
|
5
|
+
export function createLocalObjectStore(): ObjectStore {
|
|
6
|
+
const root = path.join(process.cwd(), 'public', 'uploads')
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
async delete(key) {
|
|
10
|
+
try {
|
|
11
|
+
await unlink(resolveLocalKey(root, key))
|
|
12
|
+
} catch (error) {
|
|
13
|
+
if (!isMissingFile(error)) throw error
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
async exists(key) {
|
|
17
|
+
try {
|
|
18
|
+
await access(resolveLocalKey(root, key))
|
|
19
|
+
return true
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (isMissingFile(error)) return false
|
|
22
|
+
throw error
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
async get(key) {
|
|
26
|
+
return { body: await readFile(resolveLocalKey(root, key)) }
|
|
27
|
+
},
|
|
28
|
+
async put(key, body) {
|
|
29
|
+
const filePath = resolveLocalKey(root, key)
|
|
30
|
+
await mkdir(path.dirname(filePath), { recursive: true })
|
|
31
|
+
await writeFile(filePath, body)
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function resolveLocalKey(root: string, key: string) {
|
|
37
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_./-]*$/.test(key) || key.includes('..')) {
|
|
38
|
+
throw new Error('Invalid object key.')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return path.join(root, key)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isMissingFile(error: unknown) {
|
|
45
|
+
return (
|
|
46
|
+
error instanceof Error &&
|
|
47
|
+
'code' in error &&
|
|
48
|
+
(error as NodeJS.ErrnoException).code === 'ENOENT'
|
|
49
|
+
)
|
|
50
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { createLocalObjectStore } from '#devpush/server/storage/local-object-store'
|
|
2
|
+
|
|
3
|
+
import { config } from '#devpush/server/config'
|
|
4
|
+
import {
|
|
5
|
+
isCloudflareRuntime,
|
|
6
|
+
getR2ObjectStore,
|
|
7
|
+
} from '#devpush/server/runtime/platform-env'
|
|
8
|
+
import type { S3Client } from '@aws-sdk/client-s3'
|
|
9
|
+
|
|
10
|
+
type StoredObject = {
|
|
11
|
+
body: Buffer
|
|
12
|
+
contentType?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ObjectStore {
|
|
16
|
+
delete: (key: string) => Promise<void>
|
|
17
|
+
exists: (key: string) => Promise<boolean>
|
|
18
|
+
get: (key: string) => Promise<StoredObject>
|
|
19
|
+
put: (key: string, body: Uint8Array, contentType?: string) => Promise<void>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let store: ObjectStore | undefined
|
|
23
|
+
|
|
24
|
+
export function getObjectStore() {
|
|
25
|
+
const r2 = getR2ObjectStore()
|
|
26
|
+
|
|
27
|
+
if (r2) {
|
|
28
|
+
return createR2ObjectStore(r2)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Node may pool a client; Worker invocations must not share its transport I/O.
|
|
32
|
+
if (isCloudflareRuntime()) {
|
|
33
|
+
if (!isRemoteObjectStorageConfigured())
|
|
34
|
+
throw new Error('Object storage is not configured.')
|
|
35
|
+
return createS3ObjectStore()
|
|
36
|
+
}
|
|
37
|
+
store ??= isRemoteObjectStorageConfigured()
|
|
38
|
+
? createS3ObjectStore()
|
|
39
|
+
: createLocalObjectStore()
|
|
40
|
+
|
|
41
|
+
return store
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function isObjectStorageConfigured() {
|
|
45
|
+
return (
|
|
46
|
+
!isCloudflareRuntime() ||
|
|
47
|
+
Boolean(getR2ObjectStore()) ||
|
|
48
|
+
isRemoteObjectStorageConfigured()
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function createR2ObjectStore(bucket: R2Bucket): ObjectStore {
|
|
53
|
+
return {
|
|
54
|
+
async delete(key) {
|
|
55
|
+
await bucket.delete(key)
|
|
56
|
+
},
|
|
57
|
+
async exists(key) {
|
|
58
|
+
return Boolean(await bucket.head(key))
|
|
59
|
+
},
|
|
60
|
+
async get(key) {
|
|
61
|
+
const object = await bucket.get(key)
|
|
62
|
+
if (!object) throw new Error(`Object ${key} was not found.`)
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
body: Buffer.from(await object.arrayBuffer()),
|
|
66
|
+
contentType: object.httpMetadata?.contentType,
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
async put(key, body, contentType) {
|
|
70
|
+
await bucket.put(key, body, {
|
|
71
|
+
httpMetadata: contentType ? { contentType } : undefined,
|
|
72
|
+
})
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isRemoteObjectStorageConfigured() {
|
|
78
|
+
return Boolean(
|
|
79
|
+
config.DEVPUSH_STORAGE_ENDPOINT &&
|
|
80
|
+
config.DEVPUSH_STORAGE_BUCKET &&
|
|
81
|
+
config.DEVPUSH_STORAGE_ACCESS_KEY_ID &&
|
|
82
|
+
config.DEVPUSH_STORAGE_SECRET_ACCESS_KEY,
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function createS3ObjectStore(): ObjectStore {
|
|
87
|
+
const bucket = config.DEVPUSH_STORAGE_BUCKET!
|
|
88
|
+
let clientPromise: Promise<S3Client> | undefined
|
|
89
|
+
const getClient = () =>
|
|
90
|
+
(clientPromise ??= import('@aws-sdk/client-s3').then(
|
|
91
|
+
({ S3Client }) =>
|
|
92
|
+
new S3Client({
|
|
93
|
+
endpoint: config.DEVPUSH_STORAGE_ENDPOINT,
|
|
94
|
+
region: config.DEVPUSH_STORAGE_REGION ?? 'auto',
|
|
95
|
+
credentials: {
|
|
96
|
+
accessKeyId: config.DEVPUSH_STORAGE_ACCESS_KEY_ID!,
|
|
97
|
+
secretAccessKey: config.DEVPUSH_STORAGE_SECRET_ACCESS_KEY!,
|
|
98
|
+
},
|
|
99
|
+
}),
|
|
100
|
+
))
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
async delete(key) {
|
|
104
|
+
const [{ DeleteObjectCommand }, client] = await Promise.all([
|
|
105
|
+
import('@aws-sdk/client-s3'),
|
|
106
|
+
getClient(),
|
|
107
|
+
])
|
|
108
|
+
await client.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }))
|
|
109
|
+
},
|
|
110
|
+
async exists(key) {
|
|
111
|
+
try {
|
|
112
|
+
const [{ HeadObjectCommand }, client] = await Promise.all([
|
|
113
|
+
import('@aws-sdk/client-s3'),
|
|
114
|
+
getClient(),
|
|
115
|
+
])
|
|
116
|
+
await client.send(new HeadObjectCommand({ Bucket: bucket, Key: key }))
|
|
117
|
+
return true
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (
|
|
120
|
+
error instanceof Error &&
|
|
121
|
+
'name' in error &&
|
|
122
|
+
(error.name === 'NotFound' || error.name === 'NoSuchKey')
|
|
123
|
+
) {
|
|
124
|
+
return false
|
|
125
|
+
}
|
|
126
|
+
throw error
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
async get(key) {
|
|
130
|
+
const [{ GetObjectCommand }, client] = await Promise.all([
|
|
131
|
+
import('@aws-sdk/client-s3'),
|
|
132
|
+
getClient(),
|
|
133
|
+
])
|
|
134
|
+
const result = await client.send(
|
|
135
|
+
new GetObjectCommand({ Bucket: bucket, Key: key }),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
if (!result.Body) throw new Error(`Object ${key} has no content.`)
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
body: Buffer.from(await result.Body.transformToByteArray()),
|
|
142
|
+
contentType: result.ContentType,
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
async put(key, body, contentType) {
|
|
146
|
+
const [{ PutObjectCommand }, client] = await Promise.all([
|
|
147
|
+
import('@aws-sdk/client-s3'),
|
|
148
|
+
getClient(),
|
|
149
|
+
])
|
|
150
|
+
await client.send(
|
|
151
|
+
new PutObjectCommand({
|
|
152
|
+
Body: body,
|
|
153
|
+
Bucket: bucket,
|
|
154
|
+
ContentType: contentType,
|
|
155
|
+
Key: key,
|
|
156
|
+
}),
|
|
157
|
+
)
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { sql } from 'drizzle-orm'
|
|
2
|
+
import type { AtomicStore } from '#devpush/server/db/atomic'
|
|
3
|
+
import { teamMembers, teams, user } from '#devpush/server/db/schema'
|
|
4
|
+
|
|
5
|
+
type TeamInput = {
|
|
6
|
+
userId: string
|
|
7
|
+
teamId: string
|
|
8
|
+
name: string
|
|
9
|
+
baseSlug: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function teamSlug(input: TeamInput) {
|
|
13
|
+
return sql`case when exists(select 1 from ${teams} where slug = ${input.baseSlug})
|
|
14
|
+
then ${`${input.baseSlug.slice(0, 32)}-${input.teamId.slice(0, 7)}`}
|
|
15
|
+
else ${input.baseSlug} end`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function insertOwnedTeam(store: AtomicStore, input: TeamInput) {
|
|
19
|
+
const now = Date.now()
|
|
20
|
+
const [created] = await store.batch([
|
|
21
|
+
store.all(sql`insert into ${teams} (id, name, slug, created_at, updated_at)
|
|
22
|
+
select ${input.teamId}, ${input.name}, ${teamSlug(input)}, ${now}, ${now}
|
|
23
|
+
from ${user} where id = ${input.userId} and status = 'active' returning id`),
|
|
24
|
+
store.run(sql`insert into ${teamMembers} (id, team_id, user_id, role, created_at)
|
|
25
|
+
select ${crypto.randomUUID()}, id, ${input.userId}, 'owner', ${now}
|
|
26
|
+
from ${teams} where id = ${input.teamId}`),
|
|
27
|
+
])
|
|
28
|
+
return created.length === 1
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function bootstrapOwnedTeam(store: AtomicStore, input: TeamInput) {
|
|
32
|
+
const now = Date.now()
|
|
33
|
+
const activeMemberships = sql`select membership.team_id from ${teamMembers} as membership
|
|
34
|
+
join ${teams} as member_team on member_team.id = membership.team_id
|
|
35
|
+
where membership.user_id = ${input.userId} and member_team.status = 'active'`
|
|
36
|
+
const [, , updated] = await store.batch([
|
|
37
|
+
store.run(sql`insert into ${teams} (id, name, slug, created_at, updated_at)
|
|
38
|
+
select ${input.teamId}, ${input.name}, ${teamSlug(input)}, ${now}, ${now}
|
|
39
|
+
from ${user} where id = ${input.userId} and status = 'active'
|
|
40
|
+
and not exists(${activeMemberships})`),
|
|
41
|
+
store.run(sql`insert into ${teamMembers} (id, team_id, user_id, role, created_at)
|
|
42
|
+
select ${crypto.randomUUID()}, id, ${input.userId}, 'owner', ${now}
|
|
43
|
+
from ${teams} where id = ${input.teamId}`),
|
|
44
|
+
store.all(sql`update ${user} set default_team_id = case
|
|
45
|
+
when default_team_id in (${activeMemberships}) then default_team_id
|
|
46
|
+
else (${activeMemberships} order by membership.created_at desc limit 1) end,
|
|
47
|
+
updated_at = ${now}
|
|
48
|
+
where id = ${input.userId} and status = 'active' returning id`),
|
|
49
|
+
])
|
|
50
|
+
return updated.length === 1
|
|
51
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TeamRole } from '#devpush/lib/team-role'
|
|
2
|
+
|
|
3
|
+
export function canDeleteTeam({
|
|
4
|
+
defaultUserIds,
|
|
5
|
+
}: {
|
|
6
|
+
defaultUserIds: Array<string>
|
|
7
|
+
}) {
|
|
8
|
+
return defaultUserIds.length === 0
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function canDeleteProject(role: TeamRole) {
|
|
12
|
+
return role === 'owner' || role === 'admin'
|
|
13
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { releaseBackupCleanupRuns } from '#devpush/server/backups/queue-state'
|
|
2
|
+
import { sql } from 'drizzle-orm'
|
|
3
|
+
import type { SQL } from 'drizzle-orm'
|
|
4
|
+
import type { AtomicStore } from '#devpush/server/db/atomic'
|
|
5
|
+
import { runtimeCleanupInsert } from '#devpush/server/agents/cleanup'
|
|
6
|
+
|
|
7
|
+
export function teamDeletionStatements(input: {
|
|
8
|
+
teamIds: SQL
|
|
9
|
+
guard: SQL
|
|
10
|
+
reason: string
|
|
11
|
+
message: string
|
|
12
|
+
now: Date
|
|
13
|
+
clearDefaults?: boolean
|
|
14
|
+
}): [SQL, ...SQL[]] {
|
|
15
|
+
const now = input.now.getTime()
|
|
16
|
+
const projects = sql`select id from projects where team_id in (${input.teamIds})`
|
|
17
|
+
const servers = sql`select id from servers where team_id in (${input.teamIds})`
|
|
18
|
+
const environments = sql`select id from environments where project_id in (${projects})`
|
|
19
|
+
return [
|
|
20
|
+
releaseBackupCleanupRuns({
|
|
21
|
+
jobIds: sql`select id from agent_jobs where server_id in (${servers})`,
|
|
22
|
+
guard: input.guard,
|
|
23
|
+
message: input.message,
|
|
24
|
+
now: input.now,
|
|
25
|
+
}),
|
|
26
|
+
sql`update agent_jobs set status = 'canceled', error_message = ${input.message}, finished_at = ${now}, updated_at = ${now}
|
|
27
|
+
where server_id in (${servers}) and type in ('deploy', 'backup', 'restore', 'backup_cleanup')
|
|
28
|
+
and status in ('queued', 'running') and ${input.guard}`,
|
|
29
|
+
sql`update backup_runs set status = 'canceled', error_message = ${input.message}, finished_at = ${now}, updated_at = ${now}
|
|
30
|
+
where server_id in (${servers}) and status in ('queued', 'running') and ${input.guard}`,
|
|
31
|
+
runtimeCleanupInsert({
|
|
32
|
+
projectIds: projects,
|
|
33
|
+
guard: input.guard,
|
|
34
|
+
reason: input.reason,
|
|
35
|
+
now: input.now,
|
|
36
|
+
}),
|
|
37
|
+
sql`update environments set status = 'deleted', is_production = 0, updated_at = ${now}
|
|
38
|
+
where project_id in (${projects}) and ${input.guard}`,
|
|
39
|
+
sql`update deployments set status = 'canceled', phase = 'canceled', error_message = ${input.message}, finished_at = ${now}
|
|
40
|
+
where environment_id in (${environments}) and status in ('queued', 'running') and ${input.guard}`,
|
|
41
|
+
sql`update projects set status = 'deleted', updated_at = ${now} where team_id in (${input.teamIds}) and ${input.guard}`,
|
|
42
|
+
sql`update servers set status = 'deleted', updated_at = ${now} where team_id in (${input.teamIds}) and ${input.guard}`,
|
|
43
|
+
...(input.clearDefaults
|
|
44
|
+
? [
|
|
45
|
+
sql`update user set default_team_id = null, updated_at = ${now}
|
|
46
|
+
where default_team_id in (${input.teamIds}) and ${input.guard}`,
|
|
47
|
+
]
|
|
48
|
+
: []),
|
|
49
|
+
sql`update teams set status = 'deleted', updated_at = ${now} where id in (${input.teamIds}) and ${input.guard} returning id`,
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function deleteTeamState(
|
|
54
|
+
store: AtomicStore,
|
|
55
|
+
input: {
|
|
56
|
+
teamId: string
|
|
57
|
+
userId: string
|
|
58
|
+
now: Date
|
|
59
|
+
},
|
|
60
|
+
) {
|
|
61
|
+
const guard = sql`exists(select 1 from teams t join team_members m on m.team_id = t.id
|
|
62
|
+
join user u on u.id = m.user_id where t.id = ${input.teamId} and t.status = 'active'
|
|
63
|
+
and m.user_id = ${input.userId} and m.role = 'owner' and u.status = 'active')
|
|
64
|
+
and not exists(select 1 from user where default_team_id = ${input.teamId} and status = 'active')`
|
|
65
|
+
const [first, ...rest] = teamDeletionStatements({
|
|
66
|
+
teamIds: sql`select id from teams where id = ${input.teamId}`,
|
|
67
|
+
guard,
|
|
68
|
+
reason: 'Team deleted.',
|
|
69
|
+
message: 'Canceled because the team was deleted.',
|
|
70
|
+
now: input.now,
|
|
71
|
+
})
|
|
72
|
+
const results = await store.batch([
|
|
73
|
+
store.run(first),
|
|
74
|
+
...rest.map((query, index) =>
|
|
75
|
+
index === rest.length - 1 ? store.all(query) : store.run(query),
|
|
76
|
+
),
|
|
77
|
+
])
|
|
78
|
+
const changed = results.at(-1)
|
|
79
|
+
return Array.isArray(changed) && changed.length === 1
|
|
80
|
+
}
|