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