devpush 0.0.0-stage → 1.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -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 +170 -4
- 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,198 @@
|
|
|
1
|
+
import { notifyProject } from '#devpush/server/realtime/service'
|
|
2
|
+
import { notifyAfterProjectUpdate } from '#devpush/server/notifications/observe'
|
|
3
|
+
import { handleGitHubInstallationWebhook } from '#devpush/server/github/service'
|
|
4
|
+
import {
|
|
5
|
+
getBranchFromGitHubRef,
|
|
6
|
+
InvalidGitHubInstallationPayloadError,
|
|
7
|
+
parseGitHubInstallationWebhookPayload,
|
|
8
|
+
parseGitHubPushPayload,
|
|
9
|
+
requireGitHubWebhookConfiguration,
|
|
10
|
+
validateGitHubWebhookHeaders,
|
|
11
|
+
verifyGitHubWebhookSignature,
|
|
12
|
+
} from '#devpush/server/github/webhooks'
|
|
13
|
+
import { createDeploymentsForGitHubPush } from '#devpush/server/projects/deployments'
|
|
14
|
+
import {
|
|
15
|
+
RequestBodyTooLargeError,
|
|
16
|
+
readRequestText,
|
|
17
|
+
} from '#devpush/server/http/request-body'
|
|
18
|
+
import { enforceRateLimit } from '#devpush/server/http/rate-limit'
|
|
19
|
+
|
|
20
|
+
// GitHub permits larger payloads, but this endpoint buffers the body before
|
|
21
|
+
// signature verification and only consumes compact push/installation fields.
|
|
22
|
+
const maxGitHubWebhookBytes = 5 * 1024 * 1024
|
|
23
|
+
|
|
24
|
+
export async function handleGitHubWebhook(request: Request) {
|
|
25
|
+
const webhookHeaders = validateGitHubWebhookHeaders(request.headers)
|
|
26
|
+
|
|
27
|
+
if (!webhookHeaders.ok) {
|
|
28
|
+
if (webhookHeaders.status === 401) {
|
|
29
|
+
const limited = await enforceRateLimit({
|
|
30
|
+
limit: 120,
|
|
31
|
+
request,
|
|
32
|
+
scope: 'github-webhook',
|
|
33
|
+
windowMs: 60_000,
|
|
34
|
+
})
|
|
35
|
+
if (limited) return limited
|
|
36
|
+
}
|
|
37
|
+
return Response.json(
|
|
38
|
+
{ error: webhookHeaders.error },
|
|
39
|
+
{ status: webhookHeaders.status },
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
requireGitHubWebhookConfiguration()
|
|
45
|
+
} catch (error) {
|
|
46
|
+
return Response.json(
|
|
47
|
+
{
|
|
48
|
+
error:
|
|
49
|
+
error instanceof Error
|
|
50
|
+
? error.message
|
|
51
|
+
: 'GitHub webhook verification failed.',
|
|
52
|
+
},
|
|
53
|
+
{ status: 503 },
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let body: string
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
body = await readRequestText(request, maxGitHubWebhookBytes)
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (error instanceof RequestBodyTooLargeError) {
|
|
63
|
+
return Response.json(
|
|
64
|
+
{ error: 'Webhook payload is too large.' },
|
|
65
|
+
{ status: 413 },
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
throw error
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const isValidSignature = verifyGitHubWebhookSignature({
|
|
74
|
+
body,
|
|
75
|
+
signature: webhookHeaders.signature,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
if (!isValidSignature) {
|
|
79
|
+
const rateLimitResponse = await enforceRateLimit({
|
|
80
|
+
limit: 120,
|
|
81
|
+
request,
|
|
82
|
+
scope: 'github-webhook',
|
|
83
|
+
windowMs: 60_000,
|
|
84
|
+
})
|
|
85
|
+
if (rateLimitResponse) {
|
|
86
|
+
return rateLimitResponse
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return Response.json({ error: 'Invalid signature.' }, { status: 401 })
|
|
90
|
+
}
|
|
91
|
+
} catch (error) {
|
|
92
|
+
return Response.json(
|
|
93
|
+
{
|
|
94
|
+
error:
|
|
95
|
+
error instanceof Error
|
|
96
|
+
? error.message
|
|
97
|
+
: 'GitHub webhook verification failed.',
|
|
98
|
+
},
|
|
99
|
+
{ status: 503 },
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let payload: ReturnType<typeof parseGitHubPushPayload> | null = null
|
|
104
|
+
const { deliveryId, event } = webhookHeaders
|
|
105
|
+
|
|
106
|
+
if (
|
|
107
|
+
event === 'installation' ||
|
|
108
|
+
event === 'installation_repositories' ||
|
|
109
|
+
event === 'installation_target'
|
|
110
|
+
) {
|
|
111
|
+
try {
|
|
112
|
+
const installationPayload = parseGitHubInstallationWebhookPayload(body)
|
|
113
|
+
await handleGitHubInstallationWebhook({
|
|
114
|
+
action: installationPayload.action,
|
|
115
|
+
event,
|
|
116
|
+
installationId: installationPayload.installationId,
|
|
117
|
+
})
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (error instanceof InvalidGitHubInstallationPayloadError) {
|
|
120
|
+
return Response.json({ error: error.message }, { status: 400 })
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
throw error
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return Response.json({ ok: true })
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (event === 'push') {
|
|
130
|
+
try {
|
|
131
|
+
payload = parseGitHubPushPayload(body)
|
|
132
|
+
} catch (error) {
|
|
133
|
+
return Response.json(
|
|
134
|
+
{
|
|
135
|
+
error:
|
|
136
|
+
error instanceof Error
|
|
137
|
+
? error.message
|
|
138
|
+
: 'Invalid GitHub webhook payload.',
|
|
139
|
+
},
|
|
140
|
+
{ status: 400 },
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (event !== 'push') {
|
|
146
|
+
return Response.json({ ok: true, ignored: event })
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!payload) {
|
|
150
|
+
return Response.json(
|
|
151
|
+
{ error: 'Invalid GitHub webhook payload.' },
|
|
152
|
+
{ status: 400 },
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const branch = getBranchFromGitHubRef(payload.ref)
|
|
157
|
+
const installationId = payload.installation?.id
|
|
158
|
+
const repositoryId = payload.repository?.id
|
|
159
|
+
const repositoryFullName = payload.repository?.full_name
|
|
160
|
+
const gitSha = payload.head_commit?.id
|
|
161
|
+
|
|
162
|
+
if (
|
|
163
|
+
!branch ||
|
|
164
|
+
typeof installationId !== 'number' ||
|
|
165
|
+
!Number.isSafeInteger(installationId) ||
|
|
166
|
+
typeof repositoryId !== 'number' ||
|
|
167
|
+
!Number.isSafeInteger(repositoryId) ||
|
|
168
|
+
!repositoryFullName ||
|
|
169
|
+
!gitSha
|
|
170
|
+
) {
|
|
171
|
+
return Response.json({ ok: true, ignored: 'unsupported_push' })
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const result = await createDeploymentsForGitHubPush({
|
|
175
|
+
deliveryId,
|
|
176
|
+
installationId,
|
|
177
|
+
repositoryId,
|
|
178
|
+
repositoryFullName,
|
|
179
|
+
branch,
|
|
180
|
+
gitSha,
|
|
181
|
+
message: payload.head_commit?.message ?? null,
|
|
182
|
+
authorName: payload.head_commit?.author?.name ?? null,
|
|
183
|
+
authoredAt: payload.head_commit?.timestamp ?? null,
|
|
184
|
+
commitUrl: payload.head_commit?.url ?? null,
|
|
185
|
+
githubSenderId:
|
|
186
|
+
Number.isSafeInteger(payload.sender?.id) && (payload.sender?.id ?? 0) > 0
|
|
187
|
+
? String(payload.sender!.id)
|
|
188
|
+
: undefined,
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
for (const projectId of new Set(
|
|
192
|
+
[...result.created, ...result.skipped].map((item) => item.projectId),
|
|
193
|
+
)) {
|
|
194
|
+
await notifyProject(projectId)
|
|
195
|
+
notifyAfterProjectUpdate(projectId)
|
|
196
|
+
}
|
|
197
|
+
return Response.json({ ok: true, ...result })
|
|
198
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
import { config } from '#devpush/server/config'
|
|
4
|
+
|
|
5
|
+
export type GitHubPushWebhookPayload = {
|
|
6
|
+
sender?: { id?: number } | null
|
|
7
|
+
ref?: string
|
|
8
|
+
head_commit?: {
|
|
9
|
+
id?: string
|
|
10
|
+
message?: string
|
|
11
|
+
author?: { name?: string }
|
|
12
|
+
timestamp?: string
|
|
13
|
+
url?: string
|
|
14
|
+
} | null
|
|
15
|
+
installation?: {
|
|
16
|
+
id?: number
|
|
17
|
+
} | null
|
|
18
|
+
repository?: {
|
|
19
|
+
id?: number
|
|
20
|
+
full_name?: string
|
|
21
|
+
} | null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type GitHubInstallationWebhookPayload = {
|
|
25
|
+
action: string
|
|
26
|
+
installationId: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type GitHubInstallationWebhookEvent =
|
|
30
|
+
| 'installation'
|
|
31
|
+
| 'installation_repositories'
|
|
32
|
+
| 'installation_target'
|
|
33
|
+
|
|
34
|
+
export type GitHubInstallationChange =
|
|
35
|
+
| { type: 'refresh' }
|
|
36
|
+
| { type: 'status'; status: 'deleted' | 'suspended' }
|
|
37
|
+
| { type: 'ignore' }
|
|
38
|
+
|
|
39
|
+
type GitHubWebhookHeadersResult =
|
|
40
|
+
| {
|
|
41
|
+
ok: true
|
|
42
|
+
deliveryId: string
|
|
43
|
+
event: string
|
|
44
|
+
signature: string
|
|
45
|
+
}
|
|
46
|
+
| { ok: false; error: string; status: 400 | 401 }
|
|
47
|
+
|
|
48
|
+
export function validateGitHubWebhookHeaders(
|
|
49
|
+
headers: Headers,
|
|
50
|
+
): GitHubWebhookHeadersResult {
|
|
51
|
+
const signature = headers.get('x-hub-signature-256')?.trim() ?? ''
|
|
52
|
+
if (!/^sha256=[a-f0-9]{64}$/i.test(signature)) {
|
|
53
|
+
return { ok: false, error: 'Invalid signature.', status: 401 }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const deliveryId = headers.get('x-github-delivery')?.trim() ?? ''
|
|
57
|
+
if (!/^[a-z0-9-]{1,255}$/i.test(deliveryId)) {
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
error: 'Missing or invalid GitHub delivery ID.',
|
|
61
|
+
status: 400,
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const event = headers.get('x-github-event')?.trim() ?? ''
|
|
66
|
+
if (!/^[a-z][a-z0-9_]{0,63}$/.test(event)) {
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
error: 'Missing or invalid GitHub event.',
|
|
70
|
+
status: 400,
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { ok: true, deliveryId, event, signature }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function requireGitHubWebhookConfiguration() {
|
|
78
|
+
if (!config.GITHUB_APP_WEBHOOK_SECRET) {
|
|
79
|
+
throw new Error('GitHub webhook secret is not configured.')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return config.GITHUB_APP_WEBHOOK_SECRET
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function verifyGitHubWebhookSignature({
|
|
86
|
+
body,
|
|
87
|
+
signature,
|
|
88
|
+
}: {
|
|
89
|
+
body: string
|
|
90
|
+
signature: string | null
|
|
91
|
+
}) {
|
|
92
|
+
const webhookSecret = requireGitHubWebhookConfiguration()
|
|
93
|
+
|
|
94
|
+
if (!signature?.startsWith('sha256=')) {
|
|
95
|
+
return false
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const expected = createHmac('sha256', webhookSecret)
|
|
99
|
+
.update(body)
|
|
100
|
+
.digest('hex')
|
|
101
|
+
const actual = signature.slice('sha256='.length)
|
|
102
|
+
|
|
103
|
+
return safeEqualHex(actual, expected)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function parseGitHubPushPayload(body: string): GitHubPushWebhookPayload {
|
|
107
|
+
const payload: unknown = JSON.parse(body)
|
|
108
|
+
|
|
109
|
+
if (!payload || typeof payload !== 'object') {
|
|
110
|
+
throw new Error('Invalid GitHub webhook payload.')
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return payload
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export class InvalidGitHubInstallationPayloadError extends Error {
|
|
117
|
+
constructor() {
|
|
118
|
+
super('Invalid GitHub installation webhook payload.')
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function parseGitHubInstallationWebhookPayload(
|
|
123
|
+
body: string,
|
|
124
|
+
): GitHubInstallationWebhookPayload {
|
|
125
|
+
let payload: unknown
|
|
126
|
+
try {
|
|
127
|
+
payload = JSON.parse(body)
|
|
128
|
+
} catch {
|
|
129
|
+
throw new InvalidGitHubInstallationPayloadError()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
133
|
+
throw new InvalidGitHubInstallationPayloadError()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const record = payload as Record<string, unknown>
|
|
137
|
+
const installation = record.installation
|
|
138
|
+
|
|
139
|
+
if (
|
|
140
|
+
typeof record.action !== 'string' ||
|
|
141
|
+
!installation ||
|
|
142
|
+
typeof installation !== 'object' ||
|
|
143
|
+
Array.isArray(installation)
|
|
144
|
+
) {
|
|
145
|
+
throw new InvalidGitHubInstallationPayloadError()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const installationId = (installation as Record<string, unknown>).id
|
|
149
|
+
|
|
150
|
+
if (
|
|
151
|
+
typeof installationId !== 'number' ||
|
|
152
|
+
!Number.isSafeInteger(installationId)
|
|
153
|
+
) {
|
|
154
|
+
throw new InvalidGitHubInstallationPayloadError()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { action: record.action, installationId }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function getGitHubInstallationChange(
|
|
161
|
+
event: GitHubInstallationWebhookEvent,
|
|
162
|
+
action: string,
|
|
163
|
+
): GitHubInstallationChange {
|
|
164
|
+
if (event === 'installation') {
|
|
165
|
+
if (action === 'deleted') {
|
|
166
|
+
return { type: 'status', status: 'deleted' }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (action === 'suspend') {
|
|
170
|
+
return { type: 'status', status: 'suspended' }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (
|
|
174
|
+
!['created', 'new_permissions_accepted', 'unsuspend'].includes(action)
|
|
175
|
+
) {
|
|
176
|
+
return { type: 'ignore' }
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return { type: 'refresh' }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function getBranchFromGitHubRef(ref: string | undefined) {
|
|
184
|
+
const prefix = 'refs/heads/'
|
|
185
|
+
|
|
186
|
+
if (!ref?.startsWith(prefix)) {
|
|
187
|
+
return null
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return ref.slice(prefix.length)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function safeEqualHex(actualHex: string, expectedHex: string) {
|
|
194
|
+
if (
|
|
195
|
+
!/^[a-f0-9]+$/i.test(actualHex) ||
|
|
196
|
+
actualHex.length !== expectedHex.length
|
|
197
|
+
) {
|
|
198
|
+
return false
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return timingSafeEqual(
|
|
202
|
+
Buffer.from(actualHex, 'hex'),
|
|
203
|
+
Buffer.from(expectedHex, 'hex'),
|
|
204
|
+
)
|
|
205
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { timingSafeEqual } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
export function matchesBearerSecret(
|
|
4
|
+
authorization: string | null,
|
|
5
|
+
secret: string,
|
|
6
|
+
) {
|
|
7
|
+
if (!authorization || !secret) return false
|
|
8
|
+
const actual = Buffer.from(authorization)
|
|
9
|
+
const expected = Buffer.from(`Bearer ${secret}`)
|
|
10
|
+
return actual.length === expected.length && timingSafeEqual(actual, expected)
|
|
11
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Keep source locations for investigation without retaining exception messages,
|
|
2
|
+
// query text, URLs, or serialized request values. This works on Node and Workers.
|
|
3
|
+
export function safeErrorDiagnostics(error: unknown) {
|
|
4
|
+
if (!(error instanceof Error))
|
|
5
|
+
return { errorName: 'unknown', stackFrames: [] as string[] }
|
|
6
|
+
try {
|
|
7
|
+
const errorName = /^[A-Za-z][A-Za-z0-9_.-]{0,79}$/.test(error.name)
|
|
8
|
+
? error.name
|
|
9
|
+
: 'Error'
|
|
10
|
+
if (typeof error.stack !== 'string')
|
|
11
|
+
return { errorName, stackFrames: [] as string[] }
|
|
12
|
+
// A multiline exception message can itself contain frame-shaped text.
|
|
13
|
+
// Remove the complete message before examining actual stack frames.
|
|
14
|
+
const messagePosition = error.message
|
|
15
|
+
? error.stack.indexOf(error.message)
|
|
16
|
+
: -1
|
|
17
|
+
const stack =
|
|
18
|
+
messagePosition >= 0
|
|
19
|
+
? error.stack.slice(messagePosition + error.message.length)
|
|
20
|
+
: error.stack.slice(error.stack.indexOf('\n') + 1)
|
|
21
|
+
const stackFrames: string[] = []
|
|
22
|
+
for (const line of stack.slice(0, 32 * 1024).split('\n')) {
|
|
23
|
+
const match =
|
|
24
|
+
/^\s*at\s+(?:(.*?)\s+\()?([^()\n]+):(\d{1,8}):(\d{1,8})\)?$/.exec(line)
|
|
25
|
+
if (!match) continue
|
|
26
|
+
const source = safeSourceLocation(match[2]!)
|
|
27
|
+
if (!source) continue
|
|
28
|
+
const functionName =
|
|
29
|
+
match[1] &&
|
|
30
|
+
/^(?:(?:async|new) )?[A-Za-z_$][A-Za-z0-9_.$]{0,100}$/.test(match[1])
|
|
31
|
+
? match[1]
|
|
32
|
+
: undefined
|
|
33
|
+
const location = `${source}:${match[3]}:${match[4]}`
|
|
34
|
+
stackFrames.push(
|
|
35
|
+
functionName ? `at ${functionName} (${location})` : `at ${location}`,
|
|
36
|
+
)
|
|
37
|
+
if (stackFrames.length === 8) break
|
|
38
|
+
}
|
|
39
|
+
return { errorName, stackFrames }
|
|
40
|
+
} catch {
|
|
41
|
+
// Nonstandard error accessors must not prevent the public error response.
|
|
42
|
+
return { errorName: 'Error', stackFrames: [] as string[] }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function safeSourceLocation(source: string) {
|
|
47
|
+
if (/^node:[A-Za-z0-9_./-]{1,180}$/.test(source)) return source
|
|
48
|
+
if (
|
|
49
|
+
/^[A-Za-z][A-Za-z0-9+.-]*:/.test(source) &&
|
|
50
|
+
!source.startsWith('file:///') &&
|
|
51
|
+
!/^[A-Za-z]:[\\/]/.test(source)
|
|
52
|
+
)
|
|
53
|
+
return null
|
|
54
|
+
const parts = source
|
|
55
|
+
.replace(/^file:\/\/\//, '/')
|
|
56
|
+
.replace(/\\/g, '/')
|
|
57
|
+
.split('/')
|
|
58
|
+
.slice(-4)
|
|
59
|
+
if (
|
|
60
|
+
!parts.length ||
|
|
61
|
+
parts.some((part) => !/^[A-Za-z0-9_$@.~-]{1,100}$/.test(part))
|
|
62
|
+
)
|
|
63
|
+
return null
|
|
64
|
+
if (!/\.(?:[cm]?js|tsx?|jsx)$/.test(parts.at(-1)!)) return null
|
|
65
|
+
return parts.join('/').slice(-200)
|
|
66
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getAuthSession } from '#devpush/server/auth/session.server'
|
|
2
|
+
import { getPluginServices } from '#devpush/server/plugins/services.server'
|
|
3
|
+
|
|
4
|
+
export async function handlePluginRequest(request: Request) {
|
|
5
|
+
const session = await getAuthSession()
|
|
6
|
+
return getPluginServices().handleRequest!(
|
|
7
|
+
request,
|
|
8
|
+
session
|
|
9
|
+
? {
|
|
10
|
+
userId: session.user.id,
|
|
11
|
+
email: session.user.email,
|
|
12
|
+
}
|
|
13
|
+
: null,
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { config } from '#devpush/server/config'
|
|
2
|
+
import { isCloudflareRuntime } from '#devpush/server/runtime/platform-env'
|
|
3
|
+
|
|
4
|
+
export function resolveProxyRequest(
|
|
5
|
+
request: Request,
|
|
6
|
+
options = {
|
|
7
|
+
trusted: config.DEVPUSH_TRUST_PROXY && !isCloudflareRuntime(),
|
|
8
|
+
origins: [
|
|
9
|
+
config.DEVPUSH_BASE_URL,
|
|
10
|
+
config.BETTER_AUTH_URL,
|
|
11
|
+
config.DEVPUSH_MCP_URL,
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
): Request {
|
|
15
|
+
if (!options.trusted) return request
|
|
16
|
+
const protocol = request.headers
|
|
17
|
+
.get('x-forwarded-proto')
|
|
18
|
+
?.split(',')
|
|
19
|
+
.at(-1)
|
|
20
|
+
?.trim()
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
if (protocol !== 'http' && protocol !== 'https') return request
|
|
23
|
+
const current = new URL(request.url)
|
|
24
|
+
const host =
|
|
25
|
+
request.headers.get('x-forwarded-host')?.split(',').at(-1)?.trim() ??
|
|
26
|
+
current.host
|
|
27
|
+
let origin: URL
|
|
28
|
+
try {
|
|
29
|
+
origin = new URL(`${protocol}://${host}`)
|
|
30
|
+
} catch {
|
|
31
|
+
return request
|
|
32
|
+
}
|
|
33
|
+
if (
|
|
34
|
+
origin.username ||
|
|
35
|
+
origin.password ||
|
|
36
|
+
origin.pathname !== '/' ||
|
|
37
|
+
origin.search ||
|
|
38
|
+
origin.hash
|
|
39
|
+
)
|
|
40
|
+
return request
|
|
41
|
+
if (!options.origins.some((value) => new URL(value).origin === origin.origin))
|
|
42
|
+
return request
|
|
43
|
+
if (current.origin === origin.origin) return request
|
|
44
|
+
const url = new URL(current.pathname + current.search, origin)
|
|
45
|
+
return new Request(url, {
|
|
46
|
+
method: request.method,
|
|
47
|
+
headers: request.headers,
|
|
48
|
+
body: request.body,
|
|
49
|
+
signal: request.signal,
|
|
50
|
+
redirect: request.redirect,
|
|
51
|
+
duplex: 'half',
|
|
52
|
+
} as RequestInit)
|
|
53
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createMiddleware } from '@tanstack/react-start'
|
|
2
|
+
import { PublicError } from '#devpush/lib/public-error'
|
|
3
|
+
import { ZodError } from 'zod'
|
|
4
|
+
import { safeErrorDiagnostics } from './error-diagnostics'
|
|
5
|
+
|
|
6
|
+
export const publicErrorsMiddleware = createMiddleware({
|
|
7
|
+
type: 'function',
|
|
8
|
+
}).server(async ({ next }) => {
|
|
9
|
+
try {
|
|
10
|
+
return await next()
|
|
11
|
+
} catch (error) {
|
|
12
|
+
throw publicErrorPayload(error)
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export function toPublicError(error: unknown): PublicError {
|
|
17
|
+
if (error instanceof PublicError) return error
|
|
18
|
+
if (
|
|
19
|
+
error &&
|
|
20
|
+
typeof error === 'object' &&
|
|
21
|
+
(('status' in error && error.status === 429) ||
|
|
22
|
+
('statusCode' in error && error.statusCode === 429))
|
|
23
|
+
)
|
|
24
|
+
return new PublicError('RATE_LIMITED')
|
|
25
|
+
if (error instanceof ZodError) return new PublicError('INVALID_INPUT')
|
|
26
|
+
if (error instanceof Error && error.message === 'Forbidden')
|
|
27
|
+
return new PublicError('FORBIDDEN')
|
|
28
|
+
if (
|
|
29
|
+
error instanceof Error &&
|
|
30
|
+
(error.message === 'Unauthorized' ||
|
|
31
|
+
error.message.startsWith('Sign in before '))
|
|
32
|
+
)
|
|
33
|
+
return new PublicError('UNAUTHORIZED')
|
|
34
|
+
console.error('Server action failed', safeErrorDiagnostics(error))
|
|
35
|
+
return new PublicError('INTERNAL_ERROR')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Plain objects survive Start's default transport without per-window adapters.
|
|
39
|
+
export function publicErrorPayload(error: unknown) {
|
|
40
|
+
const safe = toPublicError(error)
|
|
41
|
+
return {
|
|
42
|
+
name: 'PublicError' as const,
|
|
43
|
+
code: safe.code,
|
|
44
|
+
message: safe.message,
|
|
45
|
+
...(safe.policy ? { policy: safe.policy } : {}),
|
|
46
|
+
...(safe.reloadUrl ? { reloadUrl: safe.reloadUrl } : {}),
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
import { lt, sql } from 'drizzle-orm'
|
|
4
|
+
|
|
5
|
+
import { db } from '#devpush/server/db'
|
|
6
|
+
import { rateLimits } from '#devpush/server/db/schema'
|
|
7
|
+
import { getRequestPublicAddress } from './request-ip'
|
|
8
|
+
|
|
9
|
+
export async function enforceRateLimit({
|
|
10
|
+
limit,
|
|
11
|
+
request,
|
|
12
|
+
identity: explicitIdentity,
|
|
13
|
+
scope,
|
|
14
|
+
windowMs,
|
|
15
|
+
}: {
|
|
16
|
+
limit: number
|
|
17
|
+
request?: Request
|
|
18
|
+
identity?: string
|
|
19
|
+
scope: string
|
|
20
|
+
windowMs: number
|
|
21
|
+
}) {
|
|
22
|
+
const identity =
|
|
23
|
+
explicitIdentity ??
|
|
24
|
+
(request ? getRequestPublicAddress(request) : null) ??
|
|
25
|
+
'unknown-address'
|
|
26
|
+
const key = `${scope}:${createHash('sha256').update(identity).digest('hex')}`
|
|
27
|
+
const now = new Date()
|
|
28
|
+
const resetAt = new Date(now.getTime() + windowMs)
|
|
29
|
+
const rows = await db
|
|
30
|
+
.insert(rateLimits)
|
|
31
|
+
.values({ key, count: 1, resetAt })
|
|
32
|
+
.onConflictDoUpdate({
|
|
33
|
+
target: rateLimits.key,
|
|
34
|
+
set: {
|
|
35
|
+
count: sql<number>`case when ${rateLimits.resetAt} <= ${now} then 1 else ${rateLimits.count} + 1 end`,
|
|
36
|
+
resetAt: sql<Date>`case when ${rateLimits.resetAt} <= ${now} then ${resetAt} else ${rateLimits.resetAt} end`,
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
.returning({ count: rateLimits.count, resetAt: rateLimits.resetAt })
|
|
40
|
+
const state = rows[0]
|
|
41
|
+
if (!state) throw new Error('Rate limit update returned no state.')
|
|
42
|
+
|
|
43
|
+
if (state.count <= limit) {
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const retryAfter = Math.max(
|
|
48
|
+
1,
|
|
49
|
+
Math.ceil((state.resetAt.getTime() - now.getTime()) / 1000),
|
|
50
|
+
)
|
|
51
|
+
return Response.json(
|
|
52
|
+
{ error: 'Too many requests. Try again later.' },
|
|
53
|
+
{ status: 429, headers: { 'retry-after': String(retryAfter) } },
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function cleanupExpiredRateLimits() {
|
|
58
|
+
await db.delete(rateLimits).where(lt(rateLimits.resetAt, new Date()))
|
|
59
|
+
}
|